Compare commits

..

66 Commits

Author SHA1 Message Date
Manika Malhotra
7de8739f55 Merge branch 'main' into feat/input-component 2026-11-05 18:48:13 +05:30
Manika Malhotra
24d6029564 Merge branch 'main' into feat/input-component 2026-11-05 17:15:40 +05:30
Abhi kumar
8f86b59822 feat(dashboard): group small pie slices into an automatic Other slice (#13428)
#### Description

Groups small pie slices into one automatic **Other** slice, so a pie
with many groups stays readable.

- A slice under 1% of the total joins Other, and the pie draws at most
15 slices; Other needs at least 2 members and is always the last arc.
- Automatic, with no panel setting.
- Legend lists every series; hiding or showing one regroups Other from
the series left showing.
- Hovering Other lists its members in the timeseries-style tooltip;
clicking pins it, and clicking a member drills into that series through
the existing slice drilldown.
- Storybook: `Pages/Dashboards/Detail/Panels/PieOther`.



https://github.com/user-attachments/assets/143626a2-7e8a-48ac-a9cd-742f0fcd18ec



#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/363

#### Additional Information

- V2 dashboards only; V1 pie is unchanged.
2026-11-05 11:34:01 +00:00
ashu8912
e53d16157b test(e2e): scroll drivers with the mouse wheel (#13433)
#### Description

- Drivers now scroll with the mouse wheel through a new `wheelTo` helper
(`drivers/scroll.ts`) instead of `scrollIntoView` /
`scrollIntoViewIfNeeded`. Scripted scrolling moves even an `overflow:
hidden` box a user can't scroll, so the channel form clipping passed e2e
while users couldn't reach the fields.
- `wheelTo` wheels over the target's visible scroll container until the
target is centred and not covered by a sticky header, then asserts
`toBeInViewport()`. If a turn doesn't move the target, it fails with "a
container clips it instead of scrolling".
- oxlint now rejects `scrollIntoView` and `scrollIntoViewIfNeeded` in
`tests/e2e`, and `AGENTS.md` adds the rule.
- The panel handle's `scrollIntoView()` is renamed to `wheelIntoView()`.
- The channel form (`fillField`) and the alert rule form (threshold
channels, labels, advanced options, evaluation settings) wheel to lower
controls before acting, so a clipped form fails the existing specs, not
only the dedicated regression spec.

#### Additional Information

- Other `click()` / `fill()` calls still use Playwright's scripted
scroll; the wheel is applied where content can sit below the fold.
- Verified on Chromium only.


Closes: https://github.com/SigNoz/platform-pod/issues/3338
2026-11-05 11:10:34 +00:00
Ashwin Bhatkal
83ebe26877 ci(e2e): run the suite on 10 shards (#13395)
#### Description

- The e2e suite now runs on 10 shards instead of 4. Since
[#13189](https://github.com/SigNoz/signoz/pull/13189) the suite is about
twice as long, so each of the 4 shards spends 16 to 20 min in tests and
a PR waits 23 to 27 min for e2e.
- Each shard also pays about 7 min of fixed setup, so 10 shards should
bring the slowest one to about 16 to 17 min. Past 10, every 2 extra
shards save only about 1.5 min.
2026-11-05 11:04:21 +00:00
Aditya Singh
33d2ff917f feat(explorer): make the traces and logs explorer toolbars and tables match (#13411)
#### Description

- The traces and logs explorers now look the same above and around the
table. Both toolbars use the same row height, spacing and controls.
- The traces Options control is now an icon button, the same as the logs
one.
- The logs Options menu now uses the design system buttons and number
input. Its rows no longer fill on hover. The logs Options button no
longer turns blue on hover.
- Max lines per row now clamps a typed value to 1–10. Before, it ignored
1 and accepted 2.5.
- The logs list now reaches the bottom of the page.
- The logs list and table now use Inter, the same as traces. Only the
raw view keeps the mono font.
- The traces and logs table rows are now 30px tall, the row height in
the design.

#### Issues closed by this PR

Part of https://github.com/SigNoz/events-pod/issues/195

#### Screenshots

https://claude.ai/artifact/6zsvLZFYYzfFrPy9r6Xq9F?sk=ymS3KLyoZjWMf2axjY1-ng

#### Additional Information

- Stacked on #13392.
- The traces styles moved to CSS modules. The LLM Observability explorer
renders the same global classes, so global rules from one page could
restyle the other.
2026-11-05 10:22:09 +00:00
ashu8912
13440740c4 fix(alerts): keep configuration sidebar item weight constant when active (#13432)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description

- Configuration sidebar items (Notification Channels, Routing Policies,
Planned Downtime) no longer turn bold when selected; only the colour
changes, so the label no longer shifts.

Closes: https://github.com/SigNoz/pulse-pod/issues/693
2026-11-05 06:13:01 +00:00
Aditya Singh
711f0b6beb feat(traces-explorer): add infinite scroll to list view; table ui update (#13392)
#### Description

- List view now loads spans on scroll, 100 at a time. It stops once a
page comes back short or the query limit is hit. Previous / Next
controls are removed from the list.
- Traces tab now uses the table's own pager. Page and page size are kept
in the URL. Default is 20 traces per page instead of 10 so the table
fills the space. API does not send a total, so the pager offers the next
page only after a full page.
- New query clears the old rows and shows the loader. It also resets the
traces tab to page 1 and closes the span drawer.
- Span drawer is now on for everyone. Removed the
`TRACES_SPAN_DRAWER_ENABLED` flag.
- Added a hover icon at the start of each row to open the trace in a new
tab. If the API returns the same span twice, only the hovered row shows
the icon.
- Table ui: list and traces tables run edge to edge, empty cells show a
dash in the border colour, rows use the table default height and the
hover / active colours from design.

#### Issues closed by this PR

Part of https://github.com/SigNoz/events-pod/issues/195

#### Screen recording 



https://github.com/user-attachments/assets/4f7897bd-0c6b-4ecf-aaf2-231834082547




https://claude.ai/artifact/LmXzEheZYZ1xdpshNjGBRf?sk=dOC3fM6uD0n_fKnzLAnZXQ

#### Additional Information

- If the data ends exactly on a full page, Next opens an empty page and
the pager steps back to the last one.
- A failed page request hides the pager. Same as other explorers, so
keeping it as is for now.
- Icon sits in a left gutter as discussed with design.
2026-11-05 05:23:11 +00:00
ashu8912
98f9850c53 fix(alerts): fall back to default range on invalid relativeTime (#13375)
#### Description

- Alert Details crashed to the error boundary when the URL
`relativeTime` couldn't be parsed (e.g. `?relativeTime=1y`). It now
falls back to the default 30m range.
- The "triggers in …" and stats card labels show the range actually
queried, not the raw param.
- `relativeTime=3months` (accepted by validation since #12402) silently
resolved to 30 minutes. Multi-month values now resolve to 30 days per
month.
- Moved `useRouteTabUtils` into its own file to break the `hooks.tsx` ↔
`container/AlertHistory` import cycle.

#### Issues closed by this PR

Closes SigNoz/pulse-pod#674

#### Additional Information

- Repro: open `/alerts/overview?ruleId=<id>&relativeTime=1y` (crashes on
`main`), or `relativeTime=3months` (shows 30m of history).
- No story change. The fixed page renders the same as the existing
AlertOverview story.
2026-11-05 03:12:55 +00:00
Manika Malhotra
f282ae6734 fix(popover): drop nativeButton from areas where handle is passed 2026-10-16 13:48:19 +05:30
Manika Malhotra
4133182b59 fix: tooltip mock and simplify variables popover 2026-10-15 20:09:54 +05:30
Manika Malhotra
0751c4d294 feat(popover): integrate popover component 2026-10-15 18:15:47 +05:30
Manika Malhotra
d61c7643c7 fix: cleanup conflicting css 2026-10-14 21:22:03 +05:30
Manika Malhotra
e8fba3e0b6 fix: remove sonner shim and use toast 2026-10-13 13:44:10 +05:30
Manika Malhotra
fd0f5cddb3 chore: bump packages, integrate alert-strip and resizable 2026-10-12 17:26:05 +05:30
Manika Malhotra
693401dbbb feat(frontend): adopt reworked @signozhq/ui components across remaining areas 2026-10-08 18:12:17 +05:30
Manika Malhotra
c9f0cc18d2 fix: restore onboarding 2 column layout 2026-10-07 18:44:51 +05:30
Vinícius Lourenço
0b1e8aad6e fixup! feat(integrations): adopt reworked @signozhq/ui components 2026-10-06 15:06:37 -03:00
Vinícius Lourenço
5685de5494 fixup! feat(llm-observability): adopt reworked @signozhq/ui components 2026-10-06 15:06:32 -03:00
Vinícius Lourenço
b21742a128 fixup! feat(settings): adopt reworked @signozhq/ui components 2026-10-01 15:00:58 -03:00
Vinícius Lourenço
843feef61d fix(alerts): use Pill.Closeable for classic alert form labels 2026-10-01 12:02:15 -03:00
Vinícius Lourenço
6984683fd9 test(integrations): raise timeout for GCP setup drawer tests 2026-10-01 12:02:15 -03:00
Vinícius Lourenço
1407b26fbb fix(planned-downtime): use Pill.Closeable for selected alert rules 2026-10-01 12:02:15 -03:00
Vinícius Lourenço
aed715be5c fix(pipelines): submit the pipeline form from the Create and Update button 2026-10-01 12:02:14 -03:00
Vinícius Lourenço
8ef90b84b6 test(e2e): update selectors for reworked @signozhq/ui components 2026-10-01 12:02:14 -03:00
Vinícius Lourenço
2315a18fa4 feat(events-exceptions-apis-queues): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:14 -03:00
Vinícius Lourenço
77834e2b22 feat(events-traces): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:14 -03:00
Vinícius Lourenço
a37ee50c7d feat(events-logs): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:14 -03:00
Vinícius Lourenço
a05baddab0 feat(pulse-infra-monitoring): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:14 -03:00
Vinícius Lourenço
099c5135db feat(pulse-alerts): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:13 -03:00
Vinícius Lourenço
268af75098 feat(pulse-dashboards-list): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:13 -03:00
Vinícius Lourenço
a5e395120f feat(pulse-dashboards): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:13 -03:00
Vinícius Lourenço
c4be50c5c2 feat(metrics-apm): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:13 -03:00
Vinícius Lourenço
8dd0f79f70 feat(llm-observability): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:13 -03:00
Vinícius Lourenço
86212a1b1c feat(ai-assistant): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:13 -03:00
Vinícius Lourenço
9f580456ba feat(integrations): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:12 -03:00
Vinícius Lourenço
6e07d9827f feat(auth-onboarding): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:12 -03:00
Vinícius Lourenço
b146c15a97 feat(settings): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:12 -03:00
Vinícius Lourenço
738f341131 feat(navigation): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:12 -03:00
Vinícius Lourenço
fc1d81dda8 feat(query-builder): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:12 -03:00
Vinícius Lourenço
c52e41afbf feat(shared-components): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:11 -03:00
Vinícius Lourenço
e0dc068af3 feat(members-roles-service-accounts): adopt reworked @signozhq/ui components
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:11 -03:00
Vinícius Lourenço
ee4043f42d feat(authz): build AuthZ components on reworked @signozhq/ui
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:11 -03:00
Vinícius Lourenço
f3c4c8685a fix(api): treat requests cancelled before dispatch as cancellations
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:11 -03:00
Vinícius Lourenço
4e7fbdbfeb test(storybook): adopt reworked @signozhq/ui in fixtures and mocks
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:11 -03:00
Vinícius Lourenço
21e2a0bda8 chore(deps): bump @signozhq/ui to 0.2.0 and design-tokens to 2.2.1
Co-authored-by: Manika Malhotra <manika@signoz.io>
2026-10-01 12:02:11 -03:00
Vinícius Lourenço
867b958318 test(storybook): cover classic alert form label states 2026-10-01 12:00:26 -03:00
Vinícius Lourenço
088fe04a20 test(storybook): add planned downtime edit story with specific alert rules 2026-10-01 12:00:26 -03:00
Vinícius Lourenço
0002d9c5f8 test(storybook): hold Noz click-opened state past the thread remount 2026-10-01 12:00:26 -03:00
Vinícius Lourenço
c8b6e83474 test(storybook): drop the fixed pause between filter keystrokes 2026-10-01 12:00:25 -03:00
Vinícius Lourenço
5607edb302 test(storybook): find dashboard settings and panel editor controls by test id 2026-10-01 12:00:25 -03:00
Vinícius Lourenço
680ac5e88f fix(tanstack-table): keep cells mounted when inline row callbacks change 2026-10-01 12:00:25 -03:00
Vinícius Lourenço
09f9d2f3c8 test(storybook): cover panel editor chart options, thresholds and links 2026-10-01 12:00:25 -03:00
Vinícius Lourenço
203d7a0753 test(storybook): cover dashboard settings, variables and authz states 2026-10-01 12:00:25 -03:00
Vinícius Lourenço
3ba8c9390c fix(storybook): attribute failed responses to the story that sent them 2026-10-01 11:59:59 -03:00
Vinícius Lourenço
6cc07a4d75 test(storybook): cover Noz voice recording states 2026-10-01 11:59:58 -03:00
Vinícius Lourenço
856cd91491 test(storybook): assert the in-flight create key button is disabled 2026-10-01 11:59:58 -03:00
Vinícius Lourenço
f22196a2c0 test(storybook): cover ingestion create key validation, expiry and submit states 2026-10-01 11:59:58 -03:00
Vinícius Lourenço
0438850c45 test(storybook): cover ingestion key tag adding and added states 2026-10-01 11:59:58 -03:00
Vinícius Lourenço
2cc9421350 test(storybook): click exceptions filter suggestions as they are found 2026-10-01 11:59:58 -03:00
Vinícius Lourenço
c692297ca6 test(storybook): hold the pod filter stories at their opening height 2026-10-01 11:59:58 -03:00
Vinícius Lourenço
cc6ddd5233 test(storybook): wait for the filter's scrollbars before focusing it 2026-10-01 11:59:57 -03:00
Vinícius Lourenço
d72424d9d0 test(storybook): keep filter focused while scrollbars initialise 2026-10-01 11:59:57 -03:00
Vinícius Lourenço
bbec5691e0 test(storybook): add query builder filter interaction stories 2026-10-01 11:59:57 -03:00
Vinícius Lourenço
cf61f7b31a test(storybook): fix interaction stories against current main 2026-10-01 11:59:57 -03:00
Vinícius Lourenço
02055934ba test(storybook): add interaction stories for menus, drawers and selects 2026-10-01 11:59:57 -03:00
625 changed files with 7763 additions and 6806 deletions

View File

@@ -67,7 +67,7 @@ jobs:
matrix:
project:
- chromium
shard: [1, 2, 3, 4]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
needs: changes
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
@@ -162,7 +162,7 @@ jobs:
- name: playwright-test
run: |
cd tests/e2e && \
pnpm playwright test --project=${{ matrix.project }} --shard=${{ matrix.shard }}/4
pnpm playwright test --project=${{ matrix.project }} --shard=${{ matrix.shard }}/10
- name: teardown-stack
if: always()
run: |

View File

@@ -45,8 +45,8 @@ const mockAliases = [
},
{
// jest: not replaced, a test asserts on the toast it fires.
find: /^@signozhq\/ui\/sonner$/,
replacement: `${srcPath}/storybook/mocks/sonner.mock.tsx`,
find: /^@signozhq\/ui\/toast$/,
replacement: `${srcPath}/storybook/mocks/toast.mock.tsx`,
},
];

View File

@@ -0,0 +1,191 @@
# Popover migration analysis
The new `@signozhq/ui` Popover is click-only. This note maps the current app onto that API so a later migration does not break open state, focus, or the global `.ant-popover` overrides. It does not change any call site.
The component this describes lives in the design-system repo (`packages/ui/src/popover`). Storybook stories for it are `Playground`, `SaveViewForm`, `ControlledOpen`, `InsideDialog`, `MaxHeightScrolling`, and `AnchoredTypeahead`.
## The API to migrate onto
```tsx
<Popover
nativeButton
side="bottom"
align="start"
trigger={<button type="button">Open</button>}
open={open}
onOpenChange={setOpen}
>
{content}
</Popover>
```
| Today | New Popover |
| --- | --- |
| antd `children` (the trigger) | `trigger` (required) |
| antd `content`, or Radix `<PopoverContent>` | `children` |
| antd `trigger="click"` | the only mode; there is no `openOn` |
| antd `trigger="hover"` | not this component; a Tooltip, or a click popover when the body is interactive |
| `placement="bottomRight"` | `side="bottom"` `align="end"` |
| `placement="bottomLeft"` | `side="bottom"` `align="start"` |
| `open` / `onOpenChange` | kept; `onOpenChange` receives Base UI's reason and `details.cancel()` |
| `defaultOpen` | removed; nothing in the app starts open |
| `overlayClassName`, `overlayStyle`, `overlayInnerStyle`, `className` | rejected on the shell; size is `width` / `maxWidth` / `maxHeight` |
| `getPopupContainer` | `container` |
| `destroyTooltipOnHide` | the only behavior; the popup unmounts on close |
| `arrow` / `showArrow` | rejected; the shell has no arrow |
| Radix `PopoverAnchor` | `anchor`, and the trigger is still required |
| Radix `withPortal={false}` | `container` (inside a Dialog the popup already portals into the panel) |
| Radix `onInteractOutside` | `onOpenChange` + `details.cancel()` when `reason === 'outside-press'` |
| `sideOffset` / `alignOffset` | not props; fixed at 4px and 0 |
`nativeButton` is required. `true` when `trigger` renders a `<button>` (including `Button`). `false` for anything else.
A node that renders nothing (`null`, `false`, `''`) renders the trigger alone, with no listeners and no portal.
## What is in the app today
Counted from `frontend/src` on this branch:
- **29 files** import `Popover` from `antd`.
- **7 files** import `Popover` from `@signozhq/ui` (the Radix wrapper this rework replaces). Those seven break as soon as the package ships, because `children` stops being the trigger.
- **18 SCSS files** override `.ant-popover*`.
antd `Dropdown` is already on `@signozhq/ui/dropdown`. antd `Tooltip` (on the order of 100 importers) is a separate migration.
### antd files
`src/components/CustomTimePicker/CustomTimePicker.tsx`
`src/components/DownloadOptionsMenu/DownloadOptionsMenu.tsx`
`src/components/ErrorPopover/ErrorPopover.tsx`
`src/components/ExplorerCard/ExplorerCard.tsx`
`src/components/HeaderRightSection/HeaderRightSection.tsx`
`src/components/Logs/AddToQueryHOC.tsx`
`src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx`
`src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregationSelect.tsx`
`src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx`
`src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx`
`src/components/TableRenderer/LabelColumn.tsx`
`src/components/WarningPopover/WarningPopover.tsx`
`src/container/AlertHistory/AlertPopover/AlertPopover.tsx`
`src/container/CreateAlertV2/EvaluationSettings/EvaluationSettings.tsx`
`src/container/Home/Home.tsx`
`src/container/MetricsExplorer/MetricDetails/AllAttributes.tsx`
`src/container/MetricsExplorer/MetricDetails/AllAttributesValue.tsx`
`src/container/MetricsExplorer/Summary/MetricNameSearch.tsx`
`src/container/OptionsMenu/index.tsx`
`src/container/TopNav/AutoRefreshV2/index.tsx`
`src/pages/DashboardsListPage/components/ListHeader/ListHeader.tsx`
`src/pages/MetricsApplication/ApDex/ApDexApplication.tsx`
`src/pages/PublicDashboard/PublicDashboardView/PublicAutoRefresh/PublicAutoRefresh.tsx`
`src/pages/TraceDetailsV3/SpanDetailsPanel/Events/components/AttributeWithExpandablePopover.tsx`
`src/pages/TracesFunnelDetails/components/FunnelConfiguration/FunnelStepPopover.tsx`
`src/pages/TracesFunnels/components/FunnelsList/FunnelItemPopover.tsx`
`src/pages/TracesFunnels/components/SearchBar/SearchBar.tsx`
`src/periscope/components/ContextMenu/index.tsx`
`src/periscope/components/SeeMore/SeeMore.tsx`
### Existing `@signozhq/ui` popovers
`src/components/ExportMenu/ExportMenu.tsx`
`src/components/MarkdownEditor/MarkdownHelp.tsx`
`src/components/TanStackTableView/TanStackHeaderRow.tsx`
`src/container/AIAssistant/components/ChatInput/ChatInput.tsx`
`src/container/ListAlertRules/components/ColumnSelector.tsx`
`src/pages/DashboardPage/DashboardContainer/PanelEditor/ConfigPane/sections/ContextLinksSection/VariablesPopover.tsx`
`src/pages/DashboardsListPage/components/ViewsRail/ViewNamePopover.tsx`
### SCSS that overrides `.ant-popover`
`src/components/DownloadOptionsMenu/DownloadOptionsMenu.styles.scss`
`src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss`
`src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.styles.scss`
`src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss`
`src/container/AlertHistory/AlertPopover/AlertPopover.styles.scss`
`src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsCard.styles.scss`
`src/container/CreateAlertV2/EvaluationSettings/styles.scss`
`src/container/Home/Home.styles.scss`
`src/container/MetricsExplorer/MetricDetails/MetricDetails.styles.scss`
`src/container/MetricsExplorer/Summary/Summary.styles.scss`
`src/container/TopNav/AutoRefreshV2/AutoRefreshV2.styles.scss`
`src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss`
`src/container/WidgetCard/WidgetCard.styles.scss`
`src/pages/DashboardsListPage/components/ListHeader/ListHeader.module.scss`
`src/pages/TracesFunnels/TracesFunnels.styles.scss`
`src/pages/TracesFunnels/components/FunnelsList/FunnelsList.styles.scss`
`src/pages/TracesFunnels/components/SearchBar/SearchBar.styles.scss`
`src/periscope/components/ContextMenu/styles.scss`
Three of these are unscoped and restyle every antd popover on the page, not just their own:
- `src/container/MetricsExplorer/Summary/Summary.styles.scss`
- `src/container/CreateAlertV2/EvaluationSettings/styles.scss`
- `src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsCard.styles.scss`
Scope or delete those three before moving any nearby popover, or a migrated popup and a leftover antd popup will share rules that only one of them understands.
Most of the other files repeat one recipe: padding 0, 4px radius, a 1px border, a translucent background, the arrow hidden. The new shell already has zero padding, `--radius-2`, and no arrow. Per-site size (fixed widths around 187–500px, max-width 600px, max-height 254px) becomes `width` / `maxWidth` / `maxHeight`. Do not re-apply the recipe as a `className`; the component rejects it.
## What should not become this Popover
**Hover descriptions.** `AddToQueryHOC`, `LabelColumn`, the three Query Builder validation popovers, `SeeMore`, and `AttributeWithExpandablePopover` open on hover. Those are Tooltips. `AttributeWithExpandablePopover` shows a value the user copies; if Tooltip cannot hold that, make it a click popover instead of keeping hover.
**WarningPopover** (imported from about 10 files) is hover today and contains docs links. A tooltip cannot host those links. Either the links move out and the summary becomes a Tooltip, or the wrapper becomes a click Popover with `maxWidth` around 600.
**Menus.** Funnel item and funnel step action lists, the sort lists in `ListHeader` and Traces Funnels `SearchBar`, `AutoRefreshV2` / `PublicAutoRefresh` interval lists, and `AlertPopover`'s two links are rows that close when one is picked. Those are Dropdowns. `AllAttributes` per-row actions are the same shape.
**ContextMenu** (`src/periscope/components/ContextMenu/index.tsx`, imported across the dashboard drilldown) opens at chart-click coordinates. There is no trigger element. The new Popover requires `trigger`, so this stays a context menu. Do not fold the phantom span, `position: fixed` overlay, `zIndex: 10000`, or `calculatePopoverPosition.ts` into `anchor`. `anchor` is for a popover that already has a trigger and should position against something else (the typeahead input row).
**Inspect charts.** `MetricsExplorer/Inspect` `GraphPopover` and `HoverPopover` are absolutely positioned cards, not antd Popover. Leave them until there is a context-menu or anchor design that matches them.
**PanelStatusPopover** is a Tooltip despite the name.
## What should become this Popover
Click panels whose content is a form, a picker, or a composed layout:
- `CustomTimePicker` — the most visible one. Controlled `open`, `container` for `getPopupContainer`, and it already unmounts on hide. It opens from both input focus and click; that logic stays in the caller, with `onOpenChange`.
- `EvaluationSettings` / `EvaluationWindowPopover` — controlled, wide (`width` / `maxWidth`), and sitting on a global `.ant-popover` override. Migrate the override in the same change.
- `HeaderRightSection` — three controlled forms (feedback, announcements, share).
- `ExplorerCard` save-view form and `ViewNamePopover` — close only after the save succeeds, so `open` stays controlled. `ViewNamePopover` is already on the library and will not compile against the new props.
- `ApDex` threshold form.
- `Home` welcome checklist — drop the transparent-shell override; the shell is the component's.
- `OptionsMenu`, `DownloadOptionsMenu`, `LogsFormatOptionsMenu` — click panels. `LogsFormatOptionsMenu` nests a submenu; the inner popup must be portalled, and the outer `onOpenChange` must `cancel()` an outside press that lands in the inner popup.
- `MetricNameSearch` — controlled search panel. Scope the global padding override in `Summary.styles.scss` in the same change.
- `AllAttributesValue` — one popover open at a time in a list. That is controlled `open` keyed by row, which the API still allows.
- `ChatInput` context picker — already on the library; `side="top"` `align="end"` plus `width`.
- `VariablesPopover` — already on the library. Keep `anchor` on the input row, `initialFocus={false}` so the caret stays, and `details.cancel()` when the outside press is the anchor. `withPortal={false}` becomes the default dialog `container`, not an unportalled popup.
- `ColumnSelector`, `MarkdownHelp`, `TanStackHeaderRow`, `ExportMenu` — Radix `asChild` trigger plus content. The trigger moves to the `trigger` prop, content moves to `children`, and `className` on the shell becomes `width` / `maxWidth`.
`ErrorPopover` is a thin wrapper. Re-point it at the new props and its consumer follows.
## Order
Before any change: screenshot every current popover, in both themes, so each call site's pattern is on record and a component swap is reviewable against it. Flag the misusages to design as they turn up — links inside hover surfaces above all. Swapping the component during migration is expected where the old call site was on the wrong one; antd call sites stay untouched until their turn.
1. Scope the three global `.ant-popover` files so later migrations cannot restyle leftover antd popovers.
2. Move the seven existing `@signozhq/ui` call sites first. They break at the package bump; the 29 antd files do not, until each one is switched.
3. Migrate shared wrappers that are actually popovers (`ErrorPopover`, then `WarningPopover` once the links decision is made). Leave `ContextMenu` on antd until a context menu exists.
4. Migrate click forms (`CustomTimePicker`, evaluation window, header menus, save-view, ApDex).
5. Send hover descriptions to Tooltip and row menus to Dropdown, instead of forcing them through Popover.
6. Delete the matching `.ant-popover` rules in the same change as each call site, so a half-migrated page is not styled twice.
## Risks
- **Controlled `open` is load-bearing.** Save forms close from inside the popup only when the mutation succeeds. A list keeps one popover open at a time. Dropping `open` and driving everything from `onOpenChange` alone cannot express either. Keep the controlled prop at those sites.
- **Focus moves in.** Click popovers focus the first tabbable child. Typeaheads (`VariablesPopover`, and any filter that must keep the caret) need `initialFocus={false}`.
- **Outside press and anchors.** A press on the anchor looks outside. Call `details.cancel()` or the typeahead closes when the user clicks the input it is anchored to.
- **Dialogs.** Inside a Dialog or Drawer the popup portals into the panel so the focus trap can reach it. Do not set `container={document.body}` there.
- **Z-index.** The shell starts at `--popover-z-index` (50) and lifts above the layer its trigger sits in. A Tooltip inside a row must still paint above the popup. The app ladder today is tooltip 1070, dropdown 1050, popover 1030, modal 1000. Check a truncated row tooltip inside a popover after the first real migration.
- **Scroll locking.** Chart popovers measure the wrong box when a modal has locked scroll. This popover does not lock scroll (`modal` is rejected). Re-check Infrastructure Monitoring charts if a surrounding modal is open.
- **`nativeButton` mismatches.** Base UI logs an error when the flag does not match the element. Every non-button trigger (a badge, a custom row, an input) needs `nativeButton={false}`.
## `--popover-foreground` audit
The shell reads `--popover-background`, `--popover-foreground`, `--popover-border`, and `--popover-shadow`. Those semantic values belong to the token package, not to call sites.
Two legacy shadcn variables predate those hooks:
- `--popover-foreground` is the foreground hook the shell uses. It should stay pointed at the same foreground as the rest of the light and dark themes (`--l1-foreground` today).
- `--popover` (no suffix) is the old background. `--calendar-select-background` still reads it. Do not repoint `--popover` at `--popover-background` or at `surface-3` until the calendar token is audited, or date selection changes color with the popover shell.
Call sites that pass antd's `color` (a gradient string on `AlertPopover`, a raw token on `SeeMore`) do not map to a prop. The shell color comes from the tokens. Content that needs its own background paints it inside `children`.

View File

@@ -53,9 +53,9 @@
"@monaco-editor/react": "^4.7.0",
"@sentry/react": "10.57.0",
"@sentry/vite-plugin": "5.3.0",
"@signozhq/design-tokens": "2.2.1",
"@signozhq/design-tokens": "2.3.0",
"@signozhq/icons": "0.4.0",
"@signozhq/ui": "0.2.0",
"@signozhq/ui": "0.3.0",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

450
frontend/pnpm-lock.yaml generated
View File

@@ -74,14 +74,14 @@ importers:
specifier: 5.3.0
version: 5.3.0
'@signozhq/design-tokens':
specifier: 2.2.1
version: 2.2.1
specifier: 2.3.0
version: 2.3.0
'@signozhq/icons':
specifier: 0.4.0
version: 0.4.0
'@signozhq/ui':
specifier: 0.2.0
version: 0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
specifier: 0.3.0
version: 0.3.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -2909,9 +2909,6 @@ packages:
'@posthog/core@1.6.0':
resolution: {integrity: sha512-Tbh8UACwbb7jFdDC7wwXHtfNzO+4wKh3VbyMHmp2UBe6w1jliJixexTJNfkqdGZm+ht3M10mcKvGGPnoZ2zLBg==}
'@radix-ui/number@1.1.3':
resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
'@radix-ui/primitive@1.1.7':
resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==}
@@ -2928,19 +2925,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.1.15':
resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.5':
resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==}
peerDependencies:
@@ -2972,15 +2956,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-direction@1.1.4':
resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dismissable-layer@1.1.19':
resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==}
peerDependencies:
@@ -3095,71 +3070,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-progress@1.1.16':
resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-radio-group@1.4.7':
resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.19':
resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.3.7':
resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slider@1.4.7':
resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.3.3':
resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==}
peerDependencies:
@@ -3169,19 +3079,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-tabs@1.1.21':
resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-use-callback-ref@1.1.4':
resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==}
peerDependencies:
@@ -3209,15 +3106,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-is-hydrated@0.1.3':
resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.4':
resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==}
peerDependencies:
@@ -3227,15 +3115,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-previous@1.1.4':
resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-rect@1.1.4':
resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==}
peerDependencies:
@@ -3254,19 +3133,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-visually-hidden@1.2.11':
resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/rect@1.1.3':
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
@@ -3585,14 +3451,14 @@ packages:
'@sideway/pinpoint@2.0.0':
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
'@signozhq/design-tokens@2.2.1':
resolution: {integrity: sha512-ulf6lhWUP6qxqsyemQySUMfOIKRvefvZ5rBRv+wlhOpPfYPk3uatPmPkKEhAjISpX8f9g1Gg8wgRZxUCysf20Q==}
'@signozhq/design-tokens@2.3.0':
resolution: {integrity: sha512-gVTPc2uJwBsTF9VMzPghgsnv6eioK8oxzES1CLDxedxpcfwbWltuzxVPwhPuoC54E2vItDVZhR9RFI/JZ50ldw==}
'@signozhq/icons@0.4.0':
resolution: {integrity: sha512-QctNvNPlQlsefFdIVmSy/P/7a1IcQ3IryKP0KPMqtoqew3q89FGUmFa5IfEJ7Z3CHhtG1zaXwBKqP72QkLyA3g==}
'@signozhq/ui@0.2.0':
resolution: {integrity: sha512-j20ldiOkR150ON0BCDhlq6fXGoPN++7w6e4t2HtM2XMM4buJO7aHzPzNKvDRZ2UUf/LGPULPAW8w6sg6gUVHyw==}
'@signozhq/ui@0.3.0':
resolution: {integrity: sha512-Khzr3/YC/QACwBi1zfQwt+h88iyuIu9hm5hkcIkuCTteD9P+r13US/NN4kkbJe8IHyZXCoDkoIpmQxzbrA3/SA==}
peerDependencies:
'@signozhq/icons': 0.3.0
react: ^18.2.0
@@ -4925,12 +4791,6 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -7459,12 +7319,6 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
@@ -8433,8 +8287,8 @@ packages:
'@types/react':
optional: true
react-resizable-panels@4.12.3:
resolution: {integrity: sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==}
react-resizable-panels@4.12.2:
resolution: {integrity: sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
@@ -8934,12 +8788,6 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
sort-asc@0.1.0:
resolution: {integrity: sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==}
engines: {node: '>=0.10.0'}
@@ -9950,7 +9798,7 @@ snapshots:
'@ant-design/cssinjs@1.17.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@emotion/hash': 0.8.0
'@emotion/unitless': 0.7.5
classnames: 2.3.2
@@ -9966,7 +9814,7 @@ snapshots:
dependencies:
'@ant-design/colors': 7.0.0
'@ant-design/icons-svg': 4.3.1
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -9974,7 +9822,7 @@ snapshots:
'@ant-design/react-slick@1.0.2(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
json2mq: 0.2.0
react: 18.2.0
@@ -12498,8 +12346,6 @@ snapshots:
dependencies:
cross-spawn: 7.0.6
'@radix-ui/number@1.1.3': {}
'@radix-ui/primitive@1.1.7': {}
'@radix-ui/react-arrow@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
@@ -12511,18 +12357,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-collection@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-compose-refs@1.1.5(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12558,12 +12392,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-direction@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
@@ -12674,101 +12502,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-progress@1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-radio-group@1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-roving-focus@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-select@2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-focus-guards': 1.1.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-popper': 1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-portal': 1.1.17(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-previous': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
aria-hidden: 1.2.6
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.7.2(@types/react@18.0.26)(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-slider@1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-previous': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-slot@1.3.3(@types/react@18.0.26)(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
@@ -12776,22 +12509,6 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-tabs@1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-use-callback-ref@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12814,24 +12531,12 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-is-hydrated@0.1.3(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-layout-effect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-previous@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-rect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
'@radix-ui/rect': 1.1.3
@@ -12846,20 +12551,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/rect@1.1.3': {}
'@rc-component/color-picker@1.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@ctrl/tinycolor': 3.6.1
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -12879,7 +12575,7 @@ snapshots:
'@rc-component/mutate-observer@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -12895,7 +12591,7 @@ snapshots:
'@rc-component/tour@1.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
@@ -12905,7 +12601,7 @@ snapshots:
'@rc-component/trigger@1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -13150,41 +12846,33 @@ snapshots:
'@sideway/pinpoint@2.0.0': {}
'@signozhq/design-tokens@2.2.1': {}
'@signozhq/design-tokens@2.3.0': {}
'@signozhq/icons@0.4.0': {}
'@signozhq/ui@0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
'@signozhq/ui@0.3.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
dependencies:
'@base-ui/react': 1.7.0(@date-fns/tz@1.4.1)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@chenglou/pretext': 0.0.8
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-icons': 1.3.2(react@18.2.0)
'@radix-ui/react-popover': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-progress': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-radio-group': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-select': 2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slider': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-tabs': 1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@signozhq/icons': 0.4.0
'@tanstack/react-table': 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-virtual': 3.14.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@types/lodash-es': 4.17.12
clsx: 2.1.1
cmdk: 1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
copy-text-to-clipboard: 3.2.2
dayjs: 1.11.23
lodash-es: 4.18.1
motion: 11.18.2(@emotion/is-prop-valid@1.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next-themes: 0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
nuqs: 2.9.6(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-compiler-runtime: 1.0.0(react@18.2.0)
react-day-picker: 9.9.0(react@18.2.0)
react-dom: 18.2.0(react@18.2.0)
react-resizable-panels: 4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
sonner: 2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-resizable-panels: 4.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
transitivePeerDependencies:
- '@date-fns/tz'
- '@emotion/is-prop-valid'
@@ -14434,7 +14122,7 @@ snapshots:
better-xlsx@0.7.6:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
jszip: 3.10.1
kind-of: 6.0.3
@@ -14471,7 +14159,7 @@ snapshots:
broadcast-channel@3.7.0:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
detect-node: 2.1.0
js-sha3: 0.8.0
microseconds: 0.2.0
@@ -14680,18 +14368,6 @@ snapshots:
clsx@2.1.1: {}
cmdk@1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
co@4.6.0: {}
codemirror@6.0.1:
@@ -17240,7 +16916,7 @@ snapshots:
match-sorter@6.3.1:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
remove-accents: 0.4.2
math-intrinsics@1.1.0: {}
@@ -17750,11 +17426,6 @@ snapshots:
neo-async@2.6.2: {}
next-themes@0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
node-addon-api@7.1.1:
optional: true
@@ -18378,7 +18049,7 @@ snapshots:
rc-cascader@3.20.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
array-tree-filter: 2.1.0
classnames: 2.3.2
rc-select: 14.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18389,7 +18060,7 @@ snapshots:
rc-checkbox@3.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18397,7 +18068,7 @@ snapshots:
rc-collapse@3.7.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18406,7 +18077,7 @@ snapshots:
rc-dialog@9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18416,7 +18087,7 @@ snapshots:
rc-drawer@6.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18426,7 +18097,7 @@ snapshots:
rc-dropdown@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18435,7 +18106,7 @@ snapshots:
rc-field-form@1.40.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
async-validator: 4.2.5
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18443,7 +18114,7 @@ snapshots:
rc-image@7.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-dialog: 9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18454,7 +18125,7 @@ snapshots:
rc-input-number@8.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/mini-decimal': 1.0.1
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18464,7 +18135,7 @@ snapshots:
rc-input@1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18472,7 +18143,7 @@ snapshots:
rc-mentions@2.9.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18484,7 +18155,7 @@ snapshots:
rc-menu@9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18495,7 +18166,7 @@ snapshots:
rc-motion@2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18503,7 +18174,7 @@ snapshots:
rc-notification@5.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18512,7 +18183,7 @@ snapshots:
rc-overflow@1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18521,7 +18192,7 @@ snapshots:
rc-pagination@3.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18529,7 +18200,7 @@ snapshots:
rc-picker@3.14.6(date-fns@4.1.0)(dayjs@1.11.20)(moment@2.30.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18542,7 +18213,7 @@ snapshots:
rc-progress@3.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18550,7 +18221,7 @@ snapshots:
rc-rate@2.12.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18558,7 +18229,7 @@ snapshots:
rc-resize-observer@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18567,7 +18238,7 @@ snapshots:
rc-segmented@2.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18588,7 +18259,7 @@ snapshots:
rc-slider@10.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18596,7 +18267,7 @@ snapshots:
rc-steps@6.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18604,7 +18275,7 @@ snapshots:
rc-switch@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18612,7 +18283,7 @@ snapshots:
rc-table@7.35.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/context': 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18623,7 +18294,7 @@ snapshots:
rc-tabs@12.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-dropdown: 4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-menu: 9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18635,7 +18306,7 @@ snapshots:
rc-textarea@1.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18645,7 +18316,7 @@ snapshots:
rc-tooltip@6.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
react: 18.2.0
@@ -18653,7 +18324,7 @@ snapshots:
rc-tree-select@5.15.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-select: 14.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-tree: 5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18663,7 +18334,7 @@ snapshots:
rc-tree@5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18673,7 +18344,7 @@ snapshots:
rc-upload@4.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18681,14 +18352,14 @@ snapshots:
rc-util@5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 18.3.1
rc-virtual-list@3.11.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18914,7 +18585,7 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
react-resizable-panels@4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
react-resizable-panels@4.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -18965,7 +18636,7 @@ snapshots:
react-router@5.3.4(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
@@ -19505,11 +19176,6 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
sonner@2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
sort-asc@0.1.0: {}
sort-desc@0.1.1: {}

View File

@@ -1,6 +1,9 @@
.breadcrumb {
padding: var(--spacing-4) 16px var(--spacing-2);
// The antd breadcrumb provides the item spacing.
--button-padding: 0;
ol {
align-items: center;
}
@@ -13,8 +16,3 @@
padding: 0px;
}
}
.divider {
--divider-color: var(--l1-border);
--divider-margin: 10px 0 16px 0;
}

View File

@@ -28,7 +28,7 @@ function AlertBreadcrumb({
items={breadcrumbItems}
data-testid={testId}
/>
{showDivider && <Divider className={styles.divider} />}
{showDivider && <Divider spacing="10px 16px" />}
</>
);
}

View File

@@ -1,6 +1,6 @@
import { Copy } from '@signozhq/icons';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import cx from 'classnames';
import { useLayoutEffect, useRef, useState } from 'react';

View File

@@ -1,6 +1,6 @@
import { Copy } from '@signozhq/icons';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import { useCopyToClipboard } from 'react-use';

View File

@@ -17,8 +17,3 @@
letter-spacing: -0.01em;
color: var(--l1-foreground);
}
.divider {
--divider-color: var(--l1-border);
--divider-margin: 10px 0 16px 0;
}

View File

@@ -44,7 +44,7 @@ function BackHeader({
</Button>
<div className={styles.title}>{title}</div>
</div>
<Divider className={styles.divider} />
<Divider spacing="10px 16px" />
</>
);
}

View File

@@ -56,22 +56,15 @@ function ProgressRender(item: string | number): JSX.Element {
const percent = Number(Number(item).toFixed(1));
return (
<div className="progress-container">
<Progress
percent={percent}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const cpuPercent = percent;
if (cpuPercent >= 90) {
return Color.BG_SAKURA_500;
<div className="progress-bar">
<Progress
percent={percent}
showInfo
color={
percent >= 90 ? 'highlight-danger' : percent >= 60 ? 'warning' : 'success'
}
if (cpuPercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
/>
</div>
</div>
);
}

View File

@@ -72,7 +72,7 @@ export default function CeleryTaskDetail({
endTime ? `- ${formatTimestamp(endTime)}` : ''
}`}
</Typography.Text>
<Divider type="vertical" />
<Divider orientation="vertical" />
<Typography.Text className="subtitle">{taskData.value}</Typography.Text>
</div>
</div>

View File

@@ -1,36 +1,3 @@
.create-sa-modal {
max-width: 530px;
background: var(--popover);
border: 1px solid var(--secondary);
border-radius: 4px;
box-shadow: 0 4px 9px 0 rgba(0, 0, 0, 0.04);
[data-slot='dialog-header'] {
padding: var(--padding-4);
border-bottom: 1px solid var(--secondary);
flex-shrink: 0;
background: transparent;
margin: 0;
}
[data-slot='dialog-title'] {
font-size: var(--label-base-400-font-size);
font-weight: var(--label-base-400-font-weight);
line-height: var(--label-base-400-line-height);
letter-spacing: -0.065px;
color: var(--l1-foreground);
margin: 0;
}
[data-slot='dialog-description'] {
padding: 0;
.create-sa-modal__content {
padding: var(--padding-4);
}
}
}
.create-sa-form {
display: flex;
flex-direction: column;
@@ -50,25 +17,6 @@
}
}
&__input {
height: 32px;
color: var(--l1-foreground);
background-color: var(--l2-background);
border-color: var(--l1-border);
font-size: var(--paragraph-base-400-font-size);
border-radius: 2px;
width: 100%;
&::placeholder {
color: var(--l3-foreground);
}
&:focus {
border-color: var(--primary);
box-shadow: none;
}
}
&__error {
font-size: var(--paragraph-small-400-font-size);
color: var(--destructive);
@@ -83,16 +31,3 @@
line-height: var(--paragraph-small-400-line-height);
}
}
.create-sa-modal__footer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
padding: 0 var(--padding-4);
height: 56px;
min-height: 56px;
border-top: 1px solid var(--secondary);
gap: var(--spacing-4);
flex-shrink: 0;
}

View File

@@ -7,7 +7,7 @@ import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardCon
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,
@@ -116,11 +116,10 @@ function CreateServiceAccountModal(): JSX.Element {
<Input
id="sa-name"
placeholder="Enter a name"
className="create-sa-form__input"
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
data-testid="create-sa-name-input"
testId="create-sa-name-input"
/>
)}
/>

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import {
setupAuthzAdmin,
setupAuthzDenyAll,
@@ -10,9 +10,9 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);
@@ -58,7 +58,7 @@ describe('CreateServiceAccountModal', () => {
// The form only renders once the create check resolves, and the name field
// registers its `required` rule on mount, so the empty-form invalid state
// settles a tick later.
await screen.findByTestId('create-sa-name-input');
await screen.findByTestId('create-sa-name-input-field');
await waitFor(() =>
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
@@ -72,7 +72,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const submitBtn = await screen.findByTestId('create-sa-submit-btn');
await user.type(nameInput, 'test');
@@ -90,7 +90,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -128,7 +128,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId<HTMLInputElement>(
'create-sa-name-input',
'create-sa-name-input-field',
);
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -160,7 +160,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Dupe Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -209,7 +209,9 @@ describe('CreateServiceAccountModal', () => {
screen.findByText(/is not authorized to perform/i),
).resolves.toBeInTheDocument();
expect(screen.queryByTestId('create-sa-name-input')).not.toBeInTheDocument();
expect(
screen.queryByTestId('create-sa-name-input-field'),
).not.toBeInTheDocument();
expect(
screen.getByTestId('create-service-account-modal'),
).toBeInTheDocument();
@@ -244,7 +246,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Bot');
await user.clear(nameInput);

View File

@@ -34,34 +34,10 @@ $item-spacing: 8px;
@include flex-center;
gap: 6px;
width: -webkit-fill-available;
}
&__input {
@include text-style-base;
width: 100%;
background: transparent;
border: none;
border-radius: 0;
box-shadow: none;
outline: none;
height: auto;
color: var(--l1-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
padding: 0;
&:focus,
&:focus-visible,
&:hover {
border: none;
box-shadow: none;
outline: none;
}
&::placeholder {
color: var(--l2-foreground);
}
// The search row draws its own padding; the inline Input hugs the text.
--input-height: auto;
--input-padding: 0;
}
&__esc-key {

View File

@@ -65,7 +65,8 @@ function SearchBar({
/>
<Input
type="text"
className="timezone-picker__input"
variant="unstyled"
noFocusRing
placeholder="Search timezones..."
value={value}
onChange={(e): void => onChange(e.target.value)}

View File

@@ -1,7 +1,4 @@
.details-header {
// ghost + secondary missing hover bg token in @signozhq/button
--button-ghost-hover-background: var(--l3-background);
box-sizing: border-box;
display: flex;
align-items: center;

View File

@@ -5,7 +5,7 @@ import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
@@ -409,7 +409,8 @@ function EditMemberDrawer({
setShowResetLinkDialog(true);
onClose();
} else {
toast.error('Failed to generate password reset link', {
toast.danger('Failed to generate password reset link', {
action: { label: 'Dismiss' },
position: 'top-right',
});
}
@@ -438,7 +439,7 @@ function EditMemberDrawer({
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy link');
toast.danger('Failed to copy link', { action: { label: 'Dismiss' } });
}
}, [copyState.error]);
@@ -471,20 +472,19 @@ function EditMemberDrawer({
<label className="edit-member-drawer__label" htmlFor="member-name">
Name
</label>
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
<Input
id="member-name"
value={localDisplayName}
onChange={(e): void => {
setLocalDisplayName(e.target.value);
setSaveErrors((prev) =>
prev.filter((err) => err.context !== 'Name update'),
);
}}
placeholder="Enter name"
disabled={isRootUser || isDeleted}
/>
</Tooltip>
<Input
id="member-name"
value={localDisplayName}
onChange={(e): void => {
setLocalDisplayName(e.target.value);
setSaveErrors((prev) =>
prev.filter((err) => err.context !== 'Name update'),
);
}}
placeholder="Enter name"
disabled={isRootUser || isDeleted}
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : undefined}
/>
</div>
<div className="edit-member-drawer__field">

View File

@@ -1,5 +1,5 @@
import type { ReactNode } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
useCreateResetPasswordToken,
@@ -82,11 +82,11 @@ jest.mock('@signozhq/ui/dialog', () => ({
),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
success: jest.fn(),
error: jest.fn(),
danger: jest.fn(),
},
}));

View File

@@ -3,20 +3,7 @@
width: fit-content;
display: flex;
align-items: center;
gap: 4px;
border-radius: 20px;
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
padding-left: 3px;
padding-right: 8px;
cursor: pointer;
span {
color: var(--danger-background);
font-size: 10px;
font-weight: 500;
line-height: 20px; /* 200% */
letter-spacing: 0.4px;
text-transform: uppercase;
}
}
&__wrap {
background: linear-gradient(

View File

@@ -53,8 +53,13 @@ function ErrorModal({
onClick={(): void => setVisible(true)}
onKeyDown={undefined}
>
<Badge variant="solid" color="danger">
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
<Badge
variant="solid"
color="danger"
textTransform="uppercase"
prefix={<CircleAlert size={14} />}
>
error
</Badge>
</span>
) : (

View File

@@ -1,6 +1,6 @@
.export-menu-popover {
width: 240px;
padding: 0 12px 12px 12px;
// The popover shell carries no padding of its own, so the content owns it.
padding: var(--spacing-8);
.title {
display: flex;

View File

@@ -1,6 +1,6 @@
import { Download } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { Popover, usePopoverHandle } from '@signozhq/ui/popover';
import { RadioGroup } from '@signozhq/ui/radio-group';
import { Tooltip } from '@signozhq/ui/tooltip';
import { Typography } from '@signozhq/ui/typography';
@@ -36,6 +36,10 @@ export default function ExportMenu({
}: ExportMenuProps): JSX.Element {
const [exportFormat, setExportFormat] = useState<string>(ExportFormat.Csv);
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
// Driven by a handle rather than `trigger`: the button carries a Tooltip, and a Tooltip
// routes every prop it does not recognise to its own popup, so a popover's press handlers
// would never reach the button underneath.
const menu = usePopoverHandle(setIsPopoverOpen);
const { isExporting, handleExport: handleClientExport } = useClientExport({
data,
@@ -45,53 +49,62 @@ export default function ExportMenu({
});
const handleExport = useCallback((): void => {
setIsPopoverOpen(false);
menu.close();
handleClientExport({ format: exportFormat as ExportFormat });
}, [exportFormat, handleClientExport]);
}, [menu, exportFormat, handleClientExport]);
return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<>
<Tooltip title="Download">
<PopoverTrigger asChild>
<Button
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Download"
testId={`export-menu-${dataSource}`}
loading={isExporting}
>
<Download size={14} />
</Button>
</PopoverTrigger>
</Tooltip>
<PopoverContent align="end" className="export-menu-popover">
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: ExportFormat.Csv, label: 'csv' },
{ value: ExportFormat.Jsonl, label: 'jsonl' },
]}
/>
</div>
<Button
size="md"
variant="solid"
color="primary"
width="100%"
onClick={handleExport}
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Download"
aria-haspopup="dialog"
aria-expanded={isPopoverOpen}
testId={`export-menu-${dataSource}`}
loading={isExporting}
prefix={<Download size={16} />}
onClick={(event): void => menu.showAt(event.currentTarget)}
>
Export
<Download size={14} />
</Button>
</PopoverContent>
</Popover>
</Tooltip>
<Popover
side="bottom"
align="end"
width={240}
handle={menu}
aria-label="Export format"
>
<div className="export-menu-popover">
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: ExportFormat.Csv, label: 'csv' },
{ value: ExportFormat.Jsonl, label: 'jsonl' },
]}
/>
</div>
<Button
size="md"
variant="solid"
color="primary"
width="100%"
onClick={handleExport}
loading={isExporting}
prefix={<Download size={16} />}
>
Export
</Button>
</div>
</Popover>
</>
);
}

View File

@@ -32,12 +32,15 @@
}
}
// Full-bleed search bar: only top/bottom borders, square corners.
// Per-side widths cannot be expressed via --input-border-width.
.searchInput {
background-color: var(--l1-background);
height: 40px;
border-radius: 0;
border-left: none;
border-right: none;
--input-border-radius: 0;
[data-slot='input'] {
border-left: none;
border-right: none;
}
}
.section {

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -140,10 +140,10 @@ function FieldsSelectorContent({
<X className={styles.closeIcon} size={16} onClick={onClose} />
</div>
<section>
<section className={styles.searchInput}>
<Input
className={styles.searchInput}
type="text"
size="large"
value={inputValue}
placeholder="Search for a field..."
onChange={handleInputChange}

View File

@@ -13,9 +13,9 @@ jest.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
})),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
// FloatingPanel is a react-rnd/portal shell — presentation only. Render its

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useAppLocation } from 'lib/router/useAppLocation';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Button, Input } from 'antd';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
@@ -39,7 +39,8 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
})
.catch(() => {
console.error(`Failed to submit ${entityName}`);
toast.error(`Failed to submit ${entityName}`, {
toast.danger(`Failed to submit ${entityName}`, {
action: { label: 'Dismiss' },
position: 'top-right',
});
})

View File

@@ -1,6 +1,6 @@
// Mock dependencies before imports
import { useAppLocation } from 'lib/router/useAppLocation';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { logEventMock } from '__tests__/logEventMock';
@@ -13,11 +13,11 @@ jest.mock('lib/router/useAppLocation', () => ({
useAppLocation: jest.fn(),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
success: jest.fn(),
error: jest.fn(),
danger: jest.fn(),
},
}));
@@ -49,7 +49,7 @@ describe('FeedbackModal', () => {
isCloudUser: false,
});
mockToast.success.mockClear();
mockToast.error.mockClear();
mockToast.danger.mockClear();
});
it('should render feedback modal with all tabs', () => {

View File

@@ -4,6 +4,9 @@
border-radius: 2px 0px 0px 2px;
// The label chip and the Input share the --input-with-label-* theming
// contract (set by e.g. FormAlertRules, WidgetFullView) so the joined
// group recolors as one.
.label {
// Typography.Text takes its display from this token; at its `inline` default
// the label blockifies as a flex item and the text rides the top of the row.
@@ -31,66 +34,40 @@
display: flex;
justify-content: flex-start;
align-items: center;
font-weight: var(--font-weight-light);
}
.input {
flex: 1;
min-width: 150px;
font-family: 'Space Mono', monospace !important;
--input-border-radius: 0px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
color: var(--input-with-label-color, var(--l2-foreground));
font-size: 12px !important;
line-height: 25px;
position: relative;
// Square corners: the input sits mid-group between label and close button.
--input-border-radius: 0px;
--input-border: var(--input-with-label-border-color, var(--l2-border));
--input-background: var(
--input-with-label-background-color,
var(--l2-background)
);
--input-foreground: var(--input-with-label-color, var(--l2-foreground));
&:hover,
&:focus {
z-index: 1;
}
.ant-select-selector {
position: relative;
border-radius: inherit;
}
.ant-select:hover .ant-select-selector,
.ant-select-focused .ant-select-selector {
&:focus-within {
z-index: 1;
}
&.input__has-label-after {
margin-left: -1px;
.ant-select-selector {
margin-left: -1px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
&.input__has-close-button {
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
~ .close-btn {
margin-left: -1px;
}
}
&::placeholder {
color: var(--input-with-label-color, var(--l3-foreground)) !important;
font-size: 12px !important;
}
&[type='number']::-webkit-inner-spin-button,
&[type='number']::-webkit-outer-spin-button {
// The Input has no token for the native number spinners.
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@@ -114,16 +91,7 @@
&.labelAfter {
.input {
border-radius: 2px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
--input-border-radius: 2px 0 0 2px;
}
.label {

View File

@@ -50,19 +50,23 @@ function InputWithLabel({
})}
>
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
<div
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
name={label.toLowerCase()}
data-testid={`input-${label}`}
/>
>
<Input
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
disabledTooltip={undefined}
name={label.toLowerCase()}
testId={`input-${label}`}
/>
</div>
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (
<Button

View File

@@ -55,26 +55,12 @@
flex: 1 1 0;
min-width: 0;
// Matches the antd RolesSelect in the same row, which is forced to the
// same field background below.
--input-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-hover-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-focus-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-disabled-background: var(
--invite-members-field-background,
var(--l2-background)
);
input::placeholder {
color: var(--l3-foreground);
}
}
.cellRole {

View File

@@ -1,4 +1,4 @@
import { CircleAlert, Plus, Trash2 } from '@signozhq/icons';
import { CircleAlert, Plus, SolidCheckCircle2, Trash2 } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import { Input } from '@signozhq/ui/input';
@@ -97,7 +97,7 @@ function InviteMembers({
onChange={(e): void => updateEmail(row.id, e.target.value)}
name={`invite-email-${row.id}`}
autoComplete="email"
data-testid={`invite-email-${row.id}`}
testId={`invite-email-${row.id}`}
/>
{emailValidity[row.id] === false && row.email.trim() !== '' && (
<Typography.Text size="small" className={styles.errorText}>
@@ -152,61 +152,62 @@ function InviteMembers({
</div>
{hasValidationErrors && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
</div>
)}
{hasResults && hasFailures && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully.
</Typography.Text>
)}
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully.
{failedResults.length} invite(s) failed:
</Typography.Text>
)}
<Typography.Text size="small">
{failedResults.length} invite(s) failed:
</Typography.Text>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography.Text size="small">
{result.email}: {result.error}
</Typography.Text>
</li>
))}
</ul>
</div>
</Callout>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography.Text size="small">
{result.email}: {result.error}
</Typography.Text>
</li>
))}
</ul>
</div>
</Callout>
</div>
)}
{hasResults && !hasFailures && hasSuccesses && (
<Callout
type="success"
size="small"
showIcon
className={styles.callout}
data-testid="invite-success"
>
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully!
</Typography.Text>
</Callout>
<div className={styles.callout}>
<Callout
color="success"
size="sm"
icon={<SolidCheckCircle2 />}
testId="invite-success"
>
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully!
</Typography.Text>
</Callout>
</div>
)}
{renderFooter?.({

View File

@@ -12,11 +12,6 @@
gap: 8px;
}
.divider {
height: 16px;
margin: 0;
}
.timestamp {
font-family: 'Geist Mono', monospace;
font-size: var(--font-size-sm);

View File

@@ -5,7 +5,7 @@ import { Typography } from '@signozhq/ui/typography';
import { Tooltip } from '@signozhq/ui/tooltip';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import {
ChevronDown,
@@ -76,7 +76,7 @@ function LogDetailsHeader({
return (
<div className={styles.header} data-log-detail-ignore="true">
<div className={styles.leftSection}>
<Divider type="vertical" className={styles.divider} />
<Divider orientation="vertical" height={16} spacing={0} />
<Typography.Text
className={styles.timestamp}
data-testid="log-details-header-timestamp"

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { LOCALSTORAGE } from 'constants/localStorage';
import { render, screen, userEvent } from 'tests/test-utils';
import { ILog } from 'types/api/logs/log';
@@ -7,8 +7,8 @@ import LogDetail from '..';
import { VIEW_TYPES } from '../constants';
import { LogDetailProps } from '../LogDetail.interfaces';
jest.mock('@signozhq/ui/sonner', () => ({
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
toast: { success: jest.fn(), danger: jest.fn() },
}));
// DataViewer pulls in react-json-tree (ESM) + Monaco; mock it (as trace's tests

View File

@@ -18,71 +18,51 @@
right: -2px;
width: 240px;
[data-format-menu='back'],
[data-format-menu='font-option'],
[data-format-menu='font-size'],
[data-format-menu='edit-columns'] {
--button-justify-content: flex-start;
}
[data-format-menu='font-option'],
[data-format-menu='font-size'],
[data-format-menu='edit-columns'] {
--button-label-min-width: 100%;
--button-font-size: 13px;
--button-variant-link-font-weight: 400;
--button-size-line-height: 20px;
}
[data-format-menu='back'] {
text-transform: uppercase;
}
[data-format-menu='font-option'],
[data-format-menu='font-size'] {
text-transform: capitalize;
}
.menu-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.font-size-dropdown {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 12px;
.back-btn {
display: flex;
align-items: center;
gap: 4px;
padding: 12px;
border: none !important;
box-shadow: none !important;
.icon {
flex-shrink: 0;
}
.text {
color: var(--l1-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: 0.14px;
text-transform: uppercase;
}
}
.back-btn:hover {
background-color: unset !important;
.horizontal-line {
margin-inline: -12px;
}
.content {
display: flex;
flex-direction: column;
.option-btn {
display: flex;
align-items: center;
padding: 12px;
border: none !important;
box-shadow: none !important;
justify-content: space-between;
.icon {
flex-shrink: 0;
}
.text {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal; /* 142.857% */
letter-spacing: 0.14px;
text-transform: capitalize;
}
.text:hover {
color: var(--l2-foreground);
}
}
.option-btn:hover {
background-color: unset !important;
}
}
}
@@ -102,31 +82,6 @@
letter-spacing: 0.88px;
text-transform: uppercase;
}
.value {
display: flex;
height: 20px;
padding: 4px 0px;
justify-content: space-between;
align-items: center;
border: none !important;
.font-value {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
text-transform: capitalize;
}
.icon {
}
}
.value:hover {
background-color: unset !important;
}
}
.menu-container {
@@ -199,49 +154,23 @@
}
}
// Stepper group: the Input sits seamlessly between the -/+
// periscope-btns (26px tall, l1-border).
.max-lines-per-row-input {
display: flex;
.ant-input-number-handler-wrap {
display: none;
}
--input-height: 26px;
--input-border-radius: 0;
--input-border: var(--l1-border);
--input-border-width: 1px 0;
.ant-input-number {
[data-testid='max-lines-per-row'] {
min-width: 36px;
width: auto;
border-right: none;
border-left: none;
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
text-align: center;
height: 26px;
border-radius: 0;
&:active,
&:focus {
border: none;
box-shadow: none;
}
}
.ant-input-number-focused {
box-shadow: none !important;
}
.ant-input-number-input-wrap {
// No Input token for text alignment.
input {
text-align: center;
font-size: 13px;
&:active,
&:focus {
border: none;
}
}
&:active,
&:focus {
border: none;
}
}
@@ -258,30 +187,6 @@
.edit-columns-container {
padding: 12px;
.edit-columns-btn {
display: flex;
width: 100%;
height: 20px;
padding: 4px 0px;
justify-content: space-between;
align-items: center;
border: none !important;
.edit-columns-text {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
}
}
.edit-columns-btn:hover {
background-color: unset !important;
}
}
.add-new-column-container {

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { Button, InputNumber, Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { Popover, Tooltip } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import cx from 'classnames';
import {
LogViewMode,
@@ -18,6 +19,10 @@ import {
import './LogsFormatOptionsMenu.styles.scss';
const MIN_LINES_PER_ROW = 1;
const MAX_LINES_PER_ROW = 10;
const FONT_SIZES = [FontSize.SMALL, FontSize.MEDIUM, FontSize.LARGE];
interface LogsFormatOptionsMenuProps {
items: any;
selectedOptionFormat: any;
@@ -62,25 +67,11 @@ function OptionsMenu({
onChange(key);
};
const incrementMaxLinesPerRow = (): void => {
if (maxLinesPerRow < 10) {
setMaxLinesPerRow(maxLinesPerRow + 1);
}
};
const decrementMaxLinesPerRow = (): void => {
if (maxLinesPerRow > 1) {
setMaxLinesPerRow(maxLinesPerRow - 1);
}
};
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
if (
maxLinesPerRow &&
Number.isInteger(maxLinesNumber) &&
maxLinesPerRow > 1
) {
setMaxLinesPerRow(maxLinesPerRow);
const handleLinesPerRowChange = (value: number | null): void => {
if (value !== null) {
setMaxLinesPerRow(
Math.min(Math.max(value, MIN_LINES_PER_ROW), MAX_LINES_PER_ROW),
);
}
};
@@ -111,51 +102,36 @@ function OptionsMenu({
{isFontSizeOptionsOpen ? (
<div className="font-size-dropdown">
<Button
variant="link"
color="secondary"
size="sm"
width="100%"
data-format-menu="back"
testId="font-size-back"
prefix={<ChevronLeft size={14} />}
onClick={(): void => setIsFontSizeOptionsOpen(false)}
className="back-btn"
type="text"
>
<ChevronLeft size={14} className="icon" />
<Typography.Text className="text">Select font size</Typography.Text>
Select font size
</Button>
<div className="horizontal-line" />
<div className="content">
<Button
onClick={(): void => {
setFontSizeValue(FontSize.SMALL);
}}
className="option-btn"
type="text"
>
<Typography.Text className="text">{FontSize.SMALL}</Typography.Text>
{fontSizeValue === FontSize.SMALL && (
<Check size={14} className="icon" />
)}
</Button>
<Button
onClick={(): void => {
setFontSizeValue(FontSize.MEDIUM);
}}
className="option-btn"
type="text"
>
<Typography.Text className="text">{FontSize.MEDIUM}</Typography.Text>
{fontSizeValue === FontSize.MEDIUM && (
<Check size={14} className="icon" />
)}
</Button>
<Button
onClick={(): void => {
setFontSizeValue(FontSize.LARGE);
}}
className="option-btn"
type="text"
>
<Typography.Text className="text">{FontSize.LARGE}</Typography.Text>
{fontSizeValue === FontSize.LARGE && (
<Check size={14} className="icon" />
)}
</Button>
{FONT_SIZES.map((size) => (
<Button
key={size}
variant="link"
color="secondary"
size="sm"
width="100%"
data-format-menu="font-option"
testId={`font-size-option-${size}`}
onClick={(): void => setFontSizeValue(size)}
>
<span className="menu-row">
{size}
{fontSizeValue === size && <Check size={14} />}
</span>
</Button>
))}
</div>
</div>
) : (
@@ -163,14 +139,20 @@ function OptionsMenu({
<div className="font-size-container">
<div className="title">Font Size</div>
<Button
className="value"
type="text"
variant="link"
color="secondary"
size="sm"
width="100%"
data-format-menu="font-size"
testId="font-size-select"
onClick={(): void => {
setIsFontSizeOptionsOpen(true);
}}
>
<Typography.Text className="font-value">{fontSizeValue}</Typography.Text>
<ChevronRight size={14} className="icon" />
<span className="menu-row">
{fontSizeValue}
<ChevronRight size={14} />
</span>
</Button>
</div>
<div className="horizontal-line" />
@@ -204,24 +186,29 @@ function OptionsMenu({
<button
type="button"
className="periscope-btn"
onClick={decrementMaxLinesPerRow}
aria-label="Fewer lines per row"
onClick={(): void => handleLinesPerRowChange(maxLinesPerRow - 1)}
>
{' '}
<Minus size={12} />{' '}
<Minus size={12} />
</button>
<InputNumber
min={1}
max={10}
<Input.Number
min={MIN_LINES_PER_ROW}
max={MAX_LINES_PER_ROW}
format={{ maximumFractionDigits: 0 }}
value={maxLinesPerRow}
onChange={handleLinesPerRowChange}
controls={false}
noFocusRing
aria-label="Max lines per row"
testId="max-lines-per-row"
/>
<button
type="button"
className="periscope-btn"
onClick={incrementMaxLinesPerRow}
aria-label="More lines per row"
onClick={(): void => handleLinesPerRowChange(maxLinesPerRow + 1)}
>
{' '}
<Plus size={12} />{' '}
<Plus size={12} />
</button>
</div>
</div>
@@ -233,15 +220,18 @@ function OptionsMenu({
<div className="horizontal-line" />
<div className="edit-columns-container">
<Button
className="edit-columns-btn"
type="text"
variant="link"
color="secondary"
size="sm"
width="100%"
data-format-menu="edit-columns"
onClick={handleEditColumns}
data-testid="periscope-btn-edit-columns"
testId="periscope-btn-edit-columns"
>
<Typography.Text className="edit-columns-text">
<span className="menu-row">
Edit columns
</Typography.Text>
<ChevronRight size={14} className="icon" />
<ChevronRight size={14} />
</span>
</Button>
</div>
</>
@@ -280,10 +270,15 @@ function LogsFormatOptionsMenu({
>
<Tooltip title="Options">
<Button
className="periscope-btn ghost"
icon={<SlidersVertical size="md" />}
data-testid="periscope-btn-format-options"
/>
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Options"
testId="periscope-btn-format-options"
>
<SlidersVertical size={14} />
</Button>
</Tooltip>
</Popover>
);

View File

@@ -1,5 +1,11 @@
import { FontSize } from 'container/OptionsMenu/types';
import { fireEvent, render, waitFor } from 'tests/test-utils';
import {
fireEvent,
render,
screen,
userEvent,
waitFor,
} from 'tests/test-utils';
import LogsFormatOptionsMenu from '../LogsFormatOptionsMenu';
@@ -107,18 +113,12 @@ describe('LogsFormatOptionsMenu (unit)', () => {
});
it('increments max-lines and calls onChange', async () => {
const { maxLinesOnChange } = setup();
const { maxLinesOnChange, getByTestId } = setup();
// Increment max lines
const input = document.querySelector(
'.max-lines-per-row-input input',
) as HTMLInputElement;
const input = getByTestId('max-lines-per-row-field') as HTMLInputElement;
const initial = Number(input.value);
const buttons = document.querySelectorAll(
'.max-lines-per-row-input .periscope-btn',
);
const incrementBtn = buttons[1] as HTMLElement;
fireEvent.click(incrementBtn);
fireEvent.click(screen.getByRole('button', { name: 'More lines per row' }));
await waitFor(() => {
expect(Number(input.value)).toBe(initial + 1);
@@ -128,28 +128,47 @@ describe('LogsFormatOptionsMenu (unit)', () => {
});
});
it('clamps a typed max-lines value to 10', async () => {
const { maxLinesOnChange, getByTestId } = setup();
const input = getByTestId('max-lines-per-row-field') as HTMLInputElement;
const user = userEvent.setup();
await user.clear(input);
await user.type(input, '15');
await user.tab();
await waitFor(() => expect(input.value).toBe('10'));
expect(maxLinesOnChange).toHaveBeenLastCalledWith(10);
});
it('stops max-lines at 10', async () => {
const { getByTestId } = setup();
const input = getByTestId('max-lines-per-row-field') as HTMLInputElement;
const increment = screen.getByRole('button', { name: 'More lines per row' });
const user = userEvent.setup();
for (let i = 0; i < 12; i++) {
await user.click(increment);
}
await waitFor(() => expect(input.value).toBe('10'));
});
it('changes font size to MEDIUM and calls onChange', async () => {
const { fontSizeOnChange } = setup();
const { fontSizeOnChange, getByTestId } = setup();
// Open font dropdown
const fontButton = document.querySelector(
'.font-size-container .value',
) as HTMLElement;
fireEvent.click(fontButton);
fireEvent.click(getByTestId('font-size-select'));
// Choose MEDIUM
const optionButtons = Array.from(
document.querySelectorAll('.font-size-dropdown .option-btn'),
);
const mediumBtn = optionButtons[1] as HTMLElement;
fireEvent.click(mediumBtn);
fireEvent.click(getByTestId(`font-size-option-${FontSize.MEDIUM}`));
await waitFor(() => {
expect(
document.querySelectorAll('.font-size-dropdown .option-btn .icon'),
).toHaveLength(1);
expect(
(optionButtons[1] as Element).querySelector('.icon'),
getByTestId(`font-size-option-${FontSize.MEDIUM}`).querySelector('svg'),
).toBeInTheDocument();
expect(
getByTestId(`font-size-option-${FontSize.SMALL}`).querySelector('svg'),
).not.toBeInTheDocument();
});
await waitFor(() => {
expect(fontSizeOnChange).toHaveBeenCalledWith(FontSize.MEDIUM);

View File

@@ -184,8 +184,8 @@
}
.helpContent {
width: 280px;
max-height: 320px;
// The popover shell carries no padding of its own, so the content owns it.
padding: var(--spacing-8);
overflow-y: auto;
@include custom-scrollbar;
}

View File

@@ -1,6 +1,6 @@
import { CircleHelp } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { Popover } from '@signozhq/ui/popover';
import { Typography } from '@signozhq/ui/typography';
import { MARKDOWN_HELP_ITEMS } from './constants';
@@ -9,8 +9,14 @@ import styles from './MarkdownEditor.module.scss';
function MarkdownHelp(): JSX.Element {
return (
<Popover>
<PopoverTrigger asChild>
<Popover
nativeButton
side="bottom"
align="end"
width={280}
maxHeight={320}
aria-label="Markdown syntax"
trigger={
<Button
type="button"
variant="ghost"
@@ -22,8 +28,9 @@ function MarkdownHelp(): JSX.Element {
>
<CircleHelp size={14} />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className={styles.helpContent}>
}
>
<div className={styles.helpContent}>
<Typography.Text className={styles.helpTitle}>
Markdown syntax
</Typography.Text>
@@ -37,7 +44,7 @@ function MarkdownHelp(): JSX.Element {
</div>
))}
</dl>
</PopoverContent>
</div>
</Popover>
);
}

View File

@@ -1,13 +0,0 @@
.banner {
height: var(--spacing-20);
a {
color: var(--callout-warning-title);
text-decoration: underline;
&:hover {
color: var(--callout-warning-title);
opacity: 0.8;
}
}
}

View File

@@ -1,25 +1,22 @@
import { PersistedAnnouncementBanner } from '@signozhq/ui/announcement-banner';
import styles from './NoAuthBanner.module.scss';
import { AlertStrip } from '@signozhq/ui/alert-strip';
export function NoAuthBanner(): JSX.Element {
return (
<PersistedAnnouncementBanner
type="warning"
<AlertStrip.CloseablePersisted
color="warning"
side="top"
storageKey="no-auth-banner-v1"
testId="no-auth-banner"
className={styles.banner}
>
Impersonation mode: authentication is disabled. Anyone with access to this
instance has admin privileges.{' '}
<a
<AlertStrip.Link
href="https://signoz.io/docs/manage/administrator-guide/configuration/impersonation-mode/"
target="_blank"
rel="noreferrer"
>
Learn more
</a>
</PersistedAnnouncementBanner>
</AlertStrip.Link>
</AlertStrip.CloseablePersisted>
);
}

View File

@@ -19,6 +19,6 @@ describe('NoAuthBanner', () => {
render(<NoAuthBanner />);
const link = screen.getByRole('link', { name: /learn more/i });
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noreferrer');
expect(link).toHaveAttribute('rel', expect.stringContaining('noreferrer'));
});
});

View File

@@ -102,12 +102,10 @@
letter-spacing: -0.07px;
}
/** Surface and metrics come from the `--input-*` tokens above; only the family has none. */
.numberInput {
width: 104px;
font-family: 'Geist Mono';
letter-spacing: -0.07px;
/** Surface and metrics come from the `--input-*` tokens; the family and tracking are set here. */
.numberField {
--input-font-family: 'Geist Mono';
--input-letter-spacing: -0.07px;
}
/** Unfilled: a pill on the same row as the toggles above reads as another control,

View File

@@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from 'react';
import { Tooltip } from 'antd';
import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import { InputNumber } from '@signozhq/ui/input-number';
import { Input } from '@signozhq/ui/input';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { ChevronUp, Info } from '@signozhq/icons';
import {
@@ -111,19 +111,17 @@ function BucketOptions({
);
const handleMaxValueChange = useCallback(
(value: number | string | null): void => {
const next = value === null || value === '' ? null : Number(value);
setMaxValue(next);
emitLinear(next, numBuckets);
(value: number | null): void => {
setMaxValue(value);
emitLinear(value, numBuckets);
},
[emitLinear, numBuckets],
);
const handleNumBucketsChange = useCallback(
(value: number | string | null): void => {
const next = value === null || value === '' ? null : Number(value);
setNumBuckets(next);
emitLinear(maxValue, next);
(value: number | null): void => {
setNumBuckets(value);
emitLinear(maxValue, value);
},
[emitLinear, maxValue],
);
@@ -207,29 +205,29 @@ function BucketOptions({
{kind === Querybuildertypesv5BucketsKindDTO.linear && (
<>
<div className={styles.field}>
<div className={cx(styles.field, styles.numberField)}>
<span className={styles.label}>Max value</span>
<InputNumber
className={styles.numberInput}
<Input.Number
min={0}
value={maxValue}
onChange={handleMaxValueChange}
placeholder="Required"
status={maxValue !== null && maxValue <= 0 ? 'error' : undefined}
data-testid="bucket-options-max-value"
status={maxValue !== null && maxValue <= 0 ? 'danger' : undefined}
width={104}
testId="bucket-options-max-value"
/>
</div>
<div className={styles.field}>
<div className={cx(styles.field, styles.numberField)}>
<span className={styles.label}>Buckets</span>
<InputNumber
className={styles.numberInput}
<Input.Number
min={1}
max={MAX_NUM_BUCKETS}
precision={0}
format={{ maximumFractionDigits: 0 }}
value={numBuckets}
onChange={handleNumBucketsChange}
placeholder={`Default ${DEFAULT_NUM_BUCKETS}`}
data-testid="bucket-options-num-buckets"
width={104}
testId="bucket-options-num-buckets"
/>
</div>
</>

View File

@@ -65,14 +65,6 @@
display: flex;
flex-direction: column;
gap: 8px;
// Meant to fix the query builder colors
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
--input-border-color: var(--l2-border);
--input-hover-border-color: var(--internal-ant-border-color-hover);
--input-focus-border-color: var(--internal-ant-border-color-hover);
}
&-aggregation-container {

View File

@@ -181,7 +181,7 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
expect(limitInput).toBeInTheDocument();
expect(limitInput).toHaveAttribute('type', 'number');
expect(limitInput).toHaveAttribute('name', 'limit');
expect(limitInput).toHaveAttribute('data-testid', 'input-Limit');
expect(limitInput).toHaveAttribute('data-testid', 'input-Limit-field');
});
it('Cmd+Enter on an input triggers handleRunQuery via container handler', async () => {

View File

@@ -165,7 +165,7 @@ describe('QueryAddOns', () => {
/>,
);
const input = screen.getByTestId('input-Limit') as HTMLInputElement;
const input = screen.getByTestId('input-Limit-field') as HTMLInputElement;
expect(screen.getByTestId('limit-content')).toBeInTheDocument();
expect(input.value).toBe('5');
@@ -181,8 +181,8 @@ describe('QueryAddOns', () => {
});
it.each([
[QueryBuilderField.Limit, 'input-Limit', 'placeholder'],
[QueryBuilderField.Legend, 'input-Legend format', 'placeholder'],
[QueryBuilderField.Limit, 'input-Limit-field', 'placeholder'],
[QueryBuilderField.Legend, 'input-Legend format-field', 'placeholder'],
[QueryBuilderField.GroupBy, 'groupby', 'data-placeholder'],
[QueryBuilderField.Having, 'having-change', 'data-placeholder'],
[QueryBuilderField.OrderBy, 'orderby', 'data-placeholder'],
@@ -223,7 +223,7 @@ describe('QueryAddOns', () => {
fireEvent.click(screen.getByTestId('query-add-on-limit'));
expect(screen.getByTestId('input-Limit')).toHaveAttribute(
expect(screen.getByTestId('input-Limit-field')).toHaveAttribute(
'placeholder',
'Enter limit',
);
@@ -247,7 +247,9 @@ describe('QueryAddOns', () => {
);
expect(screen.getByTestId('order-by-content')).toBeInTheDocument();
const limitInput = screen.getByTestId('input-Limit') as HTMLInputElement;
const limitInput = screen.getByTestId(
'input-Limit-field',
) as HTMLInputElement;
expect(screen.getByTestId('limit-content')).toBeInTheDocument();
expect(limitInput.value).toBe('7');
});
@@ -577,7 +579,7 @@ describe('QueryAddOns', () => {
renderHeatmap({ bucketOptions: { kind: 'log', spec: { scale: 0 } } });
await user.click(screen.getByRole('button', { name: 'Linear' }));
await user.type(screen.getByTestId('bucket-options-max-value'), '500');
await user.type(screen.getByTestId('bucket-options-max-value-field'), '500');
await waitFor(() => {
expect(mockHandleChangeQueryData).toHaveBeenLastCalledWith(

View File

@@ -6,14 +6,6 @@
border-bottom: 1px solid var(--l1-border);
}
.search {
--input-background: var(--l2-background);
--input-hover-background: var(--l2-background);
--input-focus-background: var(--l2-background);
--input-border-color: var(--l2-border);
--input-focus-border-color: var(--l2-border);
}
.searchSpinner {
color: var(--l2-foreground);
animation: spin 0.8s linear infinite;

View File

@@ -19,7 +19,9 @@ import useActiveQueryIndex from 'components/QuickFilters/hooks/useActiveQueryInd
import { DEFAULT_VISIBLE_ITEMS_COUNT } from '../constants';
import useCheckboxDisclosure from '../useCheckboxDisclosure';
import useCheckboxFilterActions from '../useCheckboxFilterActions';
import useCheckboxFilterState from '../useCheckboxFilterState';
import useCheckboxFilterState, {
FILTER_DISABLED_REASON,
} from '../useCheckboxFilterState';
import { useFieldValues } from './useFieldValues';
import { useExistingQuery } from './useExistingQuery';
import { isKeyMatch } from '../utils';
@@ -208,7 +210,8 @@ export default function CheckboxFilterV2(
placeholder="Filter values"
onChange={(e): void => setSearchTextDebounced(e.target.value)}
disabled={isFilterDisabled}
data-testid="checkbox-filter-search"
disabledTooltip={FILTER_DISABLED_REASON}
testId="checkbox-filter-search"
suffix={
isFetching ? (
<LoaderCircle

View File

@@ -37,6 +37,7 @@
align-items: center;
justify-items: end;
// Compact enough to sit inside the 24px value row.
--button-height: 21px;
--button-padding: var(--spacing-5);

View File

@@ -80,7 +80,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-production');
expect(screen.getByTestId('checkbox-value-row-staging')).toBeInTheDocument();
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {
@@ -198,7 +198,7 @@ describe('CheckboxFilterV2 - interactions', () => {
// Related values now appear in "Related" section (no badge, uses divider instead)
expect(screen.getByTestId('section-divider-related')).toBeInTheDocument();
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {
@@ -250,7 +250,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-prod');
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {
@@ -295,7 +295,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-prod');
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'xyz-no-match');
await waitFor(() => {
@@ -404,7 +404,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await screen.findByTestId('checkbox-value-row-pod-a-v1');
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'pod-a');
// After search, both sections should still appear with filtered results
@@ -574,7 +574,7 @@ describe('CheckboxFilterV2 - interactions', () => {
);
await screen.findByTestId('checkbox-value-row-value-00');
await user.type(screen.getByTestId('checkbox-filter-search'), '11');
await user.type(screen.getByTestId('checkbox-filter-search-field'), '11');
await waitFor(() =>
expect(
screen.queryByTestId('checkbox-value-row-value-00'),
@@ -586,7 +586,7 @@ describe('CheckboxFilterV2 - interactions', () => {
await user.click(header);
await screen.findByTestId('checkbox-value-row-value-00');
expect(screen.getByTestId('checkbox-filter-search')).toHaveValue('');
expect(screen.getByTestId('checkbox-filter-search-field')).toHaveValue('');
});
});

View File

@@ -111,7 +111,7 @@ describe('CheckboxFilterV2 - states', () => {
await screen.findByTestId('checkbox-value-row-production');
const searchInput = screen.getByTestId('checkbox-filter-search');
const searchInput = screen.getByTestId('checkbox-filter-search-field');
await user.type(searchInput, 'prod');
await waitFor(() => {

View File

@@ -8,7 +8,7 @@ import {
useState,
} from 'react';
import { Input } from 'antd';
import { Slider } from '@signozhq/ui/slider';
import { Slider, type SliderRangeValue } from '@signozhq/ui/slider';
import { getMs } from 'utils/timeUtils';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -90,13 +90,13 @@ export function DurationSection(props: DurationProps): JSX.Element {
debouncedFunction(min, max);
};
const onRangeHandler = (value: number | number[]): void => {
const [min, max] = value as number[];
const onRangeHandler = (value: SliderRangeValue): void => {
const [min, max] = value;
updateDurationFilter(min.toString(), max.toString());
};
const TipComponent = useCallback(
(value: number) => <div>{`${value.toString()}ms`}</div>,
const formatTooltipValue = useCallback(
(value: number): string => `${value.toString()}ms`,
[],
);
@@ -125,13 +125,14 @@ export function DurationSection(props: DurationProps): JSX.Element {
/>
</div>
<div className="duration-input-slider">
<Slider
<Slider.Range
min={0}
max={100000}
range
tooltip={{ formatter: TipComponent }}
color="primary"
tooltip
formatValue={formatTooltipValue}
onChange={(value): void => {
const [min, max] = value as number[];
const [min, max] = value;
onRangeSliderHandler([String(min), String(max)]);
}}
onAfterChange={onRangeHandler}

View File

@@ -59,7 +59,8 @@
min-width: 70px;
}
.sync-tag,
// Query selector themed as the primary sync chip; the Combobox has no
// primary color variant.
.select-box {
display: flex;
padding: 5px 9px;
@@ -94,6 +95,18 @@
.select-box-item {
min-width: 60px;
text-transform: uppercase;
--combobox-trigger-background: var(--primary-background);
--combobox-trigger-border: color-mix(
in srgb,
var(--primary-background) 20%,
transparent
);
--combobox-trigger-border-hover: var(--primary-background);
--combobox-trigger-label: var(--primary-foreground);
--combobox-trigger-icon: var(--primary-foreground);
--combobox-trigger-icon-hover: var(--primary-foreground);
--combobox-trigger-min-block-size: 30px;
}
}

View File

@@ -5,14 +5,8 @@ import {
RefreshCw,
Settings2 as SettingsIcon,
} from '@signozhq/icons';
import {
Combobox,
ComboboxCommand,
ComboboxContent,
ComboboxItem,
ComboboxList,
ComboboxTrigger,
} from '@signozhq/ui/combobox';
import { Badge } from '@signozhq/ui/badge';
import { Combobox, type ComboboxItemType } from '@signozhq/ui/combobox';
import { Skeleton } from 'antd';
import { Button } from '@signozhq/ui/button';
import { Tooltip } from '@signozhq/ui/tooltip';
@@ -86,7 +80,6 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
redirectWithQueryBuilderData,
panelType,
} = useQueryBuilder();
const [open, setOpen] = useState(false);
// Sync lastUsedQuery when queries change (e.g., after deletion)
const validQueryIndex = useMemo(
@@ -113,6 +106,16 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
}));
}, [currentQuery?.builder?.queryData]);
const queryItems = useMemo<ComboboxItemType[]>(
() =>
queryOptions.map((option) => ({
type: 'item',
value: String(option.value),
label: option.label,
})),
[queryOptions],
);
// Show dropdown in ListView only for TRACES_EXPLORER source
const shouldShowDropdownInListView =
isListView && quickFilterSource === QuickFiltersSource.TRACES_EXPLORER;
@@ -188,42 +191,28 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
{queryOptions.length > 1 &&
!isAIObservabilityRowView &&
(!isListView || shouldShowDropdownInListView) ? (
<Combobox open={open} onOpenChange={setOpen}>
<ComboboxTrigger
<div className="select-box">
<Combobox
aria-label="Select a query"
placeholder="Select a query"
value={queryOptions.find((f) => f.value === validQueryIndex)?.label || ''}
className="select-box"
searchInputProps={{ placeholder: 'Search queries' }}
items={queryItems}
value={String(validQueryIndex)}
onChange={(value): void => {
if (value !== undefined) {
handleQueryChange(Number(value));
}
}}
/>
{open && (
<ComboboxContent>
<ComboboxCommand>
<ComboboxList>
{queryOptions.map((option) => (
<ComboboxItem
key={option.value}
value={String(option.value)}
onSelect={(): void => {
handleQueryChange(option.value);
setOpen(false);
}}
isSelected={validQueryIndex === option.value}
>
{option.label}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxCommand>
</ComboboxContent>
)}
</Combobox>
</div>
) : (
displayedQueryName && (
<Tooltip
title={`Filter currently in sync with query ${displayedQueryName}`}
>
<Typography.Text className="sync-tag">
<Badge variant="solid" color="primary" textTransform="uppercase">
{displayedQueryName}
</Typography.Text>
</Badge>
</Tooltip>
)
)}

View File

@@ -98,6 +98,15 @@ export const noSavedFiltersHandler = rest.get(
),
);
export const noSavedTracesFiltersHandler = rest.get(
'http://localhost/api/v2/quick_filters/traces',
(_req, res, ctx) =>
res(
ctx.status(200),
ctx.json(quickFiltersResponse(QuickfiltertypesSourceDTO.traces, [])),
),
);
export const loadingFiltersHandlers = [
rest.get('http://localhost/api/v2/quick_filters/logs', (_req, res, ctx) =>
res(ctx.delay('infinite')),

View File

@@ -1,6 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import type { ComponentProps, ComponentType } from 'react';
import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { withCanvas } from '@/storybook/decorators/withCanvas';
import type { GlobalMockArgs } from '@/storybook/globals';
@@ -16,8 +17,9 @@ import {
queryBuilder,
selectedServiceQueryBuilder,
noSavedFiltersHandler,
noSavedTracesFiltersHandler,
} from './QuickFilters.stories.mocks';
import { QuickFiltersSource } from '../types';
import { FiltersType, QuickFiltersSource } from '../types';
const meta = {
title: 'Components/Quick Filters',
@@ -58,10 +60,10 @@ type TooltipsStory = StoryObj<
* silence.
*/
const settingsControl = (canvasElement: HTMLElement): Promise<HTMLElement> =>
waitFor(() => {
waitFor(async () => {
const control = within(canvasElement).getByTestId('settings-icon-container');
expect(control).not.toHaveAttribute('aria-disabled', 'true');
await expect(control).not.toHaveAttribute('aria-disabled', 'true');
return control;
});
@@ -110,6 +112,29 @@ export const SelectedExpandedCheckbox: Story = {
},
};
/** The duration min/max row, which a checkbox-only config never reaches. */
export const DurationFilter: Story = {
args: {
quickFilterSource: QuickFiltersSource.TRACES_EXPLORER,
config: [
...checkboxConfig,
{
attributeKey: {
dataType: DataTypes.Float64,
key: 'duration_nano',
type: '',
},
defaultOpen: true,
title: 'Duration',
type: FiltersType.DURATION,
},
],
},
parameters: {
msw: { handlers: [noSavedTracesFiltersHandler, ...handlers] },
},
};
/**
* Every tooltip the panel renders, held open: the reveal on each truncated
* filter value. Nothing bounds those values, so the panel is answered with

View File

@@ -182,15 +182,29 @@ describe('Quick Filters', () => {
expect(screen.getByRole('option', { name: 'Query C' })).toBeInTheDocument();
});
// Select Query B
const queryBOption = screen.getByRole('option', { name: 'Query B' });
await user.click(queryBOption);
// Select Query B with the keyboard: jsdom cannot deliver the pointer
// click Base UI expects, so filter through the search row and pick with
// Enter instead
await waitFor(() => {
expect(
screen.getByRole('combobox', { name: 'Search queries' }),
).toHaveFocus();
});
await user.keyboard('Query B');
await waitFor(() => {
expect(
screen.queryByRole('option', { name: 'Query A' }),
).not.toBeInTheDocument();
});
await user.keyboard('{ArrowDown}{Enter}');
// Verify setLastUsedQuery was called with index 1
await waitFor(() => {
expect(setLastUsedQuery).toHaveBeenCalledWith(1);
});
});
// filtering and picking through the search row takes Base UI past the
// default 5s under jsdom
}, 20000);
it('should not display query dropdown in ListView', () => {
(useQueryBuilder as jest.Mock).mockReturnValue({

View File

@@ -1,4 +1,4 @@
import { Check, Copy } from '@signozhq/icons';
import { Check, Copy, SolidInfoCircle } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
@@ -46,11 +46,9 @@ function KeyCreatedPhase({
</div>
<div className="add-key-modal__callout-wrapper">
<Callout
type="info"
showIcon
title="Store the key securely. This is the only time it will be displayed."
/>
<Callout color="info" size="sm" icon={<SolidInfoCircle />}>
Store the key securely. This is the only time it will be displayed.
</Callout>
</div>
</div>
);

View File

@@ -53,7 +53,6 @@ function KeyFormPhase({
<Input
id="key-name"
placeholder="Enter key name e.g.: Service Owner"
className="add-key-modal__input"
testId="add-key-name-input"
{...register('keyName', {
required: true,

View File

@@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,
@@ -119,7 +119,7 @@ function AddKeyModal(): JSX.Element {
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy key');
toast.danger('Failed to copy key', { action: { label: 'Dismiss' } });
}
}, [copyState.error]);

View File

@@ -4,7 +4,7 @@ import { Button } from '@signozhq/ui/button';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { buildSADeletePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getGetServiceAccountQueryKey,

View File

@@ -72,7 +72,6 @@ function EditKeyForm({
) : (
<Input
id="edit-key-name"
className="edit-key-modal__input"
placeholder="Enter key name"
{...register('name')}
/>

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useQueryClient } from 'react-query';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccountKeys,

View File

@@ -7,7 +7,7 @@ import {
buildSADetachPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
getListServiceAccountKeysQueryKey,

View File

@@ -9,7 +9,7 @@ import { useQueryClient } from 'react-query';
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Skeleton } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { buildSAAttachPermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import {
setupAuthzAdmin,
@@ -12,9 +12,9 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import AddKeyModal from '../AddKeyModal';
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockCopyToClipboard = jest.fn();
@@ -74,7 +74,8 @@ describe('AddKeyModal', () => {
// The form only renders once the checks resolve, so waiting for it also
// guarantees the button is no longer in its authz-loading state.
const nameInput = await screen.findByTestId('add-key-name-input');
// The Input's testId lands on its frame; the native input is `-field`.
const nameInput = await screen.findByTestId('add-key-name-input-field');
const createBtn = await screen.findByTestId('add-key-submit-btn');
expect(createBtn).toHaveAttribute('aria-disabled', 'true');
@@ -94,7 +95,7 @@ describe('AddKeyModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('add-key-name-input');
const nameInput = await screen.findByTestId('add-key-name-input-field');
const submitBtn = await screen.findByTestId('add-key-submit-btn');
await user.type(nameInput, 'Deploy Key');
await waitFor(() =>
@@ -118,8 +119,9 @@ describe('AddKeyModal', () => {
);
renderModal();
const nameInput =
await screen.findByTestId<HTMLInputElement>('add-key-name-input');
const nameInput = await screen.findByTestId<HTMLInputElement>(
'add-key-name-input-field',
);
const submitBtn = await screen.findByTestId('add-key-submit-btn');
await user.type(nameInput, 'Deploy Key');
await waitFor(() =>
@@ -139,7 +141,7 @@ describe('AddKeyModal', () => {
renderModal();
const nameInput = await screen.findByTestId('add-key-name-input');
const nameInput = await screen.findByTestId('add-key-name-input-field');
const submitBtn = await screen.findByTestId('add-key-submit-btn');
await user.type(nameInput, 'Deploy Key');
await waitFor(() =>

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import {
AUTHZ_CHECK_URL,
@@ -20,9 +20,9 @@ jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
import { rest, server } from 'mocks-server/server';
@@ -16,9 +16,9 @@ jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
}): React.ReactElement => children,
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);

View File

@@ -77,6 +77,17 @@
flex: none;
}
}
.sa-status-cell {
[data-slot='badge'] {
padding: var(--padding-1) var(--padding-2);
align-items: center;
font-size: var(--uppercase-small-500-font-size);
font-weight: var(--uppercase-small-500-font-weight);
line-height: 100%;
letter-spacing: 0.44px;
}
}
}
.sa-name-email-cell {

View File

@@ -18,17 +18,11 @@
.input {
flex: 1;
min-width: 120px;
border: none;
background: transparent;
&::placeholder {
color: var(--l3-foreground);
}
}
// Wrapper around the inline chip-edit Input; width lives on the Input prop.
.editInput {
width: 160px;
height: 24px;
--input-height: 24px;
}
.error {

View File

@@ -9,7 +9,8 @@ type User = ReturnType<typeof userEvent.setup>;
const startEditingFirstChip = async (user: User): Promise<HTMLElement> => {
await user.click(screen.getAllByTestId(`${TID}-chip`)[0]);
return screen.getByTestId(`${TID}-edit`);
// The Input's testId lands on its frame; the native input is `${testId}-field`.
return screen.getByTestId(`${TID}-edit-field`);
};
describe('TagKeyValueInput — inline chip edit', () => {
@@ -101,7 +102,7 @@ describe('TagKeyValueInput — backend-rule validation', () => {
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={[]} onTagsChange={onTagsChange} />);
const input = screen.getByTestId(TID);
const input = screen.getByTestId(`${TID}-field`);
await user.type(input, 'env:pro d{Enter}');
expect(screen.getByTestId(`${TID}-error`)).toHaveTextContent(

View File

@@ -115,21 +115,22 @@ function TagKeyValueInput({
<div className={styles.field}>
{tags.map((tag, index) =>
index === editIndex ? (
<Input
key={tag}
className={styles.editInput}
value={editValue}
autoFocus
testId={`${testId}-edit`}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setEditValue(e.target.value);
if (error) {
setError('');
}
}}
onKeyDown={handleEditKeyDown}
onBlur={(): void => commitEdit(true)}
/>
<span key={tag} className={styles.editInput}>
<Input
width={160}
value={editValue}
autoFocus
testId={`${testId}-edit`}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setEditValue(e.target.value);
if (error) {
setError('');
}
}}
onKeyDown={handleEditKeyDown}
onBlur={(): void => commitEdit(true)}
/>
</span>
) : (
<Pill.Closeable
key={tag}
@@ -142,14 +143,17 @@ function TagKeyValueInput({
</Pill.Closeable>
),
)}
<Input
className={styles.input}
value={inputValue}
placeholder={placeholder}
testId={testId}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<div className={styles.input}>
<Input
variant="unstyled"
width="100%"
value={inputValue}
placeholder={placeholder}
testId={testId}
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
</div>
</div>
{error && (
<Typography className={styles.error} data-testid={`${testId}-error`}>

View File

@@ -1,6 +1,7 @@
.popoverContent {
min-width: 180px;
padding: 8px;
// The popover shell carries no padding of its own, so the content owns it.
padding: var(--spacing-8);
}
.title {

View File

@@ -2,7 +2,7 @@ import { useMemo } from 'react';
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 { Popover } from '@signozhq/ui/popover';
import styles from './ColumnSelector.module.scss';
import type { TableColumnDef } from './types';
import { hideColumn, showColumn, useHiddenColumnIds } from './useColumnStore';
@@ -38,8 +38,12 @@ function ColumnSelector<TData>({
};
return (
<Popover>
<PopoverTrigger asChild>
<Popover
nativeButton
side="bottom"
align="end"
aria-label="Toggle columns"
trigger={
<Button
variant="outlined"
size="sm"
@@ -49,8 +53,9 @@ function ColumnSelector<TData>({
>
Columns
</Button>
</PopoverTrigger>
<PopoverContent align="end" className={styles.popoverContent}>
}
>
<div className={styles.popoverContent}>
<div className={styles.title}>Toggle Columns</div>
<div className={styles.columnList}>
{selectableColumns.map((col) => {
@@ -70,7 +75,7 @@ function ColumnSelector<TData>({
);
})}
</div>
</PopoverContent>
</div>
</Popover>
);
}

View File

@@ -111,16 +111,6 @@
margin-left: auto;
}
.tanstackColumnActionsContent {
width: 140px;
padding: 0;
background: var(--tanstack-table-header-cell-bg, var(--l2-background));
border: 1px solid
var(--tanstack-table-header-cell-actions-border-color, var(--l2-border));
border-radius: 4px;
box-shadow: none;
}
.tanstackRemoveColumnAction {
display: flex;
align-items: center;

View File

@@ -14,7 +14,7 @@ import {
X,
} from '@signozhq/icons';
import { useSortable } from '@dnd-kit/sortable';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { Popover } from '@signozhq/ui/popover';
import { flexRender, Header as TanStackHeader } from '@tanstack/react-table';
import cx from 'classnames';
@@ -223,8 +223,13 @@ function TanStackHeaderRow<TData>({
</span>
)}
{isColumnRemovable && (
<Popover>
<PopoverTrigger asChild>
<Popover
nativeButton={false}
side="bottom"
align="end"
width={140}
aria-label={`Column actions for ${headerTitleAttr}`}
trigger={
<span
role="button"
aria-label={`Column actions for ${headerTitleAttr}`}
@@ -235,25 +240,20 @@ function TanStackHeaderRow<TData>({
>
<Ellipsis size="md" />
</span>
</PopoverTrigger>
<PopoverContent
align="end"
sideOffset={6}
className={headerStyles.tanstackColumnActionsContent}
}
>
<button
type="button"
className={headerStyles.tanstackRemoveColumnAction}
onClick={(event): void => {
event.preventDefault();
event.stopPropagation();
onRemoveColumn?.(column.id);
}}
>
<button
type="button"
className={headerStyles.tanstackRemoveColumnAction}
onClick={(event): void => {
event.preventDefault();
event.stopPropagation();
onRemoveColumn?.(column.id);
}}
>
<X size="md" className={headerStyles.tanstackRemoveColumnActionIcon} />
Remove column
</button>
</PopoverContent>
<X size="md" className={headerStyles.tanstackRemoveColumnActionIcon} />
Remove column
</button>
</Popover>
)}
</span>

View File

@@ -17,7 +17,7 @@ import {
horizontalListSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { Combobox } from '@signozhq/ui/combobox';
import { Pagination } from '@signozhq/ui/pagination';
import type { Row } from '@tanstack/react-table';
import {
@@ -675,14 +675,16 @@ function TanStackTableInner<TData, TItemKey = string>(
/>
{pagination.showPageSize !== false && (
<div className={viewStyles.paginationPageSize}>
<ComboboxSimple
<Combobox
testId="pagination-page-size"
aria-label="Rows per page"
placeholder="Page size"
searchInputProps={{ placeholder: 'Search page sizes' }}
value={limit?.toString()}
defaultValue="10"
onChange={(value): void => {
value ??= '10';
setLimit(+value);
pagination.onLimitChange?.(+value);
onChange={(value: string | undefined): void => {
const nextValue = value ?? '10';
setLimit(+nextValue);
pagination.onLimitChange?.(+nextValue);
if (page !== 1) {
setPage(1);
pagination.onPageChange?.(1);

View File

@@ -125,7 +125,6 @@
.paginationPageSize {
width: 80px;
--combobox-trigger-height: 2rem;
}
.paginationTotalCount {

View File

@@ -2,7 +2,7 @@ import type { CSSProperties, ReactNode } from 'react';
import type { ColumnDef } from '@tanstack/react-table';
import { RowKeyData, TableColumnDef } from './types';
import { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import type { ComboboxItemType } from '@signozhq/ui/combobox';
export const getColumnId = <TData>(column: TableColumnDef<TData>): string =>
column.id;
@@ -164,11 +164,12 @@ const DEFAULT_PAGE_SIZES = [10, 20, 30, 50, 100];
export function buildPageSizeItems(
calculatedSize?: number | null,
): ComboboxSimpleItem[] {
const items: ComboboxSimpleItem[] = [];
): ComboboxItemType[] {
const items: ComboboxItemType[] = [];
if (calculatedSize) {
items.push({
type: 'item',
value: calculatedSize.toString(),
label: `Auto (${calculatedSize})`,
displayValue: calculatedSize.toString(),
@@ -178,6 +179,7 @@ export function buildPageSizeItems(
for (const size of DEFAULT_PAGE_SIZES) {
if (size !== calculatedSize) {
items.push({
type: 'item',
value: size.toString(),
label: size.toString(),
displayValue: size.toString(),

View File

@@ -7,14 +7,6 @@
[data-slot='dialog-content'] {
position: fixed;
z-index: 1001 !important;
background: var(--l1-background);
color: var(--l1-foreground);
/* Override the background and color of the dialog content from the theme */
> div {
background: var(--l1-background);
color: var(--l1-foreground);
}
}
.cmdk-section-heading [cmdk-group-heading] {

View File

@@ -1,13 +1,8 @@
import React, { useEffect } from 'react';
import cx from 'classnames';
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandShortcut,
Command,
type CommandActionItemType,
type CommandItemType,
} from '@signozhq/ui/command';
import logEvent from 'api/common/logEvent';
import {
@@ -26,7 +21,10 @@ import { navigate } from 'lib/router/navigation';
import { useAppLocation } from 'lib/router/useAppLocation';
import { ROLES as UserRole } from 'types/roles';
import { createShortcutActions } from '../../constants/shortcutActions';
import {
type CmdAction,
createShortcutActions,
} from '../../constants/shortcutActions';
import { useCmdK } from '../../providers/cmdKProvider';
import './cmdKPalette.scss';
@@ -58,17 +56,6 @@ const openAuthZDevModal = IS_DEV
}
: undefined;
type CmdAction = {
id: string;
name: string;
shortcut?: string[];
keywords?: string;
section?: string;
icon?: React.ReactNode;
roles?: UserRole[];
perform: () => void;
};
export function CmdKPalette({
userRole,
}: {
@@ -76,7 +63,7 @@ export function CmdKPalette({
}): JSX.Element | null {
const { open, setOpen } = useCmdK();
const { setAutoSwitch, setTheme, theme } = useThemeMode();
const { setAutoSwitch, setTheme } = useThemeMode();
const location = useAppLocation();
const isAIAssistantEnabled = useIsAIAssistantEnabled();
const startNewConversation = useAIAssistantStore(
@@ -146,8 +133,28 @@ export function CmdKPalette({
(a) => !a.roles || a.roles.includes(userRole),
);
const toCommandItem = (action: CmdAction): CommandActionItemType => ({
type: 'item',
value: action.id,
label: action.name,
prefix: action.icon,
searchMetadata: action.keywords,
shortcut:
action.shortcut && action.shortcut.length > 0
? action.shortcut.join(' • ')
: undefined,
onClick: (): void => {
// the palette closes itself after a pick, also when this throws
try {
action.perform();
} catch (e) {
console.error('Error invoking action', e);
}
},
});
// group permitted actions by section
const grouped: [string, CmdAction[]][] = ((): [string, CmdAction[]][] => {
const items: CommandItemType[] = ((): CommandItemType[] => {
const map = new Map<string, CmdAction[]>();
permitted.forEach((a) => {
@@ -161,61 +168,25 @@ export function CmdKPalette({
}
});
return Array.from(map.entries());
return Array.from(map.entries()).map(([section, sectionActions]) => ({
type: 'group',
value: section.toLowerCase().replace(/\s+/g, '-'),
label: section,
items: sectionActions.map(toCommandItem),
}));
})();
const handleInvoke = (action: CmdAction): void => {
try {
action.perform();
} catch (e) {
console.error('Error invoking action', e);
} finally {
setOpen(false);
}
};
return (
<>
<CommandDialog
<Command
label="Command palette"
open={open}
onOpenChange={setOpen}
position="top"
offset={110}
>
<CommandInput placeholder="Search…" className="cmdk-input-wrapper" />
<CommandList className="cmdk-list-scroll">
<CommandEmpty>No results</CommandEmpty>
{grouped.map(([section, items]) => (
<CommandGroup
key={section}
heading={section}
className="cmdk-section-heading"
>
{items.map((it) => (
<CommandItem
key={it.id}
onSelect={(): void => handleInvoke(it)}
value={it.name}
className={theme === 'light' ? 'cmdk-item-light' : 'cmdk-item'}
>
<span
className={cx(
'cmd-item-icon',
it.id === 'ai-assistant' && 'noz-icon',
)}
>
{it.icon}
</span>
{it.name}
{it.shortcut && it.shortcut.length > 0 && (
<CommandShortcut>{it.shortcut.join(' • ')}</CommandShortcut>
)}
</CommandItem>
))}
</CommandGroup>
))}
</CommandList>
</CommandDialog>
items={items}
searchInputProps={{ placeholder: 'Search…' }}
noContent="No results"
testId="cmdk-palette"
/>
{IS_DEV && AuthZDevModal && (
<React.Suspense fallback={null}>
<AuthZDevModal />

View File

@@ -46,5 +46,4 @@ export enum LOCALSTORAGE {
DASHBOARDS_LIST_VIEWS = 'DASHBOARDS_LIST_VIEWS',
DASHBOARD_V2_PANEL_COLUMN_WIDTHS = 'DASHBOARD_V2_PANEL_COLUMN_WIDTHS',
LLM_ATTRIBUTE_MAPPING_TEST_SPAN = 'LLM_ATTRIBUTE_MAPPING_TEST_SPAN',
TRACES_SPAN_DRAWER_ENABLED = 'TRACES_SPAN_DRAWER_ENABLED',
}

View File

@@ -26,7 +26,7 @@ export type CmdAction = {
shortcut?: string[];
keywords?: string;
section?: string;
icon?: React.ReactNode;
icon?: React.ReactElement;
roles?: ROLES[];
perform: () => void;
};

View File

@@ -194,25 +194,6 @@
}
}
.contextPopover {
width: 480px !important;
max-width: min(92vw, 480px);
margin-left: 16px;
border: 1px solid var(--l2-border);
border-radius: var(--radius-2);
background: var(--l1-background);
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.3);
padding: 8px;
// Clip horizontal overflow so long entity titles can't poke past the
// popover's right edge. Vertical overflow is handled inside
// `.contextPopoverEntities`.
overflow-x: hidden;
--popover-padding: 0;
z-index: 1000;
}
.contextPopoverContent {
display: grid;
grid-template-columns: 180px 1fr;
@@ -255,11 +236,6 @@
width: 90%;
}
.contextPopoverSearchInput {
width: 100%;
box-sizing: border-box;
}
.contextPopoverEntities {
display: flex;
flex-direction: column;

View File

@@ -10,8 +10,8 @@ import cx from 'classnames';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { toast } from '@signozhq/ui/sonner';
import { Popover, usePopoverHandle } from '@signozhq/ui/popover';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import type { UploadFile } from 'antd';
import getSessionStorage from 'api/browser/sessionstorage/get';
@@ -254,6 +254,13 @@ export default function ChatInput({
const [activeContextCategory, setActiveContextCategory] =
useState<ContextCategory>('Dashboards');
const [pickerSearchQuery, setPickerSearchQuery] = useState('');
// The picker resets to its first category on every open and close, and the entity queries
// below are gated on whether it is showing.
const contextPicker = usePopoverHandle((open) => {
setIsContextPickerOpen(open);
setActiveContextCategory('Dashboards');
setPickerSearchQuery('');
});
// Refs to each category tab so we can move DOM focus to the newly-active
// tab on ArrowUp/ArrowDown. Without this the roving-tabindex pattern
// stalls: focus stays on the original button (whose closure has the old
@@ -288,6 +295,7 @@ export default function ChatInput({
const committedTextRef = useRef('');
const textareaRef = useRef<HTMLTextAreaElement>(null);
const inputRootRef = useRef<HTMLDivElement>(null);
const addContextRef = useRef<HTMLButtonElement>(null);
const capText = useCallback(
(value: string) => value.slice(0, MAX_INPUT_LENGTH),
@@ -300,20 +308,24 @@ export default function ChatInput({
const atIndex = beforeCaret.lastIndexOf('@');
// `@` only triggers at a word start, so words containing `@` stay plain text.
if (atIndex < 0 || /\S/.test(beforeCaret.charAt(atIndex - 1))) {
setIsContextPickerOpen(false);
contextPicker.close();
setMentionRange(null);
return;
}
const query = beforeCaret.slice(atIndex + 1);
if (/\s/.test(query)) {
setIsContextPickerOpen(false);
contextPicker.close();
setMentionRange(null);
return;
}
setIsContextPickerOpen(true);
// Typed `@`, not pressed: the picker still belongs beside the button it has
// always hung off, so it is shown against that element rather than the caret.
if (addContextRef.current) {
contextPicker.showAt(addContextRef.current);
}
setMentionRange({ start: atIndex, end: caret });
},
[],
[contextPicker],
);
const toggleContextSelection = useCallback(
@@ -355,7 +367,7 @@ export default function ChatInput({
committedTextRef.current = next;
setMentionRange(null);
setPickerSearchQuery('');
setIsContextPickerOpen(false);
contextPicker.close();
// Defer so React commits the new value before we place the caret.
requestAnimationFrame(() => {
const el = textareaRef.current;
@@ -502,7 +514,7 @@ export default function ChatInput({
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Escape' && isContextPickerOpen) {
setIsContextPickerOpen(false);
contextPicker.close();
return;
}
if (e.key === 'Enter' && !e.shiftKey) {
@@ -510,7 +522,7 @@ export default function ChatInput({
void handleSend();
}
},
[handleSend, isContextPickerOpen],
[handleSend, isContextPickerOpen, contextPicker],
);
const removeFile = useCallback((uid: string) => {
@@ -592,19 +604,23 @@ export default function ChatInput({
});
if (error === 'network') {
markVoiceUnavailable();
toast.error('Voice input unavailable in this browser', {
toast.danger('Voice input unavailable in this browser', {
action: { label: 'Dismiss' },
description:
'This browser cannot reach the speech recognition service. Try Google Chrome or Microsoft Edge.',
});
} else if (error === 'not-allowed') {
markVoiceUnavailable();
toast.error('Microphone access denied', {
toast.danger('Microphone access denied', {
action: { label: 'Dismiss' },
description:
'Grant microphone permission in your browser settings to use voice input.',
});
} else if (error === 'not-supported') {
markVoiceUnavailable();
toast.error('Voice input is not supported in this browser.');
toast.danger('Voice input is not supported in this browser.', {
action: { label: 'Dismiss' },
});
}
// `no-speech` is benign (just silence) — don't toast or hide.
},
@@ -1006,158 +1022,146 @@ export default function ChatInput({
<div className={styles.footer}>
<div className={styles.leftActions}>
<Popover
open={isContextPickerOpen}
onOpenChange={(open): void => {
setIsContextPickerOpen(open);
if (!open) {
setActiveContextCategory('Dashboards');
setPickerSearchQuery('');
}
}}
<Button
ref={addContextRef}
disabledTooltip={disabledTooltip}
variant="solid"
color="secondary"
size="md"
disabled={disabled}
prefix={<Plus size={10} />}
aria-haspopup="dialog"
aria-expanded={isContextPickerOpen}
onClick={(event): void => contextPicker.showAt(event.currentTarget)}
>
<PopoverTrigger asChild>
<Button
disabledTooltip={disabledTooltip}
variant="solid"
color="secondary"
size="md"
disabled={disabled}
onClick={(): void => {
setActiveContextCategory('Dashboards');
setPickerSearchQuery('');
}}
prefix={<Plus size={10} />}
Add Context
</Button>
<Popover
side="top"
align="end"
width={480}
maxWidth="min(92vw, 480px)"
handle={contextPicker}
aria-label="Add context"
>
<div className={styles.contextPopoverContent}>
<div
className={styles.contextPopoverCategories}
role="tablist"
aria-orientation="vertical"
aria-label="Context categories"
>
Add Context
</Button>
</PopoverTrigger>
<PopoverContent
className={styles.contextPopover}
side="top"
align="end"
sideOffset={8}
>
<div className={styles.contextPopoverContent}>
<div
className={styles.contextPopoverCategories}
role="tablist"
aria-orientation="vertical"
aria-label="Context categories"
>
{CONTEXT_CATEGORIES.map((category) => {
const CategoryIcon = CONTEXT_CATEGORY_ICONS[category];
const isActive = activeContextCategory === category;
return (
<Button
key={category}
ref={(el): void => {
categoryTabRefs.current.set(category, el);
}}
type="button"
variant="ghost"
color="secondary"
size="sm"
id={`ai-context-tab-${category}`}
// Single stable panel id shared by every tab: only the
// active category's tabpanel is rendered, so per-category
// `aria-controls` ids would point at nonexistent nodes
// for the two inactive tabs. APG allows a single
// dynamic panel whose `aria-labelledby` swaps to the
// active tab.
aria-controls="ai-context-tabpanel"
// Roving tabindex: only the active tab participates in
// the Tab sequence; arrow keys move between tabs.
tabIndex={isActive ? 0 : -1}
aria-selected={isActive}
width="100%"
onClick={(): void => {
setActiveContextCategory(category);
setPickerSearchQuery('');
}}
onKeyDown={(e): void => handleCategoryKeyDown(e, category)}
prefix={<CategoryIcon size={13} />}
>
<span>{category}</span>
</Button>
);
})}
</div>
<div
className={styles.contextPopoverRight}
role="tabpanel"
id="ai-context-tabpanel"
aria-labelledby={`ai-context-tab-${activeContextCategory}`}
>
<div className={styles.contextPopoverSearch}>
<Input
type="text"
placeholder={`Search ${activeContextCategory.toLowerCase()}…`}
className={styles.contextPopoverSearchInput}
value={pickerSearchQuery}
onChange={(e): void => setPickerSearchQuery(e.target.value)}
prefix={<Search size={12} />}
// Skip the picker's roving keyboard focus — typing here
// shouldn't move category selection.
onKeyDown={(e): void => {
e.stopPropagation();
{CONTEXT_CATEGORIES.map((category) => {
const CategoryIcon = CONTEXT_CATEGORY_ICONS[category];
const isActive = activeContextCategory === category;
return (
<Button
key={category}
ref={(el): void => {
categoryTabRefs.current.set(category, el);
}}
/>
</div>
<div className={styles.contextPopoverEntities}>
{isActiveContextLoading ? (
<div className={styles.contextPopoverEmpty}>
Loading {activeContextCategory.toLowerCase()}...
</div>
) : isActiveContextError ? (
<div className={styles.contextPopoverEmpty}>
Failed to load {activeContextCategory.toLowerCase()}.
</div>
) : filteredContextOptions.length === 0 ? (
<ContextPickerEmptyState
category={activeContextCategory}
query={displayQuery}
onPrefill={handleContextPrefill}
/>
) : (
filteredContextOptions.map((option, index) => {
const isSelected = selectedContexts.some(
(item) =>
item.category === activeContextCategory &&
item.entityId === option.id,
);
type="button"
variant="ghost"
color="secondary"
size="sm"
id={`ai-context-tab-${category}`}
// Single stable panel id shared by every tab: only the
// active category's tabpanel is rendered, so per-category
// `aria-controls` ids would point at nonexistent nodes
// for the two inactive tabs. APG allows a single
// dynamic panel whose `aria-labelledby` swaps to the
// active tab.
aria-controls="ai-context-tabpanel"
// Roving tabindex: only the active tab participates in
// the Tab sequence; arrow keys move between tabs.
tabIndex={isActive ? 0 : -1}
aria-selected={isActive}
width="100%"
onClick={(): void => {
setActiveContextCategory(category);
setPickerSearchQuery('');
}}
onKeyDown={(e): void => handleCategoryKeyDown(e, category)}
prefix={<CategoryIcon size={13} />}
>
<span>{category}</span>
</Button>
);
})}
</div>
return (
<Button
key={option.id}
ref={(el): void => {
entityRefs.current[index] = el;
}}
type="button"
variant="ghost"
color="secondary"
size="sm"
aria-pressed={isSelected}
width="100%"
onClick={(): void =>
toggleContextSelection(
activeContextCategory,
option.id,
option.value,
)
}
onKeyDown={(e): void => handleEntityKeyDown(e, index)}
>
{option.value}
</Button>
);
})
)}
</div>
<div
className={styles.contextPopoverRight}
role="tabpanel"
id="ai-context-tabpanel"
aria-labelledby={`ai-context-tab-${activeContextCategory}`}
>
<div className={styles.contextPopoverSearch}>
<Input
type="text"
placeholder={`Search ${activeContextCategory.toLowerCase()}…`}
value={pickerSearchQuery}
onChange={(e): void => setPickerSearchQuery(e.target.value)}
prefix={<Search size={12} />}
// Skip the picker's roving keyboard focus — typing here
// shouldn't move category selection.
onKeyDown={(e): void => {
e.stopPropagation();
}}
/>
</div>
<div className={styles.contextPopoverEntities}>
{isActiveContextLoading ? (
<div className={styles.contextPopoverEmpty}>
Loading {activeContextCategory.toLowerCase()}...
</div>
) : isActiveContextError ? (
<div className={styles.contextPopoverEmpty}>
Failed to load {activeContextCategory.toLowerCase()}.
</div>
) : filteredContextOptions.length === 0 ? (
<ContextPickerEmptyState
category={activeContextCategory}
query={displayQuery}
onPrefill={handleContextPrefill}
/>
) : (
filteredContextOptions.map((option, index) => {
const isSelected = selectedContexts.some(
(item) =>
item.category === activeContextCategory &&
item.entityId === option.id,
);
return (
<Button
key={option.id}
ref={(el): void => {
entityRefs.current[index] = el;
}}
type="button"
variant="ghost"
color="secondary"
size="sm"
aria-pressed={isSelected}
width="100%"
onClick={(): void =>
toggleContextSelection(
activeContextCategory,
option.id,
option.value,
)
}
onKeyDown={(e): void => handleEntityKeyDown(e, index)}
>
{option.value}
</Button>
);
})
)}
</div>
</div>
</PopoverContent>
</div>
</Popover>
</div>

View File

@@ -88,7 +88,6 @@
margin-left: 2px;
}
.input,
.select {
background: var(--l2-background);
border: 1px solid var(--l2-border);
@@ -108,14 +107,6 @@
cursor: pointer;
}
// Constrain the Radix-based SelectContent popover so it never grows wider
// than the trigger button. `--radix-select-trigger-width` is set by Radix
// at the popper layer when `position: 'popper'` (the default).
.selectContent {
width: var(--radix-select-trigger-width);
min-width: var(--radix-select-trigger-width);
}
.radioGroup,
.checkboxGroup {
display: flex;

View File

@@ -3,12 +3,8 @@ import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Input } from '@signozhq/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from '@signozhq/ui/select';
import { Select } from '@signozhq/ui/select';
import type { SelectItemType } from '@signozhq/ui/select';
import logEvent from 'api/common/logEvent';
import { ClarificationFieldTypeDTO } from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
import type {
@@ -247,10 +243,7 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
// "Other (type your own)" entry — a plain `options: null` would
// otherwise fall through to the bare text-input renderer).
if (type === ClarificationFieldTypeDTO.select && (options || allowCustom)) {
const handleSelectChange = (next: string | string[]): void => {
// `multiple` is off → callback receives a single string. The wider
// `string | string[]` typing comes from the shared Select root.
const picked = Array.isArray(next) ? (next[0] ?? '') : next;
const handleSelectChange = (picked: string): void => {
if (picked === CUSTOM_OPTION_SENTINEL) {
setIsCustom(true);
onChange(customValue);
@@ -260,6 +253,23 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
}
};
const selectItems: SelectItemType[] = [
...(options ?? []).map(
(opt): SelectItemType => ({ type: 'item', value: opt, label: opt }),
),
...(allowCustom
? [
{
type: 'item',
value: CUSTOM_OPTION_SENTINEL,
label: CUSTOM_OPTION_LABEL,
} as SelectItemType,
]
: []),
];
const selectedValue = isCustom ? CUSTOM_OPTION_SENTINEL : String(value ?? '');
return (
<div className={styles.field}>
<label className={styles.label} htmlFor={id}>
@@ -271,34 +281,16 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
)}
</label>
<Select
value={isCustom ? CUSTOM_OPTION_SENTINEL : String(value ?? '')}
id={id}
items={selectItems}
placeholder="Select…"
aria-required={required || undefined}
value={selectedValue || undefined}
onChange={handleSelectChange}
>
<SelectTrigger
id={id}
placeholder="Select…"
aria-required={required || undefined}
/>
{/* Pin the dropdown width to the trigger via Radix's
`--radix-select-trigger-width`; otherwise the popover
sizes to its widest item and looks misaligned. */}
<SelectContent className={styles.selectContent}>
{options?.map((opt) => (
<SelectItem key={opt} value={opt}>
{opt}
</SelectItem>
))}
{allowCustom && (
<SelectItem value={CUSTOM_OPTION_SENTINEL}>
{CUSTOM_OPTION_LABEL}
</SelectItem>
)}
</SelectContent>
</Select>
/>
{isCustom && (
<Input
type="text"
className={styles.input}
placeholder="Enter a custom value"
value={customValue}
onChange={(e): void => {
@@ -407,7 +399,6 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
{isCustom && (
<Input
type="text"
className={styles.input}
placeholder="Enter a custom value"
value={customValue}
onChange={(e): void => updateCustomValue(e.target.value)}
@@ -431,7 +422,6 @@ function FieldInput({ field, value, onChange }: FieldInputProps): JSX.Element {
<Input
id={id}
type={type === 'number' ? 'number' : 'text'}
className={styles.input}
value={String(value ?? '')}
aria-required={required || undefined}
onChange={(e): void => {

View File

@@ -62,16 +62,15 @@
opacity: 0.7;
}
// Wrapper around the chromeless rename Input (variant="unstyled"); the accent
// bottom border marks the edit state, and the --input-* tokens keep the field
// the same size as the title row it replaces.
.input {
width: 100%;
background: transparent;
border: none;
border-bottom: 1px solid var(--accent-primary);
outline: none;
color: var(--l1-foreground);
font-size: 12px;
font-family: inherit;
padding: 1px 0;
--input-height: 20px;
--input-padding: 1px 0;
--input-font-size: 12px;
}
.actions {

View File

@@ -5,7 +5,7 @@ import ROUTES from 'constants/routes';
import { getAbsoluteUrl } from 'utils/basePath';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
import {
@@ -176,16 +176,21 @@ export default function ConversationItem({
<div className={styles.body}>
{isEditing ? (
<Input
ref={inputRef}
className={styles.input}
value={editValue}
onChange={(e): void => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
onClick={(e): void => e.stopPropagation()}
maxLength={80}
/>
<div className={styles.input}>
<Input
ref={inputRef}
// The wrapper's accent bottom border is the edit/focus
// treatment, so the Input renders chromeless.
variant="unstyled"
noFocusRing
value={editValue}
onChange={(e): void => setEditValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={commitEdit}
onClick={(e): void => e.stopPropagation()}
maxLength={80}
/>
</div>
) : (
<>
<span className={styles.title} title={displayTitle}>

View File

@@ -45,11 +45,6 @@
flex-shrink: 0;
}
.search {
width: 100%;
font-size: 12px;
}
.list {
position: relative;
flex: 1;

View File

@@ -193,7 +193,6 @@ export default function ConversationsList({
onChange={(e): void => setSearchQuery(e.target.value)}
placeholder="Search conversations…"
prefix={<Search size={12} />}
className={styles.search}
/>
</div>

View File

@@ -3,6 +3,7 @@ import { Tooltip } from 'antd';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { ArrowDownLeft, ArrowUpRight, Calendar } from '@signozhq/icons';
import { useAlertHistoryQueryParams } from 'pages/AlertDetails/hooks';
import { AlertRuleStats } from 'types/api/alerts/def';
import { calculateChange } from 'utils/calculateChange';
@@ -86,7 +87,7 @@ function StatsCard({
}: StatsCardProps): JSX.Element {
const urlQuery = useUrlQuery();
const relativeTime = urlQuery.get('relativeTime');
const { relativeTime } = useAlertHistoryQueryParams();
const { changePercentage, changeDirection } = calculateChange(
totalCurrentCount,

View File

@@ -46,7 +46,7 @@
height: 80px;
}
.top-contributors-progress {
--progress-background: transparent;
--progress-track-background-color: transparent;
}
&__content {

View File

@@ -1,5 +1,4 @@
import { HTMLAttributes } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Table, TableColumnsType as ColumnsType } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import logEvent from 'api/common/logEvent';
@@ -49,12 +48,13 @@ function TopContributorsRows({
relatedTracesLink={record.relatedTracesLink}
relatedLogsLink={record.relatedLogsLink}
>
<Progress
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
strokeColor={Color.BG_ROBIN_500}
className="top-contributors-progress"
/>
<div className="top-contributors-progress">
<Progress
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
color="primary"
/>
</div>
</ConditionalAlertPopover>
),
},

View File

@@ -1,6 +1,8 @@
import { useMemo } from 'react';
import useUrlQuery from 'hooks/useUrlQuery';
import { useGetAlertRuleDetailsTimelineGraphData } from 'pages/AlertDetails/hooks';
import {
useAlertHistoryQueryParams,
useGetAlertRuleDetailsTimelineGraphData,
} from 'pages/AlertDetails/hooks';
import DataStateRenderer from 'periscope/components/DataStateRenderer/DataStateRenderer';
import { AlertRuleTimelineGraphResponse } from 'types/api/alerts/def';
@@ -13,9 +15,7 @@ function GraphWrapper({
}: {
totalCurrentTriggers: number;
}): JSX.Element {
const urlQuery = useUrlQuery();
const relativeTime = urlQuery.get('relativeTime');
const { relativeTime } = useAlertHistoryQueryParams();
const { isLoading, isRefetching, isError, data, isValidRuleId, ruleId } =
useGetAlertRuleDetailsTimelineGraphData();

View File

@@ -46,13 +46,8 @@
}
.title {
color: var(--l2-foreground);
font-family: 'Geist Mono';
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
// Typography's only monospace affordance is `code`, which also draws a chip.
font-family: var(--font-mono);
}
.radio-button {
@@ -93,24 +88,13 @@
}
.domain-details-metadata-label {
color: var(--l2-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 18px;
letter-spacing: 0.44px;
// Typography has no prop for casing.
text-transform: uppercase;
}
.domain-details-metadata-value {
color: var(--l2-foreground);
font-family: 'Geist Mono';
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
// Typography's only monospace affordance is `code`, which also draws a chip.
font-family: var(--font-mono);
width: 100%;
white-space: nowrap;

Some files were not shown because too many files have changed in this diff Show More