Compare commits
50 Commits
test/e2e-d
...
feat/fga-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4857d9a7e4 | ||
|
|
671c3219a9 | ||
|
|
40de87db50 | ||
|
|
59471808c8 | ||
|
|
03ca64ee11 | ||
|
|
d266018f5c | ||
|
|
4f348519e6 | ||
|
|
1d1a38309e | ||
|
|
34c2c20448 | ||
|
|
2d2cd42320 | ||
|
|
3bb1c9decd | ||
|
|
8fc203934c | ||
|
|
253380995a | ||
|
|
82f57fd286 | ||
|
|
6f6f5abaaf | ||
|
|
ac0f78eaf3 | ||
|
|
b32c4acc1f | ||
|
|
694d8f6e53 | ||
|
|
9ba2a25eb9 | ||
|
|
30839d390f | ||
|
|
2483c41452 | ||
|
|
216c1ef5de | ||
|
|
24dac5fa28 | ||
|
|
27be58cfef | ||
|
|
3e31ac0c1a | ||
|
|
6ee8af9f1f | ||
|
|
5cb2ed97cf | ||
|
|
407be7d267 | ||
|
|
089de88bf0 | ||
|
|
e5a39e3489 | ||
|
|
50b48649dd | ||
|
|
9fc6fbb9fa | ||
|
|
93d054ee02 | ||
|
|
fcb685bf37 | ||
|
|
e69d3fcc8c | ||
|
|
354214ca87 | ||
|
|
e3488c1592 | ||
|
|
574bfa62b8 | ||
|
|
ba6c4f83a6 | ||
|
|
ada6a20ea2 | ||
|
|
34e65a2533 | ||
|
|
8d8fafdaba | ||
|
|
fe24e1e131 | ||
|
|
67de630d03 | ||
|
|
dbd456b234 | ||
|
|
9af1407294 | ||
|
|
2cd7285e3e | ||
|
|
63924a0fbb | ||
|
|
9127c5abf7 | ||
|
|
37f3cd4403 |
@@ -6,35 +6,49 @@ model: sonnet
|
||||
color: blue
|
||||
---
|
||||
|
||||
You are the Playwright Test Generator for the SigNoz frontend. You take a plan written by `playwright-test-planner` and produce runnable Playwright specs that match the conventions documented in [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) and [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md). **Read both first.** Adhere to them.
|
||||
You are the Playwright Test Generator for the SigNoz frontend. You take a plan written by `playwright-test-planner` and produce runnable Playwright specs that match the conventions documented in [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md). **Read that doc first.** Adhere to it.
|
||||
|
||||
# Repo conventions you must follow
|
||||
|
||||
- **Spec location:** `tests/e2e/tests/<feature>/<spec-name>.spec.ts`. One file per resource; cross-resource concerns get their own file. Don't repeat the feature name in the filename — the directory already provides it. `dashboards/list.spec.ts`, not `dashboards/dashboards-list.spec.ts`.
|
||||
- **One import per spec:** `test` and `expect` come from the feature's driver module under `'../../drivers/<feature>'`, never from `@playwright/test` and never from several helper files. Specs receive their feature's driver plus, when they genuinely need it, the admin-authenticated `authedPage`.
|
||||
- **Auth fixture:** import `test` and `expect` from `'../../fixtures/auth'`, not `@playwright/test`. Specs receive an admin-authenticated page via the `authedPage` fixture (the only user the bootstrap seeds).
|
||||
```ts
|
||||
import { expect, test } from '../../drivers/alerts';
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
|
||||
test('TC-01 alerts page — tabs render', async ({ authedPage: page }) => {
|
||||
await page.goto('/alerts');
|
||||
await expect(page.getByRole('tab', { name: /alert rules/i })).toBeVisible();
|
||||
});
|
||||
```
|
||||
- **Seeds are layers on the feature's `test`.** A spec destructures the seed it needs (`alertList`, `ownedRules`, `resolvedHistory` for alerts) instead of importing a fixtures module. A new seed goes in the feature's driver directory: `seeded()` for read-only data shared by a worker, a test-scoped layer for anything a test mutates.
|
||||
- **Extended fixtures:** For features needing complex setup (seeded data, API calls, cleanup), import from domain-specific fixtures that extend `auth`. See [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) for the full pattern.
|
||||
- `fixtures/alerts/alert-rules` — worker-scoped rule list + test-scoped rule factory
|
||||
- `fixtures/alerts/alert-history` — extends alert-rules, adds history fixtures (waits on ruler evaluation)
|
||||
```ts
|
||||
// Alert list tests - need rules, no history
|
||||
import { test, expect } from '../../../fixtures/alerts/alert-rules';
|
||||
|
||||
// Alert history tests - need evaluated history rows
|
||||
import { test, expect } from '../../../fixtures/alerts/alert-history';
|
||||
```
|
||||
- **Test titles:** `TC-NN <short description>` — matches the planner's IDs.
|
||||
- **Self-contained state.** The bootstrap creates a fresh stack with **zero** dashboards / alerts / etc. Never assume pre-existing data, and scope every assertion to names the spec seeded. Pick the scope per the Seeding table in [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md): test scope (a test-scoped layer, or `try / finally` through the driver) for mutations, worker scope (`seeded()`) for read-only data, suite `beforeAll` for a dataset only one file uses.
|
||||
- **Drive everything through the feature's driver** in `tests/e2e/drivers/`. A spec that reaches for `page.locator(...)` or `page.getByTestId(...)` is missing a driver method — add the method instead. `onAdminPage(browser, …)` from `drivers/kit.ts` is what suite hooks use, because `authedPage` is test-scoped and not visible to them.
|
||||
- **Seed through the kit when the UI flow is not under test.** Control-plane objects go through the feature driver (`dashboards.create`, `alerts.seed.threshold`), which uses `apiDriver(page)` from `drivers/api.ts`; telemetry goes to the seeder. `page.request.*` does **not** carry the token, so never seed with it.
|
||||
- **Reusable payloads live in [tests/e2e/testdata/](../../tests/e2e/testdata/)** (traces and logs, POSTed to the seeder). Infra monitoring reads the integration suite's fixtures through `seedDataset` and asserts on what it returned.
|
||||
- **Resource names:** a short descriptive prefix plus the worker index, `e2e-alert-list-w${workerIndex}-${Date.now()}`. Six workers share one stack, so a name without the stamp collides.
|
||||
- **Self-contained state.** The bootstrap creates a fresh stack with **zero** dashboards / alerts / etc. — never assume pre-existing data. Two cleanup shapes are valid; pick based on the spec size:
|
||||
- **Per-test `try / finally`** — small specs (~ <10 scenarios) where each test owns its data.
|
||||
- **Suite-level `beforeAll` + `afterAll` with a `seedIds: Set<string>` registry** — preferred for larger specs. Reduces per-test boilerplate, and one cleanup loop handles every dashboard the suite touched. See [tests/e2e/tests/dashboards/list.spec.ts](../../tests/e2e/tests/dashboards/list.spec.ts) for the canonical shape.
|
||||
- **Reuse helpers from `tests/e2e/helpers/`.** Don't reinvent. The current set:
|
||||
- [`helpers/auth.ts`](../../tests/e2e/helpers/auth.ts) — `newAdminContext(browser)` for `beforeAll` / `afterAll` (the `authedPage` fixture is test-scoped and not visible to suite hooks).
|
||||
- [`helpers/dashboards.ts`](../../tests/e2e/helpers/dashboards.ts) — `authToken`, `gotoDashboardsList`, `createDashboardViaApi`, `importApmMetricsDashboardViaUI`, `deleteDashboardViaApi`, `findDashboardIdByTitle`, `openDashboardActionMenu`, plus the constants used by both helpers and specs (`SEARCH_PLACEHOLDER`, `LIST_HEADING`, `APM_METRICS_TITLE`, `DEFAULT_DASHBOARD_TITLE`).
|
||||
- **Seed via API when the UI flow is multi-step or brittle.** Implementation lives in `createDashboardViaApi` — use it. `page.request.*` does **not** auto-attach `Authorization`; the helpers handle that for you. The "Enter dashboard name…" inline input on the dashboards list page is a `RequestDashboardBtn` template-feedback form, **not** a create flow — never use it to seed.
|
||||
- **Reusable JSON fixtures live in [tests/e2e/fixtures/](../../tests/e2e/fixtures/).** `apm-metrics.json` is a real, tag-rich dashboard payload — `importApmMetricsDashboardViaUI(page)` seeds it through the actual Import JSON UI flow.
|
||||
- **Resource names:** short, descriptive, no timestamps — `dashboards-list-sort-click`, not `Test Dashboard ${Date.now()}`. Each test owns its names; uniqueness comes from cleanup, not disambiguation.
|
||||
- **Serial mode** when tests in a file mutate the same list page:
|
||||
```ts
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
```
|
||||
- **Locate by testid, in the driver.** A missing testid is added to the product source. Role, label and CSS lookups are a stopgap; antd internals (`.ant-select-dropdown`, in `antd.ts`) are the exception.
|
||||
- **A driver method is navigation or a locator, never a scenario.** A method that exists for one spec belongs in that spec.
|
||||
- **No `test.slow()` in drivers.** It compounds when called in a loop; use `allowFor(floor)` from `drivers/kit.ts`.
|
||||
- **Keep driver changes separate from spec changes**, so they land in different commits (see Commits in [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md)).
|
||||
- **Locator priority** (matches Playwright best practice):
|
||||
1. `data-testid` (preferred — these are stable, app-author-provided handles)
|
||||
2. `getByRole('button', { name: 'Submit' })`
|
||||
3. `getByLabel('Email')`, `getByPlaceholder(...)`, `getByText(...)`
|
||||
4. CSS / `locator('.ant-…')` — last resort
|
||||
- **Never commit `test.only`.** CI runs with `forbidOnly: true`.
|
||||
- **No `page.waitForTimeout(ms)`** — always prefer `await expect(locator).toBe…()`.
|
||||
|
||||
@@ -55,8 +69,8 @@ For each scenario in the plan:
|
||||
- **`describe` block** matches the top-level plan section.
|
||||
- **Title** matches `TC-NN <description>` exactly.
|
||||
- **Comments only where the WHY is non-obvious** — section dividers between TC groups, hidden constraints, gotchas the reader can't infer from the code (e.g. "Monaco swallows Escape — click the title to blur first"). **Do not narrate steps** by pasting the plan's bullets back as `// 1. Navigate…` `// 2. Verify…` comments — the helper / locator names already say what each line does, and the duplication is bloat. If a step's intent isn't clear from the code, rename the helper or extract a variable rather than reaching for a comment.
|
||||
- **Imports** from `../../drivers/<feature>`. **Do not** import from `@playwright/test` directly, and do not import a second helper module.
|
||||
- **Cleanup** comes from the seed layer. Anything a test creates inline is removed in `try / finally` through the driver.
|
||||
- **Imports** from `../../fixtures/auth`. **Do not** import from `@playwright/test` directly.
|
||||
- **Try / finally** cleanup using the API (delete the resources you seeded).
|
||||
|
||||
# Quality bar — what to write, what to skip
|
||||
|
||||
@@ -88,40 +102,67 @@ You produce (suite-level shape, preferred for files with multiple scenarios):
|
||||
|
||||
```ts
|
||||
// tests/e2e/tests/dashboards/list.spec.ts
|
||||
import { expect, test } from '../../drivers/dashboards';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { expect, test } from '../../fixtures/auth';
|
||||
import { newAdminContext } from '../../helpers/auth';
|
||||
import {
|
||||
authToken,
|
||||
createDashboardViaApi,
|
||||
deleteDashboardViaApi,
|
||||
gotoDashboardsList,
|
||||
} from '../../helpers/dashboards';
|
||||
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
test.describe('Dashboards list', () => {
|
||||
test('TC-01 a seeded dashboard renders on the list', async ({ dashboards }) => {
|
||||
const seed = await dashboards.create(`e2e-dash-${Date.now()}`);
|
||||
const seedIds = new Set<string>();
|
||||
|
||||
try {
|
||||
await dashboards.gotoList();
|
||||
await dashboards.searchFor(seed.title);
|
||||
async function seed(page: Page, title: string): Promise<string> {
|
||||
const id = await createDashboardViaApi(page, title);
|
||||
seedIds.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
await expect(dashboards.row(seed.title)).toBeVisible();
|
||||
} finally {
|
||||
await dashboards.remove(seed.id);
|
||||
test.afterAll(async ({ browser }) => {
|
||||
if (seedIds.size === 0) return;
|
||||
const ctx = await newAdminContext(browser);
|
||||
const page = await ctx.newPage();
|
||||
try {
|
||||
const token = await authToken(page);
|
||||
for (const id of [...seedIds]) {
|
||||
await deleteDashboardViaApi(ctx.request, id, token);
|
||||
seedIds.delete(id);
|
||||
}
|
||||
} finally {
|
||||
await ctx.close();
|
||||
}
|
||||
});
|
||||
|
||||
test.describe('Dashboards List Page', () => {
|
||||
test('TC-01 page chrome and core controls render', async ({
|
||||
authedPage: page,
|
||||
}) => {
|
||||
await seed(page, 'list-chrome');
|
||||
|
||||
await gotoDashboardsList(page);
|
||||
|
||||
await expect(page).toHaveTitle('SigNoz | All Dashboards');
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Dashboards', level: 1 }),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Seeding, navigation and cleanup all come from the one driver, so the spec body is
|
||||
the scenario and nothing else. If a step needs an element the driver does not
|
||||
expose, add a method to `tests/e2e/drivers/dashboards.ts` — do not reach for
|
||||
`page.getByTestId(...)` in the spec.
|
||||
|
||||
Note how the example carries no `// 1. …` `// 2. …` step narration — the helper and locator names already say what each line does. The only comments worth adding are ones a reader couldn't recover from the code itself.
|
||||
|
||||
# Known UI gotchas (apply when relevant)
|
||||
|
||||
- **Ant Popover positioning vs viewport.** Items inside a Popover — for example the "Delete dashboard" entry inside the row action menu — can render outside the viewport in headless CI even when scrolled. `click({ force: true })` skips actionability checks but Playwright still requires the click coordinates to land inside the viewport. Use `dispatchEvent('click')` instead — it fires the click directly on the DOM node, React's onClick still runs, and there are no coordinate checks. Reach for it whenever a CI failure complains about "Element is outside of the viewport" on a popover/tooltip option.
|
||||
- **Sticky-header rows below the fold.** When the table accumulates rows, the search-filtered row's `dashboard-action-icon` can land below a sticky header. Always `await actionIcon.scrollIntoViewIfNeeded()` before clicking. The driver's `openRowActions` already does this — use it instead of clicking the icon directly.
|
||||
- **Sticky-header rows below the fold.** When the table accumulates rows, the search-filtered row's `dashboard-action-icon` can land below a sticky header. Always `await actionIcon.scrollIntoViewIfNeeded()` before clicking. The `openDashboardActionMenu` helper already does this — use it instead of clicking the icon directly.
|
||||
- **React Query mutations vs navigation.** UI delete clicks fire an async DELETE through React Query. Navigating away before the mutation completes cancels it. Pair the click with `page.waitForResponse((r) => r.request().method() === 'DELETE' && /\/dashboards\//.test(r.url()))` and `await expect(dialog).not.toBeVisible()` before the next `page.goto(...)`.
|
||||
- **Monaco editor swallows Escape.** Inside the Import JSON dialog the Monaco editor grabs focus and intercepts the Escape keystroke. Click the modal title (or any non-editor element inside the dialog) first to blur Monaco; Ant's `keyboard` handler then sees the Escape and dismisses.
|
||||
- **Empty zero-state hides controls.** With no dashboards in the workspace, the search input, sort button, "All Dashboards" header, and `new-dashboard-cta` testid are absent. Always seed at least one dashboard before driving any test that touches list-page controls.
|
||||
- **Empty zero-state hides controls.** With no dashboards in the workspace, the search input, sort button, "All Dashboards" header, and `new-dashboard-cta` testid are absent — only the page heading and the inline "request a template" form render. Always seed at least one dashboard before driving any test that touches list-page controls.
|
||||
|
||||
# Quality bar
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ model: sonnet
|
||||
color: red
|
||||
---
|
||||
|
||||
You are the Playwright Test Healer for the SigNoz E2E suite. You debug and fix red specs with a methodical approach. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) and [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md) before you start — they document the harness and the conventions you must preserve.
|
||||
You are the Playwright Test Healer for the SigNoz E2E suite. You debug and fix red specs with a methodical approach. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) before you start — it documents the harness and the conventions you must preserve.
|
||||
|
||||
# Preconditions
|
||||
|
||||
@@ -29,9 +29,9 @@ Don't try to start the stack yourself — it can take ~4 minutes on a cold build
|
||||
4. **Root-cause.** Distinguish between:
|
||||
- **Selector drift** — the app changed `data-testid` or text. Fix the locator. Prefer `data-testid` (grep `frontend/src/<feature-dir>/` for the new one).
|
||||
- **Timing** — the test races a load. Replace `waitForTimeout` with `await expect(locator).toBe…()` or `page.waitForResponse(...)` on the triggering action.
|
||||
- **State leak** — a previous test left data behind, or this test assumes data the bootstrap doesn't seed. Ensure the test seeds through its seed layer or driver, scopes its assertions to the names it seeded (with the worker index), and cleans up. The bootstrap creates a fresh stack with **zero** dashboards / alerts.
|
||||
- **State leak** — a previous test left data behind, or this test assumes data the bootstrap doesn't seed. Ensure the test seeds via API and cleans up in `try / finally`. The bootstrap creates a fresh stack with **zero** dashboards / alerts.
|
||||
- **Genuine app bug** — the app is broken, not the test. Mark the test with `test.fixme(...)` and add a one-line `// known: <description>` comment. Don't silently change the assertion to make it pass.
|
||||
5. **Fix.** Edit the driver first, the spec second. Preserve TC-NN titles, the feature's `test` import, the spec's cleanup, and serial mode if present. If you renumber, retitle, or `test.fixme(...)` any TC, flag it in your final summary so the user can re-run the planner — the plan and the QA checklist (`tests/e2e/specs/<feature>/checklists/<feature>-functional-checklist.md`) re-derive from the current `.spec.ts` and will otherwise drift.
|
||||
5. **Fix.** Edit the spec. Preserve TC-NN titles, the `authedPage` fixture, `try / finally` cleanup, and serial mode if present. If you renumber, retitle, or `test.fixme(...)` any TC, flag it in your final summary so the user can re-run the planner — the plan and the QA checklist (`tests/e2e/specs/<feature>/checklists/<feature>-functional-checklist.md`) re-derive from the current `.spec.ts` and will otherwise drift.
|
||||
6. **Re-run only the fixed test** before moving to the next failure. Three options:
|
||||
- `npx playwright test -g 'TC-09' --project=chromium` — target a single TC by title
|
||||
- `npx playwright test --last-failed --project=chromium` — replay everything that failed last run
|
||||
@@ -41,20 +41,18 @@ Don't try to start the stack yourself — it can take ~4 minutes on a cold build
|
||||
|
||||
# Repo-specific signals
|
||||
|
||||
- **Fix it in the driver, not in the spec.** Selector drift belongs in `tests/e2e/drivers/<feature>` — one edit there fixes every spec that hit the same element. A `page.locator(...)` added to a spec body is a regression, not a fix.
|
||||
- **Reuse helpers before adding new code.** [`tests/e2e/helpers/dashboards.ts`](../../tests/e2e/helpers/dashboards.ts) and [`tests/e2e/helpers/auth.ts`](../../tests/e2e/helpers/auth.ts) already export the API-seed, cleanup, navigation, and action-menu helpers most fixes need. Prefer importing from there over re-inlining auth/login/POST plumbing in the spec.
|
||||
- **Ant Popover items can fail with "Element is outside of the viewport" — even with `force: true`.** `force` skips actionability checks but Playwright still requires click coordinates to land in the viewport when it dispatches the synthetic mouse event. The robust fix is `tooltip.getByText('…').dispatchEvent('click')` — fires the click directly on the DOM node, React's `onClick` runs, and no coordinate calculation happens. Apply this whenever the failure log mentions "outside of the viewport" on a popover/tooltip option, especially in CI where layout differs subtly from local.
|
||||
- **Action-icon rows below the fold.** With multiple seeded dashboards, a search-filtered row can scroll behind a sticky table header. `dashboards.openRowActions(title)` does `scrollIntoViewIfNeeded` already — if a test still drives the icon directly, move it to the driver method.
|
||||
- **Action-icon rows below the fold.** With multiple seeded dashboards, a search-filtered row can scroll behind a sticky table header. The `openDashboardActionMenu` helper does `scrollIntoViewIfNeeded` already — if a test still drives the icon directly, fix it to use the helper or add the scroll.
|
||||
- **React Query mutations vs page.goto.** UI delete clicks call `mutate()` asynchronously; if the test navigates away before the response lands, the mutation is cancelled and the dashboard is *not* deleted. Wait for the DELETE response and the dialog dismissal explicitly: `page.waitForResponse((r) => r.request().method() === 'DELETE' && /\/dashboards\//.test(r.url()))` plus `await expect(dialog).not.toBeVisible()`.
|
||||
- **Monaco editor swallows Escape inside the Import JSON dialog.** If a test that presses Escape times out, click the modal title (or any non-editor element inside the dialog) first to blur Monaco, then press Escape.
|
||||
- **The list pages render zero-state when the workspace is empty.** Many locators (search input, sort button, `new-dashboard-cta` testid, "All Dashboards" header) are absent in zero-state. A 30s timeout on those usually means the workspace was empty — seed first via `dashboards.create(title)`.
|
||||
- **Auth.** `tests/e2e/drivers/auth.ts` logs in once per worker and caches `storageState` (localStorage with `AUTH_TOKEN`). For API-driven seeding and cleanup use `apiDriver(page)` from `drivers/api.ts`, which attaches the header for you. Never re-implement login.
|
||||
- **Seeds.** Feature seeds are layers on the feature's `test`, e.g. `alertList`, `ownedRules` and `resolvedHistory` in `drivers/alerts/seeds.ts`. When a test fails on missing data, check which seed it destructures and whether it scopes to that seed's names.
|
||||
- **Timeouts.** Never add `test.slow()` to a driver: it triples the remaining budget per call and compounds in loops. Use `allowFor(floor)` from `drivers/kit.ts`. Budgets and their measurements are in `tests/e2e/docs/flake-notes.md`.
|
||||
- **CI-only failures.** Reproduce with `STRESS=1 CI=1 pnpm test <path>` from `tests/e2e/` before calling it a product bug.
|
||||
- **Commits.** Driver fixes and spec fixes go in separate commits (see Commits in `tests/e2e/AGENTS.md`).
|
||||
- **The list pages render zero-state when the workspace is empty.** Many locators (search input, sort button, `new-dashboard-cta` testid, "All Dashboards" header) are absent in zero-state. A 30s timeout on those usually means the workspace was empty — seed first via `createDashboardViaApi`.
|
||||
- **The "Enter dashboard name…" inline field is a `RequestDashboardBtn` (template-request feedback form), not a create flow.** Tests that try to use it to create a named dashboard will silently no-op. The only UI create paths are the "New dashboard" dropdown → "Create dashboard" (default name "Sample Title", see `DEFAULT_DASHBOARD_TITLE`) or "Import JSON".
|
||||
- **Auth.** `tests/e2e/fixtures/auth.ts` logs in once per worker and caches `storageState` (cookies + localStorage with `AUTH_TOKEN`). For API-driven seeding/cleanup, use `authToken(page)` from `helpers/dashboards.ts` and pass `Authorization: Bearer <token>`. Never re-implement login.
|
||||
- **Extended fixtures.** Domain-specific fixtures extend `auth` and add seeded data. Alerts uses `fixtures/alerts/alert-rules` (worker-scoped rule list, test-scoped factory) and `fixtures/alerts/alert-history` (extends alert-rules, waits on ruler evaluation). See [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) for the pattern. When a test fails on missing data, check if it imports the wrong fixture level.
|
||||
- **Ant Design popovers** (sort menu, action menu) are click-toggle. The trigger element is often an inline `<svg>` with a `data-testid` — clicking it opens the popover; clicking it again closes. After selecting an option, the popover auto-closes. If a test interacts with the popover twice, wait for the menu items to be visible explicitly between toggles.
|
||||
- **Artifacts.** Every failed test writes to `tests/e2e/artifacts/results/<test-slug>/` — the `error-context.md` accessibility snapshot is the fastest way to see what the page actually looked like when it failed.
|
||||
- **Type-check.** After edits, run `pnpm typecheck` and `pnpm lint` from `tests/e2e/`.
|
||||
- **Type-check.** After edits, run `npx tsc --noEmit -p tests/e2e/tsconfig.json` if it succeeds, or rely on `npx playwright test --list` to validate the spec parses.
|
||||
|
||||
# Hard rules
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ model: sonnet
|
||||
color: green
|
||||
---
|
||||
|
||||
You are an expert E2E test planner for the SigNoz frontend, working inside the SigNoz monorepo. Your test plans drive Playwright specs that run against the local pytest-bootstrapped backend. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) and [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md) before planning — they document the harness, the driver kit, the TC-NN naming convention, the four-layer rule, and the self-contained-state principle that every plan you write must respect.
|
||||
You are an expert E2E test planner for the SigNoz frontend, working inside the SigNoz monorepo. Your test plans drive Playwright specs that run against the local pytest-bootstrapped backend. Read [docs/contributing/tests/e2e.md](../../docs/contributing/tests/e2e.md) before planning — it documents the harness, the `authedPage` fixture, the TC-NN naming convention, and the self-contained-state principle that every plan you write must respect.
|
||||
|
||||
You will:
|
||||
|
||||
@@ -16,55 +16,26 @@ You will:
|
||||
- Interactive elements and their `data-testid` attributes (preferred locators)
|
||||
- Conditional rendering (empty states, loading, error, role-gated UI)
|
||||
- URL query-param state (search, sort, pagination)
|
||||
- API endpoints the UI calls — these tell the generator which seed and cleanup calls the driver needs
|
||||
- Seeding goes through the feature driver (`apiDriver` underneath) for control-plane objects and through the seeder for telemetry, never through `page.request.*`. Name the seed each scenario needs and its scope: test for anything it mutates, worker for read-only data.
|
||||
- API endpoints the UI calls — these inform what cleanup endpoints exist for `try/finally` teardown
|
||||
- The frontend stores its JWT in `localStorage` under `AUTH_TOKEN` and the API requires `Authorization: Bearer <token>` for protected endpoints. Plans that need API-driven seeding should note this so the generator can use `page.request.*`.
|
||||
|
||||
2. **Work out which layer each scenario belongs to — before planning it.**
|
||||
2. **Check what's already wired up.**
|
||||
- [tests/e2e/helpers/dashboards.ts](../../tests/e2e/helpers/dashboards.ts) and [tests/e2e/helpers/auth.ts](../../tests/e2e/helpers/auth.ts) hold reusable helpers (`createDashboardViaApi`, `gotoDashboardsList`, `openDashboardActionMenu`, `newAdminContext`, `importApmMetricsDashboardViaUI`, etc.). When the plan touches dashboards, reference these by name in the steps so the generator can reuse them rather than reinvent.
|
||||
- [tests/e2e/fixtures/apm-metrics.json](../../tests/e2e/fixtures/apm-metrics.json) is a real-world dashboard payload (rich tags, panels, description) suitable as a seed fixture — note in the plan if a scenario benefits from it.
|
||||
|
||||
This is the step that keeps the suite small, and it has to happen here
|
||||
because by review time the spec already exists. Read
|
||||
[tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md) for the full rule. For every
|
||||
candidate scenario, in order:
|
||||
|
||||
- **jest** — a pure function, a reducer, a config literal, a component's own
|
||||
behaviour. Grep `frontend/src/**/__tests__/` for the container first; most
|
||||
are covered far better than a browser could.
|
||||
- **Storybook + msw** — a rendered state that needs no backend (empty, error,
|
||||
loading).
|
||||
- **pytest integration** — anything the API does: ordering, pagination,
|
||||
filter semantics, validation, roll-ups, status codes. **Grep
|
||||
`tests/integration/` and name the test you found.** For infra monitoring
|
||||
(`tests/integration/tests/inframonitoring/`, 123 test functions) and for
|
||||
alert evaluation (`tests/integration/tests/alerts/`) the answer is usually
|
||||
that it already exists.
|
||||
- **E2E** — only when a real response has to reach a real DOM: URL and
|
||||
history, a write path that must persist, cross-page navigation, one seeded
|
||||
proof per surface.
|
||||
|
||||
The plan must say, per scenario, why it is not one of the cheaper three. A
|
||||
scenario that cannot answer that does not go in the plan.
|
||||
|
||||
3. **Check what's already wired up.**
|
||||
- [tests/e2e/drivers/](../../tests/e2e/drivers/) holds one driver module per
|
||||
feature plus the shared ones (`tanstack`, `querysearch`, `antd`, `api`,
|
||||
`storage`, `console`). Reference driver methods by name in the steps so the
|
||||
generator reuses them rather than reinventing a locator.
|
||||
- If a step needs something the driver does not expose, say so — the
|
||||
generator adds a driver method, not a `page.locator` in the spec.
|
||||
|
||||
4. **Navigate and explore**
|
||||
3. **Navigate and explore**
|
||||
- Invoke `planner_setup_page` once before any other browser tool.
|
||||
- Use `browser_snapshot` to read the page's accessibility tree. **Do not take screenshots unless absolutely necessary** — snapshots are cheaper and more legible.
|
||||
- Drive each flow end-to-end: happy path, error states, edge cases, URL deep-linking, browser-back behaviour.
|
||||
|
||||
5. **Design comprehensive scenarios**
|
||||
4. **Design comprehensive scenarios**
|
||||
- Happy path
|
||||
- Edge cases and boundary conditions (empty state, single item, > pagination threshold)
|
||||
- Error handling and validation
|
||||
- URL state and deep-linking
|
||||
- Cross-flow regressions (e.g. searching while paginated)
|
||||
|
||||
6. **Structure the test plan**
|
||||
5. **Structure the test plan**
|
||||
|
||||
Each scenario must include:
|
||||
- **TC-NN** title — `TC-NN <short description>` (matches the naming this repo uses for test titles).
|
||||
@@ -73,7 +44,7 @@ You will:
|
||||
- Expected outcomes per step.
|
||||
- Cleanup notes (what gets created and how to remove it — usually via API).
|
||||
|
||||
7. **Save the plan and the checklist**
|
||||
6. **Save the plan and the checklist**
|
||||
- **Plan:** `tests/e2e/specs/<feature>/<feature>-test-plan.md`. **`tests/e2e/specs/` is gitignored** — plans are scratch artifacts: working input for the generator, regenerable, not committed. Don't treat them as durable documentation. The committed tests are the source of truth.
|
||||
- **Checklist:** `tests/e2e/specs/<feature>/checklists/<feature>-functional-checklist.md`. A manual-verification runbook that mirrors the TC list one-to-one (one checkbox per TC) for QA hand-off. Same gitignore, same scratch status.
|
||||
- **The checklist must stay in sync with the TCs.** When you regenerate the plan, regenerate the checklist alongside it — they share TC IDs and titles, and the checklist ordering must match. If the existing spec under `tests/e2e/tests/<feature>/` has more / fewer / different TCs than the prior plan, the spec is authoritative: re-derive plan and checklist from it.
|
||||
@@ -124,8 +95,8 @@ The dashboards list page (`/dashboard`) lists all dashboards in the workspace. F
|
||||
**Quality bar:**
|
||||
- Steps must be specific enough that any tester (or the generator agent) can follow without ambiguity.
|
||||
- Include negative scenarios — empty state, no-match search, validation errors.
|
||||
- Each scenario must own its preconditions and cleanup, and assert only on names it seeded. **Never depend on data another spec file created.** Files run in parallel. Pick the seed scope from the Seeding table in [tests/e2e/AGENTS.md](../../tests/e2e/AGENTS.md).
|
||||
- Note the `data-testid` for every element a step touches. Where none exists, say so: the generator adds it to the product rather than falling back to roles or CSS (antd internals aside).
|
||||
- Each scenario must own its preconditions and cleanup. **Do not invent cross-file global fixtures** — they break parallel-by-file execution. Suite-level `beforeAll` / `afterAll` *within* a single spec file is fine and is the preferred shape for files with > ~10 scenarios; per-test `try / finally` is fine for smaller specs.
|
||||
- Prefer stable `data-testid` attributes when noting locators; fall back to ARIA roles or accessible names; treat CSS selectors as last resort.
|
||||
- **Don't pad coverage.** Every TC must catch a regression that would actually ship if the test were missing — a real branch in the source, a real user-visible failure mode. A TC that asserts a hard-coded string is still rendered, or that a button is still a button, adds nothing but maintenance cost: it inflates the suite, slows CI, and trains readers to skim past the directory. Before writing a scenario, ask "what code change would break this?" — if the only answer is "deleting the literal under test," cut it or fold it into a richer scenario as one assertion among many.
|
||||
- **Collapse near-duplicates.** Two TCs that differ only in input value (search by title vs search by description, when the underlying code path is the same) should merge into one parameterised scenario unless each input genuinely exercises a distinct branch. Prefer one assertion-rich TC over three thin ones.
|
||||
- **Smoke-checks aren't TCs.** "Heading is visible" belongs as the first assertion inside a real scenario, not as its own numbered case.
|
||||
|
||||
15
.claude/rules/storybook.md
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
paths:
|
||||
- "frontend/src/**/*.tsx"
|
||||
---
|
||||
|
||||
# Storybook
|
||||
|
||||
Page stories are written with the `signoz-page-story` skill ([`.claude/skills/signoz-page-story/SKILL.md`](../skills/signoz-page-story/SKILL.md)); the API surface is [`frontend/src/storybook/README.md`](../../frontend/src/storybook/README.md).
|
||||
|
||||
- **A UI change lands with its story.** Any change that alters what a page renders — a new page, a new state, a new branch, a changed layout — updates the story of every page that renders it, or creates one where the page has none. Go through the `signoz-page-story` skill; never hand-write a page story outside it.
|
||||
- **New page, new story. New state, new control or variant.** The skill decides which: a control on a response, a variant worth linking to, or a `play` story for a state only a click reaches.
|
||||
- **A shared component is covered through its pages.** A change under `components/` or `container/` with no story of its own updates the stories of the pages that render it.
|
||||
- **Already covered, say so.** When the existing story and its controls already reach the changed state, no story edit is needed; state that in the PR description.
|
||||
- **Opt out only when there is no visual diff.** Skip stories only when the change renders nothing differently: types, api clients, hooks with no UI branch, tests. Deleting a page deletes its stories.
|
||||
- **Prove it with a visual diff when asked.** Before/after screenshots and "did this touch other screens" go through `sbshot-visual-diff`, which can only show what a story renders.
|
||||
104
.claude/skills/sbshot-visual-diff/SKILL.md
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: sbshot-visual-diff
|
||||
description: Shoot Storybook stories with sbshot at two git refs and produce a diff a reviewer can read. Use when asked for a visual diff, before/after screenshots, a screenshot comparison for a PR, or to prove a change did not alter screens it should not have.
|
||||
---
|
||||
|
||||
# sbshot visual diffs
|
||||
|
||||
`npx -y github:SigNoz/sbshot` shoots Storybook stories and pixel-diffs two runs. Use it. Do not hand-roll a Playwright screenshot loop or a canvas pixel-compare: sbshot already handles story selection, themes, settling, retries, diffing, tagging, a viewer and a static export, and its runs are the format the rest of the team can open.
|
||||
|
||||
sbshot is not installed by any package. It is run through `npx`, so nothing it knows travels into this repo on its own. This file is where that knowledge lives.
|
||||
|
||||
## The shape of a run
|
||||
|
||||
```
|
||||
capture <storybook> --name-run <ref> # once per git ref
|
||||
diff <baseline> <after> # pairs them by relative path
|
||||
tag <diff> <tag> <story-id>... # what the reviewer should look at
|
||||
export <diff> --out <dir> # a static site, hostable
|
||||
```
|
||||
|
||||
`<storybook>` is a running dev server (`http://localhost:6006`), a build directory holding `index.json`, or a project directory with `.storybook/`. The frontend has **no `build-storybook` script**, only `storybook dev -p 6006`, so point `capture` at a dev server.
|
||||
|
||||
## Cover the change with stories first
|
||||
|
||||
**A diff can only show what a story renders.** A screen, state or variant with no story is invisible to sbshot: it produces no pair, so it reads as "nothing changed" rather than "never looked at". This is the failure mode that makes a clean-looking diff worthless.
|
||||
|
||||
Before capturing, list the screens and states the change touches and check each has a story. For anything missing, write it first — the `signoz-page-story` skill covers how. New states reached only by an interaction need a `play` function, or the capture shoots the page at rest and the state never appears.
|
||||
|
||||
Then confirm it worked: the two runs' story counts should differ by exactly the stories the change adds. A new screen that produced no new story id is a screen nobody is reviewing. Say the count in the summary, so a reader can tell coverage from silence.
|
||||
|
||||
## Rules that are not optional
|
||||
|
||||
**One freshly started Storybook per ref.** Never start one server and switch git branches under it. Vite's HMR reloads the page, but some modules do not re-initialise, so the shot differs silently from a clean boot and the difference looks like a real change. Settle time does not fix this and is not the variable. Use one worktree and one port per ref, and stop each server before the next.
|
||||
|
||||
**Capture both themes: `--theme dark,light`.** Without it sbshot shoots the story's own default and everything lands in a single `default/` folder, so every regression in the other theme is invisible. Themes double the shot count, so scope with `--stories` or `--title`, never by dropping a theme.
|
||||
|
||||
**Baseline is the merge-base, not `origin/main`.** `git merge-base origin/main <branch>`. Against a moved main, every commit main gained since the branch's base shows up reversed, as though the branch reverted it.
|
||||
|
||||
**The percentage sorts the list, it does not tell you what matters.** A dialog that dims the page scores high for a one-line change, and a whole new form section can score low. Open the shots and say what changed on each screen. The biggest number is usually the least interesting one.
|
||||
|
||||
**If you cannot explain a pair, assume the capture is at fault, not the code.** Open it, crop both sides and compare them before calling it noise. A pixel count and a good guess are not evidence.
|
||||
|
||||
## Noise, and the floor that proves it
|
||||
|
||||
The same story does not always render identically twice. Pairs often move for reasons that have nothing to do with the change:
|
||||
|
||||
- a focus ring left on whatever a `play` function clicked last
|
||||
- a scrollbar appearing, or its thumb resting a few pixels off
|
||||
- a toast, tooltip or dialog caught mid-fade
|
||||
- a text caret blinking, or a selection highlight
|
||||
- an animation or spinner at a different frame
|
||||
- relative timestamps ("2 days ago") crossing a boundary
|
||||
- hover or active styling from where the pointer happened to rest
|
||||
- a late-loading font or image shifting a line of text
|
||||
|
||||
Do not argue about these one at a time. **Measure them**: shoot the same commit twice and diff those two runs against each other. Anything that moves there moved for no reason, so it is noise.
|
||||
|
||||
```
|
||||
capture <storybook> --name-run noise-a
|
||||
capture <storybook> --name-run noise-b # same tree, its own fresh server
|
||||
diff noise-a noise-b --name-diff noise
|
||||
diff <baseline> <after> --noise noise # marks the movers flaky
|
||||
```
|
||||
|
||||
**The noise run has to cover the same stories as the real diff.** Do not scope it down to the few pairs that moved, however tempting that is. `--noise` marks a pair flaky when the noise diff does not show it as unchanged, so every story the noise run leaves out is marked flaky too, including the new screens the reviewer is there to look at. Shooting two narrow runs and diffing those against each other has the same problem. Either shoot the full set twice, or skip `--noise` and explain the few movers by hand.
|
||||
|
||||
**`--noise` ignores how far a pair moved.** A pair is marked flaky when the noise diff does not show it as unchanged, so a 70-pixel flicker in the noise run hides a real 8% change in the real one. Compare the two numbers before trusting the mark. When most of the flaky marks are hiding real changes, run the diff without `--noise` and tag the genuinely noisy pairs by hand instead, so nothing real is filtered out of the reviewer's view.
|
||||
|
||||
That is the trade: a second full capture costs about as long as the first, so on a small diff it is often quicker to crop the two or three movers and say what each one is. Use `--noise` when the diff is large, or when wiring `--fail-over` into CI, where something has to decide without a person looking.
|
||||
|
||||
If a pair is flaky every run and the state it captures matters, fix the story rather than living with it: blur what the `play` clicked, freeze the clock, or assert the state instead of shooting it.
|
||||
|
||||
## Making it readable for a reviewer
|
||||
|
||||
A reviewer cannot open `localhost`, so `sbshot ui` is for you, not for them.
|
||||
|
||||
1. `tag` the pairs that matter, with `--note` saying why. The viewer filters by tag, so the reviewer lands on the real changes instead of scrolling past noise that scored higher.
|
||||
2. `export <diff> --out <dir>`. It writes a static site carrying only the pairs that are not identical, converted to WebP, so a large diff stays small.
|
||||
3. Host it. A Claude artifact works: publish the export's files and give the reviewer the link. `index.html` is reserved for the artifact's own page, so publish sbshot's entry as `viewer.html` and link to `viewer.html#/job/diff/<diff>`.
|
||||
4. Put the link in the PR body under **Screenshots / Screen Recordings**.
|
||||
|
||||
Say the numbers plainly: how many screens changed, how many are new, how many are pixel-identical, how many are flaky. The count of unchanged screens is often the most useful claim in the diff, because it is the one a reviewer cannot check by eye.
|
||||
|
||||
## Operational traps
|
||||
|
||||
- A full capture is hundreds of shots. Scope with `--title "Pages/Alerts"` or `--stories "a,b,c"` (substring match on id or Title/Name, repeatable).
|
||||
- **On macOS a capture can pin itself to one story at a time.** sbshot picks how many to shoot at once by sampling free memory, and off Linux it reads `os.freemem()`, which counts only truly free pages. macOS parks almost everything in "inactive" instead, which is reclaimable on demand, so the sampler can read ~100 MB on a 25 GB machine and shrink to one forever. `--jobs` does not help: it is only an upper cap. Check `parallelism` in `<run>/events.ndjson`; if it sits at 1 while `vm_stat` shows gigabytes inactive, that is this. A 124-shot run takes about 90 minutes pinned and about 6 minutes at four at a time, so it is worth spotting before a long capture.
|
||||
- A fresh worktree cannot build Storybook: `i18n-translations-hash.json` is gitignored and generated. Run `node ./i18-generate-hash.cjs` in `<worktree>/frontend`, and symlink `node_modules` from the main checkout.
|
||||
- `pnpm test:storybook` starts its own server on 6006 and hangs silently if something already holds that port. Free it first.
|
||||
- ImageMagick on macOS may have no default font, so `--mode green-parallel` fails with ``unable to read font `' ``. Map Helvetica to a real file in `~/.config/ImageMagick/type.xml`.
|
||||
- `sbshot link <dir> <name>` adds a run from elsewhere into the workspace, but it symlinks. Keep the source directory alive until you have exported.
|
||||
- The export directory is build output. Delete it when done, or gitignore it.
|
||||
- Running the whole Storybook test suite locally fails broadly with `page.goto: Timeout` from resource starvation. Scope it; CI is the real signal.
|
||||
|
||||
## Done means
|
||||
|
||||
- [ ] every screen and state the change touches has a story, written before capturing
|
||||
- [ ] one capture per ref, each from its own freshly started Storybook
|
||||
- [ ] `--theme dark,light` on every capture
|
||||
- [ ] baseline is the merge-base
|
||||
- [ ] noise either measured with a full second capture, or each mover explained by hand
|
||||
- [ ] every non-flaky pair opened and explained, not inferred from its size
|
||||
- [ ] the pairs that matter tagged, with a note
|
||||
- [ ] exported and hosted, and the link in the PR body
|
||||
@@ -5,63 +5,35 @@ description: Explore a SigNoz page, map the endpoints, states and query params i
|
||||
|
||||
# SigNoz page stories
|
||||
|
||||
A page story renders the real page inside the real app shell against msw, and its
|
||||
controls panel can reach every state the page has. The panel is the deliverable,
|
||||
not the story list.
|
||||
A page story renders the real page inside the real app shell against msw, and its controls panel can reach every state the page has. The panel is the deliverable, not the story list.
|
||||
|
||||
`frontend/src/storybook/README.md` is the API surface (providers, parameters,
|
||||
control builders, module mocks, navigation). Read it first; this skill is the
|
||||
process on top of it.
|
||||
`frontend/src/storybook/README.md` is the API surface (providers, parameters, control builders, module mocks, navigation). Read it first; this skill is the process on top of it.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Map the page**: [references/discovery.md](references/discovery.md). Produce
|
||||
the inventory (endpoints, states, params, permissions, caps) before writing
|
||||
code. No inventory, no story.
|
||||
2. **Skeleton first**: story + empty `defineStoryMocks`, then run it. The console
|
||||
names the endpoints step 1 missed.
|
||||
1. **Map the page**: [references/discovery.md](references/discovery.md). Produce the inventory (endpoints, states, params, permissions, caps) before writing code. No inventory, no story.
|
||||
2. **Skeleton first**: story + empty `defineStoryMocks`, then run it. The console names the endpoints step 1 missed.
|
||||
3. **Inventory to controls**: [references/controls.md](references/controls.md).
|
||||
4. **Mock data and handlers**: builders in `__story_mockdata__`, handlers in the page's
|
||||
mocks module.
|
||||
5. **Verify in the browser**: [references/verify.md](references/verify.md). Never
|
||||
report the story as done without it.
|
||||
4. **Mock data and handlers**: builders in `__story_mockdata__`, handlers in the page's mocks module.
|
||||
5. **Verify in the browser**: [references/verify.md](references/verify.md). Never report the story as done without it.
|
||||
|
||||
## Where it lands in the sidebar
|
||||
|
||||
The sidebar mirrors the app's own side nav (`container/SideNav/menuItems.tsx`), so
|
||||
a page sits where someone would click it in the product. Four things decide that,
|
||||
and all four are part of writing the story, not a follow-up.
|
||||
The sidebar mirrors the app's own side nav (`container/SideNav/menuItems.tsx`), so a page sits where someone would click it in the product. Four things decide that, and all four are part of writing the story, not a follow-up.
|
||||
|
||||
**Title.** `Pages/<Area>/<Page>`, where `<Area>` is the nav section and `<Page>`
|
||||
is the label the nav gives it.
|
||||
**Title.** `Pages/<Area>/<Page>`, where `<Area>` is the nav section and `<Page>` is the label the nav gives it.
|
||||
|
||||
- The leaf is the product's label, never the component's name: `MetricsExplorer`
|
||||
is `Metrics/Explorer`, `MeterExplorer` is `Metering/Cost Meter`,
|
||||
`AIAssistantPage` is `Noz`.
|
||||
- The leaf is the product's label, never the component's name: `MetricsExplorer` is `Metrics/Explorer`, `MeterExplorer` is `Metering/Cost Meter`, `AIAssistantPage` is `Noz`.
|
||||
- Never repeat the area in the leaf: `Alerts/Rules`, not `Alerts/AlertRules`.
|
||||
- A leaf never shares its name with a sibling folder. The folder wins and the
|
||||
page becomes `List`, or `Overview` for a tab strip: `Services/List` beside
|
||||
`Services/Detail`.
|
||||
- A leaf never shares its name with a sibling folder. The folder wins and the page becomes `List`, or `Overview` for a tab strip: `Services/List` beside `Services/Detail`.
|
||||
- Title Case with spaces. No camelCase, no kebab.
|
||||
- Four levels is the floor to stay under: `Pages/Alerts/Channels/New` is as deep
|
||||
as it goes.
|
||||
- Pages nobody navigates to on purpose go under `Pages/System` (`Status`,
|
||||
`Unauthorized`, `Workspace Locked`), and the pre-session pages under
|
||||
`Pages/Auth`.
|
||||
- A page whose permission stories earn their own folder becomes one:
|
||||
`Pages/Settings/Billing/Overview` beside `Pages/Settings/Billing/Authz`. See
|
||||
**Permission stories** below.
|
||||
- Four levels is the floor to stay under: `Pages/Alerts/Channels/New` is as deep as it goes.
|
||||
- Pages nobody navigates to on purpose go under `Pages/System` (`Status`, `Unauthorized`, `Workspace Locked`), and the pre-session pages under `Pages/Auth`.
|
||||
- A page whose permission stories earn their own folder becomes one: `Pages/Settings/Billing/Overview` beside `Pages/Settings/Billing/Authz`. See **Permission stories** below.
|
||||
|
||||
**Order.** The `storySort.order` literal in `.storybook/preview.tsx` carries the
|
||||
order for every level. A new page in an existing area is appended to that area's
|
||||
array, in the order the product lists it; a new area goes where the side nav
|
||||
puts it. Storybook parses the order out of the file statically, so it has to
|
||||
stay an inline literal. Missing entries fall to the end of their level rather
|
||||
than disappearing, so a forgotten edit is a page at the bottom of its area, not
|
||||
a broken sidebar.
|
||||
**Order.** The `storySort.order` literal in `.storybook/preview.tsx` carries the order for every level. A new page in an existing area is appended to that area's array, in the order the product lists it; a new area goes where the side nav puts it. Storybook parses the order out of the file statically, so it has to stay an inline literal. Missing entries fall to the end of their level rather than disappearing, so a forgotten edit is a page at the bottom of its area, not a broken sidebar.
|
||||
|
||||
**Tags.** Declared on the meta, right under `title`, and what the sidebar's tag
|
||||
filter answers questions with. Only these:
|
||||
**Tags.** Declared on the meta, right under `title`, and what the sidebar's tag filter answers questions with. Only these:
|
||||
|
||||
| Tag | When |
|
||||
| --- | --- |
|
||||
@@ -73,8 +45,7 @@ filter answers questions with. Only these:
|
||||
|
||||
`autodocs` comes from `preview.tsx` and is never written on a meta.
|
||||
|
||||
**Doc comment on the meta.** What the page is, in the page's own terms, then a
|
||||
blank line, then the route:
|
||||
**Doc comment on the meta.** What the page is, in the page's own terms, then a blank line, then the route:
|
||||
|
||||
```tsx
|
||||
const pageStory = storyMocks(logsExplorerMocks, {
|
||||
@@ -97,113 +68,56 @@ const meta = {
|
||||
} satisfies Meta<LogsExplorerArgs>;
|
||||
```
|
||||
|
||||
The `pageStory` const and the trailing `parameters` line are what make the doc
|
||||
comment safe. The comment compiles to a `parameters` property that the csf plugin
|
||||
appends after the spread, so a meta that spreads `storyMocks(...)` and stops
|
||||
there loses `parameters.signoz` and renders the page against the global handlers
|
||||
alone: every one of the page's endpoints misses. Restating `parameters` as a
|
||||
literal gives the plugin something to merge into. `resolveStory` logs the
|
||||
combination that says it happened, so the console names it rather than leaving it
|
||||
to be found by reading the page.
|
||||
The `pageStory` const and the trailing `parameters` line are what make the doc comment safe. The comment compiles to a `parameters` property that the csf plugin appends after the spread, so a meta that spreads `storyMocks(...)` and stops there loses `parameters.signoz` and renders the page against the global handlers alone: every one of the page's endpoints misses. Restating `parameters` as a literal gives the plugin something to merge into. `resolveStory` logs the combination that says it happened, so the console names it rather than leaving it to be found by reading the page.
|
||||
|
||||
It is the description on the page's Docs page, which is the only place a reader
|
||||
who is not in the code finds out what the page is for. Two or three sentences:
|
||||
what it shows, what drives it, and the gating worth knowing about (`Gated on
|
||||
authz permissions`, `follows the legacy editor role`). A control-driven route
|
||||
says so instead of a path: ``Route: `/metrics-explorer/*`, the tab control picks
|
||||
which``.
|
||||
It is the description on the page's Docs page, which is the only place a reader who is not in the code finds out what the page is for. Two or three sentences: what it shows, what drives it, and the gating worth knowing about (`Gated on authz permissions`, `follows the legacy editor role`). A control-driven route says so instead of a path: ``Route: `/metrics-explorer/*`, the tab control picks which``.
|
||||
|
||||
## Permission stories
|
||||
|
||||
A page that gates UI on `lib/authz` keeps its permission states in a folder of
|
||||
their own, so the page's own file stays about the page and the sidebar answers
|
||||
"what does this permission do" in one place.
|
||||
A page that gates UI on `lib/authz` keeps its permission states in a folder of their own, so the page's own file stays about the page and the sidebar answers "what does this permission do" in one place.
|
||||
|
||||
**Layout.** A second story file at `stories/authz/<Page>.authz.stories.tsx`,
|
||||
titled `Pages/<Area>/<Page>/Authz`, which turns the page into a folder: its own
|
||||
file is retitled `Pages/<Area>/<Page>/Overview`, and `.storybook/preview.tsx`
|
||||
gains the sub-order (`'Billing', ['Overview', 'Authz']`). Both files carry the
|
||||
`authz` tag and share the page's one mocks module, which the authz file imports
|
||||
as `../<Page>.stories.mocks`. It declares no controls and no mock data of its
|
||||
own: a permission story that needs a new response is a control the page's mocks
|
||||
were missing.
|
||||
**Layout.** A second story file at `stories/authz/<Page>.authz.stories.tsx`, titled `Pages/<Area>/<Page>/Authz`, which turns the page into a folder: its own file is retitled `Pages/<Area>/<Page>/Overview`, and `.storybook/preview.tsx` gains the sub-order (`'Billing', ['Overview', 'Authz']`). Both files carry the `authz` tag and share the page's one mocks module, which the authz file imports as `../<Page>.stories.mocks`. It declares no controls and no mock data of its own: a permission story that needs a new response is a control the page's mocks were missing.
|
||||
|
||||
**One story per permission the page reads**, named for what is gone: `NoRead`,
|
||||
`NoList`, `NoUpdate`, `NoCreate`, `NoDelete`. Then the combinations the page
|
||||
itself distinguishes, and only those: `NoManage` where two permissions gate one
|
||||
button, `ReadOnly` where everything but reading is denied, `NoSubscriptionAccess`
|
||||
where none of the resource's permissions are held, and `CheckFailed` for
|
||||
`authzState: 'error'`, which is the page's fail-open path rather than a denial.
|
||||
**One story per permission the page reads**, named for what is gone: `NoRead`, `NoList`, `NoUpdate`, `NoCreate`, `NoDelete`. Then the combinations the page itself distinguishes, and only those: `NoManage` where two permissions gate one button, `ReadOnly` where everything but reading is denied, `NoSubscriptionAccess` where none of the resource's permissions are held, and `CheckFailed` for `authzState: 'error'`, which is the page's fail-open path rather than a denial.
|
||||
|
||||
**Revoke, never allow-list.** Each story is a full grant minus what its name
|
||||
says: `args: { revoked: ['read:subscription'] }`. The `Revoked` control subtracts
|
||||
from the preset, so the story stays "an admin missing one permission" as the
|
||||
catalogue grows, and the diff against the page's `Default` is the one permission.
|
||||
Rebuilding the allow-list by hand drifts the moment a resource is added.
|
||||
**Revoke, never allow-list.** Each story is a full grant minus what its name says: `args: { revoked: ['read:subscription'] }`. The `Revoked` control subtracts from the preset, so the story stays "an admin missing one permission" as the catalogue grows, and the diff against the page's `Default` is the one permission. Rebuilding the allow-list by hand drifts the moment a resource is added.
|
||||
|
||||
**Never a role preset in this folder.** `access: 'viewer'` moves the legacy role,
|
||||
the side nav and every other resource's permissions at the same time, so the
|
||||
story no longer shows what its name claims. A persona is a story on the page's
|
||||
own file, and only when the product has that persona.
|
||||
**Never a role preset in this folder.** `access: 'viewer'` moves the legacy role, the side nav and every other resource's permissions at the same time, so the story no longer shows what its name claims. A persona is a story on the page's own file, and only when the product has that persona.
|
||||
|
||||
**Pair the revocation with the state that renders the gated control.** A button
|
||||
that only exists on a trial needs the plan too:
|
||||
`args: { plan: 'on-trial', revoked: ['create:subscription'] }`. A permission
|
||||
whose denial changes nothing on screen gets no story: say so in the PR.
|
||||
**Pair the revocation with the state that renders the gated control.** A button that only exists on a trial needs the plan too: `args: { plan: 'on-trial', revoked: ['create:subscription'] }`. A permission whose denial changes nothing on screen gets no story: say so in the PR.
|
||||
|
||||
Verify these by their disabled states, not their text. The page reads the same
|
||||
either way, so a story that is wrong looks right: read `disabled` off the buttons
|
||||
the permission gates, and check the denial callout is there or gone.
|
||||
Check these by what is turned off, not by what the page says. The page reads the same either way, so a story that is wrong still looks right. Check that the control the permission guards is off, and that the denial message is there or gone.
|
||||
|
||||
**Do not use `toBeEnabled` or `toBeDisabled`.** They only look at the `disabled` attribute. `@signozhq/ui` turns a button off with `aria-disabled` instead, so these matchers pass on a button nobody can click, and the test goes green while clicking straight through a denial. Check the attribute the component really sets:
|
||||
|
||||
```ts
|
||||
expect(save).toHaveAttribute('aria-disabled', 'true');
|
||||
```
|
||||
|
||||
**Some controls are off with no attribute at all.** The browser turns off everything inside a disabled `<fieldset>` without marking it, and a switch is a `<button>` underneath. For those, an attribute check proves nothing, and so does looking for a parent `fieldset[disabled]`. Click it and check nothing happened:
|
||||
|
||||
```ts
|
||||
const before = toggle.getAttribute('aria-checked');
|
||||
await userEvent.click(toggle);
|
||||
await expect(toggle.getAttribute('aria-checked')).toBe(before);
|
||||
```
|
||||
|
||||
**Check that a new assertion can fail.** Flip it once and run it. If that story goes red and nothing else does, the assertion is real; put it back. A test that cannot fail is worse than no test, because it looks like coverage.
|
||||
|
||||
**Delete a story once its difference disappears.** Say a story checks that a button reads "View" rather than "Edit" when a permission is missing. If the label later becomes the same either way, that story passes no matter which permissions are held, including all of them, and it is testing nothing. When a behaviour stops depending on a permission, delete the story or rewrite it to lock in the new behaviour, and say which one you did in the PR.
|
||||
|
||||
## Rules
|
||||
|
||||
- **Default is the loaded page.** `export const Default: Story = {}` with no args,
|
||||
every widget carrying data. Empty, loading and failed are variants or control
|
||||
values, never the default.
|
||||
- **A control is a knob on a response**, resolved through `handlers`, `config` or
|
||||
`effect`. Never a component prop, never a module mock added for one story.
|
||||
- **Every branch in the inventory is reachable from the panel.** A state that
|
||||
needs a code edit to see is a missing control.
|
||||
- **Never re-declare what every story already has**: banner, side nav, data state
|
||||
(loaded/loading/error), access preset, permissions, check state.
|
||||
- **A variant earns a story only when it is worth linking to**: an empty
|
||||
workspace, a viewer, a page mid-load. Everything else stays a control.
|
||||
- **Endpoints the page owns go through `response.json`**, so the Data control
|
||||
covers loaded, loading and failed in one declaration. Endpoints the page cannot
|
||||
render without (ingestion detection, preferences, feature payloads) take a
|
||||
plain resolver so the shell survives the loading and error states.
|
||||
- **Query-param state starts from `route`** (`/logs?tab=explorer`). In-page param
|
||||
navigation works inside a story; a different pathname is blocked and reported
|
||||
by the overlay. A control for a param is worth it only when the param is a page
|
||||
mode someone would want to flip.
|
||||
- **File layout**: every story file for a page lives under
|
||||
`src/pages/<Page>/stories/`: `<Page>.stories.tsx`, `<Page>.stories.mocks.tsx`,
|
||||
payload builders in `stories/__story_mockdata__/<page>.ts`. Nothing
|
||||
page-specific in `src/storybook/controls/`. A page that is a tab strip over
|
||||
several routes gets one story file per tab, in its own folder under the module
|
||||
page (`LogsModulePage/Pipelines/stories/Pipelines.stories.tsx`), each with its
|
||||
own mocks and `__story_mockdata__/`; the builders more than one tab needs stay
|
||||
in the module page's own `stories/__story_mockdata__/`
|
||||
(`AlertList/stories/__story_mockdata__/alerts.ts`), which a tab reaches as
|
||||
`../../stories/__story_mockdata__/alerts`. Every one of them renders the module page, so the tab
|
||||
strip is there, and the `route` its mocks return decides which tab is open.
|
||||
A page's permission stories go one level further down, in
|
||||
`stories/authz/<Page>.authz.stories.tsx`, on the page's own mocks: see
|
||||
**Permission stories**.
|
||||
- **A state only a click reaches is a story with a `play` function**, not a
|
||||
control: a drawer, a modal, an edit mode the page holds in component state.
|
||||
Drive it with `userEvent` and the queries from `storybook/test`, take the first
|
||||
of a repeated row action, and wait on the state's own text. The page fetches
|
||||
before it renders a row, so the finder needs a timeout past the 1s default. A
|
||||
state the app drops again on its own, such as one keyed on an array identity
|
||||
that a refetch replaces, does not get a story: it would not survive being
|
||||
looked at. A *sequence* of such states, a wizard's steps or a
|
||||
questionnaire's pages, is still a control: declare the steps in the mocks
|
||||
module and walk them from a `play` on the meta that destructures `mount`, which
|
||||
is what makes Storybook replay it on an arg change. See
|
||||
[references/controls.md](references/controls.md).
|
||||
- **The mocks are AI-owned and say so.** `<Page>.stories.mocks.tsx` and every file
|
||||
under a `__story_mockdata__/` open with this banner, above the imports:
|
||||
- **Default is the loaded page.** `export const Default: Story = {}` with no args, every widget carrying data. Empty, loading and failed are variants or control values, never the default.
|
||||
- **A control is a knob on a response**, resolved through `handlers`, `config` or `effect`. Never a component prop, never a module mock added for one story.
|
||||
- **Every branch in the inventory is reachable from the panel.** A state that needs a code edit to see is a missing control.
|
||||
- **Never re-declare what every story already has**: banner, side nav, data state (loaded/loading/error), access preset, permissions, check state.
|
||||
- **A variant earns a story only when it is worth linking to**: an empty workspace, a viewer, a page mid-load. Everything else stays a control.
|
||||
- **Endpoints the page owns go through `response.json`**, so the Data control covers loaded, loading and failed in one declaration. Endpoints the page cannot render without (ingestion detection, preferences, feature payloads) take a plain resolver so the shell survives the loading and error states.
|
||||
- **Query-param state starts from `route`** (`/logs?tab=explorer`). In-page param navigation works inside a story; a different pathname is blocked and reported by the overlay. A control for a param is worth it only when the param is a page mode someone would want to flip.
|
||||
- **File layout**: every story file for a page lives under `src/pages/<Page>/stories/`: `<Page>.stories.tsx`, `<Page>.stories.mocks.tsx`, payload builders in `stories/__story_mockdata__/<page>.ts`. Nothing page-specific in `src/storybook/controls/`. A page that is a tab strip over several routes gets one story file per tab, in its own folder under the module page (`LogsModulePage/Pipelines/stories/Pipelines.stories.tsx`), each with its own mocks and `__story_mockdata__/`; the builders more than one tab needs stay in the module page's own `stories/__story_mockdata__/` (`AlertList/stories/__story_mockdata__/alerts.ts`), which a tab reaches as `../../stories/__story_mockdata__/alerts`. Every one of them renders the module page, so the tab strip is there, and the `route` its mocks return decides which tab is open. A page's permission stories go one level further down, in `stories/authz/<Page>.authz.stories.tsx`, on the page's own mocks: see **Permission stories**.
|
||||
- **A state only a click reaches is a story with a `play` function**, not a control: a drawer, a modal, an edit mode the page holds in component state. Drive it with `userEvent` and the queries from `storybook/test`, take the first of a repeated row action, and wait on the state's own text. The page fetches before it renders a row, so the finder needs a timeout past the 1s default. A state the app drops again on its own, such as one keyed on an array identity that a refetch replaces, does not get a story: it would not survive being looked at. A _sequence_ of such states, a wizard's steps or a questionnaire's pages, is still a control: declare the steps in the mocks module and walk them from a `play` on the meta that destructures `mount`, which is what makes Storybook replay it on an arg change. See [references/controls.md](references/controls.md).
|
||||
- **The mocks are AI-owned and say so.** `<Page>.stories.mocks.tsx` and every file under a `__story_mockdata__/` open with this banner, above the imports:
|
||||
|
||||
```ts
|
||||
/**
|
||||
@@ -212,52 +126,23 @@ the permission gates, and check the denial callout is there or gone.
|
||||
*/
|
||||
```
|
||||
|
||||
The root `.gitattributes` marks both paths `linguist-generated=true`, so the
|
||||
reviewer gets them collapsed and spends the attention on the rendered page. The
|
||||
story file is the human surface and never carries the banner. A file with the
|
||||
banner has to stay regenerable from the page alone: no page knowledge in
|
||||
`src/storybook/`, and builders typed from `src/api/generated` where the endpoint
|
||||
has types, so a contract change is a compile error instead of a mock that lies.
|
||||
- **Reuse fixtures** from `src/mocks-server/` and `src/tests/fixtures/` where they
|
||||
exist. An endpoint jest needs too belongs in `src/mocks-server/handlers.ts`.
|
||||
- **Shared response builders live in `src/storybook/msw/__story_mockdata__/`**: typed
|
||||
helpers like `queryRangeV5ScalarResponse` that multiple pages need. Before
|
||||
writing a response shape inline, check if a builder exists; if not and the
|
||||
shape will repeat, add it there. Page-specific builders stay in the page's
|
||||
`__story_mockdata__/`.
|
||||
- **The story's own doc comment is per state.** Every `export const` gets one:
|
||||
what that state shows, not how it is built. It renders in the States list on
|
||||
the page's Docs page, so `Undocumented.` there is a story nobody described.
|
||||
- **Story names come from a fixed vocabulary** where one fits: `Default`,
|
||||
`Viewer`, `Empty`, `Loading`, `Error`. Page-specific states get page-specific
|
||||
names (`NoIngestion`, `Unlicensed`), never a second spelling of one of those
|
||||
(`ViewerAccess`, `NonAdmin`).
|
||||
- **No comment is the default.** Write one only for what the code cannot show:
|
||||
a shape the backend dictates, an app bug the mock reproduces, an ordering or
|
||||
cap the page depends on, a workaround and the reason for it. Never restate a
|
||||
name, a type, or what a builder plainly builds; if the sentence reads as the
|
||||
signature in prose, delete it. Nothing addressed to a reviewer. The one
|
||||
comment a story always gets is its own doc comment: what it shows, in the
|
||||
page's own terms.
|
||||
The root `.gitattributes` marks both paths `linguist-generated=true`, so the reviewer gets them collapsed and spends the attention on the rendered page. The story file is the human surface and never carries the banner. A file with the banner has to stay regenerable from the page alone: no page knowledge in `src/storybook/`, and builders typed from `src/api/generated` where the endpoint has types, so a contract change is a compile error instead of a mock that lies.
|
||||
|
||||
- **Reuse fixtures** from `src/mocks-server/` and `src/tests/fixtures/` where they exist. An endpoint jest needs too belongs in `src/mocks-server/handlers.ts`.
|
||||
- **Shared response builders live in `src/storybook/msw/__story_mockdata__/`**: typed helpers like `queryRangeV5ScalarResponse` that multiple pages need. Before writing a response shape inline, check if a builder exists; if not and the shape will repeat, add it there. Page-specific builders stay in the page's `__story_mockdata__/`.
|
||||
- **The story's own doc comment is per state.** Every `export const` gets one: what that state shows, not how it is built. It renders in the States list on the page's Docs page, so `Undocumented.` there is a story nobody described.
|
||||
- **Story names come from a fixed vocabulary** where one fits: `Default`, `Viewer`, `Empty`, `Loading`, `Error`. Page-specific states get page-specific names (`NoIngestion`, `Unlicensed`), never a second spelling of one of those (`ViewerAccess`, `NonAdmin`).
|
||||
- **No comment is the default.** Write one only for what the code cannot show: a shape the backend dictates, an app bug the mock reproduces, an ordering or cap the page depends on, a workaround and the reason for it. Never restate a name, a type, or what a builder plainly builds; if the sentence reads as the signature in prose, delete it. Nothing addressed to a reviewer. The one comment a story always gets is its own doc comment: what it shows, in the page's own terms.
|
||||
|
||||
## Done means
|
||||
|
||||
- [ ] `Default` shows the page with data, checked in dark and light
|
||||
- [ ] title follows the sidebar rules, tags declared, and the page's entry added
|
||||
to the `storySort.order` literal in `.storybook/preview.tsx`
|
||||
- [ ] the meta carries its doc comment with the `Route:` line, the meta restates
|
||||
`parameters: { ...pageStory.parameters }` after the spread, and every story
|
||||
export carries its own doc comment
|
||||
- [ ] the page's Docs page renders: description, controls table, and one row per
|
||||
state with no `Undocumented.`
|
||||
- [ ] a page tagged `authz` has its `Authz` folder: one story per permission it
|
||||
reads, each reached by `revoked`, none of them a role preset, and each one
|
||||
checked by the `disabled` state of what the permission gates
|
||||
- [ ] title follows the sidebar rules, tags declared, and the page's entry added to the `storySort.order` literal in `.storybook/preview.tsx`
|
||||
- [ ] the meta carries its doc comment with the `Route:` line, the meta restates `parameters: { ...pageStory.parameters }` after the spread, and every story export carries its own doc comment
|
||||
- [ ] the page's Docs page renders: description, controls table, and one row per state with no `Undocumented.`
|
||||
- [ ] a page tagged `authz` has its `Authz` folder: one story per permission it reads, each reached by `revoked`, none of them a role preset, and each one checked by the `disabled` state of what the permission gates
|
||||
- [ ] the mocks module and every `__story_mockdata__` file carry the AI-owned banner
|
||||
- [ ] every control flipped once, its effect seen on screen
|
||||
- [ ] console clean: no `[storybook] no msw handler`, no 501, no msw unhandled
|
||||
request, no React warning
|
||||
- [ ] console clean: no `[storybook] no msw handler`, no 501, no msw unhandled request, no React warning
|
||||
- [ ] no navigation overlay on mount
|
||||
- [ ] `pnpm tsgo --noEmit`, `pnpm exec oxlint <files>`,
|
||||
`pnpm exec oxfmt --check <files>` all clean. The repo has no
|
||||
prettier: `pnpm exec prettier` prints a pass while exiting 254
|
||||
- [ ] `pnpm tsgo --noEmit`, `pnpm exec oxlint <files>`, `pnpm exec oxfmt --check <files>` all clean. The repo has no prettier: `pnpm exec prettier` prints a pass while exiting 254
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
# Turning the inventory into controls
|
||||
|
||||
Every row of the inventory becomes a control, a global control that already
|
||||
exists, or a documented reason it cannot be one.
|
||||
Every row of the inventory becomes a control, a global control that already exists, or a documented reason it cannot be one.
|
||||
|
||||
## Imports
|
||||
|
||||
Paths written as `src/storybook/...` in prose are repo paths, not import
|
||||
specifiers. Stories import through the `@/` alias (`@/*` → `./src/*`); modules
|
||||
inside `src/storybook/` import each other relatively.
|
||||
Paths written as `src/storybook/...` in prose are repo paths, not import specifiers. Stories import through the `@/` alias (`@/*` → `./src/*`); modules inside `src/storybook/` import each other relatively.
|
||||
|
||||
| Import | From |
|
||||
| --- | --- |
|
||||
@@ -31,38 +28,21 @@ inside `src/storybook/` import each other relatively.
|
||||
|
||||
Rules that come with them:
|
||||
|
||||
- `countControl` `max` goes past what the page renders, so a story can show the
|
||||
cap being hit. `0` is the empty state, which is why an empty list rarely needs
|
||||
its own story. When the cap is in the *request* (`?limit=5`) rather than the
|
||||
renderer, stop `max` at the limit: a longer response is a body the backend
|
||||
cannot send.
|
||||
- `choiceControl` options come from a `const` array typed with
|
||||
`(typeof X)[number]`, not from string literals scattered in the handlers.
|
||||
- Defaults describe the fully-populated page. The panel starts where `Default`
|
||||
starts.
|
||||
- `group` is `'<Page> · <facet>'`, such as `'Services · lists'` or
|
||||
`'Alerts · rules'`. Keep a page's knobs in two or three groups, not one per
|
||||
control.
|
||||
- `description` only when the name does not carry the effect (what dismissing
|
||||
does, what the cap is, which widget it feeds).
|
||||
- `countControl` `max` goes past what the page renders, so a story can show the cap being hit. `0` is the empty state, which is why an empty list rarely needs its own story. When the cap is in the _request_ (`?limit=5`) rather than the renderer, stop `max` at the limit: a longer response is a body the backend cannot send.
|
||||
- `choiceControl` options come from a `const` array typed with `(typeof X)[number]`, not from string literals scattered in the handlers.
|
||||
- Defaults describe the fully-populated page. The panel starts where `Default` starts.
|
||||
- `group` is `'<Page> · <facet>'`, such as `'Services · lists'` or `'Alerts · rules'`. Keep a page's knobs in two or three groups, not one per control.
|
||||
- `description` only when the name does not carry the effect (what dismissing does, what the cap is, which widget it feeds).
|
||||
|
||||
## Which hook
|
||||
|
||||
`defineStoryMocks` takes three, all optional:
|
||||
|
||||
- `handlers(values, response)`: the page's endpoints. Everything the page owns
|
||||
goes through `response.json`, so the global Data control turns the whole page
|
||||
into loading or failed without a second declaration. An endpoint the page
|
||||
cannot render at all without (ingestion detection, preferences, license
|
||||
payloads) takes a plain `rest.get(...)` resolver instead, so the shell stays
|
||||
visible while the rest hangs or fails.
|
||||
- `config(values)`: `SignozStoryConfig` for knobs no endpoint covers: `route`,
|
||||
`appContext`, `reduxState`, `queryBuilder`, `theme`.
|
||||
- `handlers(values, response)`: the page's endpoints. Everything the page owns goes through `response.json`, so the global Data control turns the whole page into loading or failed without a second declaration. An endpoint the page cannot render at all without (ingestion detection, preferences, license payloads) takes a plain `rest.get(...)` resolver instead, so the shell stays visible while the rest hangs or fails.
|
||||
- `config(values)`: `SignozStoryConfig` for knobs no endpoint covers: `route`, `appContext`, `reduxState`, `queryBuilder`, `theme`.
|
||||
- `effect(values)`: module-level state no provider exposes.
|
||||
|
||||
One endpoint feeding several widgets stays one handler that reads the request.
|
||||
`response.json` hands the request to the builder and awaits it, so reading a
|
||||
query param, or a POST body, does not cost the Data control:
|
||||
One endpoint feeding several widgets stays one handler that reads the request. `response.json` hands the request to the builder and awaits it, so reading a query param, or a POST body, does not cost the Data control:
|
||||
|
||||
```ts
|
||||
rest.get(
|
||||
@@ -85,18 +65,11 @@ rest.post(
|
||||
),
|
||||
```
|
||||
|
||||
Reach for a plain `rest.post(url, async (req, res, ctx) => …)` only when the
|
||||
endpoint has to keep answering while the Data control is on `loading` or
|
||||
`error`: detection calls the page cannot render without.
|
||||
Reach for a plain `rest.post(url, async (req, res, ctx) => …)` only when the endpoint has to keep answering while the Data control is on `loading` or `error`: detection calls the page cannot render without.
|
||||
|
||||
## Mutations
|
||||
|
||||
A control drives the response, so a write the page makes against state a control
|
||||
owns does not stick: the refetch answers with the control's value and the button
|
||||
appears to do nothing. Two honest options: leave it declarative and say so in
|
||||
the PR, or move the state into `effect` so the handler can read what the page
|
||||
wrote. Never fake the write by mutating a builder's module state without saying
|
||||
where the state lives.
|
||||
A control drives the response, so a write the page makes against state a control owns does not stick: the refetch answers with the control's value and the button appears to do nothing. Two honest options: leave it declarative and say so in the PR, or move the state into `effect` so the handler can read what the page wrote. Never fake the write by mutating a builder's module state without saying where the state lives.
|
||||
|
||||
## Wiring it up
|
||||
|
||||
@@ -146,15 +119,11 @@ const meta = {
|
||||
} satisfies Meta<ServicesArgs>;
|
||||
```
|
||||
|
||||
`PageStoryArgs` folds in the global controls, so a story's `args` can set
|
||||
`access`, `dataState` or `banner` next to the page's own knobs and stay typed.
|
||||
`PageStoryArgs` folds in the global controls, so a story's `args` can set `access`, `dataState` or `banner` next to the page's own knobs and stay typed.
|
||||
|
||||
## A step the page keeps in component state
|
||||
|
||||
A wizard's step, a questionnaire's page, a picker's next question: the page holds
|
||||
it in `useState` and nothing in the URL says which one is open. It is still a
|
||||
control. Declare the steps in the mocks module and drive them from a `play` on
|
||||
the **meta**, so every story of the page inherits the walk and only sets `args`:
|
||||
A wizard's step, a questionnaire's page, a picker's next question: the page holds it in `useState` and nothing in the URL says which one is open. It is still a control. Declare the steps in the mocks module and drive them from a `play` on the **meta**, so every story of the page inherits the walk and only sets `args`:
|
||||
|
||||
```tsx
|
||||
// <Page>.stories.mocks.tsx
|
||||
@@ -179,39 +148,23 @@ const meta = {
|
||||
export const Configure: Story = { args: { step: 'configure' } };
|
||||
```
|
||||
|
||||
**Destructuring `mount` is what makes it a control.** Storybook re-runs a play
|
||||
function on an arg change only for a story whose play asks to be remounted
|
||||
(`usesMount`); otherwise it re-renders the tree the previous walk left behind and
|
||||
the panel looks broken. With `mount` destructured, the story renders when `play`
|
||||
calls it, and every arg change replays the walk from a fresh mount.
|
||||
**Destructuring `mount` is what makes it a control.** Storybook re-runs a play function on an arg change only for a story whose play asks to be remounted (`usesMount`); otherwise it re-renders the tree the previous walk left behind and the panel looks broken. With `mount` destructured, the story renders when `play` calls it, and every arg change replays the walk from a fresh mount.
|
||||
|
||||
The walk itself:
|
||||
|
||||
- one `answer` function per step, in an array indexed the same as the step list,
|
||||
so reaching step *n* is `answers.slice(0, STEPS.indexOf(step))`;
|
||||
- answer each step with the least its Next button accepts, and prefer a "do this
|
||||
later" over filling a slider;
|
||||
- run them sequentially (`reduce` over a promise), since each answer is what
|
||||
renders the step the next one reads;
|
||||
- bail out when the page did not start where the walk expects, such as a source
|
||||
deep-linked past the questions. Check for the first step's own text rather than
|
||||
reading another control's value.
|
||||
- one `answer` function per step, in an array indexed the same as the step list, so reaching step _n_ is `answers.slice(0, STEPS.indexOf(step))`;
|
||||
- answer each step with the least its Next button accepts, and prefer a "do this later" over filling a slider;
|
||||
- run them sequentially (`reduce` over a promise), since each answer is what renders the step the next one reads;
|
||||
- bail out when the page did not start where the walk expects, such as a source deep-linked past the questions. Check for the first step's own text rather than reading another control's value.
|
||||
|
||||
An endpoint that only settles the transition between two steps (the profile a
|
||||
questionnaire saves before its last page) takes a plain resolver, or the Data
|
||||
control on `loading` strands the walk halfway.
|
||||
An endpoint that only settles the transition between two steps (the profile a questionnaire saves before its last page) takes a plain resolver, or the Data control on `loading` strands the walk halfway.
|
||||
|
||||
## Not a control
|
||||
|
||||
- Anything the global controls already cover: banner, side nav, data state,
|
||||
access preset, granted permissions, revoked permissions, check state.
|
||||
- A knob whose effect nobody can see on the page. Delete it or find the widget it
|
||||
was supposed to drive.
|
||||
- A raw payload as an object control. Controls carry intent (`5 dashboards`,
|
||||
`viewer`), and the builder turns intent into the payload.
|
||||
- Anything that needs a module mock or a component prop to work. If the state
|
||||
cannot be produced from a response, config or module state, say so in the PR
|
||||
instead of faking it.
|
||||
- Anything the global controls already cover: banner, side nav, data state, access preset, granted permissions, revoked permissions, check state.
|
||||
- A knob whose effect nobody can see on the page. Delete it or find the widget it was supposed to drive.
|
||||
- A raw payload as an object control. Controls carry intent (`5 dashboards`, `viewer`), and the builder turns intent into the payload.
|
||||
- Anything that needs a module mock or a component prop to work. If the state cannot be produced from a response, config or module state, say so in the PR instead of faking it.
|
||||
|
||||
## Control or story
|
||||
|
||||
@@ -220,12 +173,8 @@ Default to a control. Write a story when the state is worth a link:
|
||||
- the fresh workspace, because that is what a new user sees
|
||||
- a page-defining mode (a tab, a category) that has its own layout
|
||||
|
||||
A permission that visibly changes the page is a story too, but it goes in the
|
||||
page's `Authz` folder, one per permission, turned with the `Revoked` control.
|
||||
See **Permission stories** in SKILL.md.
|
||||
A permission that visibly changes the page is a story too, but it goes in the page's `Authz` folder, one per permission, turned with the `Revoked` control. See **Permission stories** in SKILL.md.
|
||||
|
||||
Combinations of controls do not need stories, which is what the panel is for.
|
||||
|
||||
Each story gets one prose doc comment: what it shows, in the page's own terms.
|
||||
Everywhere else the comment rule in SKILL.md applies: write one only for what
|
||||
the code cannot show.
|
||||
Each story gets one prose doc comment: what it shows, in the page's own terms. Everywhere else the comment rule in SKILL.md applies: write one only for what the code cannot show.
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
# Mapping a page
|
||||
|
||||
Two passes: read the code, then let the running story correct you. Write the
|
||||
inventory down: it is what the controls are derived from, and the only
|
||||
protection against a story that renders one state and calls it a page.
|
||||
Two passes: read the code, then let the running story correct you. Write the inventory down: it is what the controls are derived from, and the only protection against a story that renders one state and calls it a page.
|
||||
|
||||
## Pass 1: read the page
|
||||
|
||||
Start at `src/pages/<Page>/` and follow it outward: the containers it mounts
|
||||
(`src/container/<Feature>/`), the hooks those use, the components with their own
|
||||
fetches. Stop at leaf components that take props only.
|
||||
Start at `src/pages/<Page>/` and follow it outward: the containers it mounts (`src/container/<Feature>/`), the hooks those use, the components with their own fetches. Stop at leaf components that take props only.
|
||||
|
||||
Grep recipes, run against the page's directories:
|
||||
|
||||
@@ -24,41 +20,32 @@ Grep recipes, run against the page's directories:
|
||||
| empty and error branches | `isLoading\|isError\|isFetching\|length === 0\|!data` |
|
||||
| render caps | `slice(0,\|PAGE_SIZE\|pageSize\|limit` |
|
||||
|
||||
`src/constants/routes.ts` has the route, `src/constants/query.ts` the param names,
|
||||
`src/lib/authz/README.md` how a permission check resolves.
|
||||
`src/constants/routes.ts` has the route, `src/constants/query.ts` the param names, `src/lib/authz/README.md` how a permission check resolves.
|
||||
|
||||
## The inventory
|
||||
|
||||
One table, in the story's PR or scratch notes:
|
||||
|
||||
| Endpoint | Feeds | States it can be in |
|
||||
| --- | --- | --- |
|
||||
| Endpoint | Feeds | States it can be in |
|
||||
| --------------- | ---------------- | ---------------------- |
|
||||
| `GET /api/v1/x` | the header count | populated, zero, error |
|
||||
|
||||
Plus four short lists:
|
||||
|
||||
- **Query params** the page reads, and what each one switches.
|
||||
- **Permission checks** the page makes, and what disappears when each is denied.
|
||||
- **Preferences and flags** that change layout (dismissed banners, onboarding
|
||||
checklists, opt-in views).
|
||||
- **Preferences and flags** that change layout (dismissed banners, onboarding checklists, opt-in views).
|
||||
- **Caps**: how many rows each list renders before it truncates or paginates.
|
||||
|
||||
A state that appears in this inventory and not in the controls panel is a bug in
|
||||
the story.
|
||||
A state that appears in this inventory and not in the controls panel is a bug in the story.
|
||||
|
||||
## Pass 2: let it run
|
||||
|
||||
Write the story and an empty `defineStoryMocks({ controls: {} })`, point it at the
|
||||
route with `layout: 'app'`, then open it (see verify.md). The console is the
|
||||
oracle:
|
||||
Write the story and an empty `defineStoryMocks({ controls: {} })`, point it at the route with `layout: 'app'`, then open it (see verify.md). The console is the oracle:
|
||||
|
||||
- `[storybook] no msw handler` or a 501 from the catch-all: an endpoint pass 1
|
||||
missed. Add it to the inventory.
|
||||
- an msw unhandled-request warning: a request going to an origin the handlers do
|
||||
not answer on. handlers are declared against `http://localhost`.
|
||||
- a spinner that never resolves with the Data control on `loaded`: a handler
|
||||
whose URL does not match what the page calls.
|
||||
- the navigation overlay on mount: the page redirects, usually because `route`
|
||||
is wrong or a guard is failing on a permission the controls have not granted.
|
||||
- `[storybook] no msw handler` or a 501 from the catch-all: an endpoint pass 1 missed. Add it to the inventory.
|
||||
- an msw unhandled-request warning: a request going to an origin the handlers do not answer on. handlers are declared against `http://localhost`.
|
||||
- a spinner that never resolves with the Data control on `loaded`: a handler whose URL does not match what the page calls.
|
||||
- the navigation overlay on mount: the page redirects, usually because `route` is wrong or a guard is failing on a permission the controls have not granted.
|
||||
|
||||
Repeat until the console is silent. Only then start declaring controls.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Verifying a page story
|
||||
|
||||
A story is not done because it compiles. It is done when each control has been
|
||||
seen changing the page and the console is silent.
|
||||
A story is not done because it compiles. It is done when each control has been seen changing the page and the console is silent.
|
||||
|
||||
## Run it
|
||||
|
||||
@@ -9,12 +8,9 @@ seen changing the page and the console is silent.
|
||||
cd frontend && pnpm storybook --ci --quiet # :6006, background it
|
||||
```
|
||||
|
||||
A newly added `.stories.tsx` takes a few seconds to appear in `index.json` on an
|
||||
already-running server; an empty first poll is not a broken `stories` glob.
|
||||
A newly added `.stories.tsx` takes a few seconds to appear in `index.json` on an already-running server; an empty first poll is not a broken `stories` glob.
|
||||
|
||||
Story ids come from the meta title: `Pages/Services/List` →
|
||||
`pages-services-list`, plus the story export in kebab-case. Render one story on
|
||||
its own:
|
||||
Story ids come from the meta title: `Pages/Services/List` → `pages-services-list`, plus the story export in kebab-case. Render one story on its own:
|
||||
|
||||
```
|
||||
http://localhost:6006/iframe.html?id=pages-services-list--default&viewMode=story
|
||||
@@ -22,9 +18,7 @@ http://localhost:6006/iframe.html?id=pages-services-list--default&viewMode=story
|
||||
|
||||
## Flip controls from the URL
|
||||
|
||||
Args are settable in the iframe URL, so a whole sweep runs headless without
|
||||
touching the panel. Booleans go as `!true` / `!false`, numbers bare, arrays
|
||||
indexed, several separated by `;`, and the theme through `globals`:
|
||||
Args are settable in the iframe URL, so a whole sweep runs headless without touching the panel. Booleans go as `!true` / `!false`, numbers bare, arrays indexed, several separated by `;`, and the theme through `globals`:
|
||||
|
||||
```
|
||||
&args=services:0;apdex:poor;access:viewer;dataState:loading
|
||||
@@ -32,13 +26,11 @@ indexed, several separated by `;`, and the theme through `globals`:
|
||||
&globals=theme:light
|
||||
```
|
||||
|
||||
That is the cheap way to check a control does something: load with and without
|
||||
it, diff the page text.
|
||||
That is the cheap way to check a control does something: load with and without it, diff the page text.
|
||||
|
||||
## Drive it
|
||||
|
||||
Playwright lives in the repo's e2e workspace, so a scratch script can use it
|
||||
directly:
|
||||
Playwright lives in the repo's e2e workspace, so a scratch script can use it directly:
|
||||
|
||||
```js
|
||||
import pw from '<repo>/tests/e2e/node_modules/playwright/index.js';
|
||||
@@ -58,36 +50,18 @@ console.log((await page.locator('body').innerText()).slice(0, 1500), problems);
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
Screenshots are worth taking for `Default` in both themes: text extraction does
|
||||
not catch an unstyled page. Take them with
|
||||
[sbshot](https://github.com/SigNoz/sbshot), e.g.
|
||||
`sbshot capture http://localhost:6006 --title <story title> --theme dark,light`.
|
||||
Screenshots are worth taking for `Default` in both themes: text extraction does not catch an unstyled page. Take them with [sbshot](https://github.com/SigNoz/sbshot), e.g. `sbshot capture http://localhost:6006 --title <story title> --theme dark,light`.
|
||||
|
||||
That is one run, for checking the story you just wrote. Comparing two branches is a different job: see the `sbshot-visual-diff` skill, which covers the baseline to pick, measuring the noise and exporting something a reviewer can open.
|
||||
|
||||
## Gates
|
||||
|
||||
- **Console silent.** `[storybook] no msw handler`, a 501 from the catch-all, an
|
||||
msw unhandled-request warning, a React key or state warning: assume the story
|
||||
is wrong first. A warning that survives is sometimes the app's. Prove it by
|
||||
turning off the control that renders the widget and watching the warning go
|
||||
with it, and by finding the same component elsewhere doing it right. Then
|
||||
report the app bug in the PR. Never invent a field the API does not return to
|
||||
silence a warning.
|
||||
- **No navigation overlay on mount.** "Navigation blocked in Storybook" on load
|
||||
means the page is trying to leave: wrong `route`, or a guard denying on a
|
||||
permission the controls did not grant.
|
||||
- **Every control moves something.** Sweep them one at a time from the URL and
|
||||
diff the page text. A control with no diff is either wired to nothing or aimed
|
||||
at a widget that is not rendering. Some only show their effect after an
|
||||
interaction, such as a tab that has to be clicked or a select that has to be
|
||||
opened. Drive that interaction rather than calling the control unobservable.
|
||||
- **Both themes render styled.** An unstyled page means the story is not inside
|
||||
the provider decorator, or `<body data-theme>` was lost.
|
||||
- **Roles agree.** `<body data-signoz-story-role>` and
|
||||
`<body data-signoz-context-role>` disagreeing means the page reads a different
|
||||
`AppContext` than the story config fills.
|
||||
- **The page's own navigation works.** Tabs, filters and pagination that write
|
||||
query params should re-render the page in place; only leaving the page belongs
|
||||
in the overlay.
|
||||
- **Console silent.** `[storybook] no msw handler`, a 501 from the catch-all, an msw unhandled-request warning, a React key or state warning: assume the story is wrong first. A warning that survives is sometimes the app's. Prove it by turning off the control that renders the widget and watching the warning go with it, and by finding the same component elsewhere doing it right. Then report the app bug in the PR. Never invent a field the API does not return to silence a warning.
|
||||
- **No navigation overlay on mount.** "Navigation blocked in Storybook" on load means the page is trying to leave: wrong `route`, or a guard denying on a permission the controls did not grant.
|
||||
- **Every control moves something.** Sweep them one at a time from the URL and diff the page text. A control with no diff is either wired to nothing or aimed at a widget that is not rendering. Some only show their effect after an interaction, such as a tab that has to be clicked or a select that has to be opened. Drive that interaction rather than calling the control unobservable.
|
||||
- **Both themes render styled.** An unstyled page means the story is not inside the provider decorator, or `<body data-theme>` was lost.
|
||||
- **Roles agree.** `<body data-signoz-story-role>` and `<body data-signoz-context-role>` disagreeing means the page reads a different `AppContext` than the story config fills.
|
||||
- **The page's own navigation works.** Tabs, filters and pagination that write query params should re-render the page in place; only leaving the page belongs in the overlay.
|
||||
|
||||
## Then the usual
|
||||
|
||||
@@ -97,9 +71,7 @@ pnpm exec oxlint <changed files>
|
||||
pnpm exec oxfmt --check <changed files>
|
||||
```
|
||||
|
||||
There is no prettier in this repo. `pnpm exec prettier --check` fetches something
|
||||
else, prints `Prettier: All files formatted correctly` and exits 254: a pass that is
|
||||
not one.
|
||||
There is no prettier in this repo. `pnpm exec prettier --check` fetches something else, prints `Prettier: All files formatted correctly` and exits 254: a pass that is not one.
|
||||
|
||||
## Common failures
|
||||
|
||||
|
||||
6
.gitattributes
vendored
@@ -2,9 +2,3 @@
|
||||
*.stories.mocks.tsx linguist-generated=true
|
||||
**/__story_mockdata__/** linguist-generated=true
|
||||
tests/e2e/drivers/** linguist-generated=true
|
||||
|
||||
# Transcribed E2E data: seeded telemetry, golden payloads and the entity
|
||||
# datasets. Regenerated from the seeder rather than hand-edited, and re-derived
|
||||
# by the specs that read them, so they are collapsed in review.
|
||||
tests/e2e/testdata/** linguist-generated=true
|
||||
tests/e2e/**/__generated__/** linguist-generated=true
|
||||
|
||||
10
.github/CODEOWNERS
vendored
@@ -213,9 +213,7 @@ go.mod @therealpandey
|
||||
## Notification Channels
|
||||
/frontend/src/pages/ChannelsEdit/ @SigNoz/pulse-frontend
|
||||
/frontend/src/pages/ChannelsNew/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/AllAlertChannels/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/CreateAlertChannels/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/EditAlertChannels/ @SigNoz/pulse-frontend
|
||||
/frontend/src/container/FormAlertChannels/ @SigNoz/pulse-frontend
|
||||
/frontend/src/hooks/notificationChannels/ @SigNoz/pulse-frontend
|
||||
|
||||
@@ -292,6 +290,14 @@ go.mod @therealpandey
|
||||
/frontend/src/components/MessagingQueueHealthCheck/ @SigNoz/events-frontend
|
||||
/frontend/src/hooks/messagingQueue/ @SigNoz/events-frontend
|
||||
|
||||
## Saved Views
|
||||
/frontend/src/container/SavedViews/ @aks07
|
||||
/frontend/src/container/Home/SavedViews/ @aks07
|
||||
/frontend/src/hooks/saveViews/ @aks07
|
||||
|
||||
## Bottom Strip
|
||||
/frontend/src/container/BottomStrip/ @aks07
|
||||
|
||||
## Storybook
|
||||
/frontend/.storybook/ @H4ad
|
||||
/frontend/src/storybook/ @H4ad
|
||||
|
||||
1
.github/workflows/integrationci.yaml
vendored
@@ -75,6 +75,7 @@ jobs:
|
||||
- ttl
|
||||
- clickhousecluster
|
||||
- metricreduction
|
||||
- metricsexplorer
|
||||
sqlstore-provider:
|
||||
- postgres
|
||||
- sqlite
|
||||
|
||||
@@ -41,7 +41,6 @@ linters:
|
||||
no-global: all
|
||||
context: all
|
||||
static-msg: true
|
||||
key-naming-case: snake
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
|
||||
25
cmd/authz.go
@@ -93,18 +93,19 @@ func runGenerateAuthz(_ context.Context) error {
|
||||
registry := coretypes.NewRegistry()
|
||||
|
||||
allowedResources := map[string]bool{
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceDashboard).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceLicense).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSubscription).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceLogs).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceTraces).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMeterMetrics).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceQuickFilter).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSavedView).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceServiceAccount).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceRole).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceFactorAPIKey).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceDashboard).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceLicense).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSubscription).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceLogs).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceTraces).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMetrics).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceTelemetryResourceMeterMetrics).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceQuickFilter).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceSavedView).String(): true,
|
||||
coretypes.NewResourceRef(coretypes.ResourceMetaResourceNotificationChannel).String(): true,
|
||||
}
|
||||
|
||||
allowedTypes := map[string]bool{}
|
||||
|
||||
@@ -251,6 +251,7 @@ components:
|
||||
opsgenie: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelOpsgenieConfig'
|
||||
pagerduty: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelPagerdutyConfig'
|
||||
slack: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfig'
|
||||
telegram: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig'
|
||||
webhook: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelWebhookConfig'
|
||||
propertyName: kind
|
||||
oneOf:
|
||||
@@ -264,6 +265,7 @@ components:
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJiraConfig'
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJSMOpsConfig'
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfig'
|
||||
- $ref: '#/components/schemas/AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig'
|
||||
type: object
|
||||
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelEmailConfig:
|
||||
properties:
|
||||
@@ -373,6 +375,18 @@ components:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfig:
|
||||
properties:
|
||||
kind:
|
||||
enum:
|
||||
- telegram
|
||||
type: string
|
||||
spec:
|
||||
$ref: '#/components/schemas/AlertmanagertypesChannelTelegramConfig'
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
type: object
|
||||
AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelWebhookConfig:
|
||||
properties:
|
||||
kind:
|
||||
@@ -525,6 +539,7 @@ components:
|
||||
- jira
|
||||
- jsmops
|
||||
- incidentio
|
||||
- telegram
|
||||
type: string
|
||||
AlertmanagertypesChannelListOrder:
|
||||
enum:
|
||||
@@ -729,6 +744,31 @@ components:
|
||||
- title
|
||||
- value
|
||||
type: object
|
||||
AlertmanagertypesChannelTelegramConfig:
|
||||
properties:
|
||||
apiUrl:
|
||||
type: string
|
||||
botToken:
|
||||
format: password
|
||||
type: string
|
||||
chatId:
|
||||
format: int64
|
||||
type: integer
|
||||
disableNotifications:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
messageThreadId:
|
||||
type: integer
|
||||
sendResolved:
|
||||
nullable: true
|
||||
type: boolean
|
||||
title:
|
||||
type: string
|
||||
required:
|
||||
- botToken
|
||||
- chatId
|
||||
type: object
|
||||
AlertmanagertypesChannelWebhookConfig:
|
||||
properties:
|
||||
bearerToken:
|
||||
@@ -1022,6 +1062,8 @@ components:
|
||||
- jsmops_configs
|
||||
- required:
|
||||
- incidentio_configs
|
||||
- required:
|
||||
- telegram_configs
|
||||
- required:
|
||||
- discord_configs
|
||||
- required:
|
||||
@@ -1042,8 +1084,6 @@ components:
|
||||
- victorops_configs
|
||||
- required:
|
||||
- sns_configs
|
||||
- required:
|
||||
- telegram_configs
|
||||
- required:
|
||||
- webex_configs
|
||||
- required:
|
||||
@@ -1119,7 +1159,7 @@ components:
|
||||
type: array
|
||||
telegram_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigTelegramConfig'
|
||||
$ref: '#/components/schemas/AlertmanagertypesTelegramReceiverConfig'
|
||||
type: array
|
||||
victorops_configs:
|
||||
items:
|
||||
@@ -1263,7 +1303,7 @@ components:
|
||||
type: array
|
||||
telegram_configs:
|
||||
items:
|
||||
$ref: '#/components/schemas/ConfigTelegramConfig'
|
||||
$ref: '#/components/schemas/AlertmanagertypesTelegramReceiverConfig'
|
||||
type: array
|
||||
victorops_configs:
|
||||
items:
|
||||
@@ -1334,6 +1374,34 @@ components:
|
||||
- timezone
|
||||
- startTime
|
||||
type: object
|
||||
AlertmanagertypesTelegramReceiverConfig:
|
||||
properties:
|
||||
api_url:
|
||||
$ref: '#/components/schemas/ConfigURLType2'
|
||||
chat:
|
||||
format: int64
|
||||
type: integer
|
||||
chat_file:
|
||||
type: string
|
||||
disable_notifications:
|
||||
type: boolean
|
||||
http_config:
|
||||
$ref: '#/components/schemas/ConfigHTTPClientConfig'
|
||||
message:
|
||||
type: string
|
||||
message_thread_id:
|
||||
type: integer
|
||||
parse_mode:
|
||||
type: string
|
||||
send_resolved:
|
||||
type: boolean
|
||||
title:
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
token_file:
|
||||
type: string
|
||||
type: object
|
||||
AlertmanagertypesTestableNotificationChannel:
|
||||
properties:
|
||||
config:
|
||||
@@ -1374,9 +1442,13 @@ components:
|
||||
AuthtypesAuthDomainConfigGoogle:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
description: The kind of authn provider.
|
||||
spec:
|
||||
$ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesGoogleConfig'
|
||||
description: The google auth configuration.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -1384,9 +1456,13 @@ components:
|
||||
AuthtypesAuthDomainConfigOIDC:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
description: The kind of authn provider.
|
||||
spec:
|
||||
$ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesOIDCConfig'
|
||||
description: The oidc configuration.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -1394,9 +1470,13 @@ components:
|
||||
AuthtypesAuthDomainConfigSAML:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesAuthNProvider'
|
||||
description: The kind of authn provider.
|
||||
spec:
|
||||
$ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/AuthtypesSamlConfig'
|
||||
description: The saml configuration.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -3501,6 +3581,7 @@ components:
|
||||
- meter-metrics
|
||||
- logs-field
|
||||
- traces-field
|
||||
- metrics-field
|
||||
type: string
|
||||
CoretypesObject:
|
||||
properties:
|
||||
@@ -8152,9 +8233,13 @@ components:
|
||||
Querybuildertypesv5BucketOptionsLinear:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
description: How the upper bounds are spaced.
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5LinearBucketsSpec'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5LinearBucketsSpec'
|
||||
description: The evenly spaced bucket specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -8162,9 +8247,13 @@ components:
|
||||
Querybuildertypesv5BucketOptionsLog:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5BucketsKind'
|
||||
description: How the upper bounds are spaced.
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5LogBucketsSpec'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5LogBucketsSpec'
|
||||
description: The logarithmic bucket specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -8753,54 +8842,78 @@ components:
|
||||
Querybuildertypesv5QueryEnvelopeBuilder:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5BuilderQuerySpec'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5BuilderQuerySpec'
|
||||
description: The builder query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeBuilderAI:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregation'
|
||||
description: The AI builder query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeClickHouseSQL:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5ClickHouseQuery'
|
||||
description: The ClickHouse SQL query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeFormula:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderFormula'
|
||||
description: The formula specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopePromQL:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5PromQuery'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5PromQuery'
|
||||
description: The PromQL query specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
Querybuildertypesv5QueryEnvelopeTraceOperator:
|
||||
properties:
|
||||
spec:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryBuilderTraceOperator'
|
||||
description: The trace operator specification.
|
||||
type:
|
||||
$ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Querybuildertypesv5QueryType'
|
||||
description: The type of the query.
|
||||
required:
|
||||
- type
|
||||
type: object
|
||||
@@ -9360,9 +9473,13 @@ components:
|
||||
RuletypesEvaluationCumulative:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
description: The kind of evaluation.
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesCumulativeWindow'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesCumulativeWindow'
|
||||
description: The cumulative window evaluation specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -9385,9 +9502,13 @@ components:
|
||||
RuletypesEvaluationRolling:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesEvaluationKind'
|
||||
description: The kind of evaluation.
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesRollingWindow'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesRollingWindow'
|
||||
description: The rolling window evaluation specification.
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -9768,9 +9889,13 @@ components:
|
||||
RuletypesThresholdBasic:
|
||||
properties:
|
||||
kind:
|
||||
$ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesThresholdKind'
|
||||
description: The kind of threshold.
|
||||
spec:
|
||||
$ref: '#/components/schemas/RuletypesBasicRuleThresholds'
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/RuletypesBasicRuleThresholds'
|
||||
description: The basic threshold specification (array of thresholds).
|
||||
required:
|
||||
- kind
|
||||
- spec
|
||||
@@ -12994,9 +13119,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- planned-maintenance:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- planned-maintenance:list
|
||||
summary: List downtime schedules
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13051,9 +13176,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- planned-maintenance:create
|
||||
- planned-maintenance:attach
|
||||
- rule:attach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- planned-maintenance:create
|
||||
- planned-maintenance:attach
|
||||
- rule:attach
|
||||
summary: Create downtime schedule
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13098,9 +13227,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- planned-maintenance:delete
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- planned-maintenance:delete
|
||||
summary: Delete downtime schedule
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13156,9 +13285,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- planned-maintenance:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- planned-maintenance:read
|
||||
summary: Get downtime schedule by ID
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -13213,9 +13342,17 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- planned-maintenance:update
|
||||
- planned-maintenance:attach
|
||||
- planned-maintenance:detach
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- planned-maintenance:update
|
||||
- planned-maintenance:attach
|
||||
- planned-maintenance:detach
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
summary: Update downtime schedule
|
||||
tags:
|
||||
- downtimeschedules
|
||||
@@ -14498,9 +14635,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- route-policy:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- route-policy:list
|
||||
summary: List route policies
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14555,9 +14692,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- route-policy:create
|
||||
- route-policy:attach
|
||||
- notification-channel:attach
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- route-policy:create
|
||||
- route-policy:attach
|
||||
- notification-channel:attach
|
||||
summary: Create route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14602,9 +14743,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- route-policy:delete
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- route-policy:delete
|
||||
summary: Delete route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14660,9 +14801,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- route-policy:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- route-policy:read
|
||||
summary: Get route policy by ID
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -14729,9 +14870,17 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- route-policy:update
|
||||
- route-policy:attach
|
||||
- route-policy:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
- route-policy:update
|
||||
- route-policy:attach
|
||||
- route-policy:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
summary: Update route policy
|
||||
tags:
|
||||
- routepolicies
|
||||
@@ -20513,9 +20662,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics-field:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics-field:list
|
||||
summary: List metric names
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20582,9 +20731,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Get metric alerts
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20666,9 +20815,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics-field:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics-field:read
|
||||
summary: Get metric attributes
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20736,9 +20885,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
summary: Get metric highlights
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20795,9 +20944,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
summary: Inspect raw metric data points
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20865,9 +21014,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics-field:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics-field:read
|
||||
summary: Get metric metadata
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20911,9 +21060,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- metrics-field:update
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- metrics-field:update
|
||||
summary: Update metric metadata
|
||||
tags:
|
||||
- metrics
|
||||
@@ -20959,9 +21108,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
summary: Check if non-SigNoz metrics have been received
|
||||
tags:
|
||||
- metrics
|
||||
@@ -21018,9 +21167,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
summary: Get metrics statistics
|
||||
tags:
|
||||
- metrics
|
||||
@@ -21077,9 +21226,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- metrics:read
|
||||
summary: Get metrics treemap
|
||||
tags:
|
||||
- metrics
|
||||
@@ -22129,9 +22278,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: List rule saved views
|
||||
tags:
|
||||
- rules
|
||||
@@ -22187,9 +22336,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Create rule saved view
|
||||
tags:
|
||||
- rules
|
||||
@@ -22241,9 +22390,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Delete rule saved view
|
||||
tags:
|
||||
- rules
|
||||
@@ -22310,9 +22459,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: Update rule saved view
|
||||
tags:
|
||||
- rules
|
||||
@@ -22361,9 +22510,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: List alert rules
|
||||
tags:
|
||||
- rules
|
||||
@@ -23202,9 +23351,23 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Create alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -23249,9 +23412,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:delete
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:delete
|
||||
summary: Delete alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -23307,9 +23470,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get alert rule by ID
|
||||
tags:
|
||||
- rules
|
||||
@@ -24160,9 +24323,27 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Patch alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -25001,9 +25182,27 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:update
|
||||
- rule:attach
|
||||
- rule:detach
|
||||
- notification-channel:attach
|
||||
- notification-channel:detach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Update alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -25070,9 +25269,25 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:read
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:read
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Clone alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -25172,9 +25387,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- logs-field:list
|
||||
- traces-field:list
|
||||
- metrics-field:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- logs-field:list
|
||||
- traces-field:list
|
||||
- metrics-field:list
|
||||
summary: Get rule history filter keys
|
||||
tags:
|
||||
- rules
|
||||
@@ -25282,9 +25501,13 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- logs-field:read
|
||||
- traces-field:read
|
||||
- metrics-field:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- logs-field:read
|
||||
- traces-field:read
|
||||
- metrics-field:read
|
||||
summary: Get rule history filter values
|
||||
tags:
|
||||
- rules
|
||||
@@ -25357,9 +25580,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule overall status timeline
|
||||
tags:
|
||||
- rules
|
||||
@@ -25429,9 +25652,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
summary: Get rule history stats
|
||||
tags:
|
||||
- rules
|
||||
@@ -25521,9 +25744,19 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Get rule history timeline
|
||||
tags:
|
||||
- rules
|
||||
@@ -25596,9 +25829,19 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:read
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Get top contributors to rule firing
|
||||
tags:
|
||||
- rules
|
||||
@@ -26438,9 +26681,23 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
- tokenizer:
|
||||
- EDITOR
|
||||
- rule:create
|
||||
- rule:attach
|
||||
- notification-channel:attach
|
||||
- logs:read
|
||||
- traces:read
|
||||
- metrics:read
|
||||
- audit-logs:read
|
||||
- meter-metrics:read
|
||||
summary: Test alert rule
|
||||
tags:
|
||||
- rules
|
||||
@@ -28218,9 +28475,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- dashboard:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- dashboard:list
|
||||
summary: Get metric dashboards (v2)
|
||||
tags:
|
||||
- metrics
|
||||
@@ -28306,9 +28563,9 @@ paths:
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
- rule:list
|
||||
summary: List alert rules (v3)
|
||||
tags:
|
||||
- rules
|
||||
|
||||
@@ -43,6 +43,25 @@ type Resource interface {
|
||||
|
||||
All resources are registered in [pkg/types/coretypes/registry_resource.go](/pkg/types/coretypes/registry_resource.go) using constructors like `NewResourceMetaResource(KindDashboard)` or `NewResourceTelemetryResource(KindLogs)`.
|
||||
|
||||
### Telemetry resources and metaresources
|
||||
|
||||
Almost every feature resource is one of these two types, and they answer different questions.
|
||||
|
||||
- A **telemetry resource** (`logs`, `traces`, `metrics`, `meter-metrics`, `audit-logs`) is the data itself. It only supports `read`. Its selector is not an ID but the query: the route wires a `TelemetryResourceDef` whose extractor turns the caller's filter into `<query_type>/<key>/<value>` IDs, and the check ladders each one through `<query_type>/<key>/*`, `<query_type>/*` and `*`. A grant such as `builder_query/signoz.workspace.key.id/key-a` therefore decides which rows a role may read; a query that does not pin a granted key resolves to `<query_type>/*` and is denied for key-scoped roles. Managed roles hold `*`.
|
||||
- A **metaresource** (`dashboard`, `rule`, `logs-field`, ...) is an object that describes or configures the system. It supports whichever of `create`, `read`, `update`, `delete` and `list` the resource declares, its selectors are object IDs or `*`, and routes wire a `BasicResourceDef`. It says nothing about rows: `dashboard:read` on `*` does not grant the telemetry a dashboard's panels query.
|
||||
|
||||
The metrics explorer uses both on one surface:
|
||||
|
||||
| Route | Resource | Why |
|
||||
|---|---|---|
|
||||
| `POST /api/v2/metrics/stats` | `metrics:read` (telemetry) | Returns sample and series counts, i.e. the data. The selector comes from `filter.expression`, so a role scoped to a workspace key sees only its own samples. |
|
||||
| `GET /api/v2/metrics` | `metrics-field:list` (meta) | Lists metric names, types and units: the catalogue, not the samples. |
|
||||
| `POST /api/v2/metrics/metadata` | `metrics-field:update` (meta) | Edits a metric's type, unit and description. |
|
||||
|
||||
The two are independent: a role holding `metrics-field` `list` and `read` can browse the catalogue but cannot read a sample, and a role scoped to `builder_query/signoz.workspace.key.id/key-a` on `metrics` can query its samples but cannot list the catalogue.
|
||||
|
||||
For a new route, ask what it returns. Rows or aggregates over them go on the signal's telemetry resource with an extractor; anything that names, describes or configures telemetry goes on a metaresource.
|
||||
|
||||
### Selector
|
||||
|
||||
A `Selector` identifies *which* instance(s) of a resource a check is about — a UUID, a role name, or the wildcard `*`. A `SelectorFunc` maps the extracted resource ID to selectors at request time. Two prebuilt ones cover most routes ([pkg/types/coretypes/selector.go](/pkg/types/coretypes/selector.go)):
|
||||
@@ -142,6 +161,7 @@ Because both paths go through the same middleware and the same `ResourceDef` dec
|
||||
## What should I remember?
|
||||
|
||||
- Declare authz in the registries ([pkg/types/coretypes](/pkg/types/coretypes)), not in migrations — tuples for new organizations are derived from code at bootstrap.
|
||||
- Telemetry resources are the data: `read` only, selector derived from the query. Metaresources are objects about the data: CRUD and list, ID or wildcard selectors. A route that returns rows or aggregates is telemetry; one that names or configures them is meta, as `metrics` versus `metrics-field` shows.
|
||||
- A new kind never needs an OpenFGA schema change; only a new type does, and then **both** [pkg/authz/openfgaschema/base.fga](/pkg/authz/openfgaschema/base.fga) and [ee/authz/openfgaschema/base.fga](/ee/authz/openfgaschema/base.fga) must be updated together.
|
||||
- Prefer `CheckResources` + `ResourceDef` over the coarse `ViewAccess`/`EditAccess`/`AdminAccess` gates for new routes.
|
||||
- Use `WildcardSelector` for `create`/`list`, `IDSelector` for instance operations, and a custom `SelectorFunc` when the request ID is not the FGA selector.
|
||||
|
||||
@@ -11,7 +11,7 @@ Before running E2E tests, ensure you have the following installed:
|
||||
- Python 3.13+
|
||||
- [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||
- Docker (for containerized services)
|
||||
- Node 18+ and pnpm
|
||||
- Node 18+ and Yarn
|
||||
|
||||
### Initial Setup
|
||||
|
||||
@@ -24,8 +24,8 @@ uv sync
|
||||
2. Install Node deps and Playwright browsers:
|
||||
```bash
|
||||
cd e2e
|
||||
pnpm install
|
||||
pnpm install:browsers # one-time Playwright browser install
|
||||
yarn install
|
||||
yarn install:browsers # one-time Playwright browser install
|
||||
```
|
||||
|
||||
### Starting the Test Environment
|
||||
@@ -84,117 +84,126 @@ tests/
|
||||
├── playwright.config.ts # loads .env + .env.local via dotenv
|
||||
├── .env.example # staging-mode template
|
||||
├── .env.local # generated by bootstrap/setup.py (gitignored)
|
||||
├── AGENTS.md # read this before writing a spec
|
||||
├── bootstrap/
|
||||
│ └── setup.py # test_setup / test_teardown — pytest lifecycle
|
||||
├── drivers/ # the driver kit: one module per feature
|
||||
│ ├── kit.ts # layer(), seeded(), the root `test`
|
||||
│ ├── auth.ts # login + per-worker storageState
|
||||
│ ├── api.ts # authed HTTP client + seeder URL
|
||||
│ ├── tanstack.ts # the shared table driver
|
||||
│ ├── alerts/ # feature driver, seeds, payloads
|
||||
│ └── infra/ # feature driver, seeds, __generated__/datasets.ts
|
||||
├── docs/flake-notes.md # measurements and timeout budgets
|
||||
├── testdata/ # static data files (marked generated)
|
||||
├── tests/ # Playwright .spec.ts files, one dir per feature
|
||||
├── drivers/ # the driver kit and one driver per feature
|
||||
│ ├── kit.ts
|
||||
│ └── routing.ts
|
||||
├── fixtures/ # Playwright test fixtures (test.extend) only
|
||||
│ └── auth.ts
|
||||
├── helpers/ # function helpers + the constants they share with tests
|
||||
│ ├── auth.ts
|
||||
│ └── dashboards.ts
|
||||
├── testdata/ # static data files (JSON) used by helpers and tests
|
||||
│ └── apm-metrics.json # (example)
|
||||
├── tests/ # Playwright .spec.ts files, one dir per feature area
|
||||
│ └── alerts/
|
||||
│ └── list.spec.ts # (example)
|
||||
│ └── alerts.spec.ts # (example)
|
||||
└── artifacts/ # per-run output (gitignored)
|
||||
├── html/ # HTML reporter output
|
||||
├── json/ # JSON reporter output
|
||||
└── results/ # per-test traces / screenshots / videos on failure
|
||||
```
|
||||
|
||||
### The driver kit
|
||||
### `fixtures/` vs `helpers/` — what goes where
|
||||
|
||||
A spec imports **one** module: its feature's `test` constant. That constant is
|
||||
built by composing layers, and each layer adds one named driver to the test
|
||||
arguments — so a spec body never handles a `Page`:
|
||||
These two folders look similar but mean different things:
|
||||
|
||||
- **`fixtures/`** holds *Playwright test fixtures* (created via `test.extend({...})`). By the canonical definition, a fixture is "a consistent, predefined set of data, objects, or environmental conditions used to ensure tests run in a stable state" — i.e. setup/teardown that runs *automatically* around each test or worker. `auth.ts` matches: it extends Playwright's `test` with an `authedPage` that's logged-in before every test runs and torn down after. If the only thing in this folder ever is `auth.ts`, that's fine — fixtures are a deliberately small surface.
|
||||
- **`helpers/`** holds plain function helpers that you call *explicitly* from a test or hook — they don't extend Playwright's `test`. This covers both behaviour helpers (e.g. `gotoDashboardsList(page)`) and the constants those helpers and the tests both refer to (e.g. `SEARCH_PLACEHOLDER`). Constants live next to the helpers that use them so a single import line in a test covers both.
|
||||
- **`testdata/`** holds static data files (typically JSON / YAML) consumed by the helpers — for example, `apm-metrics.json`, a real dashboard payload uploaded through the UI by an importer helper.
|
||||
|
||||
Rule of thumb: if it's a `test.extend` fixture, put it in `fixtures/`. If it's a function you call explicitly (or a constant the function uses), put it in `helpers/`. If it's a static file the helpers read, put it in `testdata/`.
|
||||
|
||||
### Drivers
|
||||
|
||||
New suites use a driver rather than loose helpers. A driver is a function that closes over the page and returns the feature's actions and locators. `drivers/kit.ts` turns it into a fixture, so a spec imports one module and never passes a `page` around:
|
||||
|
||||
```ts
|
||||
import { expect, test } from '../../drivers/alerts';
|
||||
import { expect, PATH, test } from '../../drivers/routing';
|
||||
|
||||
test('TC-01 seeded rules render', async ({ alerts, alertList }) => {
|
||||
await alerts.list.goto({ search: alertList.namePrefix });
|
||||
await expect(alerts.list.rows()).toHaveCount(10);
|
||||
test('TC-09 RouteTab switches the url', async ({ routing }) => {
|
||||
await routing.goto(PATH.metricsSummary);
|
||||
await expect(routing.activeRouteTab(PATH.metricsSummary)).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
- **`drivers/kit.ts`** holds the composition primitives. `driver(name, build)`
|
||||
binds a factory to the authenticated page; `seeded(name, open, close)` seeds
|
||||
data once per worker on a throwaway admin page and tears it down after;
|
||||
`allowFor(floor)` raises the test timeout to a floor and never lowers it.
|
||||
`test.slow()` is banned in drivers, because it compounds when called in a loop.
|
||||
- **`drivers/<feature>.ts`** (or `drivers/<feature>/`) exports the feature's
|
||||
driver, its layers, and the composed `test`. A driver is a plain function
|
||||
returning an object of closures — no classes, and no `page` parameter on
|
||||
anything a spec calls.
|
||||
- **Shared drivers** — `tanstack.ts` (the table), `querysearch.ts` (the
|
||||
expression editor), `antd.ts` (select primitives), `console.ts`, `api.ts`,
|
||||
`storage.ts` — are composed into feature drivers by direct call, not by
|
||||
reaching for another fixture.
|
||||
- **`drivers/` is AI-owned**: coding agents generate and maintain it with the
|
||||
specs, and `.gitattributes` collapses it in diffs.
|
||||
- **`testdata/`** and any `__generated__/` directory hold transcribed data. Both
|
||||
are marked `linguist-generated`; something in the suite re-derives them at
|
||||
runtime, so a stale value fails loudly rather than silently.
|
||||
- `driver(name, build)` adds a test-scoped fixture built on `authedPage`.
|
||||
- `seeded(name, open, close)` adds a worker-scoped seed. `open` and `close` run on a throwaway admin page.
|
||||
- A driver reuses helpers by calling them, never by reaching for another fixture.
|
||||
- `drivers/` is AI-owned: coding agents generate and maintain it with the specs, and `.gitattributes` collapses it in diffs.
|
||||
|
||||
### Extended fixtures
|
||||
|
||||
For features needing complex setup (API-seeded data, ruler evaluation waits, cleanup), create domain-specific fixtures that extend `auth`. Group them in `fixtures/<domain>/`.
|
||||
|
||||
**Fixture scopes:**
|
||||
- **test scope** — fresh data per test. Use for mutations (edit, delete, rename).
|
||||
- **worker scope** — shared across a worker. Use for read-only data; it pays the
|
||||
setup cost once per worker instead of once per test. Playwright builds them
|
||||
lazily, so a spec that never destructures a seed never pays for it.
|
||||
- **suite scope**: a dataset only one file uses. Seed it in `test.beforeAll`
|
||||
through `onAdminPage(browser, …)`, since `authedPage` is test-scoped.
|
||||
- **worker scope** — shared across tests in one worker. Use for read-only data. Worker scope pays the setup cost once per worker instead of once per test.
|
||||
|
||||
### Which layer does this test belong to?
|
||||
**The alerts pattern** (`fixtures/alerts/`) demonstrates extending fixtures:
|
||||
|
||||
Four layers run against this codebase, in cost order: **jest → Storybook + msw →
|
||||
pytest integration → Playwright**. Before writing an E2E, check the cheaper
|
||||
three. [`tests/e2e/AGENTS.md`](../../../tests/e2e/AGENTS.md) has the full rule;
|
||||
the short version:
|
||||
```
|
||||
fixtures/alerts/
|
||||
├── alert-rules.ts # extends auth — worker-scoped rule list + test-scoped factory
|
||||
└── alert-history.ts # extends alert-rules — adds history fixtures (waits on ruler)
|
||||
```
|
||||
|
||||
- A pure function, a reducer, a config literal or a component's own behaviour is
|
||||
a **jest** test. Most containers under `frontend/src/container/` already have
|
||||
one.
|
||||
- A rendered state that needs no backend (empty, error, loading) is a
|
||||
**Storybook** story or a jest test with a fixed mock.
|
||||
- Anything the **API** does — ordering, pagination, filter semantics, validation,
|
||||
roll-ups — is a pytest integration test, and for infra monitoring and alert
|
||||
evaluation it very likely already exists. Grep `tests/integration/` first.
|
||||
- An **E2E** is for a real response reaching a real DOM: URL and history, write
|
||||
paths that must persist, cross-page navigation, and one seeded proof per
|
||||
surface.
|
||||
Specs import from the fixture they need:
|
||||
|
||||
```ts
|
||||
// List tests — just need rules, no history
|
||||
import { test, expect } from '../../../fixtures/alerts/alert-rules';
|
||||
|
||||
// History tests — need history rows from ruler evaluation
|
||||
import { test, expect } from '../../../fixtures/alerts/alert-history';
|
||||
```
|
||||
|
||||
**When creating new fixtures:**
|
||||
|
||||
1. **Identify scope** — Will tests mutate the data? If yes, test-scoped. If read-only, worker-scoped.
|
||||
2. **Group by domain** — Put fixtures in `fixtures/<domain>/`. Helpers in `helpers/<domain>/`.
|
||||
3. **Extend existing fixtures** — Chain from `auth` or another fixture to inherit its setup.
|
||||
4. **Handle timeouts** — Worker-scoped fixtures that wait on backend processing need explicit timeouts.
|
||||
5. **Clean up** — Always delete seeded data in the fixture teardown (after `use()`).
|
||||
6. **Extract logic into functions** — Keep the `test.extend()` block lean; move setup/teardown logic to named functions so the extend block reads as a manifest of "what fixtures exist."
|
||||
|
||||
Each spec follows these principles:
|
||||
|
||||
1. **Directory per feature**: `tests/e2e/tests/<feature>/*.spec.ts`. Cross-resource junction concerns (e.g. cascade-delete) go in their own file, not packed into one giant spec.
|
||||
2. **Test titles use `TC-NN`**: `test('TC-01 alerts page — tabs render', ...)`. Preserves ordering at a glance and maps to external coverage tracking.
|
||||
3. **UI-first**: drive flows through the UI. Playwright traces capture every BE request/response the UI triggers, so asserting on UI outcomes implicitly validates BE contracts. To assert a write, attach `page.waitForResponse` before the click, then reload and re-read from the server.
|
||||
4. **Self-contained state**: each spec seeds its own data, scopes its assertions to the names it seeded, and cleans up. The pytest harness creates a fresh stack with **zero** dashboards / alerts / etc. Names carry the worker index, so parallel workers never collide. Pick the scope from the list above.
|
||||
5. **Seed through the kit when the UI flow is not under test.** `apiDriver(page)` (`drivers/api.ts`) creates control-plane objects with the session's token, and the seeder takes telemetry. The JWT lives in `localStorage`, so a spec never improvises with `page.request.*`. Feature drivers wrap both (`dashboards.create`, `alerts.seed.threshold`).
|
||||
6. **Reusable static data lives in `tests/e2e/testdata/`.** For example, `traces/large-trace.json` is POSTed to the seeder by `drivers/traces.ts`. Infra monitoring reads the integration suite's fixtures through `seedDataset` (`drivers/infra/seed.ts`) instead of keeping a copy, and asserts on what it returned.
|
||||
7. **Locate by testid, in the driver.** A missing testid is added to the product in the same change. Role, label and CSS lookups are a stopgap, converted as that code is touched; antd internals (`.ant-select-dropdown`) are the exception, since the markup is not ours.
|
||||
3. **UI-first**: drive flows through the UI. Playwright traces capture every BE request/response the UI triggers, so asserting on UI outcomes implicitly validates BE contracts. Reach for direct `page.request.*` only when the test's *purpose* is asserting a response contract (use `page.waitForResponse` on a UI click) or when a specific UI step is structurally flaky (e.g. Ant DatePicker calendar-cell indices) — and even then try UI first.
|
||||
4. **Self-contained state**: each spec seeds its own data and cleans up at suite teardown. The pytest harness creates a fresh stack with **zero** dashboards / alerts / etc. — never assume pre-existing data. Two patterns work:
|
||||
- **Per-test seed + cleanup in `try / finally`** — small specs where each test owns its data.
|
||||
- **Suite-level seed + `afterAll` teardown** — preferred for larger specs. Each `createDashboard(...)` call adds the resulting ID to a module-level `Set<string>`, and one `test.afterAll(...)` deletes everything in the set. See `tests/e2e/tests/dashboards/list.spec.ts` for the full pattern. `test.beforeAll` / `test.afterAll` cannot use `authedPage` directly (it's test-scoped); use `newAdminContext(browser)` from `helpers/auth.ts` instead — it performs one fresh login per suite hook.
|
||||
5. **Seed via API when the UI flow is multi-step or brittle.** The frontend stores its JWT in `localStorage` under `AUTH_TOKEN`; `page.request.*` inherits the auth fixture's storage state. A typical pattern:
|
||||
```ts
|
||||
const token = await page.evaluate(
|
||||
() => (globalThis as any).localStorage.getItem('AUTH_TOKEN') || '',
|
||||
);
|
||||
await page.request.post('/api/v1/dashboards', {
|
||||
data: { title: 'my-name', uploadedGrafana: false },
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
```
|
||||
This is faster and more reliable than a multi-step UI seed. Reach for the UI flow only when the test's *purpose* is asserting that flow.
|
||||
6. **Reusable static data lives in `tests/e2e/testdata/`.** For example, `apm-metrics.json` is a real dashboard payload that `importApmMetricsDashboardViaUI` (in `helpers/dashboards.ts`) uploads through the actual Import JSON UI flow to seed a richly-tagged dashboard for search/list tests.
|
||||
|
||||
## How to write an E2E test?
|
||||
|
||||
Create a new file `tests/e2e/tests/alerts/smoke.spec.ts`:
|
||||
|
||||
```typescript
|
||||
import { ALERT_LIST_PAGE_SIZE, expect, test } from '../../drivers/alerts';
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
|
||||
test('TC-01 seeded rules render and paginate', async ({ alerts, alertList }) => {
|
||||
await alerts.list.goto({ search: alertList.namePrefix });
|
||||
await expect(alerts.list.table.totalCount()).toContainText(`of ${alertList.count}`);
|
||||
await expect(alerts.list.rows()).toHaveCount(ALERT_LIST_PAGE_SIZE);
|
||||
test('TC-01 alerts page — tabs render', async ({ authedPage: page }) => {
|
||||
await page.goto('/alerts');
|
||||
await expect(page.getByRole('tab', { name: /alert rules/i })).toBeVisible();
|
||||
await expect(page.getByRole('tab', { name: /configuration/i })).toBeVisible();
|
||||
});
|
||||
```
|
||||
|
||||
`alertList` is a worker-scoped seed: the rules exist before the test starts, are
|
||||
shared by every test in the worker that asks for them, and are deleted when the
|
||||
worker ends.
|
||||
|
||||
The `authedPage` fixture (from `tests/e2e/drivers/kit.ts`, re-exported by every feature's `test`) gives you a `Page` whose browser context is already authenticated as the admin user. First use per worker triggers one login; the resulting `storageState` is held in memory and reused for later requests. Take it only when the driver genuinely does not cover what you need — navigation, `goBack`, `waitForResponse`, a second tab.
|
||||
The `authedPage` fixture (from `tests/e2e/fixtures/auth.ts`) gives you a `Page` whose browser context is already authenticated as the admin user. First use per worker triggers one login; the resulting `storageState` is held in memory and reused for later requests.
|
||||
|
||||
To run just this test (assuming the stack is up via `test_setup`):
|
||||
|
||||
@@ -203,56 +212,52 @@ cd tests/e2e
|
||||
npx playwright test tests/alerts/smoke.spec.ts --project=chromium
|
||||
```
|
||||
|
||||
Here's a more comprehensive example, driven entirely through the feature's
|
||||
driver:
|
||||
Here's a more comprehensive example that exercises a CRUD flow via the UI:
|
||||
|
||||
```typescript
|
||||
import { expect, test } from '../../drivers/alerts';
|
||||
import { test, expect } from '../../fixtures/auth';
|
||||
|
||||
test('TC-02 row actions persist: disable, then enable', async ({
|
||||
authedPage: page,
|
||||
alerts,
|
||||
ownedRules,
|
||||
}) => {
|
||||
const name = `smoke-rule-${Date.now()}`;
|
||||
const ruleId = await ownedRules.threshold(name);
|
||||
test.describe.configure({ mode: 'serial' });
|
||||
|
||||
await alerts.list.goto({ search: name });
|
||||
await expect(alerts.list.stateCell(ruleId)).toHaveText('OK');
|
||||
test('TC-02 alerts list — create, toggle, delete', async ({ authedPage: page }) => {
|
||||
await page.goto('/alerts?tab=AlertRules');
|
||||
const name = 'smoke-rule';
|
||||
|
||||
await alerts.list.openFirstRowActions();
|
||||
await Promise.all([
|
||||
// waitForResponse captures the call the UI triggers — no parallel fetch.
|
||||
page.waitForResponse(
|
||||
(res) =>
|
||||
res.url().includes(`/api/v2/rules/${ruleId}`) &&
|
||||
res.request().method() === 'PATCH',
|
||||
),
|
||||
alerts.list.menuItem('Disable').click(),
|
||||
]);
|
||||
// Seed via UI — click "New Alert", fill form, save.
|
||||
await page.getByRole('button', { name: /new alert/i }).click();
|
||||
await page.getByTestId('alert-name-input').fill(name);
|
||||
// ... fill metric / threshold / save ...
|
||||
|
||||
// Re-read from the server rather than trusting the optimistic update.
|
||||
await page.reload();
|
||||
await expect(alerts.list.stateCell(ruleId)).toHaveText('Disabled');
|
||||
// Find the row and exercise the action menu.
|
||||
const row = page.locator('tr', { hasText: name });
|
||||
await expect(row).toBeVisible();
|
||||
await row.locator('[data-testid="alert-actions"] button').first().click();
|
||||
|
||||
// waitForResponse captures the network call the UI triggers — no parallel fetch needed.
|
||||
const patchWait = page.waitForResponse(
|
||||
(r) => r.url().includes('/rules/') && r.request().method() === 'PATCH',
|
||||
);
|
||||
await page.getByRole('menuitem').filter({ hasText: /^disable$/i }).click();
|
||||
await patchWait;
|
||||
await expect(row).toContainText(/disabled/i);
|
||||
});
|
||||
```
|
||||
|
||||
`ownedRules` is a test-scoped seed: every rule it creates is deleted when the
|
||||
test finishes, so the spec owns its data and nothing leaks into the next one.
|
||||
### Locator priority
|
||||
|
||||
### Commits
|
||||
|
||||
Commit driver changes apart from specs, product testids and tests moved to
|
||||
another layer, so each commit can be reviewed on its own.
|
||||
[`tests/e2e/AGENTS.md`](../../../tests/e2e/AGENTS.md#commits) has the order.
|
||||
`pnpm typecheck`, `pnpm lint` and `pnpm fmt:check` gate the suite.
|
||||
1. `getByTestId('...')` — preferred when the source exposes one. Stable, app-author-provided handle that survives copy-edits.
|
||||
2. `getByRole('button', { name: 'Submit' })`
|
||||
3. `getByLabel('Email')`
|
||||
4. `getByPlaceholder('...')`
|
||||
5. `getByText('...')`
|
||||
6. `locator('.ant-select')` — last resort (Ant Design dropdowns often have no semantic alternative)
|
||||
|
||||
## Agents
|
||||
|
||||
Three Claude agents in `.claude/agents/` accelerate writing and maintaining E2E specs:
|
||||
|
||||
- **`playwright-test-planner`** — explores a feature in a real browser plus the local frontend source and writes a test plan as a scratch markdown file (under `tests/e2e/specs/`, which is gitignored — plans are working artifacts for the generator, not committed docs).
|
||||
- **`playwright-test-generator`** — converts a test plan into Playwright spec files under `tests/e2e/tests/<feature>/`. Drives each scenario through MCP browser tools and emits TC-NN-titled tests on the feature's driver, adding the driver methods and seeds the plan needs.
|
||||
- **`playwright-test-generator`** — converts a test plan into Playwright spec files under `tests/e2e/tests/<feature>/`. Drives each scenario through MCP browser tools and emits TC-NN-titled tests using the `authedPage` fixture and the API-seed pattern.
|
||||
- **`playwright-test-healer`** — runs failing specs, debugs them with snapshots / console / network introspection, and edits the spec to fix selector drift, timing, or state-leak issues.
|
||||
|
||||
The agents rely on the Playwright-test MCP server (`mcp__playwright-test__*` tools). Configure it in your Claude MCP settings; the permission allowlist lives in [.claude/settings.local.json](../../../.claude/settings.local.json).
|
||||
@@ -264,15 +269,15 @@ The agents rely on the Playwright-test MCP server (`mcp__playwright-test__*` too
|
||||
With the stack already up, from `tests/e2e/`:
|
||||
|
||||
```bash
|
||||
pnpm test # headless, all projects
|
||||
yarn test # headless, all projects
|
||||
```
|
||||
|
||||
### Running Specific Projects
|
||||
|
||||
```bash
|
||||
pnpm test:chromium # chromium only
|
||||
pnpm test:firefox
|
||||
pnpm test:webkit
|
||||
yarn test:chromium # chromium only
|
||||
yarn test:firefox
|
||||
yarn test:webkit
|
||||
```
|
||||
|
||||
### Running Specific Tests
|
||||
@@ -290,17 +295,17 @@ npx playwright test tests/alerts/history/ --project=chromium
|
||||
npx playwright test tests/alerts/page.spec.ts --project=chromium
|
||||
|
||||
# Single test by title grep
|
||||
npx playwright test --project=chromium -g "TC-01"
|
||||
npx playwright test --project=chromium -g "AL-01"
|
||||
```
|
||||
|
||||
### Iterative modes
|
||||
|
||||
```bash
|
||||
pnpm test:ui # Playwright UI mode — watch + step through
|
||||
pnpm test:headed # headed browser
|
||||
pnpm test:debug # Playwright inspector, pause-on-breakpoint
|
||||
pnpm codegen # record-and-replay locator generation
|
||||
pnpm report # open the last HTML report (artifacts/html)
|
||||
yarn test:ui # Playwright UI mode — watch + step through
|
||||
yarn test:headed # headed browser
|
||||
yarn test:debug # Playwright inspector, pause-on-breakpoint
|
||||
yarn codegen # record-and-replay locator generation
|
||||
yarn report # open the last HTML report (artifacts/html)
|
||||
```
|
||||
|
||||
### Staging fallback
|
||||
@@ -310,7 +315,7 @@ Point `SIGNOZ_E2E_BASE_URL` at a remote env via `.env` — no local backend brin
|
||||
```bash
|
||||
cd tests/e2e
|
||||
cp .env.example .env # fill SIGNOZ_E2E_USERNAME / PASSWORD
|
||||
pnpm test:staging
|
||||
yarn test:staging
|
||||
```
|
||||
|
||||
## How to configure different options for E2E tests?
|
||||
@@ -358,14 +363,12 @@ The same pytest flags integration tests expose work here, since E2E reuses the s
|
||||
- **Always use the `--reuse` flag** when setting up the E2E stack. `--with-web` adds a ~4 min frontend build on a cold start; later builds are incremental.
|
||||
- **Changed backend or frontend code? Re-run the setup command** — it passes `--rebuild`, swapping the SigNoz container for one built from your current sources while the rest of the stack stays up.
|
||||
- **Don't teardown before setup.** `--reuse` correctly handles partially-set-up state, so chaining teardown → setup wastes time.
|
||||
- **Prefer UI-driven flows.** Playwright captures BE requests in the trace; a parallel `fetch` probe is almost always redundant. Seed through the kit, never with `page.request.*`.
|
||||
- **Prefer UI-driven flows.** Playwright captures BE requests in the trace; a parallel `fetch` probe is almost always redundant. Drop to `page.request.*` only when the UI can't reach what you need.
|
||||
- **Use `page.waitForResponse` on UI clicks** to assert BE contracts — it still exercises the UI trigger path.
|
||||
- **Title every test `TC-NN <short description>`** — keeps the suite navigable and reportable.
|
||||
- **Split by resource, not by regression suite.** One spec per feature resource; cross-resource junction concerns (cascade-delete, linked-edit) get their own file.
|
||||
- **Name resources by what they are, plus the worker index** (`e2e-alert-list-w${workerIndex}-${Date.now()}`). Six workers share one stack, so assert only on names you seeded.
|
||||
- **Use short descriptive resource names** (`alerts-list-rule`, `labels-rule`, `downtime-once`) — no timestamp disambiguation. Each test owns its resources and cleans up in `try/finally`.
|
||||
- **Never commit `test.only`** — a pre-commit check or CI runs with `forbidOnly: true`.
|
||||
- **Prefer explicit waits over `page.waitForTimeout(ms)`.** `await expect(locator).toBeVisible()` is always better than `waitForTimeout(5000)`.
|
||||
- **Unique test names won't save you from shared-tenant state.** When two tests hit the same list page, either serialize (`describe.configure({ mode: 'serial' })`) or isolate cleanup religiously.
|
||||
- **Green locally, red on CI?** Run `STRESS=1 CI=1 pnpm test <path>` (4x CPU throttle, 2 workers, 2 retries) before filing a product bug.
|
||||
- **Commit drivers apart from specs.** One concern per commit; see [Commits](#commits).
|
||||
- **Artifacts go to `tests/e2e/artifacts/`** — HTML report at `artifacts/html`, traces at `artifacts/results/<test>/`. All gitignored; archive the dir in CI.
|
||||
|
||||
@@ -116,7 +116,7 @@ func (c *clickhouse) EstimateCardinality(ctx context.Context, metricName string,
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
reducedExpr := "1"
|
||||
reducedExpr := "toUInt64(1)"
|
||||
if len(keptLabels) > 0 {
|
||||
reducedExpr = "uniq(("
|
||||
for i, label := range keptLabels {
|
||||
|
||||
@@ -277,10 +277,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (int, error) {
|
||||
lb.Set(ruletypes.AlertRuleIDLabel, r.ID())
|
||||
lb.Set(ruletypes.RuleSourceLabel, r.GeneratorURL())
|
||||
|
||||
annotations := make(ruletypes.Labels, 0, len(r.Annotations().Map()))
|
||||
for name, value := range r.Annotations().Map() {
|
||||
annotations = append(annotations, ruletypes.Label{Name: name, Value: expand(value)})
|
||||
}
|
||||
annotations := r.ExpandAnnotations(expand)
|
||||
if smpl.IsMissing {
|
||||
lb.Set(ruletypes.AlertNameLabel, "[No data] "+r.Name())
|
||||
lb.Set(ruletypes.NoDataLabel, "true")
|
||||
|
||||
@@ -262,23 +262,20 @@
|
||||
// React Hooks rules - Enforces Rules of Hooks (only call at top level)
|
||||
"react-hooks/exhaustive-deps": "warn",
|
||||
// Warns about missing dependencies in useEffect/useMemo/useCallback
|
||||
// NOTE: The following react-hooks rules are not supported right know, follow the progress at https://github.com/oxc-project/oxc/issues/1022
|
||||
// Most of them are for React Compiler, which we don't have enabled right know
|
||||
// "react-hooks/config": "error",
|
||||
// "react-hooks/error-boundaries": "error",
|
||||
// "react-hooks/component-hook-factories": "error",
|
||||
// "react-hooks/gating": "error",
|
||||
// "react-hooks/globals": "error",
|
||||
// "react-hooks/immutability": "error",
|
||||
// "react-hooks/preserve-manual-memoization": "error",
|
||||
// "react-hooks/purity": "error",
|
||||
// "react-hooks/refs": "error",
|
||||
// "react-hooks/set-state-in-effect": "error",
|
||||
// "react-hooks/set-state-in-render": "error",
|
||||
// "react-hooks/static-components": "error",
|
||||
// "react-hooks/unsupported-syntax": "warn",
|
||||
// "react-hooks/use-memo": "error",
|
||||
// "react-hooks/incompatible-library": "warn",
|
||||
// React Compiler rules, off because we don't have React Compiler enabled
|
||||
"react/error-boundaries": "off",
|
||||
"react/globals": "off",
|
||||
"react/immutability": "off",
|
||||
"react/incompatible-library": "off",
|
||||
"react/preserve-manual-memoization": "off",
|
||||
"react/purity": "off",
|
||||
"react/refs": "off",
|
||||
"react/set-state-in-effect": "off",
|
||||
"react/set-state-in-render": "off",
|
||||
"react/static-components": "off",
|
||||
"react/unsupported-syntax": "off",
|
||||
"react/use-memo": "off",
|
||||
"react/void-use-memo": "off",
|
||||
"signoz/no-unsupported-asset-pattern": "error",
|
||||
// Prevents the wrong usage of assets to break custom base path installations
|
||||
"signoz/no-zustand-getstate-in-hooks": "error",
|
||||
@@ -582,6 +579,16 @@
|
||||
"rules": {
|
||||
"signoz/no-dashboard-fetch-outside-root": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
// TypeScript reports real redeclarations; this rule also flags `const X` + `type X` pairs.
|
||||
"files": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"rules": {
|
||||
"no-redeclare": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ const preview: Preview = {
|
||||
'Planned Downtime',
|
||||
'Routing Policies',
|
||||
'Channels',
|
||||
['List', 'New', 'Edit'],
|
||||
['List', 'List Authz', 'New', 'New Authz', 'Edit', 'Edit Authz'],
|
||||
],
|
||||
'Dashboards',
|
||||
[
|
||||
|
||||
@@ -204,9 +204,9 @@
|
||||
"lint-staged": "^17.0.4",
|
||||
"msw": "1.3.2",
|
||||
"orval": "8.22.0",
|
||||
"oxfmt": "0.54.0",
|
||||
"oxlint": "1.69.0",
|
||||
"oxlint-tsgolint": "0.23.0",
|
||||
"oxfmt": "0.73.0",
|
||||
"oxlint": "1.88.0",
|
||||
"oxlint-tsgolint": "7.0.2004",
|
||||
"postcss": "8.5.26",
|
||||
"postcss-scss": "4.0.9",
|
||||
"react-resizable": "3.0.4",
|
||||
|
||||
403
frontend/pnpm-lock.yaml
generated
@@ -487,14 +487,14 @@ importers:
|
||||
specifier: 8.22.0
|
||||
version: 8.22.0(prettier@3.8.3)(typescript@5.9.3)
|
||||
oxfmt:
|
||||
specifier: 0.54.0
|
||||
version: 0.54.0
|
||||
specifier: 0.73.0
|
||||
version: 0.73.0
|
||||
oxlint:
|
||||
specifier: 1.69.0
|
||||
version: 1.69.0(oxlint-tsgolint@0.23.0)
|
||||
specifier: 1.88.0
|
||||
version: 1.88.0(oxlint-tsgolint@7.0.2004)
|
||||
oxlint-tsgolint:
|
||||
specifier: 0.23.0
|
||||
version: 0.23.0
|
||||
specifier: 7.0.2004
|
||||
version: 7.0.2004
|
||||
postcss:
|
||||
specifier: 8.5.26
|
||||
version: 8.5.26
|
||||
@@ -533,7 +533,7 @@ importers:
|
||||
version: 1.6.0(react@18.2.0)
|
||||
vite-plugin-checker:
|
||||
specifier: 0.12.0
|
||||
version: 0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.69.0(oxlint-tsgolint@0.23.0))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
version: 0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.88.0(oxlint-tsgolint@7.0.2004))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)
|
||||
vite-plugin-compression:
|
||||
specifier: 0.5.1
|
||||
version: 0.5.1(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))
|
||||
@@ -2555,276 +2555,276 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-android-arm-eabi@0.54.0':
|
||||
resolution: {integrity: sha512-NAtpl/SiaeU103e7/OmZw0MvUnsUUopW7hEm/ecegJg7YM0skQaA0IXEZoyTV6NUdiNPupdIUreRqUZTShbn/g==}
|
||||
'@oxfmt/binding-android-arm-eabi@0.73.0':
|
||||
resolution: {integrity: sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxfmt/binding-android-arm64@0.54.0':
|
||||
resolution: {integrity: sha512-B4VZfBUlKK1rmMChsssNZbkZjE8+FzG3avMjGgMDwbGxXRoXkoeXiAZ+78Oa+eyDPHvDCiUb4zH/vmCOUSafLQ==}
|
||||
'@oxfmt/binding-android-arm64@0.73.0':
|
||||
resolution: {integrity: sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxfmt/binding-darwin-arm64@0.54.0':
|
||||
resolution: {integrity: sha512-i02vF75b+ePsQP3tHqSxVYI5S6b8X/xqdPu7/mDHXtpgXLTYXi3jJmfHU0j+dnZZDKaYTx/ioCK7QYJmtiJR2g==}
|
||||
'@oxfmt/binding-darwin-arm64@0.73.0':
|
||||
resolution: {integrity: sha512-Tn7Y+qWIpCQK+qdYwBcn6YvqeBuPiuG3tcNlYg16a5QL2o3rSmrgBB1nCyYqo0uvc1LNrfQW9/CK2tzFuID/Fg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxfmt/binding-darwin-x64@0.54.0':
|
||||
resolution: {integrity: sha512-8VMFvGvooXj7mswkbrhdVZ2/sgiDaBzWpkkbtO+qGDLV4EfJd67nQadHkQC0ZNbaWA9ajXfqI6i7PZLIeDzxEQ==}
|
||||
'@oxfmt/binding-darwin-x64@0.73.0':
|
||||
resolution: {integrity: sha512-yVb0E0tg/+9Xc9YJCBq66qF1DC/aN5mhb5nnHz0k4ikwkMANtSul2VPDSwjYdXR6/DFimc+pPUgLBqkIa5Ctuw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxfmt/binding-freebsd-x64@0.54.0':
|
||||
resolution: {integrity: sha512-0cRHnp43WN1Jrc5s0BdbdKgR1XirdvHy7TAFi3JEsoEVQVJxTXMbpVd76sxXlgRswNMDhVFSJw+y7Eb8mEavFQ==}
|
||||
'@oxfmt/binding-freebsd-x64@0.73.0':
|
||||
resolution: {integrity: sha512-IKR4lFnBkYNL9Uqdxa5GMTQLvnLj+3sDwTvh2atHCzah0xeo+1xR5bigRU8uyXlOsA4VpXUn0UAimq4E4RBGmQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.54.0':
|
||||
resolution: {integrity: sha512-JyQAk3hK/OEtup7Rw6kZwfdzbKqTVD5jXXb8Xpfay29suwZyfBDMVW/bj4RqEPySYWc6zCp198pOluf8n5uYzg==}
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.73.0':
|
||||
resolution: {integrity: sha512-7qnZ4g0ZCO3aZevOOQpZIABYwwXJW1lLDo2elviGdPQ0i11255xKEMFsi97fC1/nauQFseCJfELUy/k2JoOP+g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.54.0':
|
||||
resolution: {integrity: sha512-qnvLatTpM8vtvjOfcckBOzJjk+n6ce/wwpP8OFeUrD5aNLYcKyWAitwj+Rk3PK9jGanbZvKsJnv14JGQ6XqFdw==}
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.73.0':
|
||||
resolution: {integrity: sha512-36eBico2j/WPB84gb4FYd/jjqGCKOwU4M+TWLBUIqlnzTozQueLy0joA5BwsSp2OZFVkOlpCwvA3ndPamdiyLw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-SMkhnCzIYZYDk9vw3W/80eeYKmrMpGF0Giuxt4HruFlCH7jEtnPeb3SdQKMfgYi/dgtaf+hZAb5XWPYnxqCQ3w==}
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-VZ3cSgCAT18knKo52DcACmAuIoqG6qVEyeZRFbJd0xmYQ8GyYjuBc6IAh6MZHijT6q7p4/HPI+6ZWsCuhdV6iw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-musl@0.54.0':
|
||||
resolution: {integrity: sha512-QrwJlBFFKnxOd95TAaszpMbZBLzMoYMpGaQTZF8oibacnF5rv8l12IhILhQRPmksWiBqg0YSe2Mnl7ayeJAHSA==}
|
||||
'@oxfmt/binding-linux-arm64-musl@0.73.0':
|
||||
resolution: {integrity: sha512-8YVzH59XSHcqwXNgc5qK851zCqwpmBgOifARXEPPe5/xTsIbHceYoPZAyqfpYV0CfKmzSaEDBgYHFza4rhx+Zw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-WILatiol/TUHTlhod7R09+7Az/XlhKwmY1MHfLZNmewltPWNN/EwxP2rQSHahibZ/cB8gmckEBjBOByD+5bYsQ==}
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-MqRJnmCys0rwOgpWyEidSA7v/bvOqfNDJhPkQoR9mPVYrvSmMaW2Xhdpl28cbTA+brOM1is62GjIvJplVlUVpw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-f05YMG4BH4G8S4ME6UM6fi1MnJ9094mrnvO5Pa4SJlMfWlUM+1/ZWMEF4NnjM7shZAvbHsHRuVYpUo0PHC4P9Q==}
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-4t2F3SWJSK/O/kRRFnL0elJzn75fJGGu71gs1HtiWcS80SmJOpkHiwwLnTp9suGdns8VHEPxYS0GTcyVwHKWBA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.54.0':
|
||||
resolution: {integrity: sha512-UfL+2hj1ClNqcCRT9s8vBU4axDpjxgVxX96G+9DYAYjoc5b0u15CJtn2jgsi9iM+EbGNc5CW1HVRgwVu76UsSA==}
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.73.0':
|
||||
resolution: {integrity: sha512-D8b4I8I1kLdJQFwdFXNV+cwh3xWSiNKofjdGUk0nDpc0AHOds8T+poGbP9RWQMvgsvMb9OMgp/aV7ahBR37TbQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-3/XZe931Hka+J6NjnaqJzYpsWWxDTuRdUdwSQHnOuJEgbC+SehIMFJS8hsEjV7LBhVSL2OCnRLvbVW8O97XIyw==}
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-QrC1S5VL1+N5rvorB15jH7CPvjVHA7GyUI5oVEd8Bd9m5605RDBQYTxDQ920DuylRoZOh2V7/xXpfCtFUrojUQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.54.0':
|
||||
resolution: {integrity: sha512-Ik93RlObtu43GbxApafayFjwYE06L6Xr08cSwpBPYbDrLp2ReZx0Jm1DqwRyYRnukUJy+rK2WaEvUQOxdytU9Q==}
|
||||
'@oxfmt/binding-linux-x64-gnu@0.73.0':
|
||||
resolution: {integrity: sha512-dFcUKQPltCEPa+YlkF3zqufn6nJPZdG73LpnjBvZFFsU3laCMssDXSYB99mVaj43FqdEgjpz5TO6gbYXcczd9g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-musl@0.54.0':
|
||||
resolution: {integrity: sha512-yZcakmPlD86CNymknd7KfW+FH+qfbqJH+i0h69CYfV1+KMoVeM9UED+8+TDVoU4haxI0NxY7RPCvRLy3Sqd2Qg==}
|
||||
'@oxfmt/binding-linux-x64-musl@0.73.0':
|
||||
resolution: {integrity: sha512-LmQe+nV8e+eUwfPNCxDF+owbsg88toBiR/8rZRgVOeKQP3MaGefXZRvBoc1UG37iWEQ/9JlCXG9Mc4nf4N3agw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-openharmony-arm64@0.54.0':
|
||||
resolution: {integrity: sha512-GiVBZNnEZnKu00f1jTg49nomv187d0GQX+O+ocykoLeiaALuEO+swoTehHn9TehTfi7V8H0i0e/yvUjCqnwk1w==}
|
||||
'@oxfmt/binding-openharmony-arm64@0.73.0':
|
||||
resolution: {integrity: sha512-8weOr1OKknE0G2DbR6ejJUT9AOsoBU6cXlsetmbihHv1K+bK7qP61T6lJxY0k/aAO7GyOJiRGNlL+04r4eH8Xg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.54.0':
|
||||
resolution: {integrity: sha512-J0SSB8Z1Fre2sxRolYcW6Rl1RQmKdQ2hnHyq4YJrfBRiXTObLw4DXnIVraM/UyqGqwOi7yTrQA4VT7DPxlHVKA==}
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.73.0':
|
||||
resolution: {integrity: sha512-mZV+QxXYgb8E18gzAFItb937VIsE4dqWdStrD3tRRC52UubOYEuU+e7NaIqUsGdB5YsJqNeuck3Ue/af2r9wyA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.54.0':
|
||||
resolution: {integrity: sha512-O61UDVj8zz6yXJjkHPf05VaMLOXmEF8P5kf/N0W7AQMmd6bcQogl+KJc7rMutKTL524oE9iH32JXZClBFmEQIg==}
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.73.0':
|
||||
resolution: {integrity: sha512-QJPUsbgUb97ZPPMkFUq6VQq0fLhC4Np19KL9aB/qKVwXnpKE6MIiILXqzhKnQ/UbnEDjKJ7Xv1d6zH4bYPCUdQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-win32-x64-msvc@0.54.0':
|
||||
resolution: {integrity: sha512-1MDpqJPiFqxWtIHas8vkb1VZ7f7eKyTffAwmO8isxQYMaG1OFKsH666BWLeXQLO+IWNfiMssLD55hbR1lIPTqg==}
|
||||
'@oxfmt/binding-win32-x64-msvc@0.73.0':
|
||||
resolution: {integrity: sha512-cGcze2fu+ykIaFtkEvZlkZ3HvnCLk+WISthtq99zWTWn0x13t5L9TxvGdLZMUMvtfYR+ICkiCwKnguVubdcw0w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.23.0':
|
||||
resolution: {integrity: sha512-gOs9PVr2wEg4ox9z0aJo+RKhhImW86YL5N6yav8BK/rgPsIrwN/igSZ+pbRr723NFvUNKde9fgMhRA6JrXAOZw==}
|
||||
'@oxlint-tsgolint/darwin-arm64@7.0.2004':
|
||||
resolution: {integrity: sha512-uJk3ttju7Lz+Nv8WG4CsLyewoL1qf/Ny8S8OksJ7Bm/F14Tc9gMfW6arM1PCc57IzH/X/I79YsCWTgxX1FKibQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint-tsgolint/darwin-x64@0.23.0':
|
||||
resolution: {integrity: sha512-kjJ8B+7n4tB9VJdxS5A9GdJt6/bYpzbu4lXp2uO1S3sRmCB5gDEABlGoiePNApRWaW+xqL4b4xgiE727jSLhuA==}
|
||||
'@oxlint-tsgolint/darwin-x64@7.0.2004':
|
||||
resolution: {integrity: sha512-0BFfHTzDxNDFPcu0UZAXT6CyCJHAXM0dZveyDb4KEHa2qF/DSnOKOrmvK/wFuFol9kvijbGM9JfuOsor+zWXRw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint-tsgolint/linux-arm64@0.23.0':
|
||||
resolution: {integrity: sha512-6dCZuKNu135seMXilkRk9SpCx6i1XgmiipYGalLij5WVRX6ZYS8c4xI7preN/zv9fCXhsQclTIMDu2Y/cytTjw==}
|
||||
'@oxlint-tsgolint/linux-arm64@7.0.2004':
|
||||
resolution: {integrity: sha512-TAehQc+qdRXyDN3nl/r/3/oPrBVrbtrDeGMkFyG6mf+XphpoJ2lAMVFLclyvrVZCw02RbKROjyw8h72XwQQHcw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.23.0':
|
||||
resolution: {integrity: sha512-3bdilnyA7kmSTjK27rvjIjSxL5SIg3wt7vwNiRkouWB83ytssyKnuGvxSYJxgMEmFpSutzaBzcCUM2jDtPGcgA==}
|
||||
'@oxlint-tsgolint/linux-x64@7.0.2004':
|
||||
resolution: {integrity: sha512-LPunZvm6ZbWPfHMAyfZf9ci3HHvNEzLejO8zdqO3GoVGoSEHqqufHUD7+OgeatVSaJP7uXDJ2HaNOHIoH7go4Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.23.0':
|
||||
resolution: {integrity: sha512-j+OEp44SVYiQ+ZD+uttsX7u6L9SvmbbQ77SO1pSFCcJlsVMeCk8qZsjhKfGKuT/jIA+ipOJMVs/+pqUfObBWNw==}
|
||||
'@oxlint-tsgolint/win32-arm64@7.0.2004':
|
||||
resolution: {integrity: sha512-FVWplzDImWhiuawnIZVETaQecv4GWom6Jv4Fx2xj7hhXXDZ5okEgiw1gTCImHTIQsi2ycC5oSxVwrn69bMh7iw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.23.0':
|
||||
resolution: {integrity: sha512-5MyjFuqf+g8OUPJBSGWHJtmoWnzFJYyOg4To9WMQshZYEWig/vtu7JtJ03VWnzHv9LJkAUeApY0gVCOywFR/iQ==}
|
||||
'@oxlint-tsgolint/win32-x64@7.0.2004':
|
||||
resolution: {integrity: sha512-7PsxuUx11Ry9DYTWcljTzRbmZY9B6O9fFFfZV+aodQEMy6pfRT3fhoQRv9PtGN/89z1pPz3nFKrsFm6WqNtX7w==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-android-arm-eabi@1.69.0':
|
||||
resolution: {integrity: sha512-DKQQbD5cZ/MYfDgDI7YGyGD9FSxABlsBsYFo5p26lloob543tP9+4N3guwdXIYJN+7HSZxLe8YJuwcOWw5qnHg==}
|
||||
'@oxlint/binding-android-arm-eabi@1.88.0':
|
||||
resolution: {integrity: sha512-k6Fgkf86ubfeH4GvqYsRolfuVAp0pfYVZvifSlXNHPdMaWUOfxUrmLEeDD7hF6U2oJuhsVxhSWq86/xCJ+9MmA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxlint/binding-android-arm64@1.69.0':
|
||||
resolution: {integrity: sha512-lEhb+I5pr4inux+JFwfCa1HRq3Os7NirEFQ0H1I35SVEHPm6byX0Ah47xmRha3qi6LAkxUcxViL8o/9PivjzBg==}
|
||||
'@oxlint/binding-android-arm64@1.88.0':
|
||||
resolution: {integrity: sha512-AsIyhl9o1ejwtac7NMbhKpMdSml6NWR0ljKjZERxC8OHYDAW8o/Om9n1QHVLi4VCEUo42Zte2/Op9kyoGpJZMg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxlint/binding-darwin-arm64@1.69.0':
|
||||
resolution: {integrity: sha512-GY2YE8lOZW59BW1Ia1y+1gR0XyjrZRvVWHAr8LGeGhYHE0OQJ/7cRKXTkx1P+E9/6awEc3SX8a68SFTjh/E//A==}
|
||||
'@oxlint/binding-darwin-arm64@1.88.0':
|
||||
resolution: {integrity: sha512-qgBjB7eEKcCB4RqjXGuoSZq74g0wD09fWFJ+HWcCJcKaga8TeUHtb+BY0SsxdleSLBgx4RZ7qmgo0EzqEq7JoQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint/binding-darwin-x64@1.69.0':
|
||||
resolution: {integrity: sha512-ax1oZnOjHX3LB7myQyHEaQkDwfLb6str3/nSP6O7EVUviQGNkEGzGV0EqcBJWK+Ufwx0l4xPgyYayurvhAdl2Q==}
|
||||
'@oxlint/binding-darwin-x64@1.88.0':
|
||||
resolution: {integrity: sha512-sCz3X5Mw/xdzEzn4fsCt1Iq8NKJnGNfhR+b9B5k+4DysYAHsmIhYoocnBPiQ9kvY3+pUun2A0Z8Vtun/Nv6YoQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint/binding-freebsd-x64@1.69.0':
|
||||
resolution: {integrity: sha512-kHWeHv4g2h8NY+mpCxzCtY4uerMJWTN/TSnNj1CPbakFpHEJ6cTya2wWV0pDSYWOJ2+0UiEbhn3AtXxHtsnKjg==}
|
||||
'@oxlint/binding-freebsd-x64@1.88.0':
|
||||
resolution: {integrity: sha512-xTOdYOIqCufgIj0knvxu86FBfovP6QBiD7ngNva01gb8xPQCD5gXkE5APGczH35zStim1O3zqvFbLQmY85xXRg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.69.0':
|
||||
resolution: {integrity: sha512-gq84vM1a1oEehXo27YCDzGVcxPsZDI1yswZwz2Da1/cbnWtrL16XZZnz0G/+gIU8edtHpfjxq5c+vWEHqJfWoQ==}
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.88.0':
|
||||
resolution: {integrity: sha512-BLadtw88NdYJBrAtC1dYzrPfSP2d5zgx6iuZycdD0YmZBJ2Nqbit9Z4PSaIb6gxK25dweArGMPbOmq/0ugwUPg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.69.0':
|
||||
resolution: {integrity: sha512-kIqEa98JQ0VRyrcncxA417m2AzasqTlD+FyVT1AksjvjkqQcvm7pBWYvoW3/mpyOP2XYvi5nSCCTIe6De1yu5g==}
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.88.0':
|
||||
resolution: {integrity: sha512-wILSczl/DaZsCcf7j7tvNbssp8hxEHIJxrv4j7+JjI3TAL1U/JTWbeUtoJJC/vo8+2hkTmHLYoKJjmiNbijdXw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/binding-linux-arm64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-j+xYiXozxGWx2cpjCrwwGR4awTxPFsRv3JZrv23RCogEPMc4R7UqjHW47p/RG0aRlbWiROCJ8coUfCwy0dvzHA==}
|
||||
'@oxlint/binding-linux-arm64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-jutT2Hy8JCuZuMwIFHo2HtyxfOfBJHyQxlBHBK1SL9W2f8n/WhjAP0FnDMz9d/mjhv9+QA0mx5AdQ9fDjsrFFg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.69.0':
|
||||
resolution: {integrity: sha512-xEPpNppTfN1l/nM7gYSf9iocscu/as+p/7vxkLeLEKnYU+09Dm+5V6IhDYDh+Uz6FajEupWwCLt5SOG0y1PCKg==}
|
||||
'@oxlint/binding-linux-arm64-musl@1.88.0':
|
||||
resolution: {integrity: sha512-8N8iGV9hh4Lkeuq7QsEkjoXNRRflqR1G2qyoGYXcA0k5qllrdqmfylKEXyHIVERxqNDyMO4StqDQ0uLxXcBQAg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-Ug0+eU7HJBlek+SjklYH62IlOMirEJsdxpihH0kSqX0XdrDD4NdHpQc10fK1JC35yn6KrrcN+uYzlHD38XAf8Q==}
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-5ydcQt3znNQ/rGEWYRvpS2cK9+T18P2VYgnyJ+eloEAwi9YUmsXy5VGrRqNWIK6qzWwHGnEE3ojVqqQNo+f0hA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-iEyI3GIg0l/s3G4qy2TlaaWKdzj4PJJStwtlocpDTC00PY9hZueotf6OKUj9+yfQh0lrpBW/pLMgTztbAHKJEg==}
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-iGx0uvFT54b/5DPJdEBCxYIrVB3v0uTy+JhRh3CyEp8/22BXDkG8rffkFwREdLsdj9mOrPbJYVOZw8ipcBuzjw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.69.0':
|
||||
resolution: {integrity: sha512-NjHjpiI4WIKSMwuoJSZi5VToPeoYOS1FR52HLIDG6lidMdqquusgtODb4iLk0+lb1q3Z0nv2/aPRcC/olmpQGg==}
|
||||
'@oxlint/binding-linux-riscv64-musl@1.88.0':
|
||||
resolution: {integrity: sha512-Wt47mKMWelVjRjuR7BCFxXkB0m79K1sTEbqMHRWciKUgo5iv7sWsF4RXSOyFYHD9zerNrkRZblnN273rhwOA1Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-Ai/prDewoItkDXbp38gwGZi41DycZbUTZJ3UidwoHgQC0/DaqC2TGdtBTQLJ6hSD+SAxASzh8+/eSBPmxfOacA==}
|
||||
'@oxlint/binding-linux-s390x-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-g3L12KCS4+1gtUt3RbJ4F+Yxx4LNmmZLEPfhd8rAHEJjJk2lAgNfnjGoP/phJyhG5WS6VbNYbJtadJelNfvTMA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.69.0':
|
||||
resolution: {integrity: sha512-Gt3KHgp46mRKz4sJeaASmKvD8ayXookRw07RMf+NowhEztGGDZ7VrXpoW96XuKJLjFukWizOFVNjmYb/u7caNQ==}
|
||||
'@oxlint/binding-linux-x64-gnu@1.88.0':
|
||||
resolution: {integrity: sha512-og/XHVDOD9ZJgRiZkT9DgzQd4sr+JZW7e4l17Q/jVMZiIbeEd4uIu9iAoUbTF1gIvVZoRc4QDOxyVUvQGp4WHg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.69.0':
|
||||
resolution: {integrity: sha512-7tQhJ2+p/oHv1zcfnjYI7YVzC/7iBaVOfIvFYtxdJ5F45mWgEdrCyXZXZGfiLey5t/5JhOhsaMnnv1kAzckd7g==}
|
||||
'@oxlint/binding-linux-x64-musl@1.88.0':
|
||||
resolution: {integrity: sha512-EmhBsp+xuKb6YbZNEMLBo80Xw71AZOIm18do8ni/GLX/Lw1XzNhVYpbxO1KasYtoAvrGtCOdCPULRlIE0bPbTQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.69.0':
|
||||
resolution: {integrity: sha512-vmWz6TKp/3hfA4lksR0zHBv/6xuX1jhym6eqOjdH2DXsDDHZWcp2f0KG0VCAnlVbIrjk29G4wAWMXb/Hn1YobA==}
|
||||
'@oxlint/binding-openharmony-arm64@1.88.0':
|
||||
resolution: {integrity: sha512-DBUcx/KwYdg1DAz+G01P/GFv512J04EsZadbHptx2FTpWT/MWRg21ZToNHt9pnsfQRBQ3et/g+vqzbvrdAVbuA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxlint/binding-win32-arm64-msvc@1.69.0':
|
||||
resolution: {integrity: sha512-9RExaLgmaw6IoIkU9cTpT71mLfI0xZ86iZH8x518LVsOkjquJMYqb9P7KpC8lgd1t0Dxs41p2pxynq4XR3Ttzw==}
|
||||
'@oxlint/binding-win32-arm64-msvc@1.88.0':
|
||||
resolution: {integrity: sha512-xeVd0n4XG5HvynxfYvXi6uB+JvZbVZataowPS8cYpMbYg9mwE4sSyElFQd4LVJFifq8bRLDSR18XgEildmcXAg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-win32-ia32-msvc@1.69.0':
|
||||
resolution: {integrity: sha512-1907kRPF8/PrcIw1E7LMs9JbVrpgnt/MvFdss3an8oDkYNAACXzTntV3t3869ZZhMZxb2AzRGbz1pA/jdFatXA==}
|
||||
'@oxlint/binding-win32-ia32-msvc@1.88.0':
|
||||
resolution: {integrity: sha512-MA6iOw//amleaz2mVMsZrA7NHlEexjTrhIxSMFCXQi6IUuQO+3Od00GZhr2iCYw6RFzMW36jkKK/WHBXMsoQAw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-win32-x64-msvc@1.69.0':
|
||||
resolution: {integrity: sha512-w8SOXv3mT9Fi6jY8OXdXCfnvX/3KNLXGNr4HEz2TA7S4Mv/PYAOmpB8y/ge40mxvBMgGNaSaaDwZpAsQn7HtWA==}
|
||||
'@oxlint/binding-win32-x64-msvc@1.88.0':
|
||||
resolution: {integrity: sha512-3Sz6rVWe8Dy+ufsXurFN1GX2dX43F/lQMSLelK9gFjiba7nHvLlqlhR5cHlWVnCpSqUY1hN9pyCX9XToe6ve6g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -7785,29 +7785,32 @@ packages:
|
||||
oxc-resolver@11.24.2:
|
||||
resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==}
|
||||
|
||||
oxfmt@0.54.0:
|
||||
resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==}
|
||||
oxfmt@0.73.0:
|
||||
resolution: {integrity: sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@astrojs/compiler-rs': ^0.4.0 || ^0.5.0
|
||||
svelte: ^5.0.0
|
||||
vite-plus: '*'
|
||||
peerDependenciesMeta:
|
||||
'@astrojs/compiler-rs':
|
||||
optional: true
|
||||
svelte:
|
||||
optional: true
|
||||
vite-plus:
|
||||
optional: true
|
||||
|
||||
oxlint-tsgolint@0.23.0:
|
||||
resolution: {integrity: sha512-3mBv3CoPbh8dFbzfDGIWa2ytZjn2v+3EX4aKRXjIhsoGFzG8GCjfRirz3rwZf1wYbZzsNLTSgpw8VjQuWdp/jA==}
|
||||
oxlint-tsgolint@7.0.2004:
|
||||
resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==}
|
||||
hasBin: true
|
||||
|
||||
oxlint@1.69.0:
|
||||
resolution: {integrity: sha512-ypZkK/aDc5NQV8zIR6s2H2Tl3aNW8FmJ1m9+2qsaYuRenl8vgnHNCGwTHviWJdUQzglOlHFchgopdtGhSy17Rw==}
|
||||
oxlint@1.88.0:
|
||||
resolution: {integrity: sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
oxlint-tsgolint: '>=0.22.1'
|
||||
oxlint-tsgolint: '>=7.0.2003'
|
||||
vite-plus: '*'
|
||||
peerDependenciesMeta:
|
||||
oxlint-tsgolint:
|
||||
@@ -9399,8 +9402,8 @@ packages:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@2.1.0:
|
||||
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
|
||||
tinypool@2.2.0:
|
||||
resolution: {integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0}
|
||||
|
||||
tinyrainbow@2.0.0:
|
||||
@@ -12469,136 +12472,136 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-x64-msvc@11.24.2':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-android-arm-eabi@0.54.0':
|
||||
'@oxfmt/binding-android-arm-eabi@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-android-arm64@0.54.0':
|
||||
'@oxfmt/binding-android-arm64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-darwin-arm64@0.54.0':
|
||||
'@oxfmt/binding-darwin-arm64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-darwin-x64@0.54.0':
|
||||
'@oxfmt/binding-darwin-x64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-freebsd-x64@0.54.0':
|
||||
'@oxfmt/binding-freebsd-x64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.54.0':
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.54.0':
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-arm64-musl@0.54.0':
|
||||
'@oxfmt/binding-linux-arm64-musl@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.54.0':
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.54.0':
|
||||
'@oxfmt/binding-linux-x64-gnu@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-linux-x64-musl@0.54.0':
|
||||
'@oxfmt/binding-linux-x64-musl@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-openharmony-arm64@0.54.0':
|
||||
'@oxfmt/binding-openharmony-arm64@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.54.0':
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.54.0':
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/binding-win32-x64-msvc@0.54.0':
|
||||
'@oxfmt/binding-win32-x64-msvc@0.73.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.23.0':
|
||||
'@oxlint-tsgolint/darwin-arm64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/darwin-x64@0.23.0':
|
||||
'@oxlint-tsgolint/darwin-x64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-arm64@0.23.0':
|
||||
'@oxlint-tsgolint/linux-arm64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.23.0':
|
||||
'@oxlint-tsgolint/linux-x64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.23.0':
|
||||
'@oxlint-tsgolint/win32-arm64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.23.0':
|
||||
'@oxlint-tsgolint/win32-x64@7.0.2004':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-android-arm-eabi@1.69.0':
|
||||
'@oxlint/binding-android-arm-eabi@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-android-arm64@1.69.0':
|
||||
'@oxlint/binding-android-arm64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-darwin-arm64@1.69.0':
|
||||
'@oxlint/binding-darwin-arm64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-darwin-x64@1.69.0':
|
||||
'@oxlint/binding-darwin-x64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-freebsd-x64@1.69.0':
|
||||
'@oxlint/binding-freebsd-x64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.69.0':
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.69.0':
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-arm64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.69.0':
|
||||
'@oxlint/binding-linux-arm64-musl@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.69.0':
|
||||
'@oxlint/binding-linux-riscv64-musl@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-s390x-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.69.0':
|
||||
'@oxlint/binding-linux-x64-gnu@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.69.0':
|
||||
'@oxlint/binding-linux-x64-musl@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.69.0':
|
||||
'@oxlint/binding-openharmony-arm64@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-arm64-msvc@1.69.0':
|
||||
'@oxlint/binding-win32-arm64-msvc@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-ia32-msvc@1.69.0':
|
||||
'@oxlint/binding-win32-ia32-msvc@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-x64-msvc@1.69.0':
|
||||
'@oxlint/binding-win32-x64-msvc@1.88.0':
|
||||
optional: true
|
||||
|
||||
'@parcel/watcher-android-arm64@2.5.1':
|
||||
@@ -18323,61 +18326,61 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-arm64-msvc': 11.24.2
|
||||
'@oxc-resolver/binding-win32-x64-msvc': 11.24.2
|
||||
|
||||
oxfmt@0.54.0:
|
||||
oxfmt@0.73.0:
|
||||
dependencies:
|
||||
tinypool: 2.1.0
|
||||
tinypool: 2.2.0
|
||||
optionalDependencies:
|
||||
'@oxfmt/binding-android-arm-eabi': 0.54.0
|
||||
'@oxfmt/binding-android-arm64': 0.54.0
|
||||
'@oxfmt/binding-darwin-arm64': 0.54.0
|
||||
'@oxfmt/binding-darwin-x64': 0.54.0
|
||||
'@oxfmt/binding-freebsd-x64': 0.54.0
|
||||
'@oxfmt/binding-linux-arm-gnueabihf': 0.54.0
|
||||
'@oxfmt/binding-linux-arm-musleabihf': 0.54.0
|
||||
'@oxfmt/binding-linux-arm64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-arm64-musl': 0.54.0
|
||||
'@oxfmt/binding-linux-ppc64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-riscv64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-riscv64-musl': 0.54.0
|
||||
'@oxfmt/binding-linux-s390x-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-x64-gnu': 0.54.0
|
||||
'@oxfmt/binding-linux-x64-musl': 0.54.0
|
||||
'@oxfmt/binding-openharmony-arm64': 0.54.0
|
||||
'@oxfmt/binding-win32-arm64-msvc': 0.54.0
|
||||
'@oxfmt/binding-win32-ia32-msvc': 0.54.0
|
||||
'@oxfmt/binding-win32-x64-msvc': 0.54.0
|
||||
'@oxfmt/binding-android-arm-eabi': 0.73.0
|
||||
'@oxfmt/binding-android-arm64': 0.73.0
|
||||
'@oxfmt/binding-darwin-arm64': 0.73.0
|
||||
'@oxfmt/binding-darwin-x64': 0.73.0
|
||||
'@oxfmt/binding-freebsd-x64': 0.73.0
|
||||
'@oxfmt/binding-linux-arm-gnueabihf': 0.73.0
|
||||
'@oxfmt/binding-linux-arm-musleabihf': 0.73.0
|
||||
'@oxfmt/binding-linux-arm64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-arm64-musl': 0.73.0
|
||||
'@oxfmt/binding-linux-ppc64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-riscv64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-riscv64-musl': 0.73.0
|
||||
'@oxfmt/binding-linux-s390x-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-x64-gnu': 0.73.0
|
||||
'@oxfmt/binding-linux-x64-musl': 0.73.0
|
||||
'@oxfmt/binding-openharmony-arm64': 0.73.0
|
||||
'@oxfmt/binding-win32-arm64-msvc': 0.73.0
|
||||
'@oxfmt/binding-win32-ia32-msvc': 0.73.0
|
||||
'@oxfmt/binding-win32-x64-msvc': 0.73.0
|
||||
|
||||
oxlint-tsgolint@0.23.0:
|
||||
oxlint-tsgolint@7.0.2004:
|
||||
optionalDependencies:
|
||||
'@oxlint-tsgolint/darwin-arm64': 0.23.0
|
||||
'@oxlint-tsgolint/darwin-x64': 0.23.0
|
||||
'@oxlint-tsgolint/linux-arm64': 0.23.0
|
||||
'@oxlint-tsgolint/linux-x64': 0.23.0
|
||||
'@oxlint-tsgolint/win32-arm64': 0.23.0
|
||||
'@oxlint-tsgolint/win32-x64': 0.23.0
|
||||
'@oxlint-tsgolint/darwin-arm64': 7.0.2004
|
||||
'@oxlint-tsgolint/darwin-x64': 7.0.2004
|
||||
'@oxlint-tsgolint/linux-arm64': 7.0.2004
|
||||
'@oxlint-tsgolint/linux-x64': 7.0.2004
|
||||
'@oxlint-tsgolint/win32-arm64': 7.0.2004
|
||||
'@oxlint-tsgolint/win32-x64': 7.0.2004
|
||||
|
||||
oxlint@1.69.0(oxlint-tsgolint@0.23.0):
|
||||
oxlint@1.88.0(oxlint-tsgolint@7.0.2004):
|
||||
optionalDependencies:
|
||||
'@oxlint/binding-android-arm-eabi': 1.69.0
|
||||
'@oxlint/binding-android-arm64': 1.69.0
|
||||
'@oxlint/binding-darwin-arm64': 1.69.0
|
||||
'@oxlint/binding-darwin-x64': 1.69.0
|
||||
'@oxlint/binding-freebsd-x64': 1.69.0
|
||||
'@oxlint/binding-linux-arm-gnueabihf': 1.69.0
|
||||
'@oxlint/binding-linux-arm-musleabihf': 1.69.0
|
||||
'@oxlint/binding-linux-arm64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-arm64-musl': 1.69.0
|
||||
'@oxlint/binding-linux-ppc64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-riscv64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-riscv64-musl': 1.69.0
|
||||
'@oxlint/binding-linux-s390x-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-x64-gnu': 1.69.0
|
||||
'@oxlint/binding-linux-x64-musl': 1.69.0
|
||||
'@oxlint/binding-openharmony-arm64': 1.69.0
|
||||
'@oxlint/binding-win32-arm64-msvc': 1.69.0
|
||||
'@oxlint/binding-win32-ia32-msvc': 1.69.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.69.0
|
||||
oxlint-tsgolint: 0.23.0
|
||||
'@oxlint/binding-android-arm-eabi': 1.88.0
|
||||
'@oxlint/binding-android-arm64': 1.88.0
|
||||
'@oxlint/binding-darwin-arm64': 1.88.0
|
||||
'@oxlint/binding-darwin-x64': 1.88.0
|
||||
'@oxlint/binding-freebsd-x64': 1.88.0
|
||||
'@oxlint/binding-linux-arm-gnueabihf': 1.88.0
|
||||
'@oxlint/binding-linux-arm-musleabihf': 1.88.0
|
||||
'@oxlint/binding-linux-arm64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-arm64-musl': 1.88.0
|
||||
'@oxlint/binding-linux-ppc64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-riscv64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-riscv64-musl': 1.88.0
|
||||
'@oxlint/binding-linux-s390x-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-x64-gnu': 1.88.0
|
||||
'@oxlint/binding-linux-x64-musl': 1.88.0
|
||||
'@oxlint/binding-openharmony-arm64': 1.88.0
|
||||
'@oxlint/binding-win32-arm64-msvc': 1.88.0
|
||||
'@oxlint/binding-win32-ia32-msvc': 1.88.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.88.0
|
||||
oxlint-tsgolint: 7.0.2004
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
@@ -20206,7 +20209,7 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
tinypool@2.1.0: {}
|
||||
tinypool@2.2.0: {}
|
||||
|
||||
tinyrainbow@2.0.0: {}
|
||||
|
||||
@@ -20588,7 +20591,7 @@ snapshots:
|
||||
unist-util-stringify-position: 4.0.0
|
||||
vfile-message: 4.0.2
|
||||
|
||||
vite-plugin-checker@0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.69.0(oxlint-tsgolint@0.23.0))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3):
|
||||
vite-plugin-checker@0.12.0(eslint@10.2.1(jiti@2.6.1))(meow@13.2.0)(optionator@0.9.4)(oxlint@1.88.0(oxlint-tsgolint@7.0.2004))(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.9.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.9.1))(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
chokidar: 4.0.3
|
||||
@@ -20603,7 +20606,7 @@ snapshots:
|
||||
eslint: 10.2.1(jiti@2.6.1)
|
||||
meow: 13.2.0
|
||||
optionator: 0.9.4
|
||||
oxlint: 1.69.0(oxlint-tsgolint@0.23.0)
|
||||
oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)
|
||||
stylelint: 17.15.0(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
{
|
||||
"channel_delete_unexp_error": "Something went wrong",
|
||||
"channel_delete_success": "Channel Deleted Successfully",
|
||||
"column_channel_name": "Name",
|
||||
"column_channel_type": "Type",
|
||||
"column_channel_action": "Action",
|
||||
"column_channel_edit": "Edit",
|
||||
"button_new_channel": "New Alert Channel",
|
||||
"tooltip_notification_channels": "More details on how to setting notification channels",
|
||||
"sending_channels_note": "The alerts will be sent to all the configured channels.",
|
||||
"loading_channels_message": "Loading Channels..",
|
||||
"page_title_create": "New Notification Channel",
|
||||
"page_title_edit": "Edit Notification Channel",
|
||||
"page_title_view": "View Notification Channel",
|
||||
"button_save_channel": "Save",
|
||||
"button_test_channel": "Test",
|
||||
"button_return": "Back",
|
||||
@@ -123,12 +116,9 @@
|
||||
"help_webhook_password": "Specify a password or bearer token",
|
||||
"help_pager_description": "Shows up as description in pagerduty",
|
||||
"channel_creation_done": "Successfully created the channel",
|
||||
"channel_creation_failed": "An unexpected error occurred while creating this channel",
|
||||
"channel_edit_done": "Channels Edited Successfully",
|
||||
"channel_edit_failed": "An unexpected error occurred while updating this channel",
|
||||
"selected_channel_invalid": "Channel type selected is invalid",
|
||||
"username_no_password": "A Password must be provided with user name",
|
||||
"test_unsupported": "Sorry, this channel type does not support test yet",
|
||||
"channel_test_done": "An alert has been sent to this channel",
|
||||
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
|
||||
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
|
||||
@@ -137,4 +127,4 @@
|
||||
"api_key_required": "API Key is mandatory",
|
||||
"to_required": "To field is mandatory",
|
||||
"channel_name_required": "Channel name is mandatory"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"channel_delete_unexp_error": "Something went wrong",
|
||||
"channel_delete_success": "Channel Deleted Successfully",
|
||||
"column_channel_name": "Name",
|
||||
"column_channel_type": "Type",
|
||||
"column_channel_action": "Action",
|
||||
"column_channel_edit": "Edit",
|
||||
"button_new_channel": "New Alert Channel",
|
||||
"tooltip_notification_channels": "More details on how to setting notification channels",
|
||||
"sending_channels_note": "The alerts will be sent to all the configured channels.",
|
||||
"channel_load_failed": "This channel could not be opened",
|
||||
"loading_channels_message": "Loading Channels..",
|
||||
"page_title_create": "New Notification Channel",
|
||||
"page_title_edit": "Edit Notification Channel",
|
||||
"page_title_view": "View Notification Channel",
|
||||
"button_save_channel": "Save",
|
||||
"button_test_channel": "Test",
|
||||
"button_return": "Back",
|
||||
"section_general": "General",
|
||||
"section_connection": "Connection",
|
||||
"section_message": "Message",
|
||||
"section_advanced": "Advanced",
|
||||
"description_slack_advanced": "The rest of the Slack attachment. Every box here is optional.",
|
||||
"section_recipients": "Recipients",
|
||||
"field_channel_name": "Name",
|
||||
"help_channel_name_immutable": "Renaming is blocked while alert rules and routing policies still reference a channel by name.",
|
||||
"field_send_resolved": "Send resolved alerts",
|
||||
"field_channel_type": "Type",
|
||||
"field_webhook_url": "Webhook URL",
|
||||
@@ -95,9 +96,44 @@
|
||||
"placeholder_incidentio_metadata_key": "Key",
|
||||
"placeholder_incidentio_metadata_value": "Value",
|
||||
"button_incidentio_add_metadata": "Add metadata",
|
||||
"button_incidentio_remove_metadata": "Remove metadata",
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
"field_slack_description": "Description",
|
||||
"field_slack_title_link": "Title link",
|
||||
"field_slack_color": "Color",
|
||||
"help_slack_color": "good, warning, danger, or a hex value like #439FE0. Templates are allowed.",
|
||||
"placeholder_slack_color": "{{ if eq .Status \"firing\" }}danger{{ else }}good{{ end }}",
|
||||
"field_slack_pretext": "Pretext",
|
||||
"help_slack_pretext": "Shown above the attachment block",
|
||||
"field_slack_fallback": "Fallback text",
|
||||
"help_slack_fallback": "Plain text shown where the attachment cannot render, such as push notifications",
|
||||
"field_slack_footer": "Footer",
|
||||
"field_slack_fields": "Fields",
|
||||
"help_slack_fields": "Extra entries rendered as a table inside the attachment",
|
||||
"placeholder_slack_field_title": "Title",
|
||||
"placeholder_slack_field_value": "Value",
|
||||
"field_slack_field_short": "Short",
|
||||
"add_slack_field": "Add field",
|
||||
"remove_slack_field": "Remove field",
|
||||
"field_slack_actions": "Actions",
|
||||
"help_slack_actions": "Buttons rendered under the attachment. A button with a URL links out.",
|
||||
"label_slack_action_text": "Text",
|
||||
"label_slack_action_url": "URL",
|
||||
"label_slack_action_type": "Type",
|
||||
"label_slack_action_name": "Name",
|
||||
"label_slack_action_value": "Value",
|
||||
"label_slack_action_style": "Style",
|
||||
"label_slack_action_confirm": "Confirm",
|
||||
"placeholder_slack_action_text": "Button text",
|
||||
"placeholder_slack_action_url": "https://runbook.example.com",
|
||||
"placeholder_slack_action_type": "button",
|
||||
"placeholder_slack_action_name": "Name (Slack app callbacks)",
|
||||
"placeholder_slack_action_value": "Value (Slack app callbacks)",
|
||||
"placeholder_slack_action_style": "Style: default, primary or danger",
|
||||
"placeholder_slack_action_confirm": "Confirmation prompt (optional)",
|
||||
"add_slack_action": "Add action",
|
||||
"remove_slack_action": "Remove action",
|
||||
"field_opsgenie_api_key": "API Key",
|
||||
"field_opsgenie_description": "Description",
|
||||
"placeholder_opsgenie_description": "Description",
|
||||
@@ -109,6 +145,7 @@
|
||||
"placeholder_email_html": "Email body template",
|
||||
"field_webhook_username": "User Name (optional)",
|
||||
"field_webhook_password": "Password (optional)",
|
||||
"field_webhook_bearer_token": "Bearer token (optional)",
|
||||
"field_pager_routing_key": "Routing Key",
|
||||
"field_pager_description": "Description",
|
||||
"field_pager_severity": "Severity",
|
||||
@@ -133,23 +170,66 @@
|
||||
"help_pager_severity": "Severity of the incident, must be one of: must be one of the following: 'critical', 'warning', 'error' or 'info'",
|
||||
"help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.",
|
||||
"help_webhook_password": "Specify a password or bearer token",
|
||||
"help_webhook_bearer_token": "Sent as an Authorization: Bearer header. Leave the username and password empty when using it.",
|
||||
"help_pager_description": "Shows up as description in pagerduty",
|
||||
"help_opsgenie_message": "Shows up as message in opsgenie",
|
||||
"help_opsgenie_priority": "Priority of the incident",
|
||||
"help_opsgenie_description": "Shows up as description in opsgenie",
|
||||
"channel_creation_done": "Successfully created the channel",
|
||||
"channel_creation_failed": "An unexpected error occurred while creating this channel",
|
||||
"channel_edit_done": "Channels Edited Successfully",
|
||||
"channel_edit_failed": "An unexpected error occurred while updating this channel",
|
||||
"selected_channel_invalid": "Channel type selected is invalid",
|
||||
"username_no_password": "A Password must be provided with user name",
|
||||
"test_unsupported": "Sorry, this channel type does not support test yet",
|
||||
"channel_test_done": "An alert has been sent to this channel",
|
||||
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
|
||||
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
|
||||
"webhook_url_required": "Webhook URL is mandatory",
|
||||
"routing_key_required": "Routing Key is mandatory for creating pagerduty channel",
|
||||
"pager_details_invalid_json": "Enter valid JSON, or clear the box to send no details",
|
||||
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)",
|
||||
"api_key_required": "API Key is mandatory",
|
||||
"to_required": "To field is mandatory",
|
||||
"channel_name_required": "Channel name is mandatory"
|
||||
}
|
||||
"to_required": "To field is mandatory",
|
||||
"channel_name_required": "Channel name is mandatory",
|
||||
"tooltip_send_resolved": "Also notify this channel when an alert stops firing.",
|
||||
"tooltip_channel_type": "Where the alert is delivered. It cannot be changed once the channel is saved.",
|
||||
"button_discard_channel": "Discard",
|
||||
"button_discard_confirm": "Discard",
|
||||
"discard_changes_title": "Discard your changes?",
|
||||
"discard_changes_body": "This form has unsaved changes. If you leave now, they are gone and the channel stays as it is.",
|
||||
"tooltip_save_no_changes": "Nothing has changed yet.",
|
||||
"entry_slack_field": "Field",
|
||||
"entry_slack_action": "Action",
|
||||
"entry_incidentio_metadata": "Entry",
|
||||
"label_slack_field_title": "Title",
|
||||
"tooltip_slack_field_title": "The bold label Slack shows above the value, for example Severity.",
|
||||
"label_slack_field_value": "Value",
|
||||
"tooltip_slack_field_value": "What Slack shows under the title. Templates are allowed, so `{{ .CommonLabels.severity }}` fills in from the alert.",
|
||||
"label_incidentio_metadata_key": "Key",
|
||||
"label_incidentio_metadata_value": "Value",
|
||||
"tooltip_slack_fields": "Extra entries Slack renders as a small table inside the alert attachment.",
|
||||
"tooltip_slack_actions": "Buttons Slack renders under the attachment. A button with a URL links out; the rest are for a Slack app to handle.",
|
||||
"tooltip_slack_action_text": "The label on the button.",
|
||||
"tooltip_slack_action_url": "Where the button sends the reader. Leave empty for a button a Slack app handles instead.",
|
||||
"tooltip_slack_action_name": "Identifies the button to a Slack app. Not needed for a button that only links out.",
|
||||
"tooltip_slack_action_value": "Sent to a Slack app along with the name, so one app can tell its buttons apart.",
|
||||
"tooltip_slack_action_style": "default, primary or danger. Slack colours the button accordingly.",
|
||||
"tooltip_slack_action_confirm": "Text for a confirmation prompt shown before the action runs. Leave empty for no prompt.",
|
||||
"bot_token_required": "Bot token is mandatory for creating a telegram channel",
|
||||
"chat_id_required": "Chat ID is mandatory for creating a telegram channel",
|
||||
"telegram_tip": "Create a bot with @BotFather, add it to the chat, then paste its token and the chat's numeric id below.",
|
||||
"telegram_tip_link": "Learn how",
|
||||
"field_telegram_bot_token": "Bot token",
|
||||
"tooltip_telegram_bot_token": "The token BotFather returns, in the form 123456789:AA...",
|
||||
"field_telegram_chat_id": "Chat ID",
|
||||
"tooltip_telegram_chat_id": "The numeric chat the bot posts to, negative for groups and channels. Telegram's @username form is not accepted.",
|
||||
"placeholder_telegram_chat_id": "e.g. -1001234567890",
|
||||
"field_telegram_message_thread_id": "Message thread ID",
|
||||
"tooltip_telegram_message_thread_id": "Posts into one message thread of a forum group. Leave empty to post in the group itself.",
|
||||
"field_telegram_title": "Title",
|
||||
"tooltip_telegram_title": "Sent in bold above the message. Telegram HTML, so interpolated values are escaped.",
|
||||
"field_telegram_message": "Message",
|
||||
"tooltip_telegram_message": "Template for the message body. Telegram HTML, so interpolated values are escaped.",
|
||||
"field_telegram_api_url": "Bot API URL",
|
||||
"tooltip_telegram_api_url": "Point at a self-hosted Bot API server. Leave empty to use Telegram's.",
|
||||
"field_telegram_disable_notifications": "Send silently",
|
||||
"tooltip_telegram_disable_notifications": "Delivers without a sound or vibration on the recipients' devices."
|
||||
}
|
||||
|
||||
@@ -1451,10 +1451,9 @@ describe('PrivateRoute', () => {
|
||||
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
|
||||
});
|
||||
|
||||
it('should redirect VIEWER from /alerts/channels/new (ADMIN only)', async () => {
|
||||
// After moving channels under /alerts, CHANNELS_NEW ('/alerts/channels/new')
|
||||
// is an exact, ADMIN-only route with no overlapping non-exact ALL_CHANNELS
|
||||
// route to match last, so a VIEWER is now correctly redirected.
|
||||
it('lets a VIEWER reach /alerts/channels/new, which authz then gates', () => {
|
||||
// CHANNELS_NEW runs on fine-grained authz, so the router no longer decides
|
||||
// on the role: the page's own guard denies when `create` is not granted.
|
||||
renderPrivateRoute({
|
||||
initialRoute: ROUTES.CHANNELS_NEW,
|
||||
appContext: {
|
||||
@@ -1463,7 +1462,7 @@ describe('PrivateRoute', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await assertRedirectsTo(ROUTES.UN_AUTHORIZED);
|
||||
assertStaysOnRoute(ROUTES.CHANNELS_NEW);
|
||||
});
|
||||
|
||||
it('should allow EDITOR to access /get-started-with-signoz-cloud route', () => {
|
||||
@@ -1547,6 +1546,11 @@ describe('PrivateRoute', () => {
|
||||
keyof typeof routeWithInitialAuthZSupport,
|
||||
AuthzRouteCase
|
||||
> = {
|
||||
CHANNELS_NEW: { path: ROUTES.CHANNELS_NEW, deniedRoles: DENIED_ROLES },
|
||||
CHANNELS_EDIT: {
|
||||
path: ROUTES.CHANNELS_EDIT.replace(':channelId', 'channel-id-1'),
|
||||
deniedRoles: DENIED_ROLES,
|
||||
},
|
||||
ALL_DASHBOARD: { path: ROUTES.ALL_DASHBOARD, deniedRoles: DENIED_ROLES },
|
||||
DASHBOARD: {
|
||||
path: ROUTES.DASHBOARD.replace(':dashboardId', 'dashboard-id-1'),
|
||||
|
||||
@@ -220,8 +220,7 @@ export type ClarificationSummaryDTODiscoveredContextAnyOf = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ClarificationSummaryDTODiscoveredContext =
|
||||
ClarificationSummaryDTODiscoveredContextAnyOf | null;
|
||||
export type ClarificationSummaryDTODiscoveredContext = ClarificationSummaryDTODiscoveredContextAnyOf | null;
|
||||
|
||||
export interface ClarificationSummaryDTO {
|
||||
/**
|
||||
@@ -784,8 +783,7 @@ export type ThreadDetailResponseDTOActiveExecutionId = string | null;
|
||||
|
||||
export type ThreadDetailResponseDTOPendingApproval = ApprovalSummaryDTO | null;
|
||||
|
||||
export type ThreadDetailResponseDTOPendingClarification =
|
||||
ClarificationSummaryDTO | null;
|
||||
export type ThreadDetailResponseDTOPendingClarification = ClarificationSummaryDTO | null;
|
||||
|
||||
/**
|
||||
* Full thread state for cold-start reconstruction (GET /threads/{id}).
|
||||
@@ -975,8 +973,7 @@ export type ClarificationEventDTODiscoveredContextAnyOf = {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type ClarificationEventDTODiscoveredContext =
|
||||
ClarificationEventDTODiscoveredContextAnyOf | null;
|
||||
export type ClarificationEventDTODiscoveredContext = ClarificationEventDTODiscoveredContextAnyOf | null;
|
||||
|
||||
export interface ClarificationEventDTO {
|
||||
/**
|
||||
@@ -1299,8 +1296,7 @@ export type CreateThreadApiV1AssistantThreadsPostHeaders = {
|
||||
'X-SigNoz-URL'?: string | null;
|
||||
};
|
||||
|
||||
export type CreateThreadApiV1AssistantThreadsPostBody =
|
||||
CreateThreadRequestDTO | null;
|
||||
export type CreateThreadApiV1AssistantThreadsPostBody = CreateThreadRequestDTO | null;
|
||||
|
||||
export type ListThreadsApiV1AssistantThreadsGetParams = {
|
||||
/**
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createEmail';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/channels', {
|
||||
name: props.name,
|
||||
email_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
to: props.to,
|
||||
html: props.html,
|
||||
headers: props.headers,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,40 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/channels', {
|
||||
name: props.name,
|
||||
msteamsv2_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,43 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/channels', {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,48 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createPager';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/channels', {
|
||||
name: props.name,
|
||||
pagerduty_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
routing_key: props.routing_key,
|
||||
client: props.client,
|
||||
client_url: props.client_url,
|
||||
description: props.description,
|
||||
severity: props.severity,
|
||||
class: props.class,
|
||||
component: props.component,
|
||||
group: props.group,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,41 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createSlack';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/channels', {
|
||||
name: props.name,
|
||||
slack_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
api_url: props.api_url,
|
||||
channel: props.channel,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,59 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useCreateChannel` hook (or `createChannel` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
const username = props.username ? props.username.trim() : '';
|
||||
const password = props.password ? props.password.trim() : '';
|
||||
|
||||
if (username !== '' && password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
} else if (username === '' && password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'Bearer',
|
||||
credentials: password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.post<PayloadProps>('/channels', {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
@@ -1,30 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/delete';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useDeleteChannelByID` hook (or `deleteChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const deleteChannel = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.delete<PayloadProps>(`/channels/${props.id}`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteChannel;
|
||||
@@ -1,40 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editEmail';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editEmail = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
email_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
to: props.to,
|
||||
html: props.html,
|
||||
headers: props.headers,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default editEmail;
|
||||
@@ -1,40 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editMsTeams';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editMsTeams = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
msteamsv2_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default editMsTeams;
|
||||
@@ -1,44 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editOpsgenie';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default editOpsgenie;
|
||||
@@ -1,48 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editPager';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editPager = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
pagerduty_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
routing_key: props.routing_key,
|
||||
client: props.client,
|
||||
client_url: props.client_url,
|
||||
description: props.description,
|
||||
severity: props.severity,
|
||||
class: props.class,
|
||||
component: props.component,
|
||||
group: props.group,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default editPager;
|
||||
@@ -1,41 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editSlack';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editSlack = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
slack_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
api_url: props.api_url,
|
||||
channel: props.channel,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default editSlack;
|
||||
@@ -1,59 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editWebhook';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useUpdateChannelByID` hook (or `updateChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const editWebhook = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
const username = props.username ? props.username.trim() : '';
|
||||
const password = props.password ? props.password.trim() : '';
|
||||
|
||||
if (username !== '' && password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
} else if (username === '' && password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'Bearer',
|
||||
credentials: password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.put<PayloadProps>(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: props.send_resolved,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default editWebhook;
|
||||
@@ -1,29 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/get';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useGetChannelByID` hook (or `getChannelByID` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const get = async (props: Props): Promise<SuccessResponseV2<Channels>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/channels/${props.id}`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default get;
|
||||
@@ -1,28 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Channels, PayloadProps } from 'types/api/channels/getAll';
|
||||
|
||||
/**
|
||||
* @deprecated Use the generated `useListChannels` hook (or `listChannels` fetcher) from
|
||||
* `api/generated/services/channels` instead. This hand-written client targets the
|
||||
* same endpoint and will be removed once call sites migrate.
|
||||
*
|
||||
* Part of https://github.com/SigNoz/engineering-pod/issues/5289, add a comment or update when removing this method.
|
||||
*/
|
||||
const getAll = async (): Promise<SuccessResponseV2<Channels[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>('/channels');
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
||||
@@ -1,33 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createEmail';
|
||||
|
||||
const testEmail = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/testChannel', {
|
||||
name: props.name,
|
||||
email_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
to: props.to,
|
||||
html: props.html,
|
||||
headers: props.headers,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default testEmail;
|
||||
@@ -1,33 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createMsTeams';
|
||||
|
||||
const testMsTeams = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/testChannel', {
|
||||
name: props.name,
|
||||
msteamsv2_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
webhook_url: props.webhook_url,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default testMsTeams;
|
||||
@@ -1,36 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createOpsgenie';
|
||||
|
||||
const testOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/testChannel', {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default testOpsgenie;
|
||||
@@ -1,41 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createPager';
|
||||
|
||||
const testPager = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/testChannel', {
|
||||
name: props.name,
|
||||
pagerduty_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
routing_key: props.routing_key,
|
||||
client: props.client,
|
||||
client_url: props.client_url,
|
||||
description: props.description,
|
||||
severity: props.severity,
|
||||
class: props.class,
|
||||
component: props.component,
|
||||
group: props.group,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default testPager;
|
||||
@@ -1,34 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createSlack';
|
||||
|
||||
const testSlack = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>('/testChannel', {
|
||||
name: props.name,
|
||||
slack_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
api_url: props.api_url,
|
||||
channel: props.channel,
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default testSlack;
|
||||
@@ -1,52 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
|
||||
|
||||
const testWebhook = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
const username = props.username ? props.username.trim() : '';
|
||||
const password = props.password ? props.password.trim() : '';
|
||||
|
||||
if (username !== '' && password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username,
|
||||
password,
|
||||
},
|
||||
};
|
||||
} else if (username === '' && password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'Bearer',
|
||||
credentials: password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.post<PayloadProps>('/testChannel', {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default testWebhook;
|
||||
@@ -431,8 +431,7 @@ export type AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelEmailConfigDTOHeaders =
|
||||
AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf | null;
|
||||
export type AlertmanagertypesChannelEmailConfigDTOHeaders = AlertmanagertypesChannelEmailConfigDTOHeadersAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelEmailConfigDTO {
|
||||
/**
|
||||
@@ -510,8 +509,7 @@ export type AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelPagerdutyConfigDTODetails =
|
||||
AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf | null;
|
||||
export type AlertmanagertypesChannelPagerdutyConfigDTODetails = AlertmanagertypesChannelPagerdutyConfigDTODetailsAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelPagerdutyConfigDTO {
|
||||
/**
|
||||
@@ -584,8 +582,7 @@ export type AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelOpsgenieConfigDTODetails =
|
||||
AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf | null;
|
||||
export type AlertmanagertypesChannelOpsgenieConfigDTODetails = AlertmanagertypesChannelOpsgenieConfigDTODetailsAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelOpsgenieConfigDTO {
|
||||
/**
|
||||
@@ -706,8 +703,7 @@ export type AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelJiraConfigDTOCustomFields =
|
||||
AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf | null;
|
||||
export type AlertmanagertypesChannelJiraConfigDTOCustomFields = AlertmanagertypesChannelJiraConfigDTOCustomFieldsAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelJiraConfigDTO {
|
||||
/**
|
||||
@@ -832,8 +828,7 @@ export type AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AlertmanagertypesChannelIncidentIOConfigDTOMetadata =
|
||||
AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf | null;
|
||||
export type AlertmanagertypesChannelIncidentIOConfigDTOMetadata = AlertmanagertypesChannelIncidentIOConfigDTOMetadataAnyOf | null;
|
||||
|
||||
export interface AlertmanagertypesChannelIncidentIOConfigDTO {
|
||||
/**
|
||||
@@ -872,6 +867,55 @@ export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTy
|
||||
spec: AlertmanagertypesChannelIncidentIOConfigDTO;
|
||||
}
|
||||
|
||||
export enum AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTOKind {
|
||||
telegram = 'telegram',
|
||||
}
|
||||
export interface AlertmanagertypesChannelTelegramConfigDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
apiUrl?: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format password
|
||||
*/
|
||||
botToken: string;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
chatId: number;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
disableNotifications?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
messageThreadId?: number;
|
||||
/**
|
||||
* @type boolean,null
|
||||
*/
|
||||
sendResolved?: boolean | null;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTO {
|
||||
/**
|
||||
* @enum telegram
|
||||
* @type string
|
||||
*/
|
||||
kind: AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTOKind;
|
||||
spec: AlertmanagertypesChannelTelegramConfigDTO;
|
||||
}
|
||||
|
||||
export type AlertmanagertypesChannelConfigDTO =
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelSlackConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelEmailConfigDTO
|
||||
@@ -882,7 +926,8 @@ export type AlertmanagertypesChannelConfigDTO =
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelGoogleChatConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJiraConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelJSMOpsConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfigDTO;
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelIncidentIOConfigDTO
|
||||
| AlertmanagertypesChannelConfigVariantGithubComSigNozSignozPkgTypesAlertmanagertypesChannelTelegramConfigDTO;
|
||||
|
||||
export enum AlertmanagertypesChannelDefectDTO {
|
||||
none = 'none',
|
||||
@@ -902,6 +947,7 @@ export enum AlertmanagertypesChannelKindDTO {
|
||||
jira = 'jira',
|
||||
jsmops = 'jsmops',
|
||||
incidentio = 'incidentio',
|
||||
telegram = 'telegram',
|
||||
}
|
||||
export enum AlertmanagertypesChannelListOrderDTO {
|
||||
asc = 'asc',
|
||||
@@ -2462,7 +2508,7 @@ export interface ConfigSNSConfigDTO {
|
||||
topic_arn?: string;
|
||||
}
|
||||
|
||||
export interface ConfigTelegramConfigDTO {
|
||||
export interface AlertmanagertypesTelegramReceiverConfigDTO {
|
||||
api_url?: ConfigURLType2DTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -2494,6 +2540,10 @@ export interface ConfigTelegramConfigDTO {
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
@@ -2699,7 +2749,7 @@ export type AlertmanagertypesPostableChannelDTO = unknown & {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
telegram_configs?: ConfigTelegramConfigDTO[];
|
||||
telegram_configs?: AlertmanagertypesTelegramReceiverConfigDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -2846,7 +2896,7 @@ export interface AlertmanagertypesReceiverDTO {
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
telegram_configs?: ConfigTelegramConfigDTO[];
|
||||
telegram_configs?: AlertmanagertypesTelegramReceiverConfigDTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -2899,6 +2949,9 @@ export interface AuthtypesAttributeMappingDTO {
|
||||
role?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of authn provider.
|
||||
*/
|
||||
export enum AuthtypesAuthDomainConfigSAMLDTOKind {
|
||||
saml = 'saml',
|
||||
}
|
||||
@@ -2926,11 +2979,18 @@ export interface AuthtypesAuthDomainConfigSAMLDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum saml
|
||||
* @description The kind of authn provider.
|
||||
*/
|
||||
kind: AuthtypesAuthDomainConfigSAMLDTOKind;
|
||||
/**
|
||||
* @description The saml configuration.
|
||||
*/
|
||||
spec: AuthtypesSamlConfigDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of authn provider.
|
||||
*/
|
||||
export enum AuthtypesAuthDomainConfigGoogleDTOKind {
|
||||
google = 'google',
|
||||
}
|
||||
@@ -2979,11 +3039,18 @@ export interface AuthtypesAuthDomainConfigGoogleDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum google
|
||||
* @description The kind of authn provider.
|
||||
*/
|
||||
kind: AuthtypesAuthDomainConfigGoogleDTOKind;
|
||||
/**
|
||||
* @description The google auth configuration.
|
||||
*/
|
||||
spec: AuthtypesGoogleConfigDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of authn provider.
|
||||
*/
|
||||
export enum AuthtypesAuthDomainConfigOIDCDTOKind {
|
||||
oidc = 'oidc',
|
||||
}
|
||||
@@ -3020,8 +3087,12 @@ export interface AuthtypesAuthDomainConfigOIDCDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum oidc
|
||||
* @description The kind of authn provider.
|
||||
*/
|
||||
kind: AuthtypesAuthDomainConfigOIDCDTOKind;
|
||||
/**
|
||||
* @description The oidc configuration.
|
||||
*/
|
||||
spec: AuthtypesOIDCConfigDTO;
|
||||
}
|
||||
|
||||
@@ -3080,8 +3151,7 @@ export type AuthtypesRoleMappingDTOGroupMappingsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type AuthtypesRoleMappingDTOGroupMappings =
|
||||
AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
|
||||
export type AuthtypesRoleMappingDTOGroupMappings = AuthtypesRoleMappingDTOGroupMappingsAnyOf | null;
|
||||
|
||||
export interface AuthtypesRoleMappingDTO {
|
||||
/**
|
||||
@@ -3221,6 +3291,7 @@ export enum CoretypesKindDTO {
|
||||
'meter-metrics' = 'meter-metrics',
|
||||
'logs-field' = 'logs-field',
|
||||
'traces-field' = 'traces-field',
|
||||
'metrics-field' = 'metrics-field',
|
||||
}
|
||||
export enum CoretypesTypeDTO {
|
||||
user = 'user',
|
||||
@@ -3692,8 +3763,7 @@ export type CloudintegrationtypesAgentReportDTODataAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type CloudintegrationtypesAgentReportDTOData =
|
||||
CloudintegrationtypesAgentReportDTODataAnyOf | null;
|
||||
export type CloudintegrationtypesAgentReportDTOData = CloudintegrationtypesAgentReportDTODataAnyOf | null;
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
@@ -4179,8 +4249,7 @@ export type CloudintegrationtypesPostableAgentCheckInDTODataAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type CloudintegrationtypesPostableAgentCheckInDTOData =
|
||||
CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
|
||||
export type CloudintegrationtypesPostableAgentCheckInDTOData = CloudintegrationtypesPostableAgentCheckInDTODataAnyOf | null;
|
||||
|
||||
export interface CloudintegrationtypesPostableAgentCheckInDTO {
|
||||
/**
|
||||
@@ -4422,6 +4491,48 @@ export interface ConfigJiraConfigDTO {
|
||||
wont_fix_resolution?: string;
|
||||
}
|
||||
|
||||
export interface ConfigTelegramConfigDTO {
|
||||
api_url?: ConfigURLType2DTO;
|
||||
/**
|
||||
* @type integer
|
||||
* @format int64
|
||||
*/
|
||||
chat?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
chat_file?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
disable_notifications?: boolean;
|
||||
http_config?: ConfigHTTPClientConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
message_thread_id?: number;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
parse_mode?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
send_resolved?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
token?: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
token_file?: string;
|
||||
}
|
||||
|
||||
export interface DashboardGridItemDTO {
|
||||
content?: CommonJSONRefDTO;
|
||||
/**
|
||||
@@ -4483,6 +4594,9 @@ export interface Querybuildertypesv5TraceAggregationDTO {
|
||||
expression?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the upper bounds are spaced.
|
||||
*/
|
||||
export enum Querybuildertypesv5BucketOptionsLinearDTOKind {
|
||||
linear = 'linear',
|
||||
}
|
||||
@@ -4502,11 +4616,18 @@ export interface Querybuildertypesv5BucketOptionsLinearDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum linear
|
||||
* @description How the upper bounds are spaced.
|
||||
*/
|
||||
kind: Querybuildertypesv5BucketOptionsLinearDTOKind;
|
||||
/**
|
||||
* @description The evenly spaced bucket specification.
|
||||
*/
|
||||
spec: Querybuildertypesv5LinearBucketsSpecDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the upper bounds are spaced.
|
||||
*/
|
||||
export enum Querybuildertypesv5BucketOptionsLogDTOKind {
|
||||
log = 'log',
|
||||
}
|
||||
@@ -4521,8 +4642,12 @@ export interface Querybuildertypesv5BucketOptionsLogDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum log
|
||||
* @description How the upper bounds are spaced.
|
||||
*/
|
||||
kind: Querybuildertypesv5BucketOptionsLogDTOKind;
|
||||
/**
|
||||
* @description The logarithmic bucket specification.
|
||||
*/
|
||||
spec: Querybuildertypesv5LogBucketsSpecDTO;
|
||||
}
|
||||
|
||||
@@ -4868,8 +4993,7 @@ export type DashboardtypesLegendDTOCustomColorsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesLegendDTOCustomColors =
|
||||
DashboardtypesLegendDTOCustomColorsAnyOf | null;
|
||||
export type DashboardtypesLegendDTOCustomColors = DashboardtypesLegendDTOCustomColorsAnyOf | null;
|
||||
|
||||
export interface DashboardtypesLegendDTO {
|
||||
/**
|
||||
@@ -5461,8 +5585,7 @@ export type DashboardtypesTableFormattingDTOColumnUnitsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type DashboardtypesTableFormattingDTOColumnUnits =
|
||||
DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
|
||||
export type DashboardtypesTableFormattingDTOColumnUnits = DashboardtypesTableFormattingDTOColumnUnitsAnyOf | null;
|
||||
|
||||
export interface DashboardtypesTableFormattingDTO {
|
||||
/**
|
||||
@@ -5747,26 +5870,40 @@ export type Querybuildertypesv5BuilderQuerySpecDTO =
|
||||
| Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTO
|
||||
| Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTO;
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeBuilderDTOType {
|
||||
builder_query = 'builder_query',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeBuilderDTO {
|
||||
/**
|
||||
* @description The builder query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5BuilderQuerySpecDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_query
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeBuilderAIDTOType {
|
||||
builder_ai_query = 'builder_ai_query',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeBuilderAIDTO {
|
||||
/**
|
||||
* @description The AI builder query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_ai_query
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeBuilderAIDTOType;
|
||||
}
|
||||
@@ -5804,14 +5941,21 @@ export interface Querybuildertypesv5QueryBuilderFormulaDTO {
|
||||
order?: Querybuildertypesv5OrderByDTO[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeFormulaDTOType {
|
||||
builder_formula = 'builder_formula',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeFormulaDTO {
|
||||
/**
|
||||
* @description The formula specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5QueryBuilderFormulaDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_formula
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeFormulaDTOType;
|
||||
}
|
||||
@@ -5874,14 +6018,21 @@ export interface Querybuildertypesv5QueryBuilderTraceOperatorDTO {
|
||||
stepInterval?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeTraceOperatorDTOType {
|
||||
builder_trace_operator = 'builder_trace_operator',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeTraceOperatorDTO {
|
||||
/**
|
||||
* @description The trace operator specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5QueryBuilderTraceOperatorDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum builder_trace_operator
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeTraceOperatorDTOType;
|
||||
}
|
||||
@@ -5910,14 +6061,21 @@ export interface Querybuildertypesv5PromQueryDTO {
|
||||
step?: Querybuildertypesv5StepDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopePromQLDTOType {
|
||||
promql = 'promql',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopePromQLDTO {
|
||||
/**
|
||||
* @description The PromQL query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5PromQueryDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum promql
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopePromQLDTOType;
|
||||
}
|
||||
@@ -5941,14 +6099,21 @@ export interface Querybuildertypesv5ClickHouseQueryDTO {
|
||||
query?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the query.
|
||||
*/
|
||||
export enum Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType {
|
||||
clickhouse_sql = 'clickhouse_sql',
|
||||
}
|
||||
export interface Querybuildertypesv5QueryEnvelopeClickHouseSQLDTO {
|
||||
/**
|
||||
* @description The ClickHouse SQL query specification.
|
||||
*/
|
||||
spec?: Querybuildertypesv5ClickHouseQueryDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @enum clickhouse_sql
|
||||
* @description The type of the query.
|
||||
*/
|
||||
type: Querybuildertypesv5QueryEnvelopeClickHouseSQLDTOType;
|
||||
}
|
||||
@@ -6777,8 +6942,7 @@ export type FeaturetypesGettableFeatureDTOVariantsAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type FeaturetypesGettableFeatureDTOVariants =
|
||||
FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
|
||||
export type FeaturetypesGettableFeatureDTOVariants = FeaturetypesGettableFeatureDTOVariantsAnyOf | null;
|
||||
|
||||
export interface FeaturetypesGettableFeatureDTO {
|
||||
/**
|
||||
@@ -7256,8 +7420,7 @@ export type InframonitoringtypesClusterRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesClusterRecordDTOMeta =
|
||||
InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesClusterRecordDTOMeta = InframonitoringtypesClusterRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNodeCountsByReadinessDTO {
|
||||
/**
|
||||
@@ -7530,8 +7693,7 @@ export type InframonitoringtypesContainerRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesContainerRecordDTOMeta =
|
||||
InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesContainerRecordDTOMeta = InframonitoringtypesContainerRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesContainerRecordDTO {
|
||||
containerCountsByReady: InframonitoringtypesContainerCountsByReadyDTO;
|
||||
@@ -7622,8 +7784,7 @@ export type InframonitoringtypesDaemonSetRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesDaemonSetRecordDTOMeta =
|
||||
InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesDaemonSetRecordDTOMeta = InframonitoringtypesDaemonSetRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesDaemonSetRecordDTO {
|
||||
/**
|
||||
@@ -7718,8 +7879,7 @@ export type InframonitoringtypesDeploymentRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesDeploymentRecordDTOMeta =
|
||||
InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesDeploymentRecordDTOMeta = InframonitoringtypesDeploymentRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesDeploymentRecordDTO {
|
||||
/**
|
||||
@@ -7808,8 +7968,7 @@ export type InframonitoringtypesHostRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesHostRecordDTOMeta =
|
||||
InframonitoringtypesHostRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesHostRecordDTOMeta = InframonitoringtypesHostRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesHostRecordDTO {
|
||||
/**
|
||||
@@ -7891,8 +8050,7 @@ export type InframonitoringtypesJobRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesJobRecordDTOMeta =
|
||||
InframonitoringtypesJobRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesJobRecordDTOMeta = InframonitoringtypesJobRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesJobRecordDTO {
|
||||
/**
|
||||
@@ -8010,8 +8168,7 @@ export type InframonitoringtypesNamespaceRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNamespaceRecordDTOMeta =
|
||||
InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesNamespaceRecordDTOMeta = InframonitoringtypesNamespaceRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNamespaceRecordDTO {
|
||||
/**
|
||||
@@ -8078,8 +8235,7 @@ export type InframonitoringtypesNodeRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesNodeRecordDTOMeta =
|
||||
InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesNodeRecordDTOMeta = InframonitoringtypesNodeRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesNodeRecordDTO {
|
||||
condition: InframonitoringtypesNodeConditionDTO;
|
||||
@@ -8150,8 +8306,7 @@ export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesPodRecordDTOMeta =
|
||||
InframonitoringtypesPodRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesPodRecordDTOMeta = InframonitoringtypesPodRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesPodRecordDTO {
|
||||
/**
|
||||
@@ -8538,8 +8693,7 @@ export type InframonitoringtypesStatefulSetRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesStatefulSetRecordDTOMeta =
|
||||
InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesStatefulSetRecordDTOMeta = InframonitoringtypesStatefulSetRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesStatefulSetRecordDTO {
|
||||
/**
|
||||
@@ -8615,8 +8769,7 @@ export type InframonitoringtypesVolumeRecordDTOMetaAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type InframonitoringtypesVolumeRecordDTOMeta =
|
||||
InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
|
||||
export type InframonitoringtypesVolumeRecordDTOMeta = InframonitoringtypesVolumeRecordDTOMetaAnyOf | null;
|
||||
|
||||
export interface InframonitoringtypesVolumeRecordDTO {
|
||||
/**
|
||||
@@ -9734,8 +9887,7 @@ export type PrometheusMatrixSeriesSchemaDTOMetricAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type PrometheusMatrixSeriesSchemaDTOMetric =
|
||||
PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
|
||||
export type PrometheusMatrixSeriesSchemaDTOMetric = PrometheusMatrixSeriesSchemaDTOMetricAnyOf | null;
|
||||
|
||||
export interface PrometheusMatrixSeriesSchemaDTO {
|
||||
/**
|
||||
@@ -9767,8 +9919,7 @@ export type PrometheusVectorSampleSchemaDTOMetricAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type PrometheusVectorSampleSchemaDTOMetric =
|
||||
PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
|
||||
export type PrometheusVectorSampleSchemaDTOMetric = PrometheusVectorSampleSchemaDTOMetricAnyOf | null;
|
||||
|
||||
export interface PrometheusVectorSampleSchemaDTO {
|
||||
/**
|
||||
@@ -10178,8 +10329,7 @@ export type Querybuildertypesv5RawRowDTODataAnyOf = { [key: string]: unknown };
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type Querybuildertypesv5RawRowDTOData =
|
||||
Querybuildertypesv5RawRowDTODataAnyOf | null;
|
||||
export type Querybuildertypesv5RawRowDTOData = Querybuildertypesv5RawRowDTODataAnyOf | null;
|
||||
|
||||
export interface Querybuildertypesv5RawRowDTO {
|
||||
/**
|
||||
@@ -10250,8 +10400,7 @@ export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf =
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQuery =
|
||||
Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf | null;
|
||||
export type Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQuery = Querybuildertypesv5QueryRangePreviewResponseDTOCompositeQueryAnyOf | null;
|
||||
|
||||
/**
|
||||
* Response from the v5 query range preview (dry-run) endpoint. For each query in the composite query, returns the underlying ClickHouse statement(s) it renders to without executing them (one per PromQL metric selector; exactly one for builder/ClickHouse/trace-operator queries), with the optional EXPLAIN ESTIMATE and granule analysis attached per statement when requested.
|
||||
@@ -10651,6 +10800,9 @@ export interface RuletypesCumulativeWindowDTO {
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of evaluation.
|
||||
*/
|
||||
export enum RuletypesEvaluationCumulativeDTOKind {
|
||||
cumulative = 'cumulative',
|
||||
}
|
||||
@@ -10658,11 +10810,18 @@ export interface RuletypesEvaluationCumulativeDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum cumulative
|
||||
* @description The kind of evaluation.
|
||||
*/
|
||||
kind: RuletypesEvaluationCumulativeDTOKind;
|
||||
/**
|
||||
* @description The cumulative window evaluation specification.
|
||||
*/
|
||||
spec: RuletypesCumulativeWindowDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of evaluation.
|
||||
*/
|
||||
export enum RuletypesEvaluationRollingDTOKind {
|
||||
rolling = 'rolling',
|
||||
}
|
||||
@@ -10681,8 +10840,12 @@ export interface RuletypesEvaluationRollingDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum rolling
|
||||
* @description The kind of evaluation.
|
||||
*/
|
||||
kind: RuletypesEvaluationRollingDTOKind;
|
||||
/**
|
||||
* @description The rolling window evaluation specification.
|
||||
*/
|
||||
spec: RuletypesRollingWindowDTO;
|
||||
}
|
||||
|
||||
@@ -10888,16 +11051,28 @@ export enum RuletypesSeasonalityDTO {
|
||||
daily = 'daily',
|
||||
weekly = 'weekly',
|
||||
}
|
||||
/**
|
||||
* The kind of threshold.
|
||||
*/
|
||||
export enum RuletypesThresholdBasicDTOKind {
|
||||
basic = 'basic',
|
||||
}
|
||||
/**
|
||||
* The basic threshold specification (array of thresholds).
|
||||
*/
|
||||
export type RuletypesThresholdBasicDTOSpec = RuletypesBasicRuleThresholdsDTO | null;
|
||||
|
||||
export interface RuletypesThresholdBasicDTO {
|
||||
/**
|
||||
* @type string
|
||||
* @enum basic
|
||||
* @description The kind of threshold.
|
||||
*/
|
||||
kind: RuletypesThresholdBasicDTOKind;
|
||||
spec: RuletypesBasicRuleThresholdsDTO | null;
|
||||
/**
|
||||
* @description The basic threshold specification (array of thresholds).
|
||||
*/
|
||||
spec: RuletypesThresholdBasicDTOSpec;
|
||||
}
|
||||
|
||||
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO;
|
||||
@@ -11560,8 +11735,7 @@ export type SpantypesSpanMapperTestSpanDTOAttributesAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesSpanMapperTestSpanDTOAttributes =
|
||||
SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
|
||||
export type SpantypesSpanMapperTestSpanDTOAttributes = SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
|
||||
|
||||
export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
|
||||
[key: string]: unknown;
|
||||
@@ -11570,8 +11744,7 @@ export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesSpanMapperTestSpanDTOResource =
|
||||
SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
|
||||
export type SpantypesSpanMapperTestSpanDTOResource = SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
|
||||
|
||||
export interface SpantypesSpanMapperTestSpanDTO {
|
||||
/**
|
||||
@@ -11683,8 +11856,7 @@ export type SpantypesSpanAggregationResultDTOValueAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesSpanAggregationResultDTOValue =
|
||||
SpantypesSpanAggregationResultDTOValueAnyOf | null;
|
||||
export type SpantypesSpanAggregationResultDTOValue = SpantypesSpanAggregationResultDTOValueAnyOf | null;
|
||||
|
||||
export interface SpantypesSpanAggregationResultDTO {
|
||||
aggregation: SpantypesSpanAggregationTypeDTO;
|
||||
@@ -11886,16 +12058,14 @@ export type SpantypesWaterfallSpanDTOAttributesAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesWaterfallSpanDTOAttributes =
|
||||
SpantypesWaterfallSpanDTOAttributesAnyOf | null;
|
||||
export type SpantypesWaterfallSpanDTOAttributes = SpantypesWaterfallSpanDTOAttributesAnyOf | null;
|
||||
|
||||
export type SpantypesWaterfallSpanDTOResourceAnyOf = { [key: string]: string };
|
||||
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type SpantypesWaterfallSpanDTOResource =
|
||||
SpantypesWaterfallSpanDTOResourceAnyOf | null;
|
||||
export type SpantypesWaterfallSpanDTOResource = SpantypesWaterfallSpanDTOResourceAnyOf | null;
|
||||
|
||||
export interface SpantypesWaterfallSpanDTO {
|
||||
/**
|
||||
@@ -12335,8 +12505,7 @@ export type TelemetrytypesGettableFieldKeysDTOKeysAnyOf = {
|
||||
/**
|
||||
* @nullable
|
||||
*/
|
||||
export type TelemetrytypesGettableFieldKeysDTOKeys =
|
||||
TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
|
||||
export type TelemetrytypesGettableFieldKeysDTOKeys = TelemetrytypesGettableFieldKeysDTOKeysAnyOf | null;
|
||||
|
||||
export interface TelemetrytypesGettableFieldKeysDTO {
|
||||
/**
|
||||
|
||||
@@ -165,17 +165,15 @@ function createBaseSpec(
|
||||
filter: getFilter(queryData),
|
||||
groupBy:
|
||||
queryData.groupBy?.length > 0
|
||||
? queryData.groupBy.map(
|
||||
(item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType || '',
|
||||
fieldContext: item?.type || '',
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
? queryData.groupBy.map((item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType || '',
|
||||
fieldContext: item?.type || '',
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}))
|
||||
: undefined,
|
||||
limit:
|
||||
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||
@@ -187,14 +185,12 @@ function createBaseSpec(
|
||||
: undefined,
|
||||
order:
|
||||
queryData.orderBy?.length > 0
|
||||
? queryData.orderBy.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
? queryData.orderBy.map((order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}))
|
||||
: undefined,
|
||||
legend: isEmpty(queryData.legend) ? undefined : queryData.legend,
|
||||
having: normalizeHaving(queryData.having),
|
||||
@@ -397,17 +393,15 @@ function createTraceOperatorBaseSpec(
|
||||
stepInterval: stepInterval || undefined,
|
||||
groupBy:
|
||||
groupBy?.length > 0
|
||||
? groupBy.map(
|
||||
(item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType,
|
||||
fieldContext: item?.type,
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}),
|
||||
)
|
||||
? groupBy.map((item: any): GroupByKey => ({
|
||||
name: item.key,
|
||||
fieldDataType: item?.dataType,
|
||||
fieldContext: item?.type,
|
||||
description: item?.description,
|
||||
unit: item?.unit,
|
||||
signal: item?.signal,
|
||||
materialized: item?.materialized,
|
||||
}))
|
||||
: undefined,
|
||||
limit:
|
||||
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
|
||||
@@ -416,28 +410,24 @@ function createTraceOperatorBaseSpec(
|
||||
offset: requestType === 'raw' || requestType === 'trace' ? offset : undefined,
|
||||
order:
|
||||
orderBy?.length > 0
|
||||
? orderBy.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
)
|
||||
? orderBy.map((order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}))
|
||||
: undefined,
|
||||
legend: isEmpty(legend) ? undefined : legend,
|
||||
having: normalizeHaving(having),
|
||||
selectFields: isEmpty(nonEmptySelectColumns)
|
||||
? undefined
|
||||
: nonEmptySelectColumns?.map(
|
||||
(column: any): TelemetryFieldKey => ({
|
||||
name: column.name ?? column.key,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
|
||||
signal: column?.signal ?? undefined,
|
||||
}),
|
||||
),
|
||||
: nonEmptySelectColumns?.map((column: any): TelemetryFieldKey => ({
|
||||
name: column.name ?? column.key,
|
||||
fieldDataType:
|
||||
column?.fieldDataType ?? (column?.dataType as FieldDataType),
|
||||
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
|
||||
signal: column?.signal ?? undefined,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -610,14 +600,12 @@ export const prepareQueryRangePayloadV5 = (
|
||||
disabled: formulaData.disabled,
|
||||
limit: formulaData.limit ?? undefined,
|
||||
legend: isEmpty(formulaData.legend) ? undefined : formulaData.legend,
|
||||
order: formulaData.orderBy?.map(
|
||||
(order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
}),
|
||||
),
|
||||
order: formulaData.orderBy?.map((order: any): OrderBy => ({
|
||||
key: {
|
||||
name: order.columnName,
|
||||
},
|
||||
direction: order.order,
|
||||
})),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
BIN
frontend/src/assets/Images/users/anselm.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
frontend/src/assets/Images/users/daniel.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
frontend/src/assets/Images/users/dhruv-garg.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
frontend/src/assets/Images/users/go-frendi.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
frontend/src/assets/Images/users/pranay-narang.webp
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/src/assets/Images/users/shey.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
frontend/src/assets/Images/users/subomi-oluwalana.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
frontend/src/assets/Images/users/vivek-bhakta.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -1,5 +1,5 @@
|
||||
.breadcrumb {
|
||||
padding-left: 16px;
|
||||
padding: var(--spacing-4) 16px var(--spacing-2);
|
||||
|
||||
ol {
|
||||
align-items: center;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
@@ -10,7 +11,7 @@ export type BreadcrumbItemConfig =
|
||||
route?: string;
|
||||
}
|
||||
| {
|
||||
title: string | null;
|
||||
title: ReactNode;
|
||||
isLast?: true;
|
||||
};
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-vanilla-100);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-vanilla-400);
|
||||
color: var(--l2-foreground);
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,6 @@ import { act, render, screen } from '@testing-library/react';
|
||||
|
||||
import LabelColumn from './LabelColumn';
|
||||
|
||||
// The global CSS mock maps every class to undefined; keep the names so overflow is assertable.
|
||||
jest.mock('./LabelColumn.module.scss', () => ({
|
||||
labelColumn: 'labelColumn',
|
||||
overflowed: 'overflowed',
|
||||
shrinkable: 'shrinkable',
|
||||
}));
|
||||
|
||||
const BADGE_WIDTH = 60;
|
||||
|
||||
let resizeCallbacks: ResizeObserverCallback[] = [];
|
||||
@@ -84,20 +77,6 @@ describe('LabelColumn', () => {
|
||||
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+3');
|
||||
});
|
||||
|
||||
it('should hide the labels past the visible count', () => {
|
||||
const labels = ['env', 'service', 'region', 'team', 'owner', 'version'];
|
||||
|
||||
// 3 badges (180px) + overflow reserve (40px)
|
||||
mockLayout(220);
|
||||
renderWithProviders(<LabelColumn labels={labels} />);
|
||||
|
||||
const isHidden = (label: string): boolean =>
|
||||
screen.getByTestId(`label-tag-${label}`).closest('.overflowed') !== null;
|
||||
|
||||
expect(labels.filter(isHidden)).toStrictEqual(['team', 'owner', 'version']);
|
||||
expect(screen.getByTestId('label-overflow-badge')).toHaveTextContent('+3');
|
||||
});
|
||||
|
||||
it('should render label with value when value prop provided', () => {
|
||||
const labels = ['env'];
|
||||
const value = { env: 'production' };
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--text-vanilla-400);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-vanilla-100);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--text-vanilla-400);
|
||||
color: var(--l2-foreground);
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ interface AuthErrorProps {
|
||||
|
||||
function AuthError({ error }: AuthErrorProps): JSX.Element {
|
||||
return (
|
||||
<div className="auth-error-container" data-testid="auth-error">
|
||||
<div className="auth-error-container">
|
||||
<ErrorContent
|
||||
error={error}
|
||||
icon={<CircleAlert size={12} className="auth-error-icon" />}
|
||||
|
||||
24
frontend/src/components/BackHeader/BackHeader.module.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
/* The space sits between the way back and the title, rather than above both of
|
||||
them, so the two read as separate steps. */
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-4);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
min-height: 26px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.divider {
|
||||
--divider-color: var(--l1-border);
|
||||
--divider-margin: 10px 0 16px 0;
|
||||
}
|
||||
56
frontend/src/components/BackHeader/BackHeader.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ArrowLeft } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Divider } from '@signozhq/ui/divider';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
|
||||
import styles from './BackHeader.module.scss';
|
||||
|
||||
export interface BackHeaderProps {
|
||||
/** Where the page came from, named as the place rather than the action. */
|
||||
backLabel: string;
|
||||
backRoute: string;
|
||||
title: ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A way back on its own line, then the page's own title. The two rows keep the
|
||||
* title clear of whatever sits beside it, such as a copy button.
|
||||
*/
|
||||
function BackHeader({
|
||||
backLabel,
|
||||
backRoute,
|
||||
title,
|
||||
testId,
|
||||
}: BackHeaderProps): JSX.Element {
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.header} data-testid={testId}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<ArrowLeft size={12} />}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
safeNavigate(backRoute, { newTab: isModifierKeyPressed(e) });
|
||||
}}
|
||||
testId="back-link"
|
||||
>
|
||||
{backLabel}
|
||||
</Button>
|
||||
<div className={styles.title}>{title}</div>
|
||||
</div>
|
||||
<Divider className={styles.divider} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
BackHeader.defaultProps = {
|
||||
testId: undefined,
|
||||
};
|
||||
|
||||
export default BackHeader;
|
||||
@@ -512,7 +512,6 @@ export default function CeleryOverviewTable({
|
||||
allowClear
|
||||
/>
|
||||
<ResizeTable
|
||||
data-testid="mq-overview-table"
|
||||
className="celery-overview-table"
|
||||
pagination={paginationConfig}
|
||||
size="middle"
|
||||
|
||||
@@ -28,7 +28,6 @@ function CeleryTaskConfigOptions(): JSX.Element {
|
||||
Task Name
|
||||
</Typography.Text>
|
||||
<Select
|
||||
data-testid="celery-task-name-select"
|
||||
placeholder="Task Name"
|
||||
showSearch
|
||||
mode="multiple"
|
||||
|
||||
@@ -105,10 +105,7 @@ export default function CeleryTaskGraphGrid({
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="celery-task-graph-grid-container"
|
||||
data-testid="celery-graph-grid"
|
||||
>
|
||||
<div className="celery-task-graph-grid-container">
|
||||
<div className="metric-based-graphs">
|
||||
<CardContainer className="row-card" isDarkMode={isDarkMode}>
|
||||
<div className="row-panel">
|
||||
|
||||
@@ -80,7 +80,6 @@ function CeleryTaskStateGraphConfig({
|
||||
{tabs.map((tab, index) => (
|
||||
<Col
|
||||
key={tab.key}
|
||||
data-testid={`celery-state-tab-${tab.key}`}
|
||||
onClick={(): void => handleTabClick(tab.key as CeleryTaskState)}
|
||||
className={`celery-task-states__tab ${
|
||||
tab.key === barState ? 'celery-task-states__tab--selected' : ''
|
||||
|
||||
@@ -251,7 +251,6 @@ function CustomTimePickerPopoverContent({
|
||||
<Button
|
||||
className={cx('data-time-live', isLiveLogsEnabled ? 'active' : '')}
|
||||
type="text"
|
||||
data-testid="time-picker-live"
|
||||
onClick={handleGoLive}
|
||||
>
|
||||
Live
|
||||
|
||||
@@ -88,7 +88,6 @@ function TimezoneItem({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-testid={`timezone-item-${timezone.value}`}
|
||||
className={cx('timezone-picker__item', {
|
||||
selected: isSelected,
|
||||
'has-divider': timezone.hasDivider,
|
||||
|
||||
@@ -9,3 +9,8 @@
|
||||
margin-inline-end: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--danger-background);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { message } from 'antd';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import store from 'store';
|
||||
@@ -195,23 +194,6 @@ describe.each([
|
||||
});
|
||||
});
|
||||
|
||||
it('sends the chosen jsonl format and 30k row limit', async () => {
|
||||
renderWithStore(dataSource);
|
||||
await userEvent.click(screen.getByTestId(testId));
|
||||
await userEvent.click(screen.getByRole('radio', { name: 'jsonl' }));
|
||||
await userEvent.click(screen.getByRole('radio', { name: '30k' }));
|
||||
await userEvent.click(screen.getByText('Export'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDownloadExportData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
const callArgs = mockDownloadExportData.mock.calls[0][0];
|
||||
expect(callArgs.format).toBe(DownloadFormats.JSONL);
|
||||
expect(callArgs.body.compositeQuery.queries[0].spec.limit).toBe(
|
||||
DownloadRowCounts.THIRTY_K,
|
||||
);
|
||||
});
|
||||
|
||||
it('clears groupBy and having in the export payload', async () => {
|
||||
const mockQuery = createMockStagedQuery(dataSource);
|
||||
mockQuery.builder.queryData[0].groupBy = [
|
||||
|
||||
@@ -52,7 +52,6 @@ function DeleteMemberDialog({
|
||||
variant="solid"
|
||||
color="danger"
|
||||
onClick={onConfirm}
|
||||
testId="member-delete-confirm-btn"
|
||||
loading={isDeleting}
|
||||
prefix={<Trash2 size={12} />}
|
||||
>
|
||||
|
||||
@@ -624,7 +624,6 @@ function EditMemberDrawer({
|
||||
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
|
||||
size="md"
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
testId="member-delete-btn"
|
||||
disabled={isRootUser || isSelf}
|
||||
variant="link"
|
||||
color="danger"
|
||||
@@ -638,7 +637,6 @@ function EditMemberDrawer({
|
||||
disabledTooltip={ROOT_USER_TOOLTIP}
|
||||
size="md"
|
||||
onClick={handleGenerateResetLink}
|
||||
testId="member-reset-link-btn"
|
||||
disabled={isRootUser}
|
||||
loading={isGeneratingLink || isLoadingTokenStatus}
|
||||
variant="link"
|
||||
@@ -676,7 +674,6 @@ function EditMemberDrawer({
|
||||
color="primary"
|
||||
disabled={!isDirty || isRootUser}
|
||||
onClick={handleSave}
|
||||
testId="member-save-btn"
|
||||
loading={isSaving}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Member Details'}
|
||||
|
||||
@@ -42,12 +42,7 @@ function ResetLinkDialog({
|
||||
</p>
|
||||
<div className="reset-link-dialog__link-row">
|
||||
<div className="reset-link-dialog__link-text-wrap">
|
||||
<span
|
||||
className="reset-link-dialog__link-text"
|
||||
data-testid="reset-link-text"
|
||||
>
|
||||
{resetLink}
|
||||
</span>
|
||||
<span className="reset-link-dialog__link-text">{resetLink}</span>
|
||||
</div>
|
||||
<Button
|
||||
size="md"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MetricQueryRangeSuccessResponse } from 'types/api/metrics/getQueryRange';
|
||||
import { fireEvent, render, screen, userEvent } from 'tests/test-utils';
|
||||
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import ExportMenu from '../ExportMenu';
|
||||
@@ -76,17 +76,6 @@ describe('ExportMenu', () => {
|
||||
expect(mockHandleExport).toHaveBeenCalledWith({ format: 'jsonl' });
|
||||
});
|
||||
|
||||
it('exports the picked jsonl format and closes the popover', async () => {
|
||||
renderMenu();
|
||||
await userEvent.click(screen.getByTestId(TEST_ID));
|
||||
await userEvent.click(screen.getByRole('radio', { name: 'jsonl' }));
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Export' }));
|
||||
|
||||
expect(mockHandleExport).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleExport).toHaveBeenCalledWith({ format: 'jsonl' });
|
||||
expect(screen.queryByText('FORMAT')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables the trigger while an export is in progress', () => {
|
||||
mockIsExporting = true;
|
||||
renderMenu();
|
||||
|
||||
@@ -111,14 +111,11 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||
|
||||
const lineChartRef = useRef<Chart>();
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
(): ToggleGraphProps => ({
|
||||
toggleGraph(graphIndex: number, isVisible: boolean): void {
|
||||
toggleGraph(graphIndex, isVisible, lineChartRef);
|
||||
},
|
||||
}),
|
||||
);
|
||||
useImperativeHandle(ref, (): ToggleGraphProps => ({
|
||||
toggleGraph(graphIndex: number, isVisible: boolean): void {
|
||||
toggleGraph(graphIndex, isVisible, lineChartRef);
|
||||
},
|
||||
}));
|
||||
|
||||
const getGridColor = useCallback(() => {
|
||||
if (currentTheme === undefined) {
|
||||
|
||||
@@ -307,11 +307,7 @@ function LogDetailInner({
|
||||
placement="left"
|
||||
mouseLeaveDelay={0}
|
||||
>
|
||||
<div
|
||||
className="log-body"
|
||||
data-testid="log-detail-body"
|
||||
dangerouslySetInnerHTML={htmlBody}
|
||||
/>
|
||||
<div className="log-body" dangerouslySetInnerHTML={htmlBody} />
|
||||
</Tooltip>
|
||||
|
||||
<div className="log-overflow-shadow"> </div>
|
||||
|
||||
@@ -182,7 +182,6 @@ function ListLogView({
|
||||
return (
|
||||
<>
|
||||
<Container
|
||||
data-testid={`logs-list-row-${logData.id}`}
|
||||
$isActiveLog={isHighlighted || activeLog?.id === logData.id}
|
||||
$isDarkMode={isDarkMode}
|
||||
$logType={logType}
|
||||
|
||||
@@ -23,7 +23,6 @@ function LogLinesActionButtons({
|
||||
icon={<TextSelect size={14} />}
|
||||
className="show-context-btn"
|
||||
onClick={handleShowContext}
|
||||
data-testid="log-line-show-context"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Copy Link">
|
||||
@@ -32,7 +31,6 @@ function LogLinesActionButtons({
|
||||
icon={<Link size={14} />}
|
||||
onClick={onLogCopy}
|
||||
className="copy-log-btn"
|
||||
data-testid="log-line-copy-link"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -156,7 +156,6 @@ function RawLogView({
|
||||
|
||||
return (
|
||||
<RawLogViewContainer
|
||||
data-testid={`logs-raw-row-${data.id}`}
|
||||
onClick={handleClickExpand}
|
||||
wrap={false}
|
||||
align="middle"
|
||||
|
||||
@@ -2,8 +2,11 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { Button, InputNumber, Popover, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { LogViewMode } from 'container/OptionsMenu/types';
|
||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import {
|
||||
LogViewMode,
|
||||
FontSize,
|
||||
OptionsMenuConfig,
|
||||
} from 'container/OptionsMenu/types';
|
||||
import {
|
||||
Check,
|
||||
ChevronLeft,
|
||||
@@ -175,22 +178,19 @@ function OptionsMenu({
|
||||
<div className="title">FORMAT</div>
|
||||
|
||||
<div className="menu-items">
|
||||
{items.map(
|
||||
(item: any): JSX.Element => (
|
||||
<div
|
||||
className="item"
|
||||
key={item.label}
|
||||
data-testid={`format-option-${item.key}`}
|
||||
onClick={(): void => handleMenuItemClick(item.key)}
|
||||
>
|
||||
<div className={cx('item-label')}>
|
||||
{item.label}
|
||||
{items.map((item: any): JSX.Element => (
|
||||
<div
|
||||
className="item"
|
||||
key={item.label}
|
||||
onClick={(): void => handleMenuItemClick(item.key)}
|
||||
>
|
||||
<div className={cx('item-label')}>
|
||||
{item.label}
|
||||
|
||||
{selectedItem === item.key && <Check size={12} />}
|
||||
</div>
|
||||
{selectedItem === item.key && <Check size={12} />}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import { READ_ONLY_TOOLTIP } from '../constants';
|
||||
import InsertVariableMenu from '../InsertVariableMenu';
|
||||
import type { EditorVariable } from '../types';
|
||||
|
||||
const VARIABLES: EditorVariable[] = [
|
||||
{ name: 'environment', badge: 'QUERY' },
|
||||
{ name: 'service', badge: 'CUSTOM' },
|
||||
{ name: 'service_version' },
|
||||
];
|
||||
|
||||
function renderMenu(disabled = false): jest.Mock {
|
||||
const onSelect = jest.fn();
|
||||
render(
|
||||
<InsertVariableMenu
|
||||
variables={VARIABLES}
|
||||
onSelect={onSelect}
|
||||
disabled={disabled}
|
||||
/>,
|
||||
);
|
||||
return onSelect;
|
||||
}
|
||||
|
||||
function rowNames(): string[] {
|
||||
return screen
|
||||
.queryAllByTestId(/^markdown-variable-/)
|
||||
.map((row) => row.getAttribute('data-testid') ?? '');
|
||||
}
|
||||
|
||||
describe('InsertVariableMenu', () => {
|
||||
it('narrows the rows to names matching the search', async () => {
|
||||
renderMenu();
|
||||
await userEvent.click(screen.getByTestId('markdown-insert-variable'));
|
||||
await screen.findByTestId('markdown-variable-environment');
|
||||
expect(rowNames()).toHaveLength(3);
|
||||
|
||||
await userEvent.type(screen.getByPlaceholderText('Search variables'), 'SERV');
|
||||
|
||||
await waitFor(() =>
|
||||
expect(rowNames()).toStrictEqual([
|
||||
'markdown-variable-service',
|
||||
'markdown-variable-service_version',
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('hands the clicked variable name to onSelect', async () => {
|
||||
const onSelect = renderMenu();
|
||||
await userEvent.click(screen.getByTestId('markdown-insert-variable'));
|
||||
await userEvent.click(await screen.findByTestId('markdown-variable-service'));
|
||||
|
||||
expect(onSelect).toHaveBeenCalledTimes(1);
|
||||
expect(onSelect).toHaveBeenCalledWith('service');
|
||||
});
|
||||
|
||||
it('stays closed and explains why when read-only', async () => {
|
||||
const onSelect = renderMenu(true);
|
||||
const trigger = screen.getByTestId('markdown-insert-variable');
|
||||
expect(trigger).toHaveAttribute('aria-disabled', 'true');
|
||||
|
||||
await userEvent.click(trigger);
|
||||
expect(screen.queryByRole('menu')).not.toBeInTheDocument();
|
||||
expect(rowNames()).toHaveLength(0);
|
||||
|
||||
await userEvent.hover(trigger);
|
||||
await expect(screen.findByRole('tooltip')).resolves.toHaveTextContent(
|
||||
READ_ONLY_TOOLTIP,
|
||||
);
|
||||
expect(onSelect).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CustomMultiSelect from '../CustomMultiSelect';
|
||||
@@ -678,64 +678,6 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
|
||||
expect(mockOnChange.mock.calls[0][0]).toStrictEqual(['backend']);
|
||||
});
|
||||
|
||||
it('UI-03c: clicking an option checkbox toggles it exactly once', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
onChange={mockOnChange}
|
||||
value={['frontend']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
const backendRow = (await screen.findByText('Backend')).closest(
|
||||
'[role="option"]',
|
||||
) as HTMLElement;
|
||||
mockOnChange.mockClear();
|
||||
await user.click(within(backendRow).getByRole('checkbox'));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnChange.mock.calls[0][0]).toStrictEqual(['frontend', 'backend']);
|
||||
});
|
||||
|
||||
it('UI-03d: clicking an option label toggles it exactly once', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
onChange={mockOnChange}
|
||||
value={['frontend']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
const backendLabel = await screen.findByText('Backend');
|
||||
mockOnChange.mockClear();
|
||||
await user.click(backendLabel);
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnChange.mock.calls[0][0]).toStrictEqual(['frontend', 'backend']);
|
||||
});
|
||||
|
||||
it('UI-03e: clicking "Toggle" does not also toggle the wrapping checkbox', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect
|
||||
options={mockOptions}
|
||||
onChange={mockOnChange}
|
||||
value={['frontend', 'backend']}
|
||||
/>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('combobox'));
|
||||
const backendRow = (await screen.findByText('Backend')).closest(
|
||||
'[role="option"]',
|
||||
) as HTMLElement;
|
||||
mockOnChange.mockClear();
|
||||
await user.click(within(backendRow).getByText('Toggle'));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnChange.mock.calls[0][0]).toStrictEqual(['frontend']);
|
||||
});
|
||||
|
||||
it('UI-04: Should display values with loading info at bottom', async () => {
|
||||
renderWithVirtuoso(
|
||||
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} loading />,
|
||||
|
||||
@@ -112,7 +112,6 @@ function ListViewOrderBy({
|
||||
placeholder="Select a field"
|
||||
style={{ width: 200 }}
|
||||
options={selectOptions}
|
||||
data-testid="list-view-order-by"
|
||||
filterOption={(input, option): boolean =>
|
||||
(option?.value ?? '').toLowerCase().includes(input.trim().toLowerCase())
|
||||
}
|
||||
|
||||
@@ -11,13 +11,8 @@
|
||||
border-top: 1px solid var(--l1-border);
|
||||
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
|
||||
@@ -12,13 +12,8 @@ $max-recents-shown: 5;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
'Helvetica Neue', sans-serif;
|
||||
|
||||
.query-where-clause-editor-container {
|
||||
position: relative;
|
||||
@@ -138,8 +133,7 @@ $max-recents-shown: 5;
|
||||
// least N recents visible.
|
||||
@for $i from 1 through $max-recents-shown {
|
||||
&:has(> li:nth-of-type(#{$i}) .cm-completionIcon-recent) {
|
||||
max-height: $dropdown-base-height +
|
||||
$recents-header-height +
|
||||
max-height: $dropdown-base-height + $recents-header-height +
|
||||
($i * $recent-row-height) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { jest } from '@jest/globals';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
import {
|
||||
Having,
|
||||
@@ -56,15 +53,6 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
let baseQBContext: QueryBuilderContextType;
|
||||
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get(`${ENVIRONMENT.baseURL}/api/v1/fields/keys`, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ status: 'success', data: { complete: true, keys: {} } }),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const mockCloneQuery = jest.fn() as jest.MockedFunction<
|
||||
(type: string, q: IBuilderQuery) => void
|
||||
>;
|
||||
@@ -248,71 +236,6 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('query actions menu', () => {
|
||||
const openQueryActions = async (): Promise<void> => {
|
||||
const trigger = document.querySelector(
|
||||
'.query-actions-container [aria-haspopup="menu"]',
|
||||
) as HTMLElement;
|
||||
expect(trigger).toBeInTheDocument();
|
||||
await userEvent.click(trigger);
|
||||
};
|
||||
|
||||
it('Clone calls cloneQuery with the query', async () => {
|
||||
render(<QueryBuilderV2 panelType={PANEL_TYPES.TABLE} version="v4" />);
|
||||
|
||||
await openQueryActions();
|
||||
await userEvent.click(
|
||||
await screen.findByRole('menuitem', { name: 'Clone' }),
|
||||
);
|
||||
|
||||
expect(baseQBContext.cloneQuery).toHaveBeenCalledTimes(1);
|
||||
expect(baseQBContext.cloneQuery).toHaveBeenCalledWith(
|
||||
'query',
|
||||
expect.objectContaining({ queryName: 'A' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('hides Delete when there is a single query', async () => {
|
||||
render(<QueryBuilderV2 panelType={PANEL_TYPES.TABLE} version="v4" />);
|
||||
|
||||
await openQueryActions();
|
||||
await screen.findByRole('menuitem', { name: 'Clone' });
|
||||
|
||||
expect(
|
||||
screen.queryByRole('menuitem', { name: 'Delete' }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows Delete with more than one query and deletes on click', async () => {
|
||||
const currentQueryBase = baseQBContext.currentQuery as Query;
|
||||
const [queryA] = currentQueryBase.builder.queryData;
|
||||
mockedUseQueryBuilder.mockReturnValue({
|
||||
...baseQBContext,
|
||||
currentQuery: {
|
||||
...currentQueryBase,
|
||||
builder: {
|
||||
...currentQueryBase.builder,
|
||||
queryData: [queryA, { ...queryA, queryName: 'B', legend: 'B' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const handleDeleteQuery = jest.fn();
|
||||
mockedUseQueryOperations.mockReturnValue({
|
||||
...mockedUseQueryOperations({} as Parameters<UseQueryOperations>[0]),
|
||||
handleDeleteQuery,
|
||||
});
|
||||
|
||||
render(<QueryBuilderV2 panelType={PANEL_TYPES.TABLE} version="v4" />);
|
||||
|
||||
await openQueryActions();
|
||||
await userEvent.click(
|
||||
await screen.findByRole('menuitem', { name: 'Delete' }),
|
||||
);
|
||||
|
||||
expect(handleDeleteQuery).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('fx button is disabled when functions already exist', () => {
|
||||
const currentQueryBase = baseQBContext.currentQuery as Query;
|
||||
const supersetQueryBase = baseQBContext.supersetQuery as Query;
|
||||
|
||||
@@ -167,11 +167,7 @@ export default function CheckboxFilterV2(
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.checkboxFilter}
|
||||
data-testid="checkbox-filter-v2"
|
||||
data-filter-key={filter.attributeKey.key}
|
||||
>
|
||||
<div className={styles.checkboxFilter} data-testid="checkbox-filter-v2">
|
||||
<CheckboxFilterV2Header
|
||||
title={filter.title}
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -22,9 +22,7 @@ function KeyCreatedPhase({
|
||||
<div className="add-key-modal__field">
|
||||
<span className="add-key-modal__label">Key</span>
|
||||
<div className="add-key-modal__key-display">
|
||||
<span className="add-key-modal__key-text" data-testid="created-key-value">
|
||||
{createdKey.key}
|
||||
</span>
|
||||
<span className="add-key-modal__key-text">{createdKey.key}</span>
|
||||
<Button
|
||||
size="md"
|
||||
variant="link"
|
||||
|
||||
@@ -61,7 +61,6 @@ export function RevokeKeyFooter({
|
||||
color="danger"
|
||||
loading={isRevoking}
|
||||
onClick={onConfirm}
|
||||
testId="revoke-key-confirm-btn"
|
||||
prefix={<Trash2 size={12} />}
|
||||
>
|
||||
Revoke Key
|
||||
|
||||
@@ -384,7 +384,6 @@ function ServiceAccountDrawer({
|
||||
void setIsDeleteOpen(true);
|
||||
}}
|
||||
prefix={<Trash2 size={12} />}
|
||||
testId="sa-delete-btn"
|
||||
>
|
||||
Delete Service Account
|
||||
</AuthZButton>
|
||||
@@ -410,7 +409,6 @@ function ServiceAccountDrawer({
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
testId="sa-save-btn"
|
||||
>
|
||||
Save Changes
|
||||
</AuthZButton>
|
||||
@@ -439,7 +437,6 @@ function ServiceAccountDrawer({
|
||||
type="single"
|
||||
value={activeTab}
|
||||
size="sm"
|
||||
testId="sa-tab"
|
||||
onChange={(val: string): void => {
|
||||
if (val) {
|
||||
void setActiveTab(val as ServiceAccountDrawerTab);
|
||||
@@ -481,7 +478,6 @@ function ServiceAccountDrawer({
|
||||
void setIsAddKeyOpen(true);
|
||||
}}
|
||||
prefix={<Plus size={12} />}
|
||||
testId="sa-add-key-btn"
|
||||
>
|
||||
Add Key
|
||||
</AuthZButton>
|
||||
|
||||
@@ -13,26 +13,22 @@ function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
|
||||
|
||||
return (
|
||||
<div className="label-column">
|
||||
{newLabels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<BadgeWithTooltip key={label} label={label} value={value} />
|
||||
),
|
||||
)}
|
||||
{newLabels.map((label: string): JSX.Element => (
|
||||
<BadgeWithTooltip key={label} label={label} value={value} />
|
||||
))}
|
||||
{remainingLabels.length > 0 && (
|
||||
<Popover
|
||||
placement="bottomRight"
|
||||
showArrow={false}
|
||||
content={
|
||||
<div>
|
||||
{labels.map(
|
||||
(label: string): JSX.Element => (
|
||||
<div key={label}>
|
||||
<Badge textTransform="none" variant="solid" color="secondary">
|
||||
{getLabelAndValueContent(label, value && value[label])}
|
||||
</Badge>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
{labels.map((label: string): JSX.Element => (
|
||||
<div key={label}>
|
||||
<Badge textTransform="none" variant="solid" color="secondary">
|
||||
{getLabelAndValueContent(label, value && value[label])}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
trigger="hover"
|
||||
|
||||
@@ -3,23 +3,21 @@ import { Columns3 } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
|
||||
import type { TableColumnDef } from 'components/TanStackTableView';
|
||||
import {
|
||||
hideColumn,
|
||||
showColumn,
|
||||
useHiddenColumnIds,
|
||||
} from 'components/TanStackTableView';
|
||||
|
||||
import styles from './ColumnSelector.module.scss';
|
||||
import type { TableColumnDef } from './types';
|
||||
import { hideColumn, showColumn, useHiddenColumnIds } from './useColumnStore';
|
||||
|
||||
interface ColumnSelectorProps<TData> {
|
||||
columns: TableColumnDef<TData>[];
|
||||
/** Shared with the table's `columnStorageKey`, which is what it writes to. */
|
||||
storageKey: string;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
function ColumnSelector<TData>({
|
||||
columns,
|
||||
storageKey,
|
||||
testId = 'column-selector-button',
|
||||
}: ColumnSelectorProps<TData>): JSX.Element {
|
||||
const hiddenColumnIds = useHiddenColumnIds(storageKey);
|
||||
|
||||
@@ -47,7 +45,7 @@ function ColumnSelector<TData>({
|
||||
size="sm"
|
||||
color="secondary"
|
||||
prefix={<Columns3 size={14} />}
|
||||
testId="alert-columns-button"
|
||||
testId={testId}
|
||||
>
|
||||
Columns
|
||||
</Button>
|
||||
@@ -77,4 +75,8 @@ function ColumnSelector<TData>({
|
||||
);
|
||||
}
|
||||
|
||||
ColumnSelector.defaultProps = {
|
||||
testId: 'column-selector-button',
|
||||
};
|
||||
|
||||
export default ColumnSelector;
|
||||
@@ -263,33 +263,6 @@ describe('useColumnStore', () => {
|
||||
expect(result.current).toBe(firstSnapshot);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['only hiddenColumnIds', { hiddenColumnIds: ['persisted'] }],
|
||||
['only columnOrder', { columnOrder: ['a', 'b'] }],
|
||||
['an empty object', {}],
|
||||
['a wrong-typed hiddenColumnIds', { hiddenColumnIds: 'nope' }],
|
||||
])(
|
||||
'normalises a persisted value carrying %s so selectors never yield undefined',
|
||||
(_label, stored) => {
|
||||
localStorage.setItem(
|
||||
'@signoz/table-columns/test-table',
|
||||
JSON.stringify(stored),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
useColumnStore.getState().initializeFromDefaults(TEST_KEY, []);
|
||||
});
|
||||
|
||||
const hidden = renderHook(() => useHiddenColumnIds(TEST_KEY));
|
||||
const order = renderHook(() => useColumnOrder(TEST_KEY));
|
||||
const sizing = renderHook(() => useColumnSizing(TEST_KEY));
|
||||
|
||||
expect(Array.isArray(hidden.result.current)).toBe(true);
|
||||
expect(Array.isArray(order.result.current)).toBe(true);
|
||||
expect(typeof sizing.result.current).toBe('object');
|
||||
},
|
||||
);
|
||||
|
||||
it('useColumnSizing returns sizing', () => {
|
||||
act(() => {
|
||||
useColumnStore.getState().initializeFromDefaults(TEST_KEY, []);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { TanStackHoverTooltip } from './TanStackHoverTooltip';
|
||||
import { TanStackTableBase } from './TanStackTable';
|
||||
import TanStackTableText from './TanStackTableText';
|
||||
|
||||
export { default as ColumnSelector } from './ColumnSelector';
|
||||
export * from './TanStackHoverTooltip';
|
||||
export * from './TanStackTableStateContext';
|
||||
export * from './types';
|
||||
|
||||
@@ -66,19 +66,7 @@ const loadTableFromStorage = (tableKey: string): ColumnState | null => {
|
||||
return cached.parsed;
|
||||
}
|
||||
|
||||
// Selectors read these fields directly, so a value persisted under an older
|
||||
// schema must be filled in rather than handed to callers as `undefined`.
|
||||
const stored = JSON.parse(raw) as Partial<ColumnState> | null;
|
||||
const parsed: ColumnState = {
|
||||
hiddenColumnIds: Array.isArray(stored?.hiddenColumnIds)
|
||||
? stored.hiddenColumnIds
|
||||
: [],
|
||||
columnOrder: Array.isArray(stored?.columnOrder) ? stored.columnOrder : [],
|
||||
columnSizing:
|
||||
stored?.columnSizing && typeof stored.columnSizing === 'object'
|
||||
? stored.columnSizing
|
||||
: {},
|
||||
};
|
||||
const parsed = JSON.parse(raw) as ColumnState;
|
||||
persistedTableCache.set(tableKey, { raw, parsed });
|
||||
return parsed;
|
||||
} catch {
|
||||
|
||||