mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-11 14:00:42 +01:00
Compare commits
6 Commits
main
...
feat/story
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aacce75f4 | ||
|
|
075e91cd2e | ||
|
|
b6bd48e20d | ||
|
|
785982e194 | ||
|
|
85681dd316 | ||
|
|
f2a38323b7 |
30
.github/workflows/jsci.yaml
vendored
30
.github/workflows/jsci.yaml
vendored
@@ -79,6 +79,36 @@ jobs:
|
||||
run: |
|
||||
cd frontend && pnpm generate:api
|
||||
git diff --compact-summary --exit-code || (echo; echo "Unexpected difference in generated api clients. Run pnpm generate:api in frontend/ locally and commit."; exit 1)
|
||||
storybook:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
|
||||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-noble
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
steps:
|
||||
- name: self-checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install-pnpm
|
||||
uses: pnpm/action-setup@v6
|
||||
with:
|
||||
version: 10
|
||||
- name: node-install
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: pnpm
|
||||
cache-dependency-path: frontend/pnpm-lock.yaml
|
||||
- name: install-frontend
|
||||
run: cd frontend && pnpm install
|
||||
- name: test-storybook
|
||||
run: cd frontend && pnpm test:storybook --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
web-settings:
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<!--
|
||||
`index.html` links Inter from Google Fonts here. The link is parsed before the
|
||||
msw worker has started, so that one request escapes the iframe while every
|
||||
other font request the app makes is answered with an empty stylesheet; the
|
||||
local faces stand in for both.
|
||||
-->
|
||||
<link rel="stylesheet" href="storybook-fonts.css" />
|
||||
|
||||
<link rel="stylesheet" href="css/uPlot.min.css" />
|
||||
|
||||
<script>
|
||||
|
||||
50
frontend/.storybook/public/storybook-fonts.css
Normal file
50
frontend/.storybook/public/storybook-fonts.css
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* The five families the app pulls from Google Fonts, served from the files in
|
||||
* `public/fonts` instead. `msw/appShellHandlers.ts` answers the CDN with an
|
||||
* empty stylesheet so no request leaves the iframe, which without this left
|
||||
* every story on a fallback for the four families `src/styles.scss` imports at
|
||||
* runtime, and on whatever the Inter link in `index.html` happened to fetch
|
||||
* before the worker had started.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
/* The comma is part of the filename and the dev server serves it raw, so it
|
||||
stays literal inside the quoted url rather than percent-encoded. */
|
||||
src: url('fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
src: url('fonts/WorkSans-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
src: url('fonts/SpaceMono-Regular.ttf') format('truetype');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
src: url('fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Geist Mono';
|
||||
src: url('fonts/GeistMonoVF.woff2') format('woff2');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
85
frontend/.storybook/test-runner.ts
Normal file
85
frontend/.storybook/test-runner.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { getStoryContext } from '@storybook/test-runner';
|
||||
import type { TestRunnerConfig } from '@storybook/test-runner';
|
||||
import type { Page } from 'playwright';
|
||||
|
||||
const IGNORED_MESSAGES = [
|
||||
// `preview-head.html` swaps a local stylesheet in for Google Fonts, but the
|
||||
// browser still warns on the real cross-origin one it briefly requests
|
||||
// before msw starts (no CORS headers), regardless of story content.
|
||||
/Can't access cssRules/,
|
||||
// Pre-existing dev-server noise, unrelated to any story.
|
||||
/Couldn't load preload assets/,
|
||||
// Fires because a Jest-driven browser sets a global testing flag React
|
||||
// checks for; unrelated to anything a story does.
|
||||
/current testing environment is not configured to support act/,
|
||||
// React and antd route dev-only warnings (missing keys, DOM nesting, API
|
||||
// deprecations) through `console.error` under this prefix; app-wide and
|
||||
// tracked separately from story regressions.
|
||||
/^Warning: /,
|
||||
// msw's own warning when its response listener count grows across many
|
||||
// story visits in one browser session; not a story defect.
|
||||
/MaxListenersExceededWarning/,
|
||||
// `preview-head.html`'s CSP intentionally blocks third-party iframes
|
||||
// (YouTube embeds, the docs pane) so they hit the real network instead of
|
||||
// an unanswered msw request; the block is the point, not a bug.
|
||||
/violates the following Content Security Policy directive/,
|
||||
];
|
||||
|
||||
const messagesByPage = new WeakMap<Page, string[]>();
|
||||
|
||||
/**
|
||||
* Only `console.error` fails a story. `console.warn` is dev-time advice from
|
||||
* app code (e.g. `aggregateData is null`) and from the runner itself; an
|
||||
* unmocked `/api/` call is a `console.error` in `src/storybook/msw/handlers.ts`.
|
||||
*/
|
||||
const config: TestRunnerConfig = {
|
||||
// msw logs every mocked request at `log`; keep it out of the failure dump
|
||||
// unless the job is re-run with debug logging (GitHub sets RUNNER_DEBUG=1).
|
||||
logLevel: process.env.RUNNER_DEBUG === '1' ? 'info' : 'warn',
|
||||
async preVisit(page): Promise<void> {
|
||||
const existing = messagesByPage.get(page);
|
||||
if (existing) {
|
||||
existing.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const messages: string[] = [];
|
||||
messagesByPage.set(page, messages);
|
||||
page.on('console', (message) => {
|
||||
if (
|
||||
message.type() === 'error' &&
|
||||
!IGNORED_MESSAGES.some((pattern) => pattern.test(message.text()))
|
||||
) {
|
||||
messages.push(`[error] ${message.text()}`);
|
||||
}
|
||||
});
|
||||
// The console message alone ("Failed to load resource") doesn't name the
|
||||
// URL; pairing it with the response is what makes a missing mock
|
||||
// actionable instead of just a status code.
|
||||
page.on('response', (response) => {
|
||||
if (response.status() >= 400) {
|
||||
messages.push(`[response] ${response.status()} ${response.url()}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
async postVisit(page, context): Promise<void> {
|
||||
const messages = messagesByPage.get(page) ?? [];
|
||||
if (messages.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A story that deliberately mocks a failure response (e.g. a 500 to test
|
||||
// an error state) logs the error it's testing for; opt it out per-story
|
||||
// with `parameters: { allowConsoleErrors: true }`.
|
||||
const storyContext = await getStoryContext(page, context);
|
||||
if (storyContext.parameters?.allowConsoleErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Story "${context.name}" logged console error/warning:\n${messages.join('\n')}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -9,6 +9,7 @@
|
||||
"dev": "vite",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:build": "storybook build -o storybook-static",
|
||||
"test:storybook": "bash scripts/test-storybook.sh",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prettify": "oxfmt",
|
||||
@@ -162,6 +163,7 @@
|
||||
"@jest/types": "30.2.0",
|
||||
"@storybook/addon-a11y": "10.5.9",
|
||||
"@storybook/react-vite": "10.5.9",
|
||||
"@storybook/test-runner": "0.24.5",
|
||||
"@testing-library/dom": "8.20.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "13.4.0",
|
||||
@@ -233,7 +235,7 @@
|
||||
},
|
||||
"overrides": {
|
||||
"@babel/core@<=7.29.0": ">=7.29.6 <8",
|
||||
"@istanbuljs/load-nyc-config>js-yaml": ">=4.2.0 <5",
|
||||
"@istanbuljs/load-nyc-config>js-yaml": ">=4.3.1 <5",
|
||||
"cookie@<0.7.0": ">=0.7.1 <1",
|
||||
"dompurify@<=3.4.10": ">=3.4.11 <4",
|
||||
"esbuild@>=0.27.3 <0.28.1": ">=0.28.1 <0.29.0",
|
||||
@@ -242,6 +244,14 @@
|
||||
"prismjs@<1.30.0": ">=1.30.0 <2",
|
||||
"react-router@>=6.7.0 <6.30.4": ">=6.30.4 <7",
|
||||
"tmp@<0.2.6": ">=0.2.6 <0.3.0",
|
||||
"yaml@>=1.0.0 <1.10.3": ">=1.10.3 <2"
|
||||
"yaml@>=1.0.0 <1.10.3": ">=1.10.3 <2",
|
||||
"brace-expansion@<1.1.18": ">=1.1.18 <2",
|
||||
"brace-expansion@>=2.0.0 <2.1.4": ">=2.1.4 <3",
|
||||
"brace-expansion@>=5.0.0 <5.0.9": ">=5.0.9 <6",
|
||||
"fast-uri@<3.1.5": ">=3.1.5 <4",
|
||||
"immutable@<5.1.8": ">=5.1.8 <6",
|
||||
"js-yaml@>=4.0.0 <4.3.1": ">=4.3.1 <5",
|
||||
"less@<4.5.0": ">=4.5.0 <5",
|
||||
"nanoid@<3.3.18": ">=3.3.18 <4"
|
||||
}
|
||||
}
|
||||
|
||||
1076
frontend/pnpm-lock.yaml
generated
1076
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
31
frontend/scripts/test-storybook.sh
Executable file
31
frontend/scripts/test-storybook.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
story_count=$(find src -name '*.stories.tsx' | wc -l)
|
||||
if [ "$story_count" -eq 0 ]; then
|
||||
echo "No *.stories.tsx found under src/" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# jest splits the sorted story files into contiguous shards and exits 1 when a
|
||||
# shard is empty, which happens on every shard above the file count. The runner
|
||||
# rejects jest's own `--passWithNoTests`, so skip those shards here.
|
||||
for arg in "$@"; do
|
||||
if [[ $arg == --shard=* ]]; then
|
||||
shard_index=${arg#--shard=}
|
||||
if [ "${shard_index%%/*}" -gt "$story_count" ]; then
|
||||
echo "Skipping ${arg#--shard=}: only ${story_count} story files"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
pnpm storybook --ci --quiet &
|
||||
SB_PID=$!
|
||||
trap 'kill "$SB_PID" 2>/dev/null || true' EXIT
|
||||
|
||||
until curl -sf http://127.0.0.1:6006/index.json >/dev/null 2>&1; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
pnpm exec test-storybook --ci --maxWorkers=2 "$@"
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { AxiosError, AxiosResponse, isCancel } from 'axios';
|
||||
import { ErrorResponse } from 'types/api';
|
||||
import { ErrorStatusCode } from 'types/common';
|
||||
|
||||
@@ -42,6 +42,16 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
};
|
||||
}
|
||||
if (request) {
|
||||
// Avoid logging error when the request was just cancelled for whatever reason
|
||||
if (isCancel(error)) {
|
||||
return {
|
||||
statusCode: 500,
|
||||
payload: null,
|
||||
error: 'Something went wrong',
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
|
||||
// client never received a response, or request never left
|
||||
console.error('client never received a response, or request never left');
|
||||
|
||||
|
||||
@@ -376,7 +376,9 @@ export default function BillingContainer(): JSX.Element {
|
||||
</Typography.Link>
|
||||
</AuthZTooltip>
|
||||
{` if your payment information has changed. Email us at `}
|
||||
<Typography.Text color="muted">cloud-support@signoz.io</Typography.Text>
|
||||
<Typography.Text as="span" color="muted">
|
||||
cloud-support@signoz.io
|
||||
</Typography.Text>
|
||||
{` otherwise. Be sure to provide this information immediately to avoid interruption to your service.`}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
@@ -174,6 +174,7 @@ export default function ServiceTraces({
|
||||
columns={columns}
|
||||
dataSource={top5Services}
|
||||
pagination={false}
|
||||
rowKey="serviceName"
|
||||
className="services-table"
|
||||
onRow={(record: ServicesList): Record<string, unknown> => ({
|
||||
onClick: (event: React.MouseEvent): void => {
|
||||
|
||||
@@ -216,7 +216,7 @@ function K8sOptionsSidePanel<TData>({
|
||||
);
|
||||
return (
|
||||
<div className={styles.columnItem} key={column.id}>
|
||||
<Typography.Text size="sm" className={styles.columnLabel}>
|
||||
<Typography.Text as="span" size="sm" className={styles.columnLabel}>
|
||||
{column.label}
|
||||
</Typography.Text>
|
||||
{column.canBeHidden ? (
|
||||
|
||||
@@ -10,7 +10,7 @@ function Shortcuts(): JSX.Element {
|
||||
const tableData = generateTableData(shortcutSection);
|
||||
|
||||
return (
|
||||
<section className="shortcut-section">
|
||||
<section key={shortcutSection} className="shortcut-section">
|
||||
<Typography.Text className="shortcut-section-heading">
|
||||
{shortcutSection}
|
||||
</Typography.Text>
|
||||
|
||||
@@ -45,4 +45,12 @@ export const appShellHandlers = [
|
||||
rest.get('https://cms.signoz.cloud/api/release-changelogs', (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(changelogResponse)),
|
||||
),
|
||||
|
||||
// The webfonts `index.html` links and `styles.scss` imports. The story
|
||||
// declares the same families over `public/fonts` in
|
||||
// `.storybook/public/storybook-fonts.css`, so answering the CDN with nothing
|
||||
// keeps a request from leaving the browser on every story.
|
||||
rest.get('https://fonts.googleapis.com/css2', (_req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.text('')),
|
||||
),
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user