Compare commits

..

2 Commits

Author SHA1 Message Date
Ashwin Bhatkal
212054786f Merge branch 'main' into fix/alerts-views-test-timeout 2026-11-09 16:55:54 +05:30
Ashwin Bhatkal
0d6a308179 test(alerts): stop the saved views tests timing out on CI 2026-11-09 14:41:11 +05:30
1274 changed files with 13973 additions and 20876 deletions

9
.github/CODEOWNERS vendored
View File

@@ -209,10 +209,6 @@ go.mod @therealpandey
/frontend/src/types/api/alerts/ @SigNoz/pulse-frontend
/frontend/src/providers/Alert.tsx @SigNoz/pulse-frontend
/frontend/src/constants/alerts.ts @SigNoz/pulse-frontend
/frontend/src/container/PlannedDowntime/ @SigNoz/pulse-frontend
/frontend/src/hooks/alerts/ @SigNoz/pulse-frontend
/frontend/src/container/Home/AlertRules/ @SigNoz/pulse-frontend
/frontend/src/container/Home/__tests__/AlertRules.test.tsx @SigNoz/pulse-frontend
## Notification Channels
/frontend/src/pages/ChannelsEdit/ @SigNoz/pulse-frontend
@@ -221,11 +217,6 @@ go.mod @therealpandey
/frontend/src/container/FormAlertChannels/ @SigNoz/pulse-frontend
/frontend/src/hooks/notificationChannels/ @SigNoz/pulse-frontend
## List filter + views (dashboards and alerts lists)
/frontend/src/components/ListFilter/ @SigNoz/pulse-frontend
/frontend/src/components/ListViews/ @SigNoz/pulse-frontend
/frontend/src/hooks/listViews/ @SigNoz/pulse-frontend
## OpenAPI Schema - Generated
/frontend/src/api/generated/services/ @therealpandey @vikrantgupta25 @srikanthccv
/docs/api/openapi.yml @therealpandey @vikrantgupta25 @srikanthccv

View File

@@ -10231,10 +10231,8 @@ components:
having:
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5Having'
description: A filter on the formula values, referencing the formula by
its name or as __result, for example F1 > 0. A time series keeps only
the points that match and drops a series with none left; a scalar result
keeps only the rows that match.
description: Accepted but currently ignored; the computed values are returned
unfiltered.
legend:
description: The display name template for each series; {{field}} placeholders
are replaced by label values.
@@ -32731,305 +32729,6 @@ paths:
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_anomaly_request_rate:
description: 'anomaly is the one function with named arguments. seasonality
says which past periods predict the current value: hourly, daily
or weekly, daily when absent. z_score_threshold is how many standard
deviations from the prediction count as an anomaly. It has no default:
a request without it gets bounds of zero width, so send it. 3 is
the value the UI sends. The response carries, next to the values,
a predicted series, an upper and a lower bound series and an anomaly
score series. The scoring runs in the enterprise build; the community
build returns the series unchanged.'
summary: 'anomaly: request rate scored against its daily pattern'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: http.server.duration.count
spaceAggregation: sum
timeAggregation: rate
functions:
- args:
- name: seasonality
value: daily
- name: z_score_threshold
value: 3
name: anomaly
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: metrics
stepInterval: 300s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_cumulativesum_errors_so_far:
description: cumulativeSum replaces each point with the sum of every
point up to and including it, so a per-step count becomes a running
total whose first point is the first count. It takes no arguments.
The total resets with the window, so a longer time range gives a
larger final value; it is a view of the window, not a counter.
summary: 'cumulativeSum: 5xx responses since the start of the window'
value:
compositeQuery:
queries:
- spec:
aggregations:
- alias: server_errors
expression: count()
filter:
expression: http.response.status_code >= 500
functions:
- name: cumulativeSum
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: traces
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_cutoffmin_hide_idle_hosts:
description: The four threshold functions take one number. cutOffMin
and cutOffMax remove the points below or above the threshold, so
an idle host disappears from the chart instead of drawing a line
along the bottom. clampMin and clampMax keep the points and replace
the value with the threshold, which caps spikes without hiding them.
system.cpu.utilization is a ratio, so 0.05 is 5%.
summary: 'cutOffMin: drop hosts below 5% CPU from the chart'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: system.cpu.utilization
spaceAggregation: avg
timeAggregation: avg
functions:
- args:
- value: 0.05
name: cutOffMin
groupBy:
- fieldContext: resource
name: host.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_ewma_smoothed_latency:
description: 'ewma3, ewma5 and ewma7 replace each point with an exponentially
weighted average of the point and the points before it. The number
in the name is the window the default weight is derived from: alpha
= 2 / (n + 1), so 0.5, 0.333 and 0.25. One optional argument overrides
alpha; a value close to 1 follows the raw series, a value close
to 0 smooths harder. 0.3 here smooths a little more than ewma5''s
default.'
summary: 'ewma5: smoothed p95 latency'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: http.server.duration.bucket
spaceAggregation: p95
functions:
- args:
- value: 0.3
name: ewma5
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_fillzero_sparse_errors:
description: A count over sparse events returns a point only for steps
that had a match, so a chart of failed spans has gaps where nothing
failed. fillZero adds a zero at every missing step. With no arguments
the server fills the request window at the query's step, which is
the common case. The three optional arguments are start, end and
step in milliseconds, for callers that want to fill a different
grid.
summary: 'fillZero: a zero at every step with no failed spans'
value:
compositeQuery:
queries:
- spec:
aggregations:
- alias: failed_spans
expression: count()
filter:
expression: has_error = true
functions:
- name: fillZero
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: traces
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_log10_network_bytes:
description: log2 and log10 replace each value with its logarithm,
so a loopback device moving kilobytes and an uplink moving gigabytes
fit on one chart. They take no arguments. Zero and negative values
have no logarithm and are not drawn. absolute, also without arguments,
is the usual companion for a signed series before a logarithm.
summary: 'log10: network throughput across devices that differ by
orders of magnitude'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: system.network.io
spaceAggregation: sum
timeAggregation: rate
filter:
expression: direction = 'transmit'
functions:
- name: log10
groupBy:
- fieldContext: attribute
name: device
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_median_despiked_request_rate:
description: median3, median5 and median7 replace each point with
the median of the surrounding 3, 5 or 7 points. A spike that lasts
one step vanishes, a shift that lasts longer than half the window
stays. The first and last half window, 2 points each for median5,
have no full window and keep their values, so the newest spike stays
visible; a series shorter than the window is returned unchanged.
They take no arguments. Use them when the raw series is noisy and
the chart is for a trend, not for catching the spike.
summary: 'median5: request rate without one-step spikes'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: http.server.duration.count
spaceAggregation: sum
timeAggregation: rate
functions:
- name: median5
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_runningdiff_disk_growth:
description: 'runningDiff turns a gauge into the change between consecutive
points, so a filesystem usage gauge becomes bytes written per step.
It takes no arguments and drops the first point of the window, since
that point has nothing to difference against; the server reads one
step before the window to keep the first interval. Functions apply
in order: clampMin with 0 runs on the differences and replaces the
negative value a cleanup or a volume reset produces with zero.'
summary: 'runningDiff then clampMin: bytes written to disk per step'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: system.filesystem.usage
spaceAggregation: sum
timeAggregation: latest
filter:
expression: state = 'used'
functions:
- name: runningDiff
- args:
- value: 0
name: clampMin
groupBy:
- fieldContext: resource
name: host.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_timeshift_week_over_week:
description: timeShift moves every point of its query forward by the
argument, in seconds, so a series read from the past lands on the
current timestamps. B here reads the hour exactly seven days (604800
s) before the requested window and comes back on this week's timestamps,
so it overlays A on a chart and the formula can subtract it point
by point. The argument is positive; it says how far back to read.
summary: 'timeShift: this week''s error logs next to last week''s'
value:
compositeQuery:
queries:
- spec:
aggregations:
- alias: errors
expression: count()
filter:
expression: severity_text = 'ERROR'
name: A
signal: logs
stepInterval: 60s
type: builder_query
- spec:
aggregations:
- alias: errors
expression: count()
filter:
expression: severity_text = 'ERROR'
functions:
- args:
- value: 604800
name: timeShift
name: B
signal: logs
stepInterval: 60s
type: builder_query
- spec:
expression: A - B
name: F1
type: builder_formula
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
logs_raw:
summary: 'Raw: fetch raw log records'
value:

View File

@@ -8,5 +8,5 @@
.title {
color: var(--l1-foreground);
font-size: var(--label-large-400-font-size);
font-size: var(--font-size-lg);
}

View File

@@ -497,11 +497,7 @@
// Requires final else in if-else-if chains (was disabled)
"signoz/no-conditional-text-nodes-with-siblings": "warn",
// Vendored from eslint-plugin-react-google-translate
"signoz/no-return-text-nodes": "warn",
"signoz/no-text-css": "warn",
// Font and text CSS in style props, CSSProperties objects and styled-components; text style goes through Typography props
"signoz/require-disable-reason": "error"
// A disable of signoz/no-text-css must say why Typography cannot render the text
"signoz/no-return-text-nodes": "warn"
},
"ignorePatterns": [
"src/parser/*.ts",

View File

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

View File

@@ -1,6 +1,8 @@
<!--
Every Google Fonts request the app makes is answered with an empty
stylesheet; the local faces stand in for them.
`index.html` links Inter from Google Fonts here. The link is parsed before the
msw worker has started, so that one request escapes the iframe while every
other font request the app makes is answered with an empty stylesheet; the
local faces stand in for both.
-->
<link rel="stylesheet" href="storybook-fonts.css" />

View File

@@ -1,11 +1,22 @@
/*
* The four families the app pulls from Google Fonts, served from the files in
* The five families the app pulls from Google Fonts, served from the files in
* `public/fonts` instead. `msw/appShellHandlers.ts` answers the CDN with an
* empty stylesheet so no request leaves the iframe, which without this left
* every story on a fallback for the four families `src/styles.scss` imports at
* runtime. Inter comes from `src/styles.scss` itself.
* runtime, and on whatever the Inter link in `index.html` happened to fetch
* before the worker had started.
*/
@font-face {
font-family: 'Inter';
/* The comma is part of the filename and the dev server serves it raw, so it
stays literal inside the quoted url rather than percent-encoded. */
src: url('fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Work Sans';
src: url('fonts/WorkSans-VariableFont_wght.ttf') format('truetype');

View File

@@ -14,23 +14,9 @@ module.exports = {
path.join(__dirname, 'stylelint-rules/css-modules/class-name-pattern.js'),
],
customSyntax: 'postcss-scss',
// Disables of property-disallowed-list must say why after "--"
reportDescriptionlessDisables: [
false,
{ except: ['property-disallowed-list'], severity: 'error' },
],
rules: {
// Applies to all SCSS files
'local/no-unsupported-asset-url': true,
// Text style goes through @signozhq/ui/typography props
'property-disallowed-list': [
['/^font/', '/^text-/', 'line-height', 'letter-spacing'],
{
severity: 'warning',
message: (property) =>
`Do not set "${property}" in CSS. Render the text with @signozhq/ui/typography and use its props, or disable this rule with a reason after "--" saying why Typography cannot.`,
},
],
},
overrides: [
{

View File

@@ -191,30 +191,34 @@ You can read the ./node_modules/@signozhq/design-tokens/dist/style.css to find c
### Typography
Text styles are tokens named `--{variant}-{size}-{weight}-{property}`:
- Variant: `label` (line height 1), `paragraph` (line height 18, 20 or 26px), `uppercase` (11 and 13px) or `code` (SF Mono, 11 and 13px).
- Size: `small` (11px), `base` (13px), `medium` (16px) or `large` (18px). Paragraph has no `large`; uppercase and code have only `small` and `base`.
- Weight: `400`, `500` or `600`.
- Property: `font-family`, `font-size`, `font-weight`, `line-height`, `letter-spacing` or `text-transform`.
```scss
// Font sizes (preferred)
.title {
font-family: var(--label-large-500-font-family);
font-size: var(--label-large-500-font-size);
font-weight: var(--label-large-500-font-weight);
line-height: var(--label-large-500-line-height);
letter-spacing: var(--label-large-500-letter-spacing);
font-size: var(--periscope-font-size-large); // 18px
font-size: var(--periscope-font-size-medium); // 16px
font-size: var(--periscope-font-size-base); // 13px
font-size: var(--periscope-font-size-small); // 11px
}
.hint {
font-size: var(--paragraph-small-400-font-size); // 11px
line-height: var(--paragraph-small-400-line-height); // 18px
// Alternative scale (rem-based)
.heading {
font-size: var(--font-size-xl); // 1.25rem
font-size: var(--font-size-lg); // 1.125rem
font-size: var(--font-size-base); // 1rem
font-size: var(--font-size-sm); // 0.875rem
}
.id {
font-family: var(--code-base-400-font-family);
font-size: var(--code-base-400-font-size); // 13px
// Font weights
.bold {
font-weight: var(--font-weight-semibold); // 600
font-weight: var(--font-weight-medium); // 500
font-weight: var(--font-weight-normal); // 400
}
// Line heights
.text {
line-height: var(--line-height-20); // 20px
line-height: var(--line-height-24); // 24px
}
```
@@ -308,7 +312,7 @@ Each component exposes `--<component>-<property>` variables:
--button-background: var(--success-background);
--button-border-radius: var(--radius-2);
--button-padding: var(--spacing-4) var(--spacing-8);
--button-font-size: var(--label-base-400-font-size);
--button-font-size: var(--periscope-font-size-base);
}
// Override Input
@@ -338,7 +342,7 @@ Components use data attributes for variants/states. Target them for state-specif
// Target size
.wrapper :global([data-size="sm"]) {
--button-font-size: var(--label-small-400-font-size);
--button-font-size: var(--periscope-font-size-small);
}
// Target color

View File

@@ -1,191 +0,0 @@
# 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

@@ -14,6 +14,11 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://cdn.vercel.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
rel="stylesheet"
/>
<title data-react-helmet="true">
Open source Observability platform | SigNoz
</title>

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.6.0",
"@signozhq/design-tokens": "2.2.1",
"@signozhq/icons": "0.4.0",
"@signozhq/ui": "0.3.0",
"@signozhq/ui": "0.2.0",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

View File

@@ -1,170 +0,0 @@
import { ruleTester } from './rule-tester.mjs';
const message = (property) =>
`Do not set '${property}' in CSS. Render the text with @signozhq/ui/typography and use its props, or disable this rule with a reason after \`--\` saying why Typography cannot.`;
await ruleTester({
rule: 'no-text-css',
valid: [
{
name: 'layout properties in a style prop',
code: 'export const A = () => <div style={{ width: 10, color: "red" }} />;',
},
{
name: 'font properties in an object that is not a style',
code: 'export const options = { fontSize: 12, font: "12px Inter" };',
},
{
name: 'font properties in a prop that is not a style',
code: 'export const A = () => <Chart options={{ fontSize: 12 }} />;',
},
{
name: 'computed key',
code:
'const k = "fontSize";\nexport const A = () => <div style={{ [k]: 12 }} />;',
},
{
name: 'style identifier from a parameter',
code: 'export const A = ({ s }) => <div style={s} />;',
},
{
name: 'template without a styled tag',
code: 'export const s = html`font-size: 12px;`;',
},
{
name: 'styled template with layout only',
code:
"import styled from 'styled-components';\nexport const A = styled.div`\n\tdisplay: flex;\n\ttext: none;\n`;",
},
{
name: 'custom property that ends in a banned name',
code:
"import styled from 'styled-components';\nexport const A = styled.div`\n\t--my-font-size: 12px;\n`;",
},
],
invalid: [
{
name: 'inline style',
code: 'export const A = () => <div style={{ fontSize: 12, width: 1 }} />;',
errors: [{ message: message('fontSize'), line: 1, column: 38 }],
},
{
name: 'string key',
code: "export const A = () => <div style={{ 'line-height': 1 }} />;",
errors: [{ message: message('line-height') }],
},
{
name: 'every banned family',
code:
'export const A = () => (\n\t<div\n\t\tstyle={{\n\t\t\tfont: "x",\n\t\t\tfontWeight: 500,\n\t\t\tfontFamily: "x",\n\t\t\ttextAlign: "center",\n\t\t\ttextTransform: "none",\n\t\t\tlineHeight: 1,\n\t\t\tletterSpacing: 1,\n\t\t}}\n\t/>\n);',
errors: [
{ message: message('font') },
{ message: message('fontWeight') },
{ message: message('fontFamily') },
{ message: message('textAlign') },
{ message: message('textTransform') },
{ message: message('lineHeight') },
{ message: message('letterSpacing') },
],
},
{
name: 'conditional, logical and spread',
code:
'export const A = ({ a, b }) => (\n\t<div style={{ ...(a ? { fontSize: 1 } : {}), ...(b && { lineHeight: 1 }) }} />\n);',
errors: [
{ message: message('fontSize') },
{ message: message('lineHeight') },
],
},
{
name: 'style prop with a Style suffix',
code:
'export const A = () => <Card headStyle={{ fontWeight: 600 }} bodyStyle={{ padding: 0 }} />;',
errors: [{ message: message('fontWeight') }],
},
{
name: 'antd semantic styles',
code:
'export const A = () => <Modal styles={{ body: { fontSize: 12 }, header: { padding: 0 } }} />;',
errors: [{ message: message('fontSize') }],
},
{
name: 'identifier resolved to a local object',
code:
'const s = { fontSize: 12 };\nexport const A = () => (\n\t<>\n\t\t<div style={s} />\n\t\t<span style={s} />\n\t</>\n);',
errors: [{ message: message('fontSize'), line: 1, column: 13 }],
},
{
name: 'useMemo style',
code:
"import { useMemo } from 'react';\nexport const A = () => {\n\tconst s = useMemo(() => ({ textAlign: 'left' }), []);\n\treturn <div style={s} />;\n};",
errors: [{ message: message('textAlign') }],
},
{
name: 'object typed CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const s: CSSProperties = { fontStyle: 'italic' };",
errors: [{ message: message('fontStyle') }],
},
{
name:
'object typed React.CSSProperties, reported once when also used as style',
code:
"import React from 'react';\nconst s: React.CSSProperties = { fontSize: 1 };\nexport const A = () => <div style={s} />;",
errors: [{ message: message('fontSize') }],
},
{
name: 'Record of CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const s: Record<string, CSSProperties> = { a: { lineHeight: 1 } };",
errors: [{ message: message('lineHeight') }],
},
{
name: 'as and satisfies CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const a = { fontSize: 1 } as CSSProperties;\nexport const b = { fontWeight: 1 } satisfies CSSProperties;",
errors: [
{ message: message('fontSize') },
{ message: message('fontWeight') },
],
},
{
name: 'styled-components template',
code:
"import styled from 'styled-components';\nexport const A = styled.div`\n\tdisplay: flex;\n\tfont-size: 12px;\n\tcolor: ${(p) => p.c};\n\ttext-transform: none;line-height: 1;\n`;",
errors: [
{ message: message('font-size'), line: 4, column: 2 },
{ message: message('text-transform'), line: 6, column: 2 },
{ message: message('line-height'), line: 6, column: 23 },
],
},
{
name: 'function returning CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const a = (): CSSProperties => ({ fontSize: 1 });\nexport function b(): CSSProperties {\n\treturn { lineHeight: 1 };\n}",
errors: [
{ message: message('fontSize') },
{ message: message('lineHeight') },
],
},
{
name: 'strings in styled interpolations',
code:
"import styled, { css } from 'styled-components';\nexport const A = styled.div`\n\t${(p) => (p.s ? 'font-size: 11px;' : `line-height: 1;`)}\n\t${(p) => p.b && css`font-weight: 500;`}\n`;",
errors: [
{ message: message('font-size'), line: 3, column: 19 },
{ message: message('line-height'), line: 3, column: 40 },
{ message: message('font-weight'), line: 4, column: 22 },
],
},
{
name: 'styled(Component) and css helper',
code:
"import styled, { css } from 'styled-components';\nexport const A = styled(B)`letter-spacing: 1px;`;\nexport const c = css`\n\tfont-weight: 500;\n`;",
errors: [
{ message: message('letter-spacing'), line: 2, column: 28 },
{ message: message('font-weight'), line: 4, column: 2 },
],
},
],
});

View File

@@ -1,49 +0,0 @@
import { ruleTester } from './rule-tester.mjs';
const message = 'Disabling signoz/no-text-css needs a reason after `--`.';
await ruleTester({
rule: 'require-disable-reason',
valid: [
{
name: 'reason after --',
code:
'// oxlint-disable-next-line signoz/no-text-css -- canvas label\nexport const a = 1;',
},
{
name: 'block comment with a reason',
code:
'/* eslint-disable signoz/no-text-css -- email HTML */\nexport const a = 1;',
},
{
name: 'another rule without a reason',
code: '// oxlint-disable-next-line no-console\nexport const a = 1;',
},
{
name: 'disable of every rule',
code: '// eslint-disable-next-line\nexport const a = 1;',
},
{
name: 'plain comment naming the rule',
code: '// signoz/no-text-css flags font CSS\nexport const a = 1;',
},
],
invalid: [
{
name: 'next-line without a reason',
code: '// oxlint-disable-next-line signoz/no-text-css\nexport const a = 1;',
errors: [{ message, line: 1, column: 1 }],
},
{
name: 'empty reason',
code: '// eslint-disable-line signoz/no-text-css --\nexport const a = 1;',
errors: [{ message }],
},
{
name: 'among other rules',
code:
'/* oxlint-disable no-console, signoz/no-text-css */\nexport const a = 1;',
errors: [{ message }],
},
],
});

View File

@@ -1,324 +0,0 @@
/**
* Rule: no-text-css
*
* Text style (font, line height, letter spacing, case, alignment, overflow)
* belongs to Typography props, not to CSS written in a component.
*
* This rule catches font and text properties in:
* <div style={{ fontSize: 12 }} /> style and *Style props
* <Modal styles={{ body: { fontWeight: 500 } }} />
* const s: CSSProperties = { lineHeight: 1 } objects typed CSSProperties
* ({ ... }) as CSSProperties, satisfies CSSProperties
* (): CSSProperties => ({ ... }) functions returning CSSProperties
* styled.div`font-size: 12px;` styled-components templates,
* ${(p) => p.small && 'font-size: 11px;'} strings in their interpolations
*
* Identifiers passed to a style prop are followed to a local object literal.
* SCSS is covered by the stylelint config.
*/
const MESSAGE = (property) =>
`Do not set '${property}' in CSS. Render the text with @signozhq/ui/typography and use its props, or disable this rule with a reason after \`--\` saying why Typography cannot.`;
const BANNED_JS = /^(font([A-Z]\w*)?|text[A-Z]\w*|lineHeight|letterSpacing)$/;
const BANNED_CSS =
/(^|[\s;{])(font(-[a-z]+)*|text-[a-z-]+|line-height|letter-spacing)\s*:/g;
const STYLE_TAGS = new Set(['css', 'createGlobalStyle', 'keyframes']);
const FUNCTIONS = new Set([
'ArrowFunctionExpression',
'FunctionExpression',
'FunctionDeclaration',
]);
const camelCase = (name) =>
name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
function propertyName(property) {
if (property.computed) {
return null;
}
if (property.key.type === 'Identifier') {
return property.key.name;
}
if (
property.key.type === 'Literal' &&
typeof property.key.value === 'string'
) {
return property.key.value;
}
return null;
}
function isCssPropertiesType(typeNode) {
if (!typeNode || typeNode.type !== 'TSTypeReference') {
return false;
}
const name = typeNode.typeName;
if (name.type === 'Identifier') {
return name.name === 'CSSProperties';
}
return name.type === 'TSQualifiedName' && name.right.name === 'CSSProperties';
}
// Record<K, CSSProperties> and { [key: string]: CSSProperties }
function isCssPropertiesMapType(typeNode) {
if (!typeNode) {
return false;
}
if (
typeNode.type === 'TSTypeReference' &&
typeNode.typeName.type === 'Identifier' &&
typeNode.typeName.name === 'Record'
) {
const params = (typeNode.typeArguments ?? typeNode.typeParameters)?.params;
return isCssPropertiesType(params?.[1]);
}
if (typeNode.type === 'TSTypeLiteral') {
return typeNode.members.some(
(member) =>
member.type === 'TSIndexSignature' &&
isCssPropertiesType(member.typeAnnotation?.typeAnnotation),
);
}
return false;
}
function rootIdentifier(node) {
let current = node;
while (current) {
if (current.type === 'Identifier') {
return current.name;
}
if (current.type === 'MemberExpression') {
current = current.object;
} else if (current.type === 'CallExpression') {
current = current.callee;
} else {
return null;
}
}
return null;
}
function isStyleTag(tag) {
if (tag.type === 'Identifier') {
return tag.name === 'styled' || STYLE_TAGS.has(tag.name);
}
return rootIdentifier(tag) === 'styled';
}
export default {
create(context) {
const reported = new Set();
const followed = new Set();
const reportedCss = new Set();
function report(property, name) {
if (reported.has(property)) {
return;
}
reported.add(property);
context.report({ node: property.key, message: MESSAGE(name) });
}
function resolveIdentifier(identifier) {
const scope = context.sourceCode.getScope(identifier);
let current = scope;
while (current) {
const variable = current.set.get(identifier.name);
if (variable) {
const definition = variable.defs[0];
if (
definition?.node?.type === 'VariableDeclarator' &&
definition.node.init
) {
return definition.node.init;
}
return null;
}
current = current.upper;
}
return null;
}
function checkObject(object) {
for (const property of object.properties) {
if (property.type === 'SpreadElement') {
checkStyle(property.argument);
continue;
}
const name = propertyName(property);
if (name && BANNED_JS.test(camelCase(name))) {
report(property, name);
}
}
}
function checkStyle(expression) {
if (!expression) {
return;
}
switch (expression.type) {
case 'ObjectExpression':
checkObject(expression);
return;
case 'ConditionalExpression':
checkStyle(expression.consequent);
checkStyle(expression.alternate);
return;
case 'LogicalExpression':
checkStyle(expression.left);
checkStyle(expression.right);
return;
case 'TSAsExpression':
case 'TSSatisfiesExpression':
case 'TSNonNullExpression':
case 'ParenthesizedExpression':
checkStyle(expression.expression);
return;
case 'CallExpression':
// useMemo(() => ({ ... }), deps)
if (
expression.callee.type === 'Identifier' &&
expression.callee.name === 'useMemo'
) {
checkReturned(expression.arguments[0]);
}
return;
case 'Identifier': {
const init = resolveIdentifier(expression);
if (!init || followed.has(init)) {
return;
}
followed.add(init);
checkStyle(init);
return;
}
default:
}
}
function checkReturned(fn) {
if (!fn || !FUNCTIONS.has(fn.type)) {
return;
}
if (fn.body.type !== 'BlockStatement') {
checkStyle(fn.body);
return;
}
for (const statement of fn.body.body) {
if (statement.type === 'ReturnStatement') {
checkStyle(statement.argument);
}
}
}
function checkStyleMap(expression) {
if (expression?.type !== 'ObjectExpression') {
return;
}
for (const property of expression.properties) {
if (property.type === 'Property') {
checkStyle(property.value);
}
}
}
// `start` is the index of the first character after the opening quote.
function checkCss(text, start) {
for (const match of text.matchAll(BANNED_CSS)) {
const index = start + match.index + match[1].length;
if (reportedCss.has(index)) {
continue;
}
reportedCss.add(index);
context.report({
loc: {
start: context.sourceCode.getLocFromIndex(index),
end: context.sourceCode.getLocFromIndex(index + match[2].length),
},
message: MESSAGE(match[2]),
});
}
}
// CSS returned from interpolations: ${(p) => p.small && 'font-size: 11px;'}
function checkCssStrings(node) {
if (!node || typeof node.type !== 'string') {
return;
}
if (node.type === 'TemplateLiteral') {
for (const quasi of node.quasis) {
checkCss(quasi.value.raw, quasi.range[0] + 1);
}
} else if (node.type === 'Literal' && typeof node.value === 'string') {
checkCss(node.raw.slice(1, -1), node.range[0] + 1);
return;
}
for (const [key, value] of Object.entries(node)) {
if (key === 'parent') {
continue;
}
if (Array.isArray(value)) {
value.forEach(checkCssStrings);
} else if (value && typeof value === 'object') {
checkCssStrings(value);
}
}
}
function checkFunction(node) {
if (isCssPropertiesType(node.returnType?.typeAnnotation)) {
checkReturned(node);
}
}
return {
JSXAttribute(node) {
if (
node.name.type !== 'JSXIdentifier' ||
node.value?.type !== 'JSXExpressionContainer'
) {
return;
}
const name = node.name.name;
if (name === 'styles') {
checkStyleMap(node.value.expression);
} else if (name === 'style' || name.endsWith('Style')) {
checkStyle(node.value.expression);
}
},
VariableDeclarator(node) {
const type = node.id.typeAnnotation?.typeAnnotation;
if (isCssPropertiesType(type)) {
checkStyle(node.init);
} else if (isCssPropertiesMapType(type)) {
checkStyleMap(node.init);
}
},
TSAsExpression(node) {
if (isCssPropertiesType(node.typeAnnotation)) {
checkStyle(node.expression);
}
},
TSSatisfiesExpression(node) {
if (isCssPropertiesType(node.typeAnnotation)) {
checkStyle(node.expression);
} else if (isCssPropertiesMapType(node.typeAnnotation)) {
checkStyleMap(node.expression);
}
},
TaggedTemplateExpression(node) {
if (!isStyleTag(node.tag)) {
return;
}
checkCssStrings(node.quasi);
},
ArrowFunctionExpression: checkFunction,
FunctionExpression: checkFunction,
FunctionDeclaration: checkFunction,
};
},
};

View File

@@ -1,38 +0,0 @@
/**
* Rule: require-disable-reason
*
* A disable directive that names one of RULES must say why after `--`:
* // oxlint-disable-next-line signoz/no-text-css -- canvas text, not DOM
*
* Directives that disable every rule are not checked.
*/
const RULES = new Set(['signoz/no-text-css']);
const DIRECTIVE = /^\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?\s+(.*)$/s;
export default {
create(context) {
return {
Program() {
for (const comment of context.sourceCode.getAllComments()) {
const match = comment.value.match(DIRECTIVE);
if (!match) {
continue;
}
const [rules, reason] = match[1].split(/\s--\s?/);
const named = rules
.split(',')
.map((rule) => rule.trim())
.filter((rule) => RULES.has(rule));
if (named.length > 0 && !reason?.trim()) {
context.report({
node: comment,
message: `Disabling ${named.join(', ')} needs a reason after \`--\`.`,
});
}
}
},
};
},
};

View File

@@ -17,8 +17,6 @@ import noConditionalTextNodesWithSiblings from './rules/no-conditional-text-node
import noReturnTextNodes from './rules/no-return-text-nodes.mjs';
import noMswInStoryFile from './rules/no-msw-in-story-file.mjs';
import noDirectReactRouterImport from './rules/no-direct-react-router-import.mjs';
import noTextCss from './rules/no-text-css.mjs';
import requireDisableReason from './rules/require-disable-reason.mjs';
export default {
meta: {
@@ -37,7 +35,5 @@ export default {
'no-return-text-nodes': noReturnTextNodes,
'no-msw-in-story-file': noMswInStoryFile,
'no-direct-react-router-import': noDirectReactRouterImport,
'no-text-css': noTextCss,
'require-disable-reason': requireDisableReason,
},
};

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.6.0
version: 2.6.0
specifier: 2.2.1
version: 2.2.1
'@signozhq/icons':
specifier: 0.4.0
version: 0.4.0
'@signozhq/ui':
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)
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)
'@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,6 +2909,9 @@ 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==}
@@ -2925,6 +2928,19 @@ 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:
@@ -2956,6 +2972,15 @@ 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:
@@ -3070,6 +3095,71 @@ 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:
@@ -3079,6 +3169,19 @@ 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:
@@ -3106,6 +3209,15 @@ 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:
@@ -3115,6 +3227,15 @@ 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:
@@ -3133,6 +3254,19 @@ 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==}
@@ -3451,14 +3585,14 @@ packages:
'@sideway/pinpoint@2.0.0':
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
'@signozhq/design-tokens@2.6.0':
resolution: {integrity: sha512-7+lBxC3N4r1A2M8awhop7mtYMMd5yOg4BKfRAN3hnle4lclZHlxm7b3gdplfNN10JA1mcwRSQvWrjQPmA/euqw==}
'@signozhq/design-tokens@2.2.1':
resolution: {integrity: sha512-ulf6lhWUP6qxqsyemQySUMfOIKRvefvZ5rBRv+wlhOpPfYPk3uatPmPkKEhAjISpX8f9g1Gg8wgRZxUCysf20Q==}
'@signozhq/icons@0.4.0':
resolution: {integrity: sha512-QctNvNPlQlsefFdIVmSy/P/7a1IcQ3IryKP0KPMqtoqew3q89FGUmFa5IfEJ7Z3CHhtG1zaXwBKqP72QkLyA3g==}
'@signozhq/ui@0.3.0':
resolution: {integrity: sha512-Khzr3/YC/QACwBi1zfQwt+h88iyuIu9hm5hkcIkuCTteD9P+r13US/NN4kkbJe8IHyZXCoDkoIpmQxzbrA3/SA==}
'@signozhq/ui@0.2.0':
resolution: {integrity: sha512-j20ldiOkR150ON0BCDhlq6fXGoPN++7w6e4t2HtM2XMM4buJO7aHzPzNKvDRZ2UUf/LGPULPAW8w6sg6gUVHyw==}
peerDependencies:
'@signozhq/icons': 0.3.0
react: ^18.2.0
@@ -4791,6 +4925,12 @@ 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'}
@@ -7319,6 +7459,12 @@ 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==}
@@ -8287,8 +8433,8 @@ packages:
'@types/react':
optional: true
react-resizable-panels@4.12.2:
resolution: {integrity: sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==}
react-resizable-panels@4.12.3:
resolution: {integrity: sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
@@ -8788,6 +8934,12 @@ 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'}
@@ -9798,7 +9950,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.29.2
'@babel/runtime': 7.28.2
'@emotion/hash': 0.8.0
'@emotion/unitless': 0.7.5
classnames: 2.3.2
@@ -9814,7 +9966,7 @@ snapshots:
dependencies:
'@ant-design/colors': 7.0.0
'@ant-design/icons-svg': 4.3.1
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -9822,7 +9974,7 @@ snapshots:
'@ant-design/react-slick@1.0.2(react@18.2.0)':
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.2
classnames: 2.3.2
json2mq: 0.2.0
react: 18.2.0
@@ -12346,6 +12498,8 @@ 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)':
@@ -12357,6 +12511,18 @@ 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
@@ -12392,6 +12558,12 @@ 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
@@ -12502,6 +12674,101 @@ 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)
@@ -12509,6 +12776,22 @@ 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
@@ -12531,12 +12814,24 @@ 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
@@ -12551,11 +12846,20 @@ 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.29.2
'@babel/runtime': 7.28.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)
@@ -12575,7 +12879,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.29.2
'@babel/runtime': 7.28.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
@@ -12591,7 +12895,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.29.2
'@babel/runtime': 7.28.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
@@ -12601,7 +12905,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.29.2
'@babel/runtime': 7.28.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)
@@ -12846,33 +13150,41 @@ snapshots:
'@sideway/pinpoint@2.0.0': {}
'@signozhq/design-tokens@2.6.0': {}
'@signozhq/design-tokens@2.2.1': {}
'@signozhq/icons@0.4.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)':
'@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)':
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.2(react-dom@18.2.0(react@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)
transitivePeerDependencies:
- '@date-fns/tz'
- '@emotion/is-prop-valid'
@@ -14122,7 +14434,7 @@ snapshots:
better-xlsx@0.7.6:
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.2
jszip: 3.10.1
kind-of: 6.0.3
@@ -14159,7 +14471,7 @@ snapshots:
broadcast-channel@3.7.0:
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.2
detect-node: 2.1.0
js-sha3: 0.8.0
microseconds: 0.2.0
@@ -14368,6 +14680,18 @@ 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:
@@ -16916,7 +17240,7 @@ snapshots:
match-sorter@6.3.1:
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.2
remove-accents: 0.4.2
math-intrinsics@1.1.0: {}
@@ -17426,6 +17750,11 @@ 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
@@ -18049,7 +18378,7 @@ snapshots:
rc-cascader@3.20.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18060,7 +18389,7 @@ snapshots:
rc-checkbox@3.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18068,7 +18397,7 @@ snapshots:
rc-collapse@3.7.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18077,7 +18406,7 @@ snapshots:
rc-dialog@9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18087,7 +18416,7 @@ snapshots:
rc-drawer@6.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18097,7 +18426,7 @@ snapshots:
rc-dropdown@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18106,7 +18435,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.29.2
'@babel/runtime': 7.28.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
@@ -18114,7 +18443,7 @@ snapshots:
rc-image@7.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18125,7 +18454,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.29.2
'@babel/runtime': 7.28.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)
@@ -18135,7 +18464,7 @@ snapshots:
rc-input@1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18143,7 +18472,7 @@ snapshots:
rc-mentions@2.9.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18155,7 +18484,7 @@ snapshots:
rc-menu@9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18166,7 +18495,7 @@ snapshots:
rc-motion@2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18174,7 +18503,7 @@ snapshots:
rc-notification@5.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18183,7 +18512,7 @@ snapshots:
rc-overflow@1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18192,7 +18521,7 @@ snapshots:
rc-pagination@3.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18200,7 +18529,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.29.2
'@babel/runtime': 7.28.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)
@@ -18213,7 +18542,7 @@ snapshots:
rc-progress@3.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18221,7 +18550,7 @@ snapshots:
rc-rate@2.12.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18229,7 +18558,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.29.2
'@babel/runtime': 7.28.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
@@ -18238,7 +18567,7 @@ snapshots:
rc-segmented@2.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18259,7 +18588,7 @@ snapshots:
rc-slider@10.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18267,7 +18596,7 @@ snapshots:
rc-steps@6.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18275,7 +18604,7 @@ snapshots:
rc-switch@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18283,7 +18612,7 @@ snapshots:
rc-table@7.35.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18294,7 +18623,7 @@ snapshots:
rc-tabs@12.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18306,7 +18635,7 @@ snapshots:
rc-textarea@1.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18316,7 +18645,7 @@ snapshots:
rc-tooltip@6.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18324,7 +18653,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.29.2
'@babel/runtime': 7.28.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)
@@ -18334,7 +18663,7 @@ snapshots:
rc-tree@5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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)
@@ -18344,7 +18673,7 @@ snapshots:
rc-upload@4.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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
@@ -18352,14 +18681,14 @@ snapshots:
rc-util@5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.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.29.2
'@babel/runtime': 7.28.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)
@@ -18585,7 +18914,7 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
react-resizable-panels@4.12.2(react-dom@18.2.0(react@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):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -18636,7 +18965,7 @@ snapshots:
react-router@5.3.4(react@18.2.0):
dependencies:
'@babel/runtime': 7.29.2
'@babel/runtime': 7.28.2
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
@@ -19176,6 +19505,11 @@ 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,92 +0,0 @@
Copyright (c) 2023 Vercel, in collaboration with basement.studio
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@@ -7079,7 +7079,7 @@ export interface Querybuildertypesv5QueryBuilderFormulaDTO {
*/
functions?: Querybuildertypesv5FunctionDTO[] | null;
/**
* @description A filter on the formula values, referencing the formula by its name or as __result, for example F1 > 0. A time series keeps only the points that match and drops a series with none left; a scalar result keeps only the rows that match.
* @description Accepted but currently ignored; the computed values are returned unfiltered.
*/
having?: Querybuildertypesv5HavingDTO;
/**

View File

@@ -90,17 +90,10 @@ function AddCreditCardModal({
</AuthZTooltip>,
]}
>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text className="add-credit-card-text">
You&apos;re currently on <span className="highlight-text">Trial plan</span>.
Add a credit card to access SigNoz chat support to your workspace.
</Typography>
</Typography.Text>
</Modal>
);
}

View File

@@ -1,9 +1,6 @@
.breadcrumb {
padding: var(--spacing-4) 16px var(--spacing-2);
// The antd breadcrumb provides the item spacing.
--button-padding: 0;
ol {
align-items: center;
}
@@ -11,4 +8,13 @@
:global(.ant-breadcrumb-separator) {
color: var(--muted-foreground);
}
[data-slot='button'] {
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 spacing="10px 16px" />}
{showDivider && <Divider className={styles.divider} />}
</>
);
}

View File

@@ -1,4 +1,5 @@
.itemLast {
color: var(--button-secondary-label);
font-size: var(--button-font-size, var(--periscope-font-size-base));
line-height: var(--button-size-line-height, 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/toast';
import { toast } from '@signozhq/ui/sonner';
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/toast';
import { toast } from '@signozhq/ui/sonner';
import { Tooltip } from '@signozhq/ui/tooltip';
import { useCopyToClipboard } from 'react-use';

View File

@@ -26,6 +26,8 @@
}
.brand-title {
font-size: 20px;
font-weight: 600;
color: var(--l1-foreground); // Primary text
margin: 0;
}

View File

@@ -28,29 +28,15 @@ function AppLoading(): JSX.Element {
<div className="brand">
<img src={signozBrandLogoUrl} alt="SigNoz" className="brand-logo" />
<Typography
variant="label"
size="large"
weight="bold"
color="main"
as="h2"
textOverflow="wrap"
>
<Typography.Title level={2} className="brand-title">
SigNoz
</Typography>
</Typography.Title>
</div>
<div className="brand-tagline">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text>
OpenTelemetry-Native Logs, Metrics and Traces in a single pane
</Typography>
</Typography.Text>
</div>
<div className="loader" />

View File

@@ -1,11 +0,0 @@
// Clears the pagination bars and footers that sit above the bottom strip, and the 56px footer of
// a drawer or dialog panel, which sits 16px from the edge of the window.
.toaster[data-slot='toaster'][data-position^='bottom'] {
bottom: calc(var(--bottom-strip-height, 0px) + var(--spacing-48));
}
// Clears the header of a drawer or dialog panel, 16px from the edge plus 48px or 56px.
:global(body):has([data-slot='drawer-popup'], [data-slot='dialog-popup'])
.toaster[data-slot='toaster'][data-position^='top'] {
top: var(--spacing-40);
}

View File

@@ -1,11 +0,0 @@
import { Toaster } from '@signozhq/ui/toast';
import styles from './AppToaster.module.scss';
function AppToaster(): JSX.Element {
return (
<Toaster position="bottom-right" offset={24} className={styles.toaster} />
);
}
export default AppToaster;

View File

@@ -35,6 +35,7 @@
&__error-code {
color: var(--bg-cherry-100);
font-size: 13px;
font-weight: 500;
line-height: 1;
letter-spacing: -0.065px;
}
@@ -42,6 +43,8 @@
&__error-message {
color: var(--bg-cherry-300);
font-size: 13px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.065px;
}
@@ -72,6 +75,7 @@
&__message-item {
color: var(--bg-cherry-300);
font-size: 13px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.065px;

View File

@@ -45,8 +45,9 @@
}
.auth-footer-text {
font-family: var(--label-small-400-font-family);
font-family: var(--font-family-inter, Inter, sans-serif);
font-size: 11px;
font-weight: 400;
line-height: 1;
color: var(--l2-foreground);
text-align: center;
@@ -56,6 +57,7 @@
display: flex;
align-items: center;
gap: 6px;
text-decoration: none;
transition: opacity 0.2s ease;
&:hover {

View File

@@ -16,6 +16,7 @@
display: flex;
align-items: center;
gap: 4.9px;
text-decoration: none;
}
.auth-header-logo-icon {
@@ -25,7 +26,7 @@
}
.auth-header-logo-text {
font-family: Satoshi, var(--label-medium-500-font-family), sans-serif;
font-family: Satoshi, var(--font-family-inter, Inter), sans-serif;
font-size: 15.4px;
font-weight: 500;
line-height: 17.5px;

View File

@@ -17,3 +17,8 @@
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 spacing="10px 16px" />
<Divider className={styles.divider} />
</>
);
}

View File

@@ -23,6 +23,7 @@
.ant-table {
.ant-table-thead > tr > th {
padding: 12px;
font-weight: 500;
font-size: 12px;
line-height: 18px;
@@ -30,7 +31,10 @@
border-bottom: none;
color: var(--l2-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
@@ -41,6 +45,7 @@
.ant-table-cell {
padding: 12px;
font-size: 13px;
line-height: 20px;
color: var(--l1-foreground);
background: var(--l1-background);
@@ -62,6 +67,12 @@
.ant-table-cell:nth-child(n + 3) {
padding-right: 24px;
}
.column-header-right {
text-align: right;
}
.column-header-left {
text-align: left;
}
.ant-table-tbody > tr > td {
border-bottom: none;
}

View File

@@ -40,27 +40,10 @@ const INITIAL_PAGE_SIZE = 20;
const showPaginationItem = (total: number, range: number[]): JSX.Element => (
<>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text className="numbers">
{range[0]} &#8212; {range[1]}
</Typography>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{' '}
of {total}
</Typography>
</Typography.Text>
<Typography.Text className="total"> of {total}</Typography.Text>
</>
);
@@ -73,15 +56,22 @@ function ProgressRender(item: string | number): JSX.Element {
const percent = Number(Number(item).toFixed(1));
return (
<div className="progress-container">
<div className="progress-bar">
<Progress
percent={percent}
showInfo
color={
percent >= 90 ? 'highlight-danger' : percent >= 60 ? 'warning' : 'success'
<Progress
percent={percent}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const cpuPercent = percent;
if (cpuPercent >= 90) {
return Color.BG_SAKURA_500;
}
/>
</div>
if (cpuPercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
</div>
);
}
@@ -536,18 +526,7 @@ export default function CeleryOverviewTable({
),
}}
locale={{
emptyText: isLoading ? null : (
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
No data
</Typography>
),
emptyText: isLoading ? null : <Typography.Text>No data</Typography.Text>,
}}
scroll={{ x: 'max-content' }}
showSorterTooltip

View File

@@ -24,16 +24,9 @@ function CeleryTaskConfigOptions(): JSX.Element {
return (
<div className="celery-task-filters">
<div className="celery-filters">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="visible"
>
<Typography.Text style={{ whiteSpace: 'nowrap' }}>
Task Name
</Typography>
</Typography.Text>
<Select
data-testid="celery-task-name-select"
placeholder="Task Name"

View File

@@ -49,7 +49,11 @@
.subtitle {
color: var(--l2-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
}
}
@@ -60,6 +64,11 @@
.footer-text {
color: var(--l2-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
}
}

View File

@@ -65,38 +65,15 @@ export default function CeleryTaskDetail({
width="45%"
title={
<div>
<Typography
variant="label"
size="large"
weight="bold"
color="main"
as="span"
textOverflow="wrap"
>{`Details - ${taskData.entity}`}</Typography>
<Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text>
<div>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
<Typography.Text className="subtitle">
{`${formatTimestamp(startTime)} ${
endTime ? `- ${formatTimestamp(endTime)}` : ''
}`}
</Typography>
<Divider orientation="vertical" />
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
{taskData.value}
</Typography>
</Typography.Text>
<Divider type="vertical" />
<Typography.Text className="subtitle">{taskData.value}</Typography.Text>
</div>
</div>
}
@@ -111,14 +88,7 @@ export default function CeleryTaskDetail({
destroyOnClose
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
footer={
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>{`Total Task: ${totalTask}`}</Typography>
<Typography.Text className="footer-text">{`Total Task: ${totalTask}`}</Typography.Text>
}
>
<CeleryTaskGraph

View File

@@ -52,6 +52,11 @@
display: flex;
justify-content: center;
align-items: center;
.celery-task-graph-worker-count-text {
font-size: 2.5vw;
text-align: center;
}
}
.worker-count-header {
@@ -143,6 +148,8 @@
.configure-option-Info-text {
color: var(--l2-foreground);
font-size: 12px;
font-weight: 400;
}
}
@@ -210,6 +217,7 @@
}
&__label {
font-family: 'Inter';
font-size: 14px;
color: var(--l2-foreground);
line-height: 20px;
@@ -217,7 +225,7 @@
}
&__value {
font-family: var(--code-base-400-font-family);
font-family: 'Geist Mono';
font-size: 24px;
color: var(--l1-foreground);
line-height: 32px;

View File

@@ -113,16 +113,9 @@ export default function CeleryTaskGraphGrid({
<CardContainer className="row-card" isDarkMode={isDarkMode}>
<div className="row-panel">
<div className="row-panel-section">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
<Typography.Text className="section-title">
Flower Metrics
</Typography>
</Typography.Text>
{collapsedSections.metricBasedGraphs ? (
<ChevronDown
size={14}
@@ -153,30 +146,14 @@ export default function CeleryTaskGraphGrid({
/>
<Card className="celery-task-graph-worker-count">
<div className="worker-count-header">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text className="worker-count-header-text">
Worker Online
</Typography>
</Typography.Text>
</div>
<div className="worker-count-text-container">
<Typography
/* TODO(typography): not lifted from className: font-size: 2.5vw */ variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
display="inline-block"
align="center"
textOverflow="wrap"
>
<Typography.Text className="celery-task-graph-worker-count-text">
{options.filter((option) => option.value).length}
</Typography>
</Typography.Text>
</div>
</Card>
</div>
@@ -187,16 +164,9 @@ export default function CeleryTaskGraphGrid({
<CardContainer className="row-card" isDarkMode={isDarkMode}>
<div className="row-panel">
<div className="row-panel-section">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
<Typography.Text className="section-title">
Span Based Stats
</Typography>
</Typography.Text>
{collapsedSections.traceBasedGraphs ? (
<ChevronDown
size={14}
@@ -215,16 +185,9 @@ export default function CeleryTaskGraphGrid({
</CardContainer>
<div className="configure-option-Info">
{configureOptionComponent}
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
<Typography.Text className="configure-option-Info-text">
Click on a graph co-ordinate to see more details
</Typography>
</Typography.Text>
</div>
</div>
{!collapsedSections.traceBasedGraphs && (

View File

@@ -11,7 +11,9 @@
justify-content: center;
}
&__label {
font-family: var(--code-base-400-font-family);
font-family: 'Geist Mono';
font-size: 12px;
line-height: normal;
}
&--positive {
.change-percentage-pill {

View File

@@ -17,6 +17,10 @@
color: var(--l2-foreground);
/* Bifrost (Ancient)/Content/sm */
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;

View File

@@ -17,13 +17,13 @@
pre {
color: var(--l2-foreground) !important;
font-family: var(--code-small-400-font-family) !important;
font-family: 'Geist Mono' !important;
font-size: 12px !important;
}
code {
color: var(--l1-foreground) !important;
font-family: var(--code-small-400-font-family) !important;
font-family: 'Geist Mono' !important;
font-size: 12px !important;
}
}

View File

@@ -1,3 +1,36 @@
.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;
@@ -10,11 +43,32 @@
margin-bottom: var(--spacing-4);
> label {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
letter-spacing: -0.07px;
}
}
&__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);
@@ -23,7 +77,22 @@
}
&__helper {
font-size: var(--paragraph-small-400-font-size);
color: var(--l3-foreground);
margin: calc(var(--spacing-2) * -1) 0 var(--spacing-4) 0;
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

@@ -5,9 +5,9 @@ import { Button } from '@signozhq/ui/button';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardContent';
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { Dialog } from '@signozhq/ui/dialog';
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,
@@ -35,7 +35,7 @@ function CreateServiceAccountModal(): JSX.Element {
);
const [, setSelectedAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
const { showErrorModal } = useErrorModal();
const { showErrorModal, isErrorModalVisible } = useErrorModal();
const {
control,
@@ -82,75 +82,84 @@ function CreateServiceAccountModal(): JSX.Element {
}
return (
<Dialog
<DialogWrapper
title="New Service Account"
open={isOpen}
onClose={handleClose}
width="base"
onOpenChange={(open): void => {
if (!open) {
handleClose();
}
}}
showCloseButton
width="narrow"
className="create-sa-modal"
disableOutsideClick={isErrorModalVisible}
testId="create-service-account-modal"
footerSuffix={
<>
<Button
size="md"
type="button"
variant="solid"
color="secondary"
onClick={handleClose}
testId="create-sa-cancel-btn"
prefix={<X size={12} />}
>
Cancel
</Button>
<AuthZButton
size="md"
checks={[SACreatePermission]}
type="submit"
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
testId="create-sa-submit-btn"
>
Create Service Account
</AuthZButton>
</>
}
>
<AuthZGuardContent
checks={[SACreatePermission]}
fallbackOnLoading={<Skeleton active paragraph={{ rows: 1 }} />}
>
<form
id="create-sa-form"
className="create-sa-form"
onSubmit={handleSubmit(handleCreate)}
<div className="create-sa-modal__content">
<AuthZGuardContent
checks={[SACreatePermission]}
fallbackOnLoading={<Skeleton active paragraph={{ rows: 1 }} />}
>
<div className="create-sa-form__item">
<label htmlFor="sa-name">Name</label>
<Controller
name="name"
control={control}
rules={{ required: 'Name is required' }}
render={({ field }): JSX.Element => (
<Input
id="sa-name"
placeholder="Enter a name"
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
testId="create-sa-name-input"
/>
<form
id="create-sa-form"
className="create-sa-form"
onSubmit={handleSubmit(handleCreate)}
>
<div className="create-sa-form__item">
<label htmlFor="sa-name">Name</label>
<Controller
name="name"
control={control}
rules={{ required: 'Name is required' }}
render={({ field }): 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"
/>
)}
/>
{errors.name && (
<p className="create-sa-form__error">{errors.name.message}</p>
)}
/>
{errors.name && (
<p className="create-sa-form__error">{errors.name.message}</p>
)}
</div>
</form>
</AuthZGuardContent>
</Dialog>
</div>
</form>
</AuthZGuardContent>
</div>
<DialogFooter className="create-sa-modal__footer">
<Button
size="md"
type="button"
variant="solid"
color="secondary"
onClick={handleClose}
testId="create-sa-cancel-btn"
prefix={<X size={12} />}
>
Cancel
</Button>
<AuthZButton
size="md"
checks={[SACreatePermission]}
type="submit"
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
testId="create-sa-submit-btn"
>
Create Service Account
</AuthZButton>
</DialogFooter>
</DialogWrapper>
);
}

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
import {
setupAuthzAdmin,
setupAuthzDenyAll,
@@ -10,9 +10,9 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
}));
const mockToast = jest.mocked(toast);
@@ -23,6 +23,7 @@ jest.mock('providers/ErrorModalProvider', () => ({
...jest.requireActual('providers/ErrorModalProvider'),
useErrorModal: jest.fn(() => ({
showErrorModal,
isErrorModalVisible: false,
})),
}));
@@ -57,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-field');
await screen.findByTestId('create-sa-name-input');
await waitFor(() =>
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
@@ -71,7 +72,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const nameInput = await screen.findByTestId('create-sa-name-input');
const submitBtn = await screen.findByTestId('create-sa-submit-btn');
await user.type(nameInput, 'test');
@@ -89,7 +90,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const nameInput = await screen.findByTestId('create-sa-name-input');
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -127,7 +128,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId<HTMLInputElement>(
'create-sa-name-input-field',
'create-sa-name-input',
);
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -159,7 +160,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const nameInput = await screen.findByTestId('create-sa-name-input');
await user.type(nameInput, 'Dupe Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -208,9 +209,7 @@ describe('CreateServiceAccountModal', () => {
screen.findByText(/is not authorized to perform/i),
).resolves.toBeInTheDocument();
expect(
screen.queryByTestId('create-sa-name-input-field'),
).not.toBeInTheDocument();
expect(screen.queryByTestId('create-sa-name-input')).not.toBeInTheDocument();
expect(
screen.getByTestId('create-service-account-modal'),
).toBeInTheDocument();
@@ -245,7 +244,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const nameInput = await screen.findByTestId('create-sa-name-input');
await user.type(nameInput, 'Bot');
await user.clear(nameInput);

View File

@@ -65,6 +65,8 @@
.valid-format-error {
margin-top: 4px;
color: var(--bg-cherry-400) !important;
font-size: 13px !important;
font-weight: 400 !important;
}
.info-text {
@@ -74,6 +76,11 @@
padding: 4px;
cursor: default;
color: var(--l2-foreground) !important;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
}
.info-text:hover {
@@ -97,11 +104,15 @@
gap: 6px;
.timezone__name {
font-size: 12px;
line-height: 16px;
color: var(--bg-robin-400);
font-weight: 500;
}
.timezone__separator {
font-size: 12px;
line-height: 16px;
color: var(--bg-robin-300);
font-weight: 500;
}
@@ -217,6 +228,10 @@
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
color: var(--l1-foreground);
font-size: 12px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.06px;
cursor: pointer;
height: 20px;
width: 20px;

View File

@@ -531,7 +531,7 @@ function CustomTimePicker({
// Use setTimeout to wait for React to update the DOM and make input editable
setTimeout(() => {
const inputElement = inputRef.current?.input;
if (inputElement && !document.activeElement?.closest('.timezone-picker')) {
if (inputElement) {
inputElement.focus();
inputElement.select();
}

View File

@@ -21,6 +21,7 @@ $item-spacing: 8px;
.timezone-picker {
width: 532px;
color: var(--l2-foreground);
font-family: $font-family;
&__search {
@include flex-center;
@@ -35,6 +36,34 @@ $item-spacing: 8px;
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);
}
}
&__esc-key {
@include text-style-base;
font-size: 8px;

View File

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

View File

@@ -12,4 +12,5 @@
.name {
color: var(--danger-background);
font-weight: var(--font-weight-medium);
}

View File

@@ -1,4 +1,7 @@
.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;
@@ -9,6 +12,7 @@
background: var(--l2-background);
&__title {
font-size: 13px;
font-weight: 500;
color: var(--l1-foreground);
flex: 1;

View File

@@ -0,0 +1,17 @@
.drawer {
overflow: hidden;
border-color: var(--l2-border);
background: var(--l2-background);
box-shadow: -4px 10px 16px 2px var(--l1-shadow);
&:focus,
&:focus-visible {
outline: none;
}
}
.body {
display: flex;
flex-direction: column;
height: 100%;
}

View File

@@ -1,49 +1,56 @@
import { ReactNode } from 'react';
import { Drawer } from '@signozhq/ui/drawer';
import { Drawer, DrawerContent } from '@signozhq/ui/drawer';
import cx from 'classnames';
import { AnimatePresence } from 'motion/react';
import styles from './DetailsPanelDrawer.module.scss';
interface DetailsPanelDrawerProps {
isOpen: boolean;
onClose: () => void;
title: string;
children: ReactNode;
headerPrefix?: ReactNode;
headerSuffix?: ReactNode;
loading?: boolean;
children: React.ReactNode;
className?: string;
testId?: string;
}
function DetailsPanelDrawer({
isOpen,
onClose,
title,
children,
headerPrefix,
headerSuffix,
loading,
className,
testId,
}: DetailsPanelDrawerProps): JSX.Element {
return (
<Drawer
type="inspector"
stack="overlay"
open={isOpen}
onClose={onClose}
title={title}
width="extra-wide"
headerPrefix={headerPrefix}
headerSuffix={headerSuffix}
loading={loading}
testId={testId}
onOpenChange={(open): void => {
if (!open) {
onClose();
}
}}
modal={false}
>
{children}
<AnimatePresence>
{isOpen && (
<DrawerContent
forceMount
direction="right"
width="extra-wide"
showOverlay={false}
// Outside presses are left to the page, which knows what counts as inside.
onInteractOutside={(e): void => e.preventDefault()}
className={cx(styles.drawer, className)}
testId={testId}
>
<div className={styles.body}>{children}</div>
</DrawerContent>
)}
</AnimatePresence>
</Drawer>
);
}
DetailsPanelDrawer.defaultProps = {
headerPrefix: undefined,
headerSuffix: undefined,
loading: false,
className: undefined,
testId: undefined,
};

View File

@@ -68,17 +68,7 @@ export default function DownloadOptionsMenu({
aria-modal="true"
>
<div className="export-format">
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
FORMAT
</Typography>
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup
color="primary"
value={exportFormat}
@@ -93,17 +83,7 @@ export default function DownloadOptionsMenu({
<div className="horizontal-line" />
<div className="row-limit">
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
Number of Rows
</Typography>
<Typography.Text className="title">Number of Rows</Typography.Text>
<RadioGroup
color="primary"
value={String(rowLimit)}
@@ -121,17 +101,7 @@ export default function DownloadOptionsMenu({
<div className="horizontal-line" />
<div className="columns-scope">
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
Columns
</Typography>
<Typography.Text className="title">Columns</Typography.Text>
<RadioGroup
color="primary"
value={columnsScope}

View File

@@ -1,5 +1,6 @@
import { Trash2, X } from '@signozhq/icons';
import { AlertDialog } from '@signozhq/ui/alert-dialog';
import { Button } from '@signozhq/ui/button';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { MemberRow } from 'components/MembersTable/MembersTable';
interface DeleteMemberDialogProps {
@@ -8,7 +9,7 @@ interface DeleteMemberDialogProps {
member: MemberRow | null;
isDeleting: boolean;
onClose: () => void;
onConfirm: () => Promise<void>;
onConfirm: () => void;
}
function DeleteMemberDialog({
@@ -35,20 +36,47 @@ function DeleteMemberDialog({
</>
);
const footer = (
<>
<Button
size="md"
variant="solid"
color="secondary"
onClick={onClose}
prefix={<X size={12} />}
>
Cancel
</Button>
<Button
size="md"
variant="solid"
color="danger"
onClick={onConfirm}
testId="member-delete-confirm-btn"
loading={isDeleting}
prefix={<Trash2 size={12} />}
>
{isDeleting ? 'Processing...' : title}
</Button>
</>
);
return (
<AlertDialog
<DialogWrapper
open={open}
onOpenChange={(isOpen): void => {
if (!isOpen) {
onClose();
}
}}
title={title}
color="danger"
confirmText={isDeleting ? 'Processing...' : title}
confirmIcon={<Trash2 size={12} />}
cancelIcon={<X size={12} />}
onConfirm={onConfirm}
onCancel={onClose}
testId="member-delete"
width="narrow"
showCloseButton={false}
disableOutsideClick={false}
footer={footer}
>
{body}
</AlertDialog>
</DialogWrapper>
);
}

View File

@@ -20,9 +20,10 @@
}
&__label {
font-size: var(--paragraph-base-400-font-size);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
color: var(--foreground);
line-height: var(--paragraph-base-400-line-height);
line-height: var(--line-height-20);
letter-spacing: -0.07px;
cursor: default;
}
@@ -52,8 +53,10 @@
}
&__email-text {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
line-height: var(--paragraph-small-400-line-height);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
white-space: nowrap;
overflow: hidden;
@@ -83,26 +86,41 @@
[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;
}
}
&__meta-label {
font-size: var(--uppercase-small-500-font-size);
font-weight: var(--uppercase-small-500-font-weight);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
color: var(--foreground);
line-height: var(--uppercase-small-500-line-height);
line-height: var(--line-height-20);
letter-spacing: 0.48px;
text-transform: uppercase;
}
&__footer {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
&__footer-left {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
&__footer-right {
display: flex;
align-items: center;
gap: var(--spacing-6);
}
&__footer-divider {
width: 1px;
height: 21px;
@@ -145,6 +163,14 @@
}
.reset-link-dialog {
background: var(--l2-background);
border: 1px solid var(--l1-border);
[data-slot='dialog-header'] {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
&__content {
display: flex;
flex-direction: column;
@@ -152,6 +178,8 @@
}
&__description {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--l2-foreground);
line-height: var(--paragraph-base-400-line-height);
letter-spacing: -0.065px;
@@ -183,8 +211,10 @@
overflow: hidden;
text-overflow: ellipsis;
padding: 0 var(--padding-2);
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--l2-foreground);
line-height: var(--paragraph-small-400-line-height);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
}
}

View File

@@ -3,9 +3,9 @@ import { useCopyToClipboard } from 'react-use';
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Drawer } from '@signozhq/ui/drawer';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
@@ -210,7 +210,7 @@ function EditMemberDrawer({
const { mutateAsync: updateMyUser } = useUpdateMyUserV2();
const { mutateAsync: updateUser } = useUpdateUser();
const { mutateAsync: deleteUser, isLoading: isDeleting } = useDeleteUser({
const { mutate: deleteUser, isLoading: isDeleting } = useDeleteUser({
mutation: {
onSuccess: (): void => {
toast.success(
@@ -378,15 +378,11 @@ function EditMemberDrawer({
onComplete,
]);
const handleDelete = useCallback(async (): Promise<void> => {
const handleDelete = useCallback((): void => {
if (!member) {
return;
}
try {
await deleteUser({ pathParams: { id: member.id } });
} catch {
// `onError` already shows the error modal, and the confirmation stays open.
}
deleteUser({ pathParams: { id: member.id } });
}, [member, deleteUser]);
const handleGenerateResetLink = useCallback(async (): Promise<void> => {
@@ -413,8 +409,7 @@ function EditMemberDrawer({
setShowResetLinkDialog(true);
onClose();
} else {
toast.danger('Failed to generate password reset link', {
action: { label: 'Dismiss' },
toast.error('Failed to generate password reset link', {
position: 'top-right',
});
}
@@ -443,7 +438,7 @@ function EditMemberDrawer({
useEffect(() => {
if (copyState.error) {
toast.danger('Failed to copy link', { action: { label: 'Dismiss' } });
toast.error('Failed to copy link');
}
}, [copyState.error]);
@@ -476,19 +471,20 @@ function EditMemberDrawer({
<label className="edit-member-drawer__label" htmlFor="member-name">
Name
</label>
<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}
/>
<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>
</div>
<div className="edit-member-drawer__field">
@@ -619,88 +615,96 @@ function EditMemberDrawer({
</div>
);
const footerStart = isDeleted ? null : (
<div className="edit-member-drawer__footer-left">
<Button
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
size="md"
onClick={(): void => setShowDeleteConfirm(true)}
testId="member-delete-btn"
disabled={isRootUser || isSelf}
variant="link"
color="danger"
prefix={<Trash2 size={12} />}
>
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
const footer = (
<div className="edit-member-drawer__footer">
{!isDeleted && (
<>
<div className="edit-member-drawer__footer-left">
<Button
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
size="md"
onClick={(): void => setShowDeleteConfirm(true)}
testId="member-delete-btn"
disabled={isRootUser || isSelf}
variant="link"
color="danger"
prefix={<Trash2 size={12} />}
>
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
<div className="edit-member-drawer__footer-divider" />
<Button
disabledTooltip={ROOT_USER_TOOLTIP}
size="md"
onClick={handleGenerateResetLink}
testId="member-reset-link-btn"
disabled={isRootUser}
loading={isGeneratingLink || isLoadingTokenStatus}
variant="link"
color="warning"
prefix={<RefreshCw size={12} />}
>
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
<div className="edit-member-drawer__footer-divider" />
<Button
disabledTooltip={ROOT_USER_TOOLTIP}
size="md"
onClick={handleGenerateResetLink}
testId="member-reset-link-btn"
disabled={isRootUser}
loading={isGeneratingLink || isLoadingTokenStatus}
variant="link"
color="warning"
prefix={<RefreshCw size={12} />}
>
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
</div>
<div className="edit-member-drawer__footer-right">
<Button
size="md"
variant="outlined"
color="secondary"
onClick={handleClose}
prefix={<X size={14} />}
>
Cancel
</Button>
<Button
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : 'No changes to save'}
size="md"
variant="solid"
color="primary"
disabled={!isDirty || isRootUser}
onClick={handleSave}
testId="member-save-btn"
loading={isSaving}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>
</div>
</>
)}
</div>
);
const footerActions = isDeleted ? null : (
<>
<Button
size="md"
variant="outlined"
color="secondary"
onClick={handleClose}
prefix={<X size={14} />}
>
Cancel
</Button>
<Button
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : 'No changes to save'}
size="md"
variant="solid"
color="primary"
disabled={!isDirty || isRootUser}
onClick={handleSave}
testId="member-save-btn"
loading={isSaving}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>
</>
);
return (
<>
<Drawer
type="task"
stack="overlay"
<DrawerWrapper
open={open}
onClose={handleClose}
onOpenChange={(isOpen): void => {
if (!isOpen) {
handleClose();
}
}}
direction="right"
showCloseButton
showOverlay={false}
title="Member Details"
footer={footer}
width="wide"
footerPrefix={footerStart}
footerSuffix={footerActions}
>
{drawerContent}
</Drawer>
</DrawerWrapper>
<ResetLinkDialog
open={showResetLinkDialog}

View File

@@ -1,6 +1,6 @@
import { Check, Copy } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Dialog } from '@signozhq/ui/dialog';
import { DialogWrapper } from '@signozhq/ui/dialog';
interface ResetLinkDialogProps {
open: boolean;
@@ -22,11 +22,17 @@ function ResetLinkDialog({
onCopy,
}: ResetLinkDialogProps): JSX.Element {
return (
<Dialog
<DialogWrapper
open={open}
onClose={onClose}
onOpenChange={(isOpen): void => {
if (!isOpen) {
onClose();
}
}}
title={linkType === 'invite' ? 'Invite Link' : 'Password Reset Link'}
showCloseButton
width="base"
className="reset-link-dialog"
>
<div className="reset-link-dialog__content">
<p className="reset-link-dialog__description">
@@ -59,7 +65,7 @@ function ResetLinkDialog({
</p>
)}
</div>
</Dialog>
</DialogWrapper>
);
}

View File

@@ -1,5 +1,5 @@
import type { ReactNode } from 'react';
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
useCreateResetPasswordToken,
@@ -41,31 +41,52 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
jest.mock('@signozhq/ui/drawer', () => ({
...jest.requireActual('@signozhq/ui/drawer'),
Drawer: ({
DrawerWrapper: ({
children,
footerPrefix,
footerSuffix,
footer,
open,
}: {
children?: ReactNode;
footerPrefix?: ReactNode;
footerSuffix?: ReactNode;
footer?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footerPrefix}
{footerSuffix}
{footer}
</div>
) : null,
}));
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
jest.mock('@signozhq/ui/dialog', () => ({
...jest.requireActual('@signozhq/ui/dialog'),
DialogWrapper: ({
children,
footer,
open,
title,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
title?: string;
}): JSX.Element | null =>
open ? (
<div role="dialog" aria-label={title}>
{children}
{footer}
</div>
) : null,
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
<div>{children}</div>
),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: {
success: jest.fn(),
danger: jest.fn(),
error: jest.fn(),
},
}));
@@ -188,7 +209,7 @@ describe('EditMemberDrawer', () => {
isLoading: false,
});
(useDeleteUser as jest.Mock).mockReturnValue({
mutateAsync: mockDeleteMutate,
mutate: mockDeleteMutate,
isLoading: false,
});
// Token query: valid token for invited members
@@ -352,7 +373,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutateAsync: mockDeleteMutate.mockImplementation(() => {
mutate: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onSuccess?.();
}),
isLoading: false,
@@ -432,7 +453,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutateAsync: mockDeleteMutate.mockImplementation(() => {
mutate: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onSuccess?.();
}),
isLoading: false,
@@ -546,7 +567,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutateAsync: mockDeleteMutate.mockImplementation(() => {
mutate: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onError?.({});
}),
isLoading: false,
@@ -577,7 +598,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutateAsync: mockDeleteMutate.mockImplementation(() => {
mutate: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onError?.({});
}),
isLoading: false,

View File

@@ -1,16 +1,22 @@
// The mask of the error modal blurs the page and a dialog opened under it, and the
// dialog's own backdrop would otherwise dim the error modal that sits above the page.
body:has(.error-modal__wrap:not([style*='display: none'])) {
--dialog-backdrop-color: transparent;
--alert-dialog-backdrop-color: transparent;
}
.error-modal {
&__trigger {
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(
@@ -59,6 +65,7 @@ body:has(.error-modal__wrap:not([style*='display: none'])) {
&__value {
padding: 4px 8px;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.48px;

View File

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

View File

@@ -38,6 +38,7 @@
margin: 0;
color: var(--l2-foreground);
font-size: 14px;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
@@ -48,6 +49,8 @@
gap: 6px;
padding: 9px 12.5px;
color: var(--l2-foreground);
font-size: 12px;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: 0.12px;
border-radius: 2px;
@@ -76,6 +79,7 @@
color: var(--l2-foreground);
font-size: 12px;
font-weight: 500;
line-height: 18px; /* 150% */
letter-spacing: 0.48px;
pointer-events: none;
}
@@ -94,6 +98,7 @@
color: var(--l1-foreground);
font-size: 10px;
font-weight: 500;
line-height: 18px; /* 180% */
letter-spacing: 0.5px;
}
}
@@ -128,12 +133,16 @@
position: relative;
margin-bottom: 4px;
color: var(--l2-foreground);
font-family: var(--code-base-400-font-family);
font-family: Geist Mono;
font-size: 12px;
font-weight: 400;
line-height: 18px;
padding: 3px 12px;
padding-left: 26px;
}
&__message-item::before {
font-family: unset;
content: '';
position: absolute;
left: 12px;

View File

@@ -1,12 +1,12 @@
.export-menu-popover {
// The popover shell carries no padding of its own, so the content owns it.
padding: var(--spacing-8);
width: 240px;
padding: 0 12px 12px 12px;
.title {
display: flex;
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--uppercase-small-500-font-size);
font-size: var(--periscope-font-size-small);
font-style: normal;
font-weight: 500;
line-height: 18px;
@@ -23,7 +23,7 @@
// radio option labels — radix popover content inherits the root font
// size; pin to the app's 13px base the antd popover used to impose
label {
font-size: var(--label-base-400-font-size);
font-size: var(--periscope-font-size-base);
}
}
}

View File

@@ -1,6 +1,6 @@
import { Download } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Popover, usePopoverHandle } from '@signozhq/ui/popover';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { RadioGroup } from '@signozhq/ui/radio-group';
import { Tooltip } from '@signozhq/ui/tooltip';
import { Typography } from '@signozhq/ui/typography';
@@ -36,10 +36,6 @@ 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,
@@ -49,72 +45,53 @@ export default function ExportMenu({
});
const handleExport = useCallback((): void => {
menu.close();
setIsPopoverOpen(false);
handleClientExport({ format: exportFormat as ExportFormat });
}, [menu, exportFormat, handleClientExport]);
}, [exportFormat, handleClientExport]);
return (
<>
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<Tooltip title="Download">
<Button
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Download"
aria-haspopup="dialog"
aria-expanded={isPopoverOpen}
testId={`export-menu-${dataSource}`}
loading={isExporting}
onClick={(event): void => menu.showAt(event.currentTarget)}
>
<Download size={14} />
</Button>
</Tooltip>
<Popover
side="bottom"
align="end"
width={240}
handle={menu}
aria-label="Export format"
>
<div className="export-menu-popover">
<div className="export-format">
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
FORMAT
</Typography>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: ExportFormat.Csv, label: 'csv' },
{ value: ExportFormat.Jsonl, label: 'jsonl' },
]}
/>
</div>
<PopoverTrigger asChild>
<Button
size="md"
variant="solid"
color="primary"
width="100%"
onClick={handleExport}
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Download"
testId={`export-menu-${dataSource}`}
loading={isExporting}
prefix={<Download size={16} />}
>
Export
<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>
</Popover>
</>
<Button
size="md"
variant="solid"
color="primary"
width="100%"
onClick={handleExport}
loading={isExporting}
prefix={<Download size={16} />}
>
Export
</Button>
</PopoverContent>
</Popover>
);
}

View File

@@ -117,16 +117,9 @@ function AddedFields({
<div className={styles.sectionHeader}>
<span>ADDED FIELDS</span>
{maxFields !== undefined && (
<Typography
variant="paragraph"
size="small"
weight="medium"
color="secondary"
as="span"
textOverflow="wrap"
>
<Typography.Text size="sm" weight="medium" color="muted">
Max Allowed: {maxFields}
</Typography>
</Typography.Text>
)}
</div>
<div className={styles.addedList}>

View File

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

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from 'react';
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
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 className={styles.searchInput}>
<section>
<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/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
}));
// FloatingPanel is a react-rnd/portal shell — presentation only. Render its

View File

@@ -4,16 +4,9 @@ function AnnouncementsModal(): JSX.Element {
return (
<div className="announcements-modal-container">
<div className="announcements-modal-container-header">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text className="announcements-modal-title">
Announcements
</Typography>
</Typography.Text>
</div>
</div>
);

View File

@@ -1,9 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import { useAppLocation } from 'lib/router/useAppLocation';
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
import { Button, Input } from 'antd';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Button as SignozButton } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
@@ -40,8 +39,7 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
})
.catch(() => {
console.error(`Failed to submit ${entityName}`);
toast.danger(`Failed to submit ${entityName}`, {
action: { label: 'Dismiss' },
toast.error(`Failed to submit ${entityName}`, {
position: 'top-right',
});
})
@@ -138,23 +136,14 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
Submit
</Button>
<div className="feedback-modal-content-footer-info-text">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text>
Have a specific issue?{' '}
<SignozButton
variant="link"
color="primary"
size="sm"
<Typography.Link
className="contact-support-link"
onClick={handleContactSupportClick}
>
Contact Support{' '}
</SignozButton>
</Typography.Link>
or{' '}
<a
href="https://signoz.io/docs/introduction/"
@@ -164,7 +153,7 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
>
Read our docs
</a>
</Typography>
</Typography.Text>
</div>
</div>
</div>

View File

@@ -68,6 +68,11 @@
.absolute-relative-time-toggler-label {
color: var(--l1-foreground);
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}

View File

@@ -128,16 +128,7 @@ function HeaderRightSection({
}
prefix={<Noz size={20} />}
>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
Noz
</Typography>
<Typography.Text>Noz</Typography.Text>
</Button>
</Tooltip>
</span>

View File

@@ -141,16 +141,9 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
{(shareURLWithTime || isRouteToBeSharedWithTime) && (
<>
<div className="absolute-relative-time-toggler-container">
<Typography
variant="paragraph"
size="base"
weight="medium"
color="main"
as="span"
textOverflow="wrap"
>
<Typography.Text className="absolute-relative-time-toggler-label">
Enable absolute time
</Typography>
</Typography.Text>
<div className="absolute-relative-time-toggler">
{!isValidateRelativeTime && (
@@ -179,16 +172,9 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
{extraOption && (
<div className="absolute-relative-time-toggler-container">
<Typography
variant="paragraph"
size="base"
weight="medium"
color="main"
as="span"
textOverflow="wrap"
>
<Typography.Text className="absolute-relative-time-toggler-label">
{extraOption.label}
</Typography>
</Typography.Text>
<div className="absolute-relative-time-toggler">
<Switch
color="primary"
@@ -203,26 +189,12 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
<div className="share-link">
<div className="url-share-container">
<div className="url-share-container-header">
<Typography
variant="paragraph"
size="base"
weight="medium"
color="main"
as="span"
textOverflow="wrap"
>
<Typography.Text className="url-share-title">
Share page link
</Typography>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="tertiary"
as="span"
textOverflow="wrap"
>
</Typography.Text>
<Typography.Text className="url-share-sub-title">
Share the current page link with your team member
</Typography>
</Typography.Text>
</div>
<Button

View File

@@ -1,6 +1,6 @@
// Mock dependencies before imports
import { useAppLocation } from 'lib/router/useAppLocation';
import { toast } from '@signozhq/ui/toast';
import { toast } from '@signozhq/ui/sonner';
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/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: {
success: jest.fn(),
danger: jest.fn(),
error: jest.fn(),
},
}));
@@ -49,7 +49,7 @@ describe('FeedbackModal', () => {
isCloudUser: false,
});
mockToast.success.mockClear();
mockToast.danger.mockClear();
mockToast.error.mockClear();
});
it('should render feedback modal with all tabs', () => {

View File

@@ -27,7 +27,7 @@
}
.infra-container-card-text {
font-size: var(--paragraph-base-400-font-size);
font-size: var(--font-size-sm);
color: var(--l2-foreground);
line-height: 20px;
letter-spacing: -0.07px;

View File

@@ -27,7 +27,7 @@
}
.infra-container-card-text {
font-size: var(--paragraph-base-400-font-size);
font-size: var(--font-size-sm);
color: var(--l2-foreground);
line-height: 20px;
letter-spacing: -0.07px;

View File

@@ -52,16 +52,9 @@ export default function WaitlistFragment({
return (
<div className="wait-list-container">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
<Typography.Text className="wait-list-text">
{t('waitlist_message')}
</Typography>
</Typography.Text>
<Button
className="periscope-btn join-waitlist-btn"

View File

@@ -4,9 +4,6 @@
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.
@@ -34,41 +31,66 @@
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-height: 36px;
--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-within {
&:focus {
z-index: 1;
}
.ant-select-selector {
position: relative;
border-radius: inherit;
}
.ant-select:hover .ant-select-selector,
.ant-select-focused .ant-select-selector {
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;
}
}
// The Input has no token for the native number spinners.
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
&::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 {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@@ -92,7 +114,16 @@
&.labelAfter {
.input {
--input-border-radius: 2px 0 0 2px;
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;
}
}
.label {

View File

@@ -49,51 +49,21 @@ function InputWithLabel({
labelAfter,
})}
>
{!labelAfter && (
<div className="label">
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="p"
textOverflow="ellipsis"
>
{label}
</Typography>
</div>
)}
<div
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
>
<Input
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
disabledTooltip={undefined}
name={label.toLowerCase()}
testId={`input-${label}`}
/>
</div>
{labelAfter && (
<div className="label">
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="p"
textOverflow="ellipsis"
>
{label}
</Typography>
</div>
)}
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
name={label.toLowerCase()}
data-testid={`input-${label}`}
/>
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (
<Button
className="periscope-btn ghost close-btn"

View File

@@ -55,12 +55,26 @@
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, SolidCheckCircle2, Trash2 } from '@signozhq/icons';
import { CircleAlert, Plus, Trash2 } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import { Input } from '@signozhq/ui/input';
@@ -68,30 +68,20 @@ function InviteMembers({
<div className={styles.table}>
{showHeader && (
<div className={styles.header}>
<span className={styles.headerCellEmail}>
<Typography
variant="paragraph"
size="base"
weight="bold"
color="inherit"
as="span"
textOverflow="wrap"
>
Email address
</Typography>
</span>
<span className={styles.headerCellRole}>
<Typography
variant="paragraph"
size="base"
weight="bold"
color="inherit"
as="span"
textOverflow="wrap"
>
Roles
</Typography>
</span>
<Typography.Text
size="base"
weight="semibold"
className={styles.headerCellEmail}
>
Email address
</Typography.Text>
<Typography.Text
size="base"
weight="semibold"
className={styles.headerCellRole}
>
Roles
</Typography.Text>
<div className={styles.headerCellAction} />
</div>
)}
@@ -107,19 +97,12 @@ function InviteMembers({
onChange={(e): void => updateEmail(row.id, e.target.value)}
name={`invite-email-${row.id}`}
autoComplete="email"
testId={`invite-email-${row.id}`}
data-testid={`invite-email-${row.id}`}
/>
{emailValidity[row.id] === false && row.email.trim() !== '' && (
<Typography
variant="paragraph"
size="small"
weight="regular"
color="danger"
as="span"
textOverflow="wrap"
>
<Typography.Text size="small" className={styles.errorText}>
Invalid email address
</Typography>
</Typography.Text>
)}
</div>
@@ -169,90 +152,61 @@ function InviteMembers({
</div>
{hasValidationErrors && (
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
</div>
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
)}
{hasResults && hasFailures && (
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{successResults.length} invite(s) sent successfully.
</Typography>
)}
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{failedResults.length} invite(s) failed:
</Typography>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{result.email}: {result.error}
</Typography>
</li>
))}
</ul>
</div>
</Callout>
</div>
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-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">
{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>
)}
{hasResults && !hasFailures && hasSuccesses && (
<div className={styles.callout}>
<Callout
color="success"
size="sm"
icon={<SolidCheckCircle2 />}
testId="invite-success"
>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{successResults.length} invite(s) sent successfully!
</Typography>
</Callout>
</div>
<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>
)}
{renderFooter?.({

View File

@@ -25,6 +25,10 @@
}
.filtersLabel {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--l2-foreground);
margin-right: 2px;
}
@@ -103,6 +107,6 @@
}
.error {
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
color: var(--danger-foreground);
}

View File

@@ -116,31 +116,15 @@ function FilterZone({
{rightSlot}
</div>
{error && (
<Typography
variant="paragraph"
size="small"
weight="regular"
color="danger"
as="span"
textOverflow="wrap"
<Typography.Text
className={styles.error}
testId={`${testIdPrefix}-query-error`}
>
{error}
</Typography>
</Typography.Text>
)}
<div className={styles.filtersRow}>
<Typography
variant="uppercase"
size="small"
weight="medium"
color="secondary"
as="span"
display="inline-block"
marginRight={1}
textOverflow="wrap"
>
Filters
</Typography>
<Typography.Text className={styles.filtersLabel}>Filters</Typography.Text>
{wrapControl(
<div
className={styles.filterControls}

View File

@@ -25,7 +25,7 @@
:global(.cm-dsl-logic) {
color: var(--bg-sakura-400);
font-weight: var(--label-base-500-font-weight);
font-weight: var(--font-weight-medium);
}
:global(.cm-dsl-string) {
@@ -59,7 +59,7 @@
content: 'SUGGESTIONS';
padding: 12px 14px 8px;
font-size: 11px;
font-weight: var(--uppercase-small-500-font-weight);
font-weight: var(--font-weight-medium);
line-height: 18px;
letter-spacing: 0.88px;
text-transform: uppercase;
@@ -71,7 +71,7 @@
padding: 10px 14px;
border-top: 1px solid var(--l1-border);
background: var(--l2-background);
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
color: var(--l2-foreground);
}
@@ -131,7 +131,7 @@
border-radius: 50px;
background: color-mix(in srgb, var(--l1-foreground) 10%, transparent);
font-style: normal;
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
color: var(--l2-foreground);
text-transform: capitalize;
}

View File

@@ -29,15 +29,15 @@
background: var(--l2-background);
border: 1px solid var(--l2-border);
color: var(--l2-foreground);
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
font-variant-numeric: tabular-nums;
}
.title {
color: var(--l1-foreground);
font-size: var(--label-large-500-font-size);
font-size: var(--font-size-lg);
font-style: normal;
font-weight: var(--label-large-500-font-weight);
font-weight: var(--font-weight-medium);
line-height: 28px;
letter-spacing: -0.09px;
overflow: hidden;

View File

@@ -22,18 +22,12 @@ function ListCommandHeader({
return (
<div className={styles.commandHeader}>
<div className={styles.headingBlock}>
<Typography
variant="label"
size="large"
weight="medium"
color="main"
as="h1"
marginY={4}
textOverflow="ellipsis"
<Typography.Title
className={styles.title}
testId={testIdPrefix && `${testIdPrefix}-list-heading`}
>
{label}
</Typography>
</Typography.Title>
{isModified && <span className={styles.dirtyDot} title="Unsaved changes" />}
<span className={styles.countPill}>{count}</span>
</div>

View File

@@ -10,6 +10,6 @@
}
.count {
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
color: var(--l3-foreground);
}

View File

@@ -36,16 +36,9 @@ function ListStatusBar({
>
{collapsed ? 'Expand' : 'Collapse'}
</Button>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="tertiary"
as="span"
textOverflow="wrap"
>
<Typography.Text className={styles.count}>
{count} of {total} {noun}
</Typography>
</Typography.Text>
</div>
);
}

View File

@@ -24,8 +24,8 @@
.headerTitle {
margin: 0;
font-size: var(--label-medium-600-font-size);
font-weight: var(--label-medium-600-font-weight);
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
letter-spacing: -0.01em;
color: var(--l1-foreground);
text-align: left;
@@ -38,7 +38,7 @@
.searchEmpty {
padding: 12px;
color: var(--l3-foreground);
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
text-align: center;
}
@@ -54,8 +54,8 @@
align-items: center;
gap: 6px;
padding: 16px 8px 8px;
font-size: var(--uppercase-small-600-font-size);
font-weight: var(--uppercase-small-600-font-weight);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--l3-foreground);
@@ -68,6 +68,9 @@
}
.groupCount {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-normal);
letter-spacing: 0;
color: var(--l3-foreground);
background: var(--l2-background);
border-radius: 10px;
@@ -76,6 +79,7 @@
.deleteName {
color: var(--danger-background);
font-weight: var(--font-weight-medium);
}
.row {
@@ -157,7 +161,7 @@
border: 1px dashed var(--l2-border);
border-radius: 8px;
color: var(--l3-foreground);
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
line-height: 1.5;
text-align: center;
}
@@ -174,8 +178,8 @@
}
.dirtyTitle {
font-size: var(--uppercase-small-600-font-size);
font-weight: var(--uppercase-small-600-font-weight);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--l2-foreground);

View File

@@ -72,6 +72,7 @@ function ViewsRail({
onViewRenamed,
onViewDeleted,
}: Props): JSX.Element {
const [saveOpen, setSaveOpen] = useState(false);
const [query, setQuery] = useState('');
const { contextHolder, confirmDelete } = useDeleteConfirm();
@@ -93,28 +94,11 @@ function ViewsRail({
(id: string, label: string): void => {
confirmDelete({
title: (
<Typography
variant="paragraph"
size="medium"
weight="bold"
color="inherit"
as="h5"
marginY={4}
textOverflow="wrap"
>
<Typography.Title level={5}>
Delete the{' '}
<Typography
variant="paragraph"
size="base"
weight="medium"
color="danger"
as="span"
textOverflow="wrap"
>
{label}
</Typography>{' '}
<Typography.Text className={styles.deleteName}>{label}</Typography.Text>{' '}
view?
</Typography>
</Typography.Title>
),
content: deleteDescription,
// Return the delete promise so the modal's Delete button stays loading
@@ -165,12 +149,14 @@ function ViewsRail({
<div className={styles.header}>
<h4 className={styles.headerTitle}>Views</h4>
<ViewNamePopover
open={saveOpen}
onOpenChange={setSaveOpen}
onSubmit={handleSaveAsView}
title="Save as view"
confirmLabel="Save view"
testIdPrefix={saveTestIdPrefix}
tooltip="Save current filters as a view"
trigger={(open): JSX.Element => (
trigger={
<Button
aria-label="Save current filters as a view"
variant="ghost"
@@ -178,11 +164,10 @@ function ViewsRail({
size="sm"
icon
testId={`${testIdPrefix}-view-save-trigger`}
onClick={(e): void => open(e.currentTarget)}
>
<Plus size={14} />
</Button>
)}
}
/>
</div>
@@ -219,18 +204,9 @@ function ViewsRail({
<>
<div className={cx(styles.groupLabel, styles.groupLabelSpaced)}>
{customSectionLabel}
<span className={styles.groupCount}>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="tertiary"
as="span"
textOverflow="wrap"
>
{customViews.length}
</Typography>
</span>
<Typography.Text className={styles.groupCount}>
{customViews.length}
</Typography.Text>
</div>
{customViewsLoading ? (
<div className={styles.empty}>Loading views…</div>
@@ -269,9 +245,8 @@ function ViewsRail({
<ViewsRailDirtyPanel
isCustomActive={isCustomActive}
testIdPrefix={testIdPrefix}
saveTestIdPrefix={saveTestIdPrefix}
onSaveChanges={handleSaveViewChanges}
onSaveAs={handleSaveAsView}
onSaveAs={(): void => setSaveOpen(true)}
onReset={onReset}
/>
)}

View File

@@ -1,23 +1,20 @@
import { Button } from '@signozhq/ui/button';
import { Plus } from '@signozhq/icons';
import cx from 'classnames';
import ViewNamePopover from 'components/ViewNamePopover/ViewNamePopover';
import styles from './ViewsRail.module.scss';
interface Props {
isCustomActive: boolean;
testIdPrefix: string;
saveTestIdPrefix: string;
onSaveChanges: () => void;
onSaveAs: (name: string) => void;
onSaveAs: () => void;
onReset: () => void;
}
function ViewsRailDirtyPanel({
isCustomActive,
testIdPrefix,
saveTestIdPrefix,
onSaveChanges,
onSaveAs,
onReset,
@@ -36,24 +33,15 @@ function ViewsRailDirtyPanel({
>
Save
</Button>
<ViewNamePopover
onSubmit={onSaveAs}
title="Save as view"
confirmLabel="Save view"
testIdPrefix={saveTestIdPrefix}
tooltip="Save current filters as a view"
trigger={(open): JSX.Element => (
<Button
variant="outlined"
color="secondary"
size="sm"
onClick={(e): void => open(e.currentTarget)}
testId={`${testIdPrefix}-view-save-as`}
>
Save as…
</Button>
)}
/>
<Button
variant="outlined"
color="secondary"
size="sm"
onClick={onSaveAs}
testId={`${testIdPrefix}-view-save-as`}
>
Save as…
</Button>
<Button
variant="ghost"
color="secondary"
@@ -72,25 +60,16 @@ function ViewsRailDirtyPanel({
<div className={cx(styles.dirtyPanel, styles.dirtyPanelDefault)}>
<div className={styles.dirtyTitle}>Filters active</div>
<div className={styles.dirtyActions}>
<ViewNamePopover
onSubmit={onSaveAs}
title="Save as view"
confirmLabel="Save view"
testIdPrefix={saveTestIdPrefix}
tooltip="Save current filters as a view"
trigger={(open): JSX.Element => (
<Button
variant="solid"
color="primary"
size="sm"
prefix={<Plus size={12} />}
onClick={(e): void => open(e.currentTarget)}
testId={`${testIdPrefix}-view-save-as-new`}
>
Save as new view
</Button>
)}
/>
<Button
variant="solid"
color="primary"
size="sm"
prefix={<Plus size={12} />}
onClick={onSaveAs}
testId={`${testIdPrefix}-view-save-as-new`}
>
Save as new view
</Button>
<Button
variant="ghost"
color="secondary"

View File

@@ -1,3 +1,4 @@
import { useState } from 'react';
import { Button } from '@signozhq/ui/button';
import { Tooltip } from '@signozhq/ui/tooltip';
import { PenLine, Trash2 } from '@signozhq/icons';
@@ -34,6 +35,8 @@ function ViewsRailItem({
onRename,
onDelete,
}: Props): JSX.Element {
const [renaming, setRenaming] = useState(false);
return (
<div className={cx(styles.row, { [styles.rowActive]: active })}>
<Button
@@ -55,13 +58,15 @@ function ViewsRailItem({
{onRename && onDelete && (
<div className={styles.itemActions}>
<ViewNamePopover
open={renaming}
onOpenChange={setRenaming}
onSubmit={(name): void => onRename(id, name)}
title="Rename view"
confirmLabel="Rename"
initialName={label}
testIdPrefix={renameTestIdPrefix}
tooltip="Rename view"
trigger={(open): JSX.Element => (
trigger={
<Button
variant="ghost"
color="secondary"
@@ -69,14 +74,11 @@ function ViewsRailItem({
icon
aria-label="Rename view"
testId={`${testIdPrefix}-view-rename-${id}`}
onClick={(e): void => {
e.stopPropagation();
open(e.currentTarget);
}}
onClick={(e): void => e.stopPropagation()}
>
<PenLine size={12} />
</Button>
)}
}
/>
<Tooltip title="Delete view">
<Button

View File

@@ -63,7 +63,7 @@ describe('ViewsRail', () => {
it('filters views by name and says when none match', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderRail();
const search = screen.getByTestId(`${PREFIX}-view-search-field`);
const search = screen.getByTestId(`${PREFIX}-view-search`);
await user.type(search, 'prod');

View File

@@ -1,13 +1,14 @@
import { MouseEventHandler } from 'react';
import { DrawerProps } from 'antd';
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { VIEWS } from './constants';
import { MouseEventHandler } from 'react';
export type LogDetailProps = {
log: ILog | null;
selectedTab?: VIEWS;
selectedTab: VIEWS;
handleChangeSelectedView?: ChangeViewFunctionType;
isListViewPanel?: boolean;
listViewPanelSelectedFields?: IField[] | null;
@@ -15,19 +16,10 @@ export type LogDetailProps = {
onNavigateLog?: (log: ILog) => void;
onScrollToLog?: (logId: string) => void;
handleOpenInExplorer?: MouseEventHandler;
getContainer?: DrawerProps['getContainer'];
onApplyLogFilter?: (expression: string) => void;
onClose: () => void;
};
} & Pick<DrawerProps, 'onClose'>;
export type LogDetailBodyProps = Pick<
LogDetailProps,
| 'selectedTab'
| 'isListViewPanel'
| 'handleChangeSelectedView'
| 'onApplyLogFilter'
| 'onClose'
> & {
log: ILog;
onPrev: () => void;
onNext: () => void;
export type LogDetailInnerProps = LogDetailProps & {
log: NonNullable<LogDetailProps['log']>;
};

View File

@@ -1,364 +0,0 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@signozhq/ui/button';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
import { Tooltip } from 'antd';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import LogStateIndicator from 'components/Logs/LogStateIndicator/LogStateIndicator';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
import { convertExpressionToFilters } from 'components/QueryBuilderV2/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
import InfraMetrics from 'container/LogDetailedView/InfraMetrics/InfraMetrics';
import Overview from 'container/LogDetailedView/Overview';
import {
getSanitizedLogBody,
removeEscapeCharacters,
} from 'container/LogDetailedView/utils';
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
import { useOptionsMenu } from 'container/OptionsMenu';
import { FontSize } from 'container/OptionsMenu/types';
import { useArrowKeyNavigation } from 'container/TracesExplorer/SpanDrawer/useArrowKeyNavigation';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getBodyMessage } from 'lib/logs/logBody';
import { cloneDeep } from 'lodash-es';
import {
ArrowDown,
ArrowUp,
Filter,
Histogram,
Table,
TextSelect,
} from '@signozhq/icons';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
import { LogDetailBodyProps } from './LogDetail.interfaces';
import LogHighlights from './LogHighlights/LogHighlights';
/* eslint-disable-next-line sonarjs/cognitive-complexity */
function LogDetailBody({
log,
onClose,
selectedTab = VIEW_TYPES.OVERVIEW,
isListViewPanel = false,
handleChangeSelectedView,
onApplyLogFilter,
onPrev,
onNext,
}: LogDetailBodyProps): JSX.Element {
const initialContextQuery = useInitialQuery(log);
const [contextQuery, setContextQuery] = useState<Query | undefined>(
initialContextQuery,
);
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
const [isFilterVisible, setIsFilterVisible] = useState<boolean>(false);
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
const { stagedQuery } = useQueryBuilder();
// Handle clicks outside to close drawer, except on explicitly ignored regions
useEffect(() => {
const handleClickOutside = (e: MouseEvent): void => {
const target = e.target as HTMLElement;
// Don't close if clicking on drawer content, overlays, or portal elements
if (
target.closest('[data-log-detail-ignore="true"]') ||
target.closest('.cm-tooltip-autocomplete') ||
target.closest('.drawer-popover') ||
target.closest('.query-status-popover') ||
target.closest('[data-radix-popper-content-wrapper]') ||
target.closest('[data-base-ui-portal]')
) {
return;
}
// Close the drawer for any other outside click
onClose();
};
document.addEventListener('mousedown', handleClickOutside);
return (): void => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [onClose]);
// Only listen in the OVERVIEW tab so we don't hijack arrow keys from the
// JSON editor / context view.
useArrowKeyNavigation({
enabled: selectedView === VIEW_TYPES.OVERVIEW,
onPrev,
onNext,
});
const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) {
return null;
}
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
}, [stagedQuery]);
const { options } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
sourcePage: SavedviewtypesSourceDTO.logs,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});
const handleModeChange = (value: string): void => {
setSelectedView(value as VIEWS);
setIsEdit(false);
setIsFilterVisible(false);
};
const handleFilterVisible = (): void => {
setIsFilterVisible(!isFilterVisible);
setIsEdit(!isEdit);
};
const logBody = useMemo(() => getBodyMessage(log?.body), [log?.body]);
const htmlBody = useMemo(
() => ({
__html: getSanitizedLogBody(logBody || '', { shouldEscapeHtml: true }),
}),
[logBody],
);
const handleQueryExpressionChange = useCallback(
(value: string, queryIndex: number) => {
// update the query at the given index
setContextQuery((prev) => {
if (!prev) {
return prev;
}
return {
...prev,
builder: {
...prev.builder,
queryData: prev.builder.queryData.map((query, idx) =>
idx === queryIndex
? {
...query,
filter: {
...query.filter,
expression: value,
},
}
: query,
),
},
};
});
},
[],
);
const handleRunQuery = (expression: string): void => {
let updatedContextQuery = cloneDeep(contextQuery);
if (!updatedContextQuery || !updatedContextQuery.builder) {
return;
}
const newFilters: TagFilter = {
items: expression ? convertExpressionToFilters(expression) : [],
op: 'AND',
};
updatedContextQuery = {
...updatedContextQuery,
builder: {
...updatedContextQuery?.builder,
queryData: updatedContextQuery?.builder.queryData.map((queryData) => ({
...queryData,
filter: {
...queryData.filter,
expression,
},
filters: {
...queryData.filters,
...newFilters,
op: queryData.filters?.op ?? 'AND',
},
})),
},
};
setContextQuery(updatedContextQuery);
if (newFilters) {
setFilters(newFilters);
}
};
return (
<OverlayScrollbar style={{ height: '100%' }}>
<div className="log-detail-drawer__content">
<div className="log-detail-drawer__log">
<LogStateIndicator
severityText={log.severity_text}
severityNumber={log.severity_number}
fontSize={options?.fontSize ?? FontSize.MEDIUM}
/>
<Tooltip
title={removeEscapeCharacters(logBody)}
placement="left"
mouseLeaveDelay={0}
>
<div
className="log-body"
data-testid="log-detail-body"
dangerouslySetInnerHTML={htmlBody}
/>
</Tooltip>
<div className="log-overflow-shadow">&nbsp;</div>
</div>
<LogHighlights log={log} />
<div className="log-detail-drawer__section-divider" />
<div className="tabs-and-search">
<ToggleGroup
variant="outlined"
color="secondary"
size="sm"
type="single"
testId="log-detail-views-tabs"
onChange={handleModeChange}
value={selectedView}
items={[
{
value: VIEW_TYPES.OVERVIEW,
label: (
<div className="view-title">
<Table size={14} />
Overview
</div>
),
},
{
value: VIEW_TYPES.CONTEXT,
label: (
<div className="view-title">
<TextSelect size={14} />
Context
</div>
),
},
{
value: VIEW_TYPES.INFRAMETRICS,
label: (
<div className="view-title">
<Histogram size="md" />
Metrics
</div>
),
},
]}
/>
<div className="log-detail-drawer__actions">
{selectedView === VIEW_TYPES.CONTEXT && (
<Tooltip
title="Show Filters"
placement="topLeft"
aria-label="Show Filters"
mouseLeaveDelay={0}
>
<Button
variant="link"
color="secondary"
size="sm"
icon
aria-label="Show Filters"
onClick={handleFilterVisible}
>
<Filter size="lg" />
</Button>
</Tooltip>
)}
</div>
</div>
{isFilterVisible && contextQuery?.builder.queryData[0] && (
<div className="log-detail-drawer-query-container">
<QuerySearch
onChange={(value): void => handleQueryExpressionChange(value, 0)}
dataSource={DataSource.LOGS}
queryData={contextQuery?.builder.queryData[0]}
onRun={handleRunQuery}
/>
</div>
)}
{selectedView === VIEW_TYPES.OVERVIEW && (
<Overview
logData={log}
isListViewPanel={isListViewPanel}
handleChangeSelectedView={handleChangeSelectedView}
onApplyLogFilter={onApplyLogFilter}
/>
)}
{selectedView === VIEW_TYPES.CONTEXT && (
<ContextView
log={log}
filters={filters}
contextQuery={contextQuery}
isEdit={isEdit}
/>
)}
{selectedView === VIEW_TYPES.INFRAMETRICS && (
<InfraMetrics
clusterName={log.resources_string?.[RESOURCE_KEYS.CLUSTER_NAME] || ''}
podName={log.resources_string?.[RESOURCE_KEYS.POD_NAME] || ''}
nodeName={log.resources_string?.[RESOURCE_KEYS.NODE_NAME] || ''}
hostName={log.resources_string?.[RESOURCE_KEYS.HOST_NAME] || ''}
timestamp={log.timestamp.toString()}
dataSource={DataSource.LOGS}
/>
)}
{selectedView === VIEW_TYPES.OVERVIEW && (
<div className="log-detail-drawer__footer-hint">
<div className="log-detail-drawer__footer-hint-content">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
Use
</Typography>
<ArrowUp size={14} className="log-detail-drawer__footer-hint-icon" />
<span>/</span>
<ArrowDown size={14} className="log-detail-drawer__footer-hint-icon" />
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
to view previous/next log
</Typography>
</div>
</div>
)}
</div>
</OverlayScrollbar>
);
}
export default LogDetailBody;

View File

@@ -1,17 +1,79 @@
[data-testid='log-detail-drawer'] {
--drawer-extra-wide-max-inline-size: 60vw;
--drawer-body-padding: 0 var(--spacing-8);
// OverlayScrollbar owns the scroll. A native scrollbar here changes the
// body width when it toggles and shifts the header grid.
--drawer-body-overflow-y: hidden;
}
.log-detail-drawer {
border-left: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
.log-detail-drawer-query-container {
margin-bottom: 16px;
}
.log-detail-drawer__title {
display: flex;
justify-content: space-between;
align-items: center;
.log-detail-drawer__content {
min-height: 100%;
.log-detail-drawer__title-left {
display: flex;
align-items: center;
gap: 8px;
}
.log-detail-drawer__title-right {
display: flex;
align-items: center;
.ant-btn {
display: flex;
align-items: center;
}
}
}
.ant-drawer-header {
padding: 8px 16px;
border-bottom: none;
align-items: stretch;
border-bottom: 1px solid var(--l1-border);
background: var(--l2-background);
}
.ant-drawer-close {
margin-inline-end: 0px;
}
.ant-drawer-body {
display: flex;
flex-direction: column;
padding: 0 16px;
// OverlayScrollbar owns the scroll. A native scrollbar here changes the
// body width when it toggles and shifts the header grid.
overflow: hidden;
}
.title {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.radio-button {
display: flex;
align-items: center;
justify-content: center;
padding-top: var(--padding-1);
border: 1px solid var(--l1-border);
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
&-query-container {
margin-bottom: 16px;
}
.log-detail-drawer__content {
min-height: 100%;
}
.log-detail-drawer__log {
width: 100%;
@@ -23,9 +85,10 @@
.log-body {
font-family: 'SF Mono';
font-family: var(--code-base-400-font-family);
font-family: 'Geist Mono';
font-size: var(--code-base-400-font-size);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
line-height: 18px;
letter-spacing: -0.07px;
white-space: nowrap;
@@ -55,11 +118,7 @@
}
.log-overflow-shadow {
background: linear-gradient(
270deg,
var(--drawer-background) 10.4%,
transparent 100%
);
background: linear-gradient(270deg, var(--card) 10.4%, transparent 100%);
width: 196px;
position: absolute;
@@ -86,6 +145,16 @@
align-items: center;
margin: 16px 0;
.action-btn {
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.log-detail-drawer__actions {
display: flex;
gap: 4px;
@@ -97,9 +166,23 @@
gap: var(--margin-2);
align-items: center;
justify-content: center;
font-size: var(--label-small-400-font-size);
font-size: var(--font-size-xs);
font-style: normal;
font-weight: var(--font-weight-normal);
}
.search-input {
margin-top: var(--margin-2);
border: 1px solid var(--l1-border);
height: 46px;
padding: var(--padding-1) var(--padding-2);
box-shadow: none;
border-radius: 0;
}
.ant-drawer-close {
padding: 0px;
}
.log-detail-drawer__footer-hint {
position: sticky;
bottom: 0;
@@ -108,7 +191,7 @@
padding: 8px 16px;
text-align: left;
color: var(--l3-foreground);
background: var(--drawer-background);
background: var(--l2-background);
z-index: 10;
.log-detail-drawer__footer-hint-content {
@@ -122,5 +205,18 @@
vertical-align: middle;
color: var(--l3-foreground);
}
.log-detail-drawer__footer-hint-text {
font-size: 13px;
margin: 0;
}
}
.log-arrows {
display: flex;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
border-radius: 6px;
padding: 2px 6px;
align-items: center;
margin-left: 8px;
}
}

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