Compare commits

..

10 Commits

Author SHA1 Message Date
Vinícius Lourenço
8c06f9735d feat(components): migrate from ant select to signozhq/ui select/combobox 2026-05-27 13:51:14 -03:00
Piyush Singariya
fd413d336d fix: ClickHouse 25.12.5 Trace Operator query analyzer fail due to dangling CTE (#11268)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
* fix: trace raw export e2e

* chore: separate e2e test file

* chore: file rename

* chore: fmtlint

* fix: remove specific of timestamp

* chore: fmt python

* chore: remove comments

* fix: alias all core columns

* fix: return spans from

* revert: double select

* revert: build list query

* chore: comments and test

* revert: unused test

* chore: tests updated

* chore: fmt py

* chore: fmt py

* fix: integration tests

* fix: tests rewritten

* fix: replace JOIN with IN

* test: wip rewrite integration tests better

* fix: tests are better

* fix: use pytest param

* chore: fix the comments

* fix: py fmt

* chore: added length checks

* chore: import functions from querier
2026-05-27 15:49:56 +00:00
Karan Balani
7b892ee1fc chore(meterreporter): document jitter config in example.yaml (#11482)
* chore(meterreporter): document jitter config in example.yaml

* chore(meterreporter): fix stale ResolvedJitter reference in Jitter doc comment

* chore: fix comment

---------

Co-authored-by: Karan Balani <29383381+balanikaran@users.noreply.github.com>
2026-05-27 15:35:18 +00:00
Yunus M
31e4012fe5 refactor: replace antd Checkbox with @signozhq/ui Checkbox (#11396)
* refactor: replace antd Checkbox with @signozhq/ui Checkbox (batch 1)

Migrates mechanical checkbox callsites from antd to @signozhq/ui/checkbox:

- Trace/Filters/Panel/.../Common/Checkbox.tsx
- NewWidget/LeftContainer/ExplorerAttributeColumns.tsx
- AnomalyAlertEvaluationView.tsx
- QuickFilters/FilterRenderers/Checkbox/Checkbox.tsx
- NewSelect/CustomMultiSelect.tsx
- RolesSelect/RolesSelect.tsx
- TraceDetailsV3/.../SpanPercentilePanel.tsx

API mapping: checked -> value, defaultChecked -> defaultValue, onChange(e)
where e.target.checked is read -> onChange(checked) where checked is
CheckedState. Dropped antd-only props (type="checkbox", rootClassName ->
className, form-value `value=` semantics). RolesSelect wraps the checkbox
in a div to host pointerEvents:none since style is not in CheckboxProps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: drop antd CheckboxChangeEvent in checkbox callsites (batch 2)

Two callsites that read e.target.checked via antd's CheckboxChangeEvent
type now receive the CheckedState (boolean | 'indeterminate') directly
from @signozhq/ui Checkbox's onChange.

- TopNav/AutoRefreshV2/index.tsx — onChangeAutoRefreshHandler signature
  changes; derives boolean checked from CheckedState.
- TracesExplorer/Filter/SectionContent.tsx — onCheckHandler likewise;
  also swap data-testid -> testId since SigNoz UI Checkbox wraps the
  input in a div that exposes testId.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: migrate complex antd Checkbox callsites to @signozhq/ui (batch 3)

Handles the four callsites that needed shape changes, not just renames:

- RegionSelector — collapses separate `checked` + `indeterminate` props
  into a single `value: true | false | 'indeterminate'` (SigNoz UI uses
  CheckedState, not two props).
- InteractiveQuestion — replaces `<Checkbox.Group>` (no SigNoz UI
  equivalent) with a plain `<div>` of individual `<Checkbox>`s that
  push/pull from the existing `selected: string[]` state on each toggle.
- GridCardLayout/types.ts + CustomCheckBox — drops the antd-only
  `CheckboxChangeEvent` type from the `checkBoxOnChangeHandler`
  signature and removes the `ConfigProvider` theme override that
  previously colored each chart-legend checkbox to match its series.
  The dynamic per-series color is now injected via the SigNoz UI
  Checkbox's exposed CSS variables
  (`--checkbox-checked-background`, `--checkbox-border-color`)
  applied through a wrapper `<span style={…}>`.

After this commit `grep -rE "from 'antd'.*Checkbox" frontend/src` and
`grep -rE "CheckboxChangeEvent" frontend/src` both return zero matches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: adapt checkbox assertions to SigNoz UI Checkbox DOM

The SigNoz UI Checkbox renders <button role="checkbox" data-state="…">
via Radix instead of antd's <input type="checkbox">. Four test suites
queried the old DOM and broke after the migration.

- TracesExplorer.test.tsx — getByTestId returns the wrapper div now (the
  SigNoz UI Checkbox exposes testId on the wrapper, not the button).
  Querying the inner [role="checkbox"] and asserting data-state.
- DynamicVariableDefaultBehavior.test.tsx, PanelManagement.test.tsx,
  getChartManagerColumns.test.tsx — replace querySelector(
  'input[type="checkbox"]') with [role="checkbox"], and .checked /
  toBeChecked() with toHaveAttribute('data-state', 'checked').

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: add min-height to checkbox value section

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:17:43 +00:00
Manika Malhotra
71cb97f52f chore: migrate antd divider to signozhq/ui divider (#11474)
* chore: migrate antd divider to signozhq/ui divider

* fix: remove divider from routing policies
2026-05-27 15:11:42 +00:00
Gaurav Tewari
11f1789dea refactor(frontend): migrate plain antd dropdown to @signozhq/ui/dropdown (#11400)
* chore: migrate dropdown

* fix: self review changes

* fix: side nav issue

* fix: another bug

* chore: migration from antd

* fix: failing test

* fix: lint

* chore: self review comments

* fix: add dropdown to list

* chore: remove internal docs

* chore: save antd file

* fix: lint

* fix: lint

* chore: remove ox lint

* fix: dropdown

---------

Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
2026-05-27 14:37:34 +00:00
Yunus M
1a97e90117 feat: replace Radio components with ToggleGroup in various components (#11391)
* refactor: replace Radio components with ToggleGroup in various components

* refactor: replace ToggleGroup with ToggleGroupSimple

* fix: update theme selection tests to use role-based queries

* feat: add styles for add-on tab title alignment and spacing
2026-05-27 14:09:28 +00:00
Aditya Singh
939f0d7a05 feat(trace-detail-v3): new soft colour palette for waterfall + flamegraph (#11468)
Replace the V3 trace colour system with the new soft 28-colour palette from
the design guide. Deterministic per group value (reuses existing hashFn),
theme-adaptive via darkenHex (bright base in dark mode, darkened variant in
light), and reserves #FC4E4E for errors. Covers waterfall + flamegraph bars,
labels, event dots, connectors, service dots, hover cards and analytics
breakdown. Heat/depth modulation intentionally left out for v1.
2026-05-27 13:29:19 +00:00
Yunus M
9d1c27cb57 fix: added utility functions to calculate minimum step intervals and time ranges (#11447) 2026-05-27 12:58:52 +00:00
Piyush Singariya
07ce2d341c chore: preserve order of pipelines between memory_limiter and batch (#11461)
* chore: var names changed

* fix: preserve order of pipelines between memory_limiter and batch

* revert: test name

* fix: remove old pipelines

* revert: var name change

* fix: changing the order; set user before custom
2026-05-27 12:47:41 +00:00
311 changed files with 5555 additions and 4694 deletions

View File

@@ -445,7 +445,12 @@ authz:
##################### Meter Reporter #####################
meterreporter:
# The interval between collection ticks. Minimum 5m.
# The interval between collection ticks. Minimum 10m, maximum 24h.
interval: 6h
# Whether to backfill sealed days from the license creation day.
backfill: true
# Random jitter applied to the first collect and to every subsequent cycle.
# The first collect fires at a random time in [0, jitter); each cycle then takes
# interval - random(0, jitter). Must be between 10m and interval. Defaults to
# min(interval, 2h) when unset.
jitter: 2h

View File

@@ -17,9 +17,16 @@ const BANNED_COMPONENTS = {
Typography:
'Use @signozhq/ui/typography Typography instead of antd Typography.',
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
Dropdown:
'Use @signozhq/ui DropdownMenuSimple (or the composable DropdownMenu primitives) from @signozhq/ui/dropdown-menu instead of antd Dropdown.',
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
Radio:
'Use @signozhq/ui/radio-group RadioGroup (dots) or @signozhq/ui/toggle-group ToggleGroup (segmented buttons) instead of antd Radio.',
Progress: 'Use @signozhq/ui/progress instead of antd Progress.',
Avatar: 'Use @signozhq/ui/avatar instead of antd Avatar.',
Divider: 'Use @signozhq/ui/divider Divider instead of antd Divider.',
Select:
'Use SelectSimple / ComboboxSimple from @signozhq/ui/select or @signozhq/ui/combobox instead of antd Select.',
};
export default {

View File

@@ -4,7 +4,7 @@
"no_alerts_found": "No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.",
"button_testrule": "Test Notification",
"label_channel_select": "Notification Channels",
"placeholder_channel_select": "select one or more channels",
"placeholder_channel_select": "Select one or more channels",
"channel_select_tooltip": "Leave empty to send this alert on all the configured channels",
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
"preview_chart_threshold_label": "Threshold",

View File

@@ -4,7 +4,7 @@
"no_alerts_found": "No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.",
"button_testrule": "Test Notification",
"label_channel_select": "Notification Channels",
"placeholder_channel_select": "select one or more channels",
"placeholder_channel_select": "Select one or more channels",
"channel_select_tooltip": "Leave empty to send this alert on all the configured channels",
"preview_chart_unexpected_error": "An unexpeced error occurred updating the chart, please check your query.",
"preview_chart_threshold_label": "Threshold",

View File

@@ -1,4 +1,5 @@
import { Breadcrumb, Divider } from 'antd';
import { Breadcrumb } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import styles from './AlertBreadcrumb.module.scss';
import BreadcrumbItem, { BreadcrumbItemConfig } from './BreadcrumbItem';

View File

@@ -1,12 +1,12 @@
import { useCallback, useMemo, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { Row, Select, Spin } from 'antd';
import { Row } from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import {
getValuesFromQueryParams,
setQueryParamsFromOptions,
} from 'components/CeleryTask/CeleryUtils';
import { useCeleryFilterOptions } from 'components/CeleryTask/useCeleryFilterOptions';
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -48,7 +48,7 @@ export function FilterSelect({
: getValuesFromQueryParams(queryParam, urlQuery) || [];
// Memoize options to include the typed value if not present
const mergedOptions = useMemo(() => {
const mergedOptions = useMemo<ComboboxSimpleItem[]>(() => {
if (
!!searchValue.trim().length &&
!options.some((opt) => opt.value === searchValue)
@@ -84,35 +84,16 @@ export function FilterSelect({
],
);
// Update searchValue on user input
const handleSearchInput = (input: string): void => {
setSearchValue(input);
handleSearch(input);
};
return (
<Select
<ComboboxSimple
key={filterType.toString()}
placeholder={placeholder}
showSearch
{...(isMultiple ? { mode: 'multiple' } : {})}
options={mergedOptions}
multiple={isMultiple}
items={mergedOptions}
loading={isFetching}
className="config-select-option"
onSearch={handleSearchInput}
maxTagCount={4}
allowClear
maxTagPlaceholder={SelectMaxTagPlaceholder}
value={selectValue}
notFoundContent={
isFetching ? (
<span>
<Spin size="small" /> Loading...
</span>
) : (
<span>No {placeholder} found</span>
)
}
emptyPlaceholder={`No ${placeholder} found`}
onChange={handleSelectChange}
/>
);

View File

@@ -1,7 +1,6 @@
import { useHistory, useLocation } from 'react-router-dom';
import { Select, Spin } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { Typography } from '@signozhq/ui/typography';
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -27,30 +26,18 @@ function CeleryTaskConfigOptions(): JSX.Element {
<Typography.Text style={{ whiteSpace: 'nowrap' }}>
Task Name
</Typography.Text>
<Select
<ComboboxSimple
placeholder="Task Name"
showSearch
mode="multiple"
options={options}
multiple
items={options}
loading={isFetching}
className="config-select-option"
onSearch={handleSearch}
maxTagCount={4}
maxTagPlaceholder={SelectMaxTagPlaceholder}
value={getValuesFromQueryParams(QueryParams.taskName, urlQuery) || []}
notFoundContent={
isFetching ? (
<span>
<Spin size="small" /> Loading...
</span>
) : (
<span>No Task Name found</span>
)
}
emptyPlaceholder="No Task Name found"
onChange={(value): void => {
handleSearch('');
setQueryParamsFromOptions(
value,
value as string[],
urlQuery,
history,
location,

View File

@@ -1,7 +1,7 @@
import { useQuery } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import type { DefaultOptionType } from 'antd/es/select';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
import { DATA_TYPE_VS_ATTRIBUTE_VALUES_KEY } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@@ -21,7 +21,7 @@ export interface Filters {
}
export interface GetAllFiltersResponse {
options: DefaultOptionType[];
options: ComboboxSimpleItem[];
isFetching: boolean;
}

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Divider, Drawer } from 'antd';
import { Drawer } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { PANEL_TYPES } from 'constants/queryBuilder';

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import type { DefaultOptionType } from 'antd/es/select';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';
@@ -17,7 +17,7 @@ export const useCeleryFilterOptions = (
searchText: string;
handleSearch: (value: string) => void;
isFetching: boolean;
options: DefaultOptionType[];
options: ComboboxSimpleItem[];
} => {
const [searchText, setSearchText] = useState<string>('');
const { isFetching, options } = useGetAllFilters({

View File

@@ -9,6 +9,7 @@ import {
useState,
} from 'react';
import { Color } from '@signozhq/design-tokens';
// oxlint-disable-next-line signoz/no-antd-components This component for now is too complex to be migrated in one PR
import { Select, Tag, Tooltip } from 'antd';
import {
OPERATORS,

View File

@@ -1,5 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Popover, Radio, Tooltip } from 'antd';
import { Button, Popover, Tooltip } from 'antd';
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
import { Typography } from '@signozhq/ui/typography';
import { TelemetryFieldKey } from 'api/v5/v5';
import { useExportRawData } from 'hooks/useDownloadOptionsMenu/useDownloadOptionsMenu';
@@ -63,27 +64,30 @@ export default function DownloadOptionsMenu({
>
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<Radio.Group
value={exportFormat}
onChange={(e): void => setExportFormat(e.target.value)}
>
<Radio value={DownloadFormats.CSV}>csv</Radio>
<Radio value={DownloadFormats.JSONL}>jsonl</Radio>
</Radio.Group>
<RadioGroup value={exportFormat} onChange={setExportFormat}>
<RadioGroupItem value={DownloadFormats.CSV}>csv</RadioGroupItem>
<RadioGroupItem value={DownloadFormats.JSONL}>jsonl</RadioGroupItem>
</RadioGroup>
</div>
<div className="horizontal-line" />
<div className="row-limit">
<Typography.Text className="title">Number of Rows</Typography.Text>
<Radio.Group
value={rowLimit}
onChange={(e): void => setRowLimit(e.target.value)}
<RadioGroup
value={String(rowLimit)}
onChange={(value): void => setRowLimit(Number(value))}
>
<Radio value={DownloadRowCounts.TEN_K}>10k</Radio>
<Radio value={DownloadRowCounts.THIRTY_K}>30k</Radio>
<Radio value={DownloadRowCounts.FIFTY_K}>50k</Radio>
</Radio.Group>
<RadioGroupItem value={String(DownloadRowCounts.TEN_K)}>
10k
</RadioGroupItem>
<RadioGroupItem value={String(DownloadRowCounts.THIRTY_K)}>
30k
</RadioGroupItem>
<RadioGroupItem value={String(DownloadRowCounts.FIFTY_K)}>
50k
</RadioGroupItem>
</RadioGroup>
</div>
{dataSource !== DataSource.TRACES && (
@@ -92,13 +96,12 @@ export default function DownloadOptionsMenu({
<div className="columns-scope">
<Typography.Text className="title">Columns</Typography.Text>
<Radio.Group
value={columnsScope}
onChange={(e): void => setColumnsScope(e.target.value)}
>
<Radio value={DownloadColumnsScopes.ALL}>All</Radio>
<Radio value={DownloadColumnsScopes.SELECTED}>Selected</Radio>
</Radio.Group>
<RadioGroup value={columnsScope} onChange={setColumnsScope}>
<RadioGroupItem value={DownloadColumnsScopes.ALL}>All</RadioGroupItem>
<RadioGroupItem value={DownloadColumnsScopes.SELECTED}>
Selected
</RadioGroupItem>
</RadioGroup>
</div>
</>
)}

View File

@@ -1,7 +0,0 @@
.dropdown-button {
color: var(--l1-foreground);
}
.dropdown-icon {
font-size: 1.2rem;
}

View File

@@ -1,51 +0,0 @@
import { useState } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import './DropDown.styles.scss';
function DropDown({
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
key: index,
}),
);
const [isDdOpen, setDdOpen] = useState<boolean>(false);
return (
<Dropdown
menu={{
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}}
open={isDdOpen}
>
<Button
type="link"
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
>
<Ellipsis className="dropdown-icon" size={16} />
</Button>
</Dropdown>
);
}
DropDown.defaultProps = {
onDropDownItemClick: (): void => {},
};
export default DropDown;

View File

@@ -1,15 +1,8 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import {
Button,
Col,
Dropdown,
MenuProps,
Popover,
Row,
Select,
Space,
} from 'antd';
import { Button, Col, Popover, Row, Space } from 'antd';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { Typography } from '@signozhq/ui/typography';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
@@ -31,11 +24,7 @@ import { popupContainer } from 'utils/selectPopupContainer';
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
import MenuItemGenerator from './MenuItemGenerator';
import SaveViewWithName from './SaveViewWithName';
import {
DropDownOverlay,
ExplorerCardHeadContainer,
OffSetCol,
} from './styles';
import { ExplorerCardHeadContainer, OffSetCol } from './styles';
import { ExplorerCardProps } from './types';
import { deleteViewHandler } from './utils';
import { Ellipsis, Save, Share2, Trash2 } from '@signozhq/icons';
@@ -164,6 +153,26 @@ function ExplorerCard({
const showSaveView = false;
const viewItems = useMemo(
() =>
(viewsData?.data.data ?? []).map((view) => ({
value: view.name,
label: (
<MenuItemGenerator
viewName={view.name}
viewKey={viewKey}
createdBy={view.createdBy}
uuid={view.id}
refetchAllView={refetchAllView}
viewData={viewsData?.data.data ?? []}
sourcePage={sourcepage}
/>
),
displayValue: view.name,
})),
[refetchAllView, sourcepage, viewKey, viewsData?.data.data],
);
return (
<>
{showSaveView && (
@@ -183,30 +192,12 @@ function ExplorerCard({
<Space size="large">
{viewsData?.data.data && viewsData?.data.data.length && (
<Space>
<Select
getPopupContainer={popupContainer}
<ComboboxSimple
loading={isLoading || isRefetching}
showSearch
placeholder="Select a view"
dropdownStyle={DropDownOverlay}
dropdownMatchSelectWidth={false}
optionLabelProp="value"
items={viewItems}
value={viewName || undefined}
>
{viewsData?.data.data.map((view) => (
<Select.Option key={view.id} value={view.name}>
<MenuItemGenerator
viewName={view.name}
viewKey={viewKey}
createdBy={view.createdBy}
uuid={view.id}
refetchAllView={refetchAllView}
viewData={viewsData.data.data}
sourcePage={sourcepage}
/>
</Select.Option>
))}
</Select>
/>
</Space>
)}
{isQueryUpdated && (
@@ -241,9 +232,9 @@ function ExplorerCard({
</Popover>
<Share2 onClick={onCopyUrlHandler} size="md" />
{viewKey && (
<Dropdown trigger={['click']} menu={moreOptionMenu}>
<Ellipsis size="md" />
</Dropdown>
<DropdownMenuSimple menu={moreOptionMenu}>
<Button type="text" size="small" icon={<Ellipsis size="md" />} />
</DropdownMenuSimple>
)}
</Space>
</OffSetCol>

View File

@@ -1,7 +1,8 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui/sonner';
import { Button, Input, Radio, RadioChangeEvent } from 'antd';
import { Button, Input } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
@@ -101,13 +102,12 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
return (
<div className="feedback-modal-container">
<div className="feedback-modal-header">
<Radio.Group
<ToggleGroupSimple
type="single"
value={activeTab}
defaultValue={activeTab}
optionType="button"
className="feedback-modal-tabs"
options={items}
onChange={(e: RadioChangeEvent): void => setActiveTab(e.target.value)}
onChange={setActiveTab}
items={items}
/>
</div>
<div className="feedback-modal-content">

View File

@@ -20,9 +20,9 @@
padding: 0px 8px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
color: var(--l2-foreground);
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));
display: flex;
justify-content: flex-start;
@@ -35,21 +35,28 @@
min-width: 150px;
font-family: 'Space Mono', monospace !important;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
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));
color: var(--input-with-label-color, var(--l2-foreground));
border-right: none;
border-left: none;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
font-size: 12px !important;
line-height: 27px;
line-height: 25px;
&.input__has-label-after {
border-left: none;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
&.input__has-close-button {
border-right: none;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
&::placeholder {
color: var(--l2-foreground) !important;
color: var(--input-with-label-color, var(--l3-foreground)) !important;
font-size: 12px !important;
}
&[type='number']::-webkit-inner-spin-button,
@@ -63,25 +70,24 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
height: 100%;
width: 38px;
}
&.labelAfter {
.input {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
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;
}
.label {
border-left: none;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
border-radius: 0px 2px 2px 0px;
}
}
}

View File

@@ -44,7 +44,10 @@ function InputWithLabel({
>
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
className="input"
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
placeholder={placeholder}
type={type}
value={inputValue}

View File

@@ -1,12 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Style } from '@signozhq/design-tokens';
import { ChevronDown, Plus, Trash2, X } from '@signozhq/icons';
import { Plus, Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
import { Input } from '@signozhq/ui/input';
import { SelectSimple } from '@signozhq/ui/select';
import { toast } from '@signozhq/ui/sonner';
import { Select } from 'antd';
import inviteUsers from 'api/v1/invite/bulk/create';
import sendInvite from 'api/v1/invite/create';
import { cloneDeep, debounce } from 'lodash-es';
@@ -15,7 +15,6 @@ import APIError from 'types/api/error';
import { ROLES } from 'types/roles';
import { EMAIL_REGEX } from 'utils/app';
import { getBaseUrl } from 'utils/basePath';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
import './InviteMembersModal.styles.scss';
@@ -254,18 +253,17 @@ function InviteMembersModal({
)}
</div>
<div className="team-member-cell role-cell">
<Select
<SelectSimple
value={row.role || undefined}
onChange={(role): void => updateRole(row.id, role as ROLES)}
className="team-member-role-select"
placeholder="Select roles"
suffixIcon={<ChevronDown size={14} />}
getPopupContainer={popupContainer}
>
<Select.Option value="VIEWER">Viewer</Select.Option>
<Select.Option value="EDITOR">Editor</Select.Option>
<Select.Option value="ADMIN">Admin</Select.Option>
</Select>
items={[
{ value: 'VIEWER', label: 'Viewer' },
{ value: 'EDITOR', label: 'Editor' },
{ value: 'ADMIN', label: 'Admin' },
]}
/>
</div>
<div className="team-member-cell action-cell">
{rows.length > 1 && (

View File

@@ -158,23 +158,23 @@
font-weight: var(--font-weight-normal);
}
.tab {
> button {
border: 1px solid var(--l1-border);
width: 114px;
}
.tab::before {
background: var(--l1-border);
}
&::before {
background: var(--l1-border);
}
.selected_view {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
}
&[data-state='on'] {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
.selected_view::before {
background: var(--l1-border);
&::before {
background: var(--l1-border);
}
}
}
}

View File

@@ -4,9 +4,10 @@ import { useSelector } from 'react-redux'; // old code, TODO: fix this correctly
import { useCopyToClipboard, useLocation } from 'react-use';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui/button';
import { Divider, Drawer, Radio, Tooltip } from 'antd';
import { Drawer, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
@@ -197,8 +198,8 @@ function LogDetailInner({
const LogJsonData = log ? aggregateAttributesResourcesToString(log) : '';
const handleModeChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
const handleModeChange = (value: string): void => {
setSelectedView(value as VIEWS);
setIsEdit(false);
setIsFilterVisible(false);
};
@@ -452,56 +453,50 @@ function LogDetailInner({
</div>
<div className="tabs-and-search">
<Radio.Group
<ToggleGroupSimple
type="single"
className="views-tabs"
onChange={handleModeChange}
value={selectedView}
>
<Radio.Button
className={
selectedView === VIEW_TYPES.OVERVIEW ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.OVERVIEW}
>
<div className="view-title">
<Table size={14} />
Overview
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.JSON ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.JSON}
>
<div className="view-title">
<Braces size={14} />
JSON
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.CONTEXT ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.CONTEXT}
>
<div className="view-title">
<TextSelect size={14} />
Context
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.INFRAMETRICS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.INFRAMETRICS}
>
<div className="view-title">
<Histogram size="md" />
Metrics
</div>
</Radio.Button>
</Radio.Group>
items={[
{
value: VIEW_TYPES.OVERVIEW,
label: (
<div className="view-title">
<Table size={14} />
Overview
</div>
),
},
{
value: VIEW_TYPES.JSON,
label: (
<div className="view-title">
<Braces size={14} />
JSON
</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 && (

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Input, InputNumber, Popover, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import type { DefaultOptionType } from 'antd/es/select';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import cx from 'classnames';
import { LogViewMode } from 'container/LogsTable';
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
@@ -113,15 +113,11 @@ function OptionsMenu({
function handleColumnSelection(
currentIndex: number,
optionsData: DefaultOptionType[],
optionsData: ComboboxSimpleItem[],
): void {
const currentItem = optionsData[currentIndex];
const itemLength = optionsData.length;
if (addColumn && addColumn?.onSelect) {
addColumn?.onSelect(selectedValue, {
label: currentItem.label,
disabled: false,
});
if (addColumn && addColumn?.onSelect && selectedValue) {
addColumn?.onSelect(selectedValue);
// if the last element is selected then select the previous one
if (currentIndex === itemLength - 1) {
@@ -175,7 +171,7 @@ function OptionsMenu({
}
case 'Enter':
e.preventDefault();
handleColumnSelection(currentIndex, optionsData);
handleColumnSelection(currentIndex, optionsData as ComboboxSimpleItem[]);
break;
default:
break;
@@ -317,7 +313,10 @@ function OptionsMenu({
}}
onClick={(eve): void => {
eve.stopPropagation();
handleColumnSelection(index, addColumn?.options || []);
handleColumnSelection(
index,
(addColumn?.options || []) as ComboboxSimpleItem[],
);
}}
>
<div className="name">

View File

@@ -10,7 +10,8 @@ import {
Loader,
X,
} from '@signozhq/icons';
import { Modal, Select, Spin, Tooltip, Tree, TreeDataNode } from 'antd';
import { Modal, Spin, Tooltip, Tree, TreeDataNode } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -181,8 +182,8 @@ function AttributeCheckList({
const [filter, setFilter] = useState<AttributesFilters>(AttributesFilters.ALL);
const [treeData, setTreeData] = useState<TreeDataNode[]>([]);
const handleFilterChange = (value: AttributesFilters): void => {
setFilter(value);
const handleFilterChange = (value: string | string[]): void => {
setFilter(value as AttributesFilters);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const history = useHistory();
@@ -237,11 +238,11 @@ function AttributeCheckList({
</div>
) : (
<div className="modal-content">
<Select
<SelectSimple
defaultValue={AttributesFilters.ALL}
className="attribute-select"
onChange={handleFilterChange}
options={[
items={[
{
value: AttributesFilters.ALL,
label: AttributeLabels({ title: 'Attributes: All' }),

View File

@@ -1,6 +1,6 @@
import { Color } from '@signozhq/design-tokens';
import { Tooltip } from 'antd';
import type { DefaultOptionType } from 'antd/es/select';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Info } from '@signozhq/icons';
import './MQCommon.styles.scss';
@@ -35,7 +35,7 @@ export function ComingSoon(): JSX.Element {
}
export function SelectMaxTagPlaceholder(
omittedValues: Partial<DefaultOptionType>[],
omittedValues: Partial<ComboboxSimpleItem>[],
): JSX.Element {
return (
<Tooltip title={omittedValues.map(({ value }) => value).join(', ')}>

View File

@@ -18,7 +18,9 @@ import {
RefreshCw,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Checkbox, Select } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
// oxlint-disable-next-line signoz/no-antd-components This component for now is too complex to be migrated in one PR
import { Button, Select } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip/TextToolTip';
@@ -749,7 +751,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
tabIndex={isActive ? 0 : -1}
>
<Checkbox
checked={isSelected}
value={isSelected}
className="option-checkbox"
onClick={(e): void => {
e.stopPropagation();
@@ -1584,7 +1586,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}}
>
<div style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Checkbox checked={allOptionsSelected} className="option-checkbox">
<Checkbox value={allOptionsSelected} className="option-checkbox">
<div className="option-content">
<div className="all-option-text">ALL</div>
</div>

View File

@@ -16,6 +16,7 @@ import {
X,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
// oxlint-disable-next-line signoz/no-antd-components This component for now is too complex to be migrated in one PR
import { Select } from 'antd';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip';

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { Select, Spin } from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
@@ -13,48 +13,25 @@ interface ListViewOrderByProps {
dataSource: DataSource;
}
// Loader component for the dropdown when loading or no results
function Loader({ isLoading }: { isLoading: boolean }): JSX.Element {
return (
<div className="order-by-loading-container">
{isLoading ? <Spin size="default" /> : 'No results found'}
</div>
);
}
function ListViewOrderBy({
value,
onChange,
dataSource,
}: ListViewOrderByProps): JSX.Element {
const [searchInput, setSearchInput] = useState('');
const [debouncedInput, setDebouncedInput] = useState('');
const [selectOptions, setSelectOptions] = useState<
{ label: string; value: string }[]
>([]);
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const [selectOptions, setSelectOptions] = useState<ComboboxSimpleItem[]>([]);
// Fetch key suggestions based on debounced input
// Fetch key suggestions once; ComboboxSimple handles local filtering.
const { data, isLoading } = useQuery({
queryKey: ['orderByKeySuggestions', dataSource, debouncedInput],
queryKey: ['orderByKeySuggestions', dataSource],
queryFn: async () => {
const response = await getKeySuggestions({
signal: dataSource,
searchText: debouncedInput,
searchText: '',
});
return response.data;
},
});
useEffect(
() => (): void => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
},
[],
);
// Update options when API data changes
useEffect(() => {
const rawKeys: QueryKeyDataSuggestionsProps[] = data?.data?.keys
@@ -62,52 +39,33 @@ function ListViewOrderBy({
: [];
const keyNames = rawKeys.map((key) => key.name);
const uniqueKeys = [
...new Set(searchInput ? keyNames : ['timestamp', ...keyNames]),
];
const uniqueKeys = [...new Set(['timestamp', ...keyNames])];
const updatedOptions = uniqueKeys.flatMap((key) => [
const updatedOptions: ComboboxSimpleItem[] = uniqueKeys.flatMap((key) => [
{ label: `${key} (desc)`, value: `${key}:desc` },
{ label: `${key} (asc)`, value: `${key}:asc` },
]);
setSelectOptions(updatedOptions);
}, [data, searchInput]);
}, [data]);
// Handle search input with debounce
const handleSearch = (input: string): void => {
setSearchInput(input);
// Filter current options for instant client-side match
const filteredOptions = selectOptions.filter((option) =>
option.value.toLowerCase().includes(input.trim().toLowerCase()),
);
// If no match found or input is empty, trigger debounced fetch
if (filteredOptions.length === 0 || input === '') {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
debounceTimer.current = setTimeout(() => {
setDebouncedInput(input);
}, 100);
}
};
const handleChange = useMemo(
() =>
(val: string | string[]): void => {
onChange(val as string);
},
[onChange],
);
return (
<Select
showSearch
<ComboboxSimple
value={value}
onChange={onChange}
onSearch={handleSearch}
notFoundContent={<Loader isLoading={isLoading} />}
onChange={handleChange}
loading={isLoading}
placeholder="Select a field"
style={{ width: 200 }}
options={selectOptions}
filterOption={(input, option): boolean =>
(option?.value ?? '').toLowerCase().includes(input.trim().toLowerCase())
}
items={selectOptions}
emptyPlaceholder="No results found"
/>
);
}

View File

@@ -22,6 +22,37 @@
border-right: none;
border-left: none;
--select-trigger-height: 2.25rem;
--select-trigger-background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
);
--select-trigger-border-color: var(
--query-builder-v2-border-color,
var(--l2-border)
);
--combobox-trigger-height: 2.25rem;
--combobox-trigger-padding: 7px var(--spacing-6);
--combobox-trigger-background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
);
--combobox-trigger-border-color: var(
--query-builder-v2-border-color,
var(--l2-border)
);
[data-slot='combobox-trigger'],
[data-slot='select-trigger'] {
color: var(--query-builder-v2-color, var(--l2-foreground));
}
[data-slot='combobox-placeholder'],
[data-slot='select-placeholder'] {
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
}
.qb-content-container {
display: flex;
flex-direction: column;
@@ -80,8 +111,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--l1-border),
var(--l1-border) 4px,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
transparent 4px,
transparent 8px
);
@@ -101,7 +132,8 @@
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted var(--l1-border);
border-left: 6px dotted
var(--query-builder-v2-border-color, var(--l2-border));
}
/* Horizontal line pointing from vertical to the item */
@@ -114,8 +146,8 @@
height: 1px;
background: repeating-linear-gradient(
to right,
var(--l1-border),
var(--l1-border) 4px,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
transparent 4px,
transparent 8px
);
@@ -241,7 +273,8 @@
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted var(--l1-border);
border-left: 6px dotted
var(--query-builder-v2-border-color, var(--l2-border));
}
/* Horizontal line pointing from vertical to the item */
@@ -254,8 +287,8 @@
height: 1px;
background: repeating-linear-gradient(
to right,
var(--l1-border),
var(--l1-border) 4px,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
transparent 4px,
transparent 8px
);
@@ -273,6 +306,16 @@
line-height: 16px; /* 128.571% */
resize: none;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
&:placeholder {
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
}
}
.formula-legend {
@@ -282,15 +325,30 @@
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background: var(--l2-background);
color: var(--l2-foreground);
background: var(
--query-builder-v2-background-color,
var(--l2-background)
);
color: var(--query-builder-v2-color, var(--l2-foreground));
font-size: 12px;
font-weight: 300;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
}
.ant-input {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
&:placeholder {
color: var(--query-builder-v2-placeholder-color, var(--l3-foreground));
}
}
}
}
@@ -323,8 +381,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--l1-border),
var(--l1-border) 4px,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
transparent 4px,
transparent 8px
);
@@ -395,8 +453,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--l1-border),
var(--l1-border) 4px,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
transparent 4px,
transparent 8px
);
@@ -412,7 +470,7 @@
min-width: 120px;
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--l1-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
@@ -457,7 +515,7 @@
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--l1-border) !important;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
background: var(--l1-background) !important;
height: 34px !important;
box-sizing: border-box !important;

View File

@@ -57,6 +57,7 @@
.group-by-filter-container {
min-width: 340px !important;
--combobox-trigger-height: auto;
}
.metrics-aggregation-section-content-item {
@@ -146,17 +147,3 @@
}
}
}
.metrics-operators-select {
border-radius: 2px;
border: 1.005px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}

View File

@@ -118,7 +118,6 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
value={queryAggregation.timeAggregation || ''}
onChange={handleChangeOperator}
operators={operators}
className="metrics-operators-select"
/>
</div>
</div>

View File

@@ -6,11 +6,13 @@
gap: 8px;
width: 100%;
--select-trigger-width: auto;
.ant-select-selection-search-input {
font-size: 12px !important;
line-height: 27px;
line-height: 25px;
&::placeholder {
color: var(--l2-foreground) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
font-size: 12px !important;
}
}
@@ -22,9 +24,9 @@
.ant-select-selector {
width: 100%;
border-radius: 2px;
border: 1px solid var(--l1-border) !important;
background: var(--l1-background);
color: var(--l1-foreground);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
background: var(--query-builder-v2-background-color, var(--l2-background));
color: var(--query-builder-v2-color, var(--l2-foreground));
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
@@ -33,20 +35,23 @@
min-height: 36px;
.ant-select-selection-placeholder {
color: var(--l2-foreground) !important;
color: var(
--query-builder-v2-placeholder-color,
var(--l3-foreground)
) !important;
font-size: 12px !important;
}
}
.ant-select-dropdown {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
.ant-select-item {
color: var(--l1-foreground);
color: var(--query-builder-v2-color, var(--l2-foreground));
font-family: 'Geist Mono';
font-size: 13px;
font-style: normal;
@@ -55,12 +60,18 @@
&:hover,
&.ant-select-item-option-active {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selected-background-color,
var(--l3-background)
) !important;
}
&.ant-select-item-option-selected {
background: var(--l3-background) !important;
border: 1px solid var(--l1-border);
background: var(
--query-builder-v2-selected-background-color,
var(--l3-background)
) !important;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
font-weight: 600;
}
}

View File

@@ -1,5 +1,5 @@
import { memo, useCallback, useMemo } from 'react';
import { Select } from 'antd';
import { SelectSimple, type SelectSimpleItem } from '@signozhq/ui/select';
import {
initialQueriesMap,
initialQueryMeterWithType,
@@ -10,7 +10,6 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
import {
getPreviousQueryFromKey,
@@ -21,7 +20,7 @@ import {
import './MetricsSelect.styles.scss';
export const SOURCE_OPTIONS: SelectOption<string, string>[] = [
export const SOURCE_OPTIONS: SelectSimpleItem[] = [
{ value: 'metrics', label: 'Metrics' },
{ value: 'meter', label: 'Meter' },
];
@@ -140,14 +139,14 @@ export const MetricsSelect = memo(function MetricsSelect({
return (
<div className="metrics-source-select-container">
{signalSourceChangeEnabled && (
<Select
<SelectSimple
className="source-selector"
placeholder="Source"
options={SOURCE_OPTIONS}
items={SOURCE_OPTIONS}
value={source}
defaultValue="metrics"
data-testid={`metrics-source-selector-${index}`}
onChange={handleSignalSourceChange}
testId={`metrics-source-selector-${index}`}
onChange={(value): void => handleSignalSourceChange(value as string)}
/>
)}

View File

@@ -2,6 +2,13 @@
.query-add-ons {
width: 100%;
.add-on-tab-title {
display: flex;
align-items: center;
justify-content: center;
gap: var(--margin-2);
}
}
.add-ons-list {
@@ -22,34 +29,35 @@
font-style: normal;
font-weight: var(--font-weight-normal);
color: var(--l2-foreground);
color: var(--query-builder-v2-color, var(--l2-foreground));
}
.tab {
border: 1px solid var(--l1-border);
> button {
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border-left: none;
min-width: 120px;
height: 36px;
line-height: 36px;
&:first-child {
border-left: 1px solid var(--l1-border);
border-left: 1px solid
var(--query-builder-v2-border-color, var(--l2-border));
}
}
.tab::before {
background: var(--l1-border);
}
&::before {
background: var(--query-builder-v2-border-color, var(--l2-border));
}
.selected-view {
color: var(--text-robin-500);
border: 1px solid var(--l1-border);
&[data-state='on'] {
color: var(--text-robin-500);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
display: none;
}
display: none;
.selected-view::before {
background: var(--l1-border);
&::before {
background: var(--query-builder-v2-border-color, var(--l2-border));
}
}
}
}
@@ -58,7 +66,7 @@
height: 30px;
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
@@ -71,10 +79,13 @@
align-items: center;
.having-filter-select-container {
position: relative;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
align-items: flex-start;
background: var(--query-builder-v2-background-color, var(--l2-background));
padding-right: 38px;
.having-filter-select-editor {
border-radius: 2px;
@@ -99,15 +110,17 @@
}
.cm-content {
border-radius: 2px;
border: 1px solid var(--l1-border);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border-left-width: 0px;
border-right-width: 0px;
padding: 0px !important;
background-color: var(--l2-background) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&:focus-within {
border-color: var(--l1-border);
border-color: var(--query-builder-v2-border-color, var(--l2-border));
}
}
@@ -211,17 +224,32 @@
}
.cm-line {
line-height: 36px !important;
min-height: 34px;
line-height: 32px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l2-background) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&,
.ͼ1a {
color: var(--query-builder-v2-color, var(--l2-foreground));
}
::-moz-selection {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
@@ -230,8 +258,11 @@
}
.chip-decorator {
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
background: var(
--query-builder-v2-chip-decorator-background-color,
var(--l3-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
border-radius: 4px;
padding: 2px 4px;
margin-right: 4px;
@@ -239,24 +270,36 @@
}
.cm-selectionBackground {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
.cm-activeLine > span {
font-size: 12px !important;
}
.cm-placeholder {
color: var(
--query-builder-v2-placeholder-color,
var(--l3-foreground)
) !important;
}
}
}
.close-btn {
position: absolute;
top: 0;
right: 0;
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
height: 100%;
width: 38px;
border-left: transparent;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Button, Radio, RadioChangeEvent, Tooltip } from 'antd';
import { Button, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import InputWithLabel from 'components/InputWithLabel/InputWithLabel';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GroupByFilter } from 'container/QueryBuilder/filters/GroupByFilter/GroupByFilter';
@@ -250,8 +251,7 @@ function QueryAddOns({
);
}, [panelType, isListViewPanel, query, showReduceTo]);
const handleOptionClick = (e: RadioChangeEvent): void => {
const clickedAddOn = e.target.value as AddOn;
const handleOptionClick = (clickedAddOn: AddOn): void => {
const isAlreadySelected = selectedViews.some(
(view) => view.key === clickedAddOn.key,
);
@@ -515,15 +515,27 @@ function QueryAddOns({
</div>
)}
<div className="add-ons-list">
<Radio.Group
className="add-ons-tabs"
onChange={handleOptionClick}
value={selectedViews}
>
{addOns.map((addOn) => (
<ToggleGroupSimple
type="multiple"
className="add-ons-tabs"
value={selectedViews.map((view) => view.key)}
onChange={(newKeys: string[]): void => {
const oldKeys = selectedViews.map((view) => view.key);
const toggledKey =
newKeys.find((k) => !oldKeys.includes(k)) ??
oldKeys.find((k) => !newKeys.includes(k));
if (!toggledKey) {
return;
}
const clickedAddOn = addOns.find((a) => a.key === toggledKey);
if (clickedAddOn) {
handleOptionClick(clickedAddOn);
}
}}
items={addOns.map((addOn) => ({
value: addOn.key,
label: (
<Tooltip
key={addOn.key}
title={
<TooltipContent
label={addOn.label}
@@ -534,26 +546,17 @@ function QueryAddOns({
placement="top"
mouseEnterDelay={0.5}
>
<Radio.Button
className={
selectedViews.find((view) => view.key === addOn.key)
? 'selected-view tab'
: 'tab'
}
value={addOn}
<span
className="add-on-tab-title"
data-testid={`query-add-on-${addOn.key}`}
>
<div
className="add-on-tab-title"
data-testid={`query-add-on-${addOn.key}`}
>
{addOn.icon}
{addOn.label}
</div>
</Radio.Button>
{addOn.icon}
{addOn.label}
</span>
</Tooltip>
))}
</Radio.Group>
</div>
),
}))}
/>
</div>
);
}

View File

@@ -23,7 +23,7 @@
flex: 1;
min-width: 0;
font-size: 12px;
color: var(--l2-foreground) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
&.error {
.cm-editor {
@@ -51,14 +51,15 @@
.cm-content {
border-radius: 2px;
border: 1px solid var(--l1-border);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
padding: 0px !important;
background-color: var(--l1-background) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&:focus-within {
border-color: var(--l1-border);
border-color: var(--query-builder-v2-border-color, var(--l2-border));
}
}
@@ -74,7 +75,7 @@
right: 0px !important;
border-radius: 4px;
border: 1px solid var(--l1-border);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
@@ -118,7 +119,7 @@
box-sizing: border-box;
overflow: hidden;
color: var(--l2-foreground) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
font-family: 'Space Mono', monospace !important;
.cm-completionIcon {
@@ -127,7 +128,10 @@
&:hover,
&[aria-selected='true'] {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
color: var(--l1-foreground) !important;
font-weight: 600 !important;
}
@@ -142,15 +146,24 @@
.cm-line {
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l2-background) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
::-moz-selection {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
@@ -159,8 +172,11 @@
}
.chip-decorator {
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
background: var(
--query-builder-v2-chip-decorator-background-color,
var(--l3-background)
) !important;
color: var(--query-builder-v2-color, var(--l1-foreground)) !important;
border-radius: 4px;
padding: 2px 4px;
margin-right: 4px;
@@ -168,7 +184,10 @@
}
.cm-selectionBackground {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
}
@@ -201,12 +220,11 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
height: 38px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
height: 100%;
width: 38px;
border-left: transparent;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}
@@ -217,13 +235,13 @@
height: 36px;
line-height: 36px;
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
font-family: 'Space Mono', monospace !important;
&::placeholder {
color: var(--l1-foreground);
color: var(--query-builder-v2-color, var(--l2-foreground));
opacity: 0.5;
}
}
@@ -240,7 +258,7 @@
input {
max-width: 120px;
&::placeholder {
color: var(--l2-foreground);
color: var(--query-builder-v2-color, var(--l2-foreground));
}
}
}
@@ -251,8 +269,8 @@
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-border);
border: 1px solid var(--l1-border);
background-color: var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border-radius: 4px;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}

View File

@@ -1,7 +1,7 @@
.add-trace-operator-button,
.add-new-query-button,
.add-formula-button {
border: 1px solid var(--l1-border);
background: var(--l2-background);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}

View File

@@ -34,11 +34,14 @@
.query-status-container {
width: 32px;
background-color: var(--l1-background) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--l1-border);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border-radius: 2px;
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
@@ -77,16 +80,16 @@
.cm-content {
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
padding: 0px !important;
&:focus-within {
border-color: var(--l1-border);
border-color: var(--query-builder-v2-border-color, var(--l2-border));
}
}
&.cm-focused {
outline: 1px solid var(--l1-border);
outline: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
}
.cm-tooltip-autocomplete {
@@ -148,11 +151,17 @@
font-family: 'Space Mono', monospace !important;
background-color: var(--l1-background) !important;
color: var(--l2-foreground) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
&:hover {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
}
.cm-completionIcon {
@@ -160,7 +169,10 @@
}
&[aria-selected='true'] {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
font-weight: 600 !important;
}
}
@@ -172,25 +184,49 @@
}
.cm-line {
line-height: 34px !important;
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(--l2-background) !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&,
.ͼ1a {
color: var(--query-builder-v2-color, var(--l2-foreground));
}
::-moz-selection {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
}
.cm-selectionBackground {
background: var(--l3-background) !important;
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
opacity: 0.5 !important;
}
.cm-placeholder {
color: var(
--query-builder-v2-placeholder-color,
var(--l3-foreground)
) !important;
}
}
.cursor-position {

View File

@@ -6,7 +6,7 @@ import {
useMemo,
useState,
} from 'react';
import { Dropdown } from 'antd';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import cx from 'classnames';
import { ENTITY_VERSION_V4, ENTITY_VERSION_V5 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -195,7 +195,7 @@ export const QueryV2 = forwardRef(function QueryV2(
)}
{isMultiQueryAllowed && (
<Dropdown
<DropdownMenuSimple
className="query-actions-dropdown"
menu={{
items: [
@@ -217,10 +217,10 @@ export const QueryV2 = forwardRef(function QueryV2(
: []),
],
}}
placement="bottomRight"
align="end"
>
<Ellipsis size={16} />
</Dropdown>
</DropdownMenuSimple>
)}
</div>
</div>

View File

@@ -55,6 +55,7 @@
display: flex;
align-items: center;
gap: 8px;
min-height: 24px;
.checkbox-value-section {
display: flex;

View File

@@ -1,6 +1,7 @@
/* eslint-disable sonarjs/no-identical-functions */
import { Fragment, useMemo, useState } from 'react';
import { Button, Checkbox, Input, Skeleton } from 'antd';
import { Button, Input, Skeleton } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import { removeKeysFromExpression } from 'components/QueryBuilderV2/utils';
@@ -634,10 +635,12 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
)}
<div className="value">
<Checkbox
onChange={(e): void => onChange(value, e.target.checked, false)}
checked={currentFilterState[value]}
onChange={(checked): void =>
onChange(value, checked === true, false)
}
value={currentFilterState[value]}
disabled={isFilterDisabled}
rootClassName="check-box"
className="check-box"
/>
<div

View File

@@ -4,14 +4,14 @@ import type {
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
} from 'antd';
import { Button, Dropdown, Flex, MenuProps } from 'antd';
import { Button, Flex } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import { Switch } from '@signozhq/ui/switch';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { SlidersHorizontal } from '@signozhq/icons';
import { popupContainer } from 'utils/selectPopupContainer';
import ResizeTable from './ResizeTable';
import { DynamicColumnTableProps } from './types';
@@ -84,8 +84,9 @@ function DynamicColumnTable({
);
};
const items: MenuProps['items'] =
const items: MenuItem[] =
dynamicColumns?.map((column, index) => ({
key: String(index),
label: (
<div
className="dynamicColumnsTable-items"
@@ -99,8 +100,6 @@ function DynamicColumnTable({
/>
</div>
),
key: index,
type: 'checkbox',
})) || [];
// Get current page from URL or default to 1
@@ -129,18 +128,14 @@ function DynamicColumnTable({
<Flex justify="flex-end" align="center" gap={8}>
{facingIssueBtn && <LaunchChatSupport {...facingIssueBtn} />}
{dynamicColumns && (
<Dropdown
getPopupContainer={popupContainer}
menu={{ items }}
trigger={['click']}
>
<DropdownMenuSimple menu={{ items }}>
<Button
className="dynamicColumnTable-button filter-btn"
size="middle"
icon={<SlidersHorizontal size={14} />}
data-testid="additional-filters-button"
/>
</Dropdown>
</DropdownMenuSimple>
)}
</Flex>

View File

@@ -1,11 +1,10 @@
import { CircleAlert, RefreshCw } from '@signozhq/icons';
import { Checkbox, Select } from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useListRoles } from 'api/generated/services/role';
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
import cx from 'classnames';
import APIError from 'types/api/error';
import { popupContainer } from 'utils/selectPopupContainer';
import './RolesSelect.styles.scss';
@@ -74,7 +73,6 @@ interface BaseProps {
id?: string;
placeholder?: string;
className?: string;
getPopupContainer?: (trigger: HTMLElement) => HTMLElement;
roles?: AuthtypesRoleDTO[];
loading?: boolean;
isError?: boolean;
@@ -112,14 +110,13 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
});
const roles = externalRoles ?? data?.data ?? [];
const options = getRoleOptions(roles);
const items: ComboboxSimpleItem[] = getRoleOptions(roles);
const {
mode,
id,
placeholder = 'Select role',
className,
getPopupContainer = popupContainer,
loading = internalLoading,
isError = internalError,
error = convertToApiError(internalErrorObj),
@@ -127,55 +124,47 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
disabled,
} = props;
const notFoundContent = isError ? (
<ErrorContent error={error} onRefetch={onRefetch} />
) : undefined;
const emptyPlaceholder = isError
? error?.message || 'Failed to load roles'
: 'No roles available';
if (mode === 'multiple') {
const { value = [], onChange } = props as MultipleProps;
return (
<Select
id={id}
mode="multiple"
value={value}
onChange={onChange}
placeholder={placeholder}
className={cx('roles-select', className)}
loading={loading}
notFoundContent={notFoundContent}
options={options}
optionFilterProp="label"
optionRender={(option): JSX.Element => (
<Checkbox
checked={value.includes(option.value as string)}
style={{ pointerEvents: 'none' }}
>
{option.label}
</Checkbox>
)}
getPopupContainer={getPopupContainer}
disabled={disabled}
/>
<>
<ComboboxSimple
id={id}
multiple
value={value}
onChange={(v): void => onChange?.(v as string[])}
placeholder={placeholder}
className={cx('roles-select', className)}
loading={loading}
emptyPlaceholder={emptyPlaceholder}
items={items}
style={disabled ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
/>
{isError && <ErrorContent error={error} onRefetch={onRefetch} />}
</>
);
}
const { value, onChange, allowClear = true } = props as SingleProps;
const { value, onChange } = props as SingleProps;
return (
<Select
id={id}
showSearch
value={value || undefined}
onChange={onChange}
placeholder={placeholder}
allowClear={allowClear}
className={cx('roles-single-select', className)}
loading={loading}
notFoundContent={notFoundContent}
options={options}
optionFilterProp="label"
getPopupContainer={getPopupContainer}
disabled={disabled}
/>
<>
<ComboboxSimple
id={id}
value={value || undefined}
onChange={(v): void => onChange?.((v as string) || undefined)}
placeholder={placeholder}
className={cx('roles-single-select', className)}
loading={loading}
emptyPlaceholder={emptyPlaceholder}
items={items}
style={disabled ? { pointerEvents: 'none', opacity: 0.5 } : undefined}
/>
{isError && <ErrorContent error={error} onRefetch={onRefetch} />}
</>
);
}

View File

@@ -2,7 +2,7 @@ import type { Control, UseFormRegister } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { DatePicker } from 'antd';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
@@ -60,30 +60,21 @@ function KeyFormPhase({
name="expiryMode"
control={control}
render={({ field }): JSX.Element => (
<ToggleGroup
<ToggleGroupSimple
type="single"
value={field.value}
onChange={(val): void => {
onChange={(val: string): void => {
if (val) {
field.onChange(val);
}
}}
size="sm"
className="add-key-modal__expiry-toggle"
>
<ToggleGroupItem
value={ExpiryMode.NONE}
className="add-key-modal__expiry-toggle-btn"
>
No Expiration
</ToggleGroupItem>
<ToggleGroupItem
value={ExpiryMode.DATE}
className="add-key-modal__expiry-toggle-btn"
>
Set Expiration Date
</ToggleGroupItem>
</ToggleGroup>
items={[
{ value: ExpiryMode.NONE, label: 'No Expiration' },
{ value: ExpiryMode.DATE, label: 'Set Expiration Date' },
]}
/>
)}
/>
</div>

View File

@@ -4,7 +4,7 @@ import { LockKeyhole, Trash2, X } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { DatePicker } from 'antd';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
@@ -101,31 +101,22 @@ function EditKeyForm({
name="expiryMode"
control={control}
render={({ field }): JSX.Element => (
<ToggleGroup
<ToggleGroupSimple
type="single"
value={field.value}
onChange={(val): void => {
onChange={(val: string): void => {
if (val && canUpdate) {
field.onChange(val);
}
}}
size="sm"
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle"
>
<ToggleGroupItem
value={ExpiryMode.NONE}
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle-btn"
>
No Expiration
</ToggleGroupItem>
<ToggleGroupItem
value={ExpiryMode.DATE}
disabled={!canUpdate}
className="edit-key-modal__expiry-toggle-btn"
>
Set Expiration Date
</ToggleGroupItem>
</ToggleGroup>
items={[
{ value: ExpiryMode.NONE, label: 'No Expiration' },
{ value: ExpiryMode.DATE, label: 'Set Expiration Date' },
]}
/>
)}
/>
</div>

View File

@@ -4,7 +4,7 @@ import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { toast } from '@signozhq/ui/sonner';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Pagination, Skeleton } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
@@ -395,11 +395,11 @@ function ServiceAccountDrawer({
const drawerContent = (
<div className="sa-drawer__layout">
<div className="sa-drawer__tabs">
<ToggleGroup
<ToggleGroupSimple
type="single"
value={activeTab}
size="sm"
onChange={(val): void => {
onChange={(val: string): void => {
if (val) {
void setActiveTab(val as ServiceAccountDrawerTab);
if (val !== ServiceAccountDrawerTab.Keys) {
@@ -409,25 +409,30 @@ function ServiceAccountDrawer({
}
}}
className="sa-drawer__tab-group"
>
<ToggleGroupItem
value={ServiceAccountDrawerTab.Overview}
className="sa-drawer__tab"
>
<LayoutGrid size={14} />
Overview
</ToggleGroupItem>
<ToggleGroupItem
value={ServiceAccountDrawerTab.Keys}
className="sa-drawer__tab"
>
<Key size={14} />
Keys
{keys.length > 0 && (
<span className="sa-drawer__tab-count">{keys.length}</span>
)}
</ToggleGroupItem>
</ToggleGroup>
items={[
{
value: ServiceAccountDrawerTab.Overview,
label: (
<>
<LayoutGrid size={14} />
Overview
</>
),
},
{
value: ServiceAccountDrawerTab.Keys,
label: (
<>
<Key size={14} />
Keys
{keys.length > 0 && (
<span className="sa-drawer__tab-count">{keys.length}</span>
)}
</>
),
},
]}
/>
{activeTab === ServiceAccountDrawerTab.Keys && (
<AuthZTooltip
checks={[

View File

@@ -1,12 +1,6 @@
.signoz-radio-group.ant-radio-group {
.signoz-radio-group {
color: var(--l2-foreground);
&.ant-radio-group-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
.view-title {
display: flex;
gap: var(--margin-2);
@@ -30,43 +24,41 @@
}
}
.tab {
> button {
border: 1px solid var(--l1-border);
background: var(--l1-background);
&:hover {
color: var(--l1-foreground);
}
&::before {
background: var(--l1-border);
}
}
.selected_view {
&,
&:hover {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
&[data-state='on'] {
&,
&:hover {
background: var(--l3-background);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
}
&::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
}
&::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
}
&.ant-radio-group-disabled {
.tab,
.selected_view {
&[disabled],
&[data-disabled] {
background: var(--l2-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
}
.tab:hover,
.selected_view:hover {
background: var(--l2-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
&:hover {
background: var(--l2-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
}
}
}
}

View File

@@ -1,4 +1,4 @@
import { Radio, RadioChangeEvent } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import './SignozRadioGroup.styles.scss';
@@ -11,7 +11,7 @@ interface Option {
interface SignozRadioGroupProps {
value: string;
options: Option[];
onChange: (e: RadioChangeEvent) => void;
onChange: (value: string) => void;
className?: string;
disabled?: boolean;
}
@@ -24,26 +24,22 @@ function SignozRadioGroup({
disabled = false,
}: SignozRadioGroupProps): JSX.Element {
return (
<Radio.Group
<ToggleGroupSimple
type="single"
value={value}
buttonStyle="solid"
className={`signoz-radio-group ${className}`}
onChange={onChange}
disabled={disabled}
>
{options.map((option) => (
<Radio.Button
key={option.value}
value={option.value}
className={value === option.value ? 'selected_view tab' : 'tab'}
>
items={options.map((option) => ({
value: option.value,
label: (
<div className="view-title-container">
{option.icon && <div className="icon-container">{option.icon}</div>}
{option.label}
</div>
</Radio.Button>
))}
</Radio.Group>
),
}))}
/>
);
}

View File

@@ -4,13 +4,10 @@ import {
ButtonProps,
Col,
ColProps,
Divider,
DividerProps,
Row,
RowProps,
Space,
SpaceProps,
TabsProps,
} from 'antd';
import {
Typography,
@@ -34,21 +31,11 @@ const StyledRow = styled(Row)<TStyledRow>`
${styledClass}
`;
type TStyledDivider = DividerProps & IStyledClass;
const StyledDivider = styled(Divider)<TStyledDivider>`
${styledClass}
`;
type TStyledSpace = SpaceProps & IStyledClass;
const StyledSpace = styled(Space)<TStyledSpace>`
${styledClass}
`;
type TStyledTabs = TabsProps & IStyledClass;
const StyledTabs = styled(Divider)<TStyledTabs>`
${styledClass}
`;
type TStyledButton = ButtonProps & IStyledClass;
const StyledButton = styled(Button)<TStyledButton>`
${styledClass}
@@ -79,9 +66,7 @@ export {
StyledButton,
StyledCol,
StyledDiv,
StyledDivider,
StyledRow,
StyledSpace,
StyledTabs,
StyledTypography,
};

View File

@@ -1,6 +1,7 @@
import { Dispatch, SetStateAction, useCallback, useMemo } from 'react';
import { ChevronDown, Globe } from '@signozhq/icons';
import { Button, Dropdown } from 'antd';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import { Button } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import TimeItems, {
timePreferance,
@@ -27,20 +28,17 @@ function TimePreference({
const menu = useMemo(
() => ({
items: menuItems,
onClick: timeMenuItemOnChangeHandler,
items: menuItems.map((item) => ({
...item,
onClick: timeMenuItemOnChangeHandler,
})),
}),
[timeMenuItemOnChangeHandler],
);
return (
<Dropdown
menu={menu}
rootClassName="time-selection-menu"
className="time-selection-target"
trigger={['click']}
>
<Button>
<DropdownMenuSimple menu={menu} className="time-selection-menu">
<Button className="time-selection-target">
<div className="button-selected-text">
<Globe size={14} />
<Typography.Text className="selected-value">
@@ -49,7 +47,7 @@ function TimePreference({
</div>
<ChevronDown size="md" />
</Button>
</Dropdown>
</DropdownMenuSimple>
);
}

View File

@@ -1,7 +1,11 @@
import { useMemo } from 'react';
import { SolidAlertTriangle } from '@signozhq/icons';
import { Select, Tooltip } from 'antd';
import type { DefaultOptionType } from 'antd/es/select';
import {
ComboboxSimple,
ComboboxSimpleGroup,
ComboboxSimpleItem,
} from '@signozhq/ui/combobox';
import { Tooltip } from 'antd';
import classNames from 'classnames';
import { UniversalYAxisUnitMappings } from './constants';
@@ -42,69 +46,53 @@ function YAxisUnitSelector({
return '';
}, [initialValue, value, loading]);
const handleSearch = (
searchTerm: string,
currentOption: DefaultOptionType | undefined,
): boolean => {
if (!currentOption?.value) {
return false;
}
const categoriesToRender = useMemo(
() => categoriesOverride || getYAxisCategories(source),
[categoriesOverride, source],
);
const search = searchTerm.toLowerCase();
const unitId = currentOption.value.toString().toLowerCase();
const unitLabel = currentOption.children?.toString().toLowerCase() || '';
const groups: ComboboxSimpleGroup[] = useMemo(
() =>
categoriesToRender.map((category) => ({
heading: category.name,
items: category.units.map((unit): ComboboxSimpleItem => {
const aliases = Array.from(
UniversalYAxisUnitMappings[unit.id as UniversalYAxisUnit] ?? [],
);
return {
value: unit.id,
label: unit.name,
keywords: aliases,
};
}),
})),
[categoriesToRender],
);
// Check label and id
if (unitId.includes(search) || unitLabel.includes(search)) {
return true;
}
// Check aliases (from the mapping) using array iteration
const aliases = Array.from(
UniversalYAxisUnitMappings[currentOption.value as UniversalYAxisUnit] ?? [],
);
return aliases.some((alias) => alias.toLowerCase().includes(search));
const handleChange = (val: string | string[]): void => {
onChange(val as UniversalYAxisUnit);
};
const categoriesToRender = useMemo(() => {
return categoriesOverride || getYAxisCategories(source);
}, [categoriesOverride, source]);
return (
<div
className={classNames('y-axis-unit-selector-component', containerClassName)}
>
<Select
showSearch
value={universalUnit}
onChange={onChange}
<ComboboxSimple
value={universalUnit ?? undefined}
onChange={handleChange}
placeholder={placeholder}
filterOption={(input, option): boolean => handleSearch(input, option)}
loading={loading}
suffixIcon={
incompatibleUnitMessage ? (
<Tooltip title={incompatibleUnitMessage}>
<SolidAlertTriangle role="img" aria-label="warning" size="md" />
</Tooltip>
) : undefined
}
className={classNames({
'warning-state': incompatibleUnitMessage,
'warning-state': !!incompatibleUnitMessage,
})}
data-testid={dataTestId}
allowClear
>
{categoriesToRender.map((category) => (
<Select.OptGroup key={category.name} label={category.name}>
{category.units.map((unit) => (
<Select.Option key={unit.id} value={unit.id}>
{unit.name}
</Select.Option>
))}
</Select.OptGroup>
))}
</Select>
testId={dataTestId}
groups={groups}
/>
{incompatibleUnitMessage && (
<Tooltip title={incompatibleUnitMessage}>
<SolidAlertTriangle role="img" aria-label="warning" size="md" />
</Tooltip>
)}
</div>
);
}

View File

@@ -1,5 +1,8 @@
.y-axis-unit-selector-component {
.ant-select {
--combobox-trigger-background-color: var(--l2-background);
--combobox-trigger-border-color: var(--l2-border);
[data-slot='combobox-trigger'] {
width: 220px;
}
}

View File

@@ -10,7 +10,7 @@ import {
DialogSubtitle,
DialogTitle,
} from '@signozhq/ui/dialog';
import { ToggleGroup, ToggleGroupItem } from '@signozhq/ui/toggle-group';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import type {
ApprovalEventDTO,
@@ -132,45 +132,43 @@ export default function ApprovalCard({
<div className={styles.diffModalBody}>
<p className={styles.diffModalSummary}>{approval.summary}</p>
<div className={styles.diffToolbarRow}>
<ToggleGroup
<ToggleGroupSimple
type="single"
size="sm"
value={viewMode}
onChange={(next): void => {
// Radix `single` group can emit '' when the active item is clicked again.
onChange={(next: string): void => {
// Radix `single` group can emit '' when the active item
// is clicked again — preserve the current mode.
if (next === 'split' || next === 'unified') {
setViewMode(next);
}
}}
>
<TooltipSimple title="Split view">
<ToggleGroupItem value="split" aria-label="Split view">
<Columns2 size={12} />
</ToggleGroupItem>
</TooltipSimple>
<TooltipSimple title="Unified view">
<ToggleGroupItem value="unified" aria-label="Unified view">
<List size={12} />
</ToggleGroupItem>
</TooltipSimple>
</ToggleGroup>
<ToggleGroup
items={[
{
value: 'split',
'aria-label': 'Split view',
label: <Columns2 size={12} />,
},
{
value: 'unified',
'aria-label': 'Unified view',
label: <List size={12} />,
},
]}
/>
<ToggleGroupSimple
type="multiple"
size="sm"
value={wrapText ? ['wrap'] : []}
onChange={(next): void => setWrapText(next.includes('wrap'))}
>
<TooltipSimple
title={wrapText ? 'Disable text wrap' : 'Wrap long lines'}
>
<ToggleGroupItem
value="wrap"
aria-label={wrapText ? 'Disable text wrap' : 'Wrap long lines'}
>
<WrapText size={12} />
</ToggleGroupItem>
</TooltipSimple>
</ToggleGroup>
onChange={(next: string[]): void => setWrapText(next.includes('wrap'))}
items={[
{
value: 'wrap',
'aria-label': wrapText ? 'Disable text wrap' : 'Wrap long lines',
label: <WrapText size={12} />,
},
]}
/>
</div>
{approval.diff && (
<DiffView

View File

@@ -2,7 +2,8 @@ import { useState } from 'react';
import cx from 'classnames';
import { Button } from '@signozhq/ui/button';
import logEvent from 'api/common/logEvent';
import { Checkbox, Radio } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
import { AIAssistantEvents } from '../../../events';
import { useAIAssistantAnalyticsContext } from '../../../hooks/useAIAssistantAnalyticsContext';
@@ -81,31 +82,43 @@ export default function InteractiveQuestion({
{question && <p className={blockStyles.title}>{question}</p>}
{type === 'radio' ? (
<Radio.Group
<RadioGroup
className={styles.options}
onChange={(e): void => {
setSelected([e.target.value]);
handleSubmit([e.target.value]);
onChange={(value): void => {
setSelected([value]);
handleSubmit([value]);
}}
>
{normalized.map((opt) => (
<Radio key={opt.value} value={opt.value} className={styles.option}>
<RadioGroupItem
key={opt.value}
value={opt.value}
className={styles.option}
>
{opt.label}
</Radio>
</RadioGroupItem>
))}
</Radio.Group>
</RadioGroup>
) : (
<>
<Checkbox.Group
className={cx(styles.options, styles.checkbox)}
onChange={(vals): void => setSelected(vals as string[])}
>
<div className={cx(styles.options, styles.checkbox)}>
{normalized.map((opt) => (
<Checkbox key={opt.value} value={opt.value} className={styles.option}>
<Checkbox
key={opt.value}
value={selected.includes(opt.value)}
onChange={(checked): void => {
setSelected((prev) =>
checked === true
? [...prev, opt.value]
: prev.filter((v) => v !== opt.value),
);
}}
className={styles.option}
>
{opt.label}
</Checkbox>
))}
</Checkbox.Group>
</div>
<Button
variant="solid"
size="sm"

View File

@@ -1,5 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { Checkbox, Input } from 'antd';
import { Input } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
import { Typography } from '@signozhq/ui/typography';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -320,10 +321,8 @@ function AnomalyAlertEvaluationView({
{filteredSeriesKeys.length > 0 && (
<Checkbox
className="anomaly-alert-evaluation-view-series-list-item"
type="checkbox"
name="series"
value="all"
checked={selectedSeries === null}
value={selectedSeries === null}
onChange={(): void => handleSeriesChange(null)}
>
Show All
@@ -335,10 +334,8 @@ function AnomalyAlertEvaluationView({
<Checkbox
className="anomaly-alert-evaluation-view-series-list-item"
key={seriesKey}
type="checkbox"
name="series"
value={seriesKey}
checked={selectedSeries === seriesKey}
value={selectedSeries === seriesKey}
onChange={(): void => handleSeriesChange(seriesKey)}
>
<div

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Select } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { initialQueriesMap } from 'constants/queryBuilder';
import {
@@ -115,11 +115,11 @@ function AllEndPoints({
// --- GROUP BY STATE SYNC (existing) ---
const handleGroupByChange = useCallback(
(value: IBuilderQuery['groupBy']) => {
(value: string[]) => {
const newGroupBy = [];
for (let index = 0; index < value.length; index++) {
const element = value[index] as unknown as string;
const element = value[index];
// Check if the key exists in our cached options first
if (allAvailableGroupByOptions[element]) {
@@ -242,17 +242,14 @@ function AllEndPoints({
</div>
<div className="group-by-container">
<div className="group-by-label"> Group by </div>
<Select
<ComboboxSimple
className="group-by-select"
loading={isLoadingGroupByFilters}
mode="multiple"
value={groupBy}
allowClear
maxTagCount="responsive"
multiple
value={groupBy.map((g) => g.key)}
placeholder="Search for attribute"
options={groupByOptions}
onChange={handleGroupByChange}
onSearch={(value: string): void => setGroupBySearchValue(value)}
items={groupByOptions}
onChange={(value): void => handleGroupByChange(value as string[])}
/>{' '}
</div>
<div className="endpoints-table-container">

View File

@@ -161,41 +161,6 @@
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--l1-border);
.views-tabs {
color: var(--l2-foreground);
.view-title {
display: flex;
gap: var(--margin-2);
align-items: center;
justify-content: center;
font-size: 14px;
font-style: normal;
font-weight: var(--font-weight-normal);
}
.tab {
padding: 0 32px;
background: var(--l3-background);
border: 1px solid var(--l1-border);
width: auto;
}
.tab::before {
background: var(--l1-border);
}
.selected_view {
background: none;
border: 1px solid var(--l1-border);
border-bottom: none;
}
.selected_view::before {
background: var(--l1-border);
}
}
}
}
@@ -211,14 +176,14 @@
.group-by-label {
display: flex;
padding: 6px 15px;
padding: 4px 15px;
justify-content: center;
align-items: center;
gap: 4px;
flex-shrink: 0;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
border-right: none;
background: var(--l3-background);
@@ -227,18 +192,15 @@
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
line-height: 22px;
letter-spacing: -0.07px;
}
.group-by-select {
width: 100%;
.ant-select-selector {
font-size: 14px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
box-sizing: border-box;
--combobox-trigger-border-radius: 0px;
}
}
// Add border-bottom to table cells when pagination is not present
@@ -461,14 +423,13 @@
}
.endpoint-details-filters-container-dropdown {
width: 120px;
width: 150px;
border-right: 1px solid var(--l1-border);
height: 36px;
display: flex;
align-items: center;
.ant-select-single {
height: 32px;
}
--combobox-trigger-border-color: transparent;
}
.endpoint-details-filters-container-search {
@@ -699,40 +660,6 @@
left: 50%;
transform: translate(-50%, -50%);
}
.views-tabs {
color: var(--l2-foreground);
.ant-btn {
box-shadow: none;
position: relative;
}
.tab {
border: 1px solid var(--l1-border);
width: 114px;
z-index: 1;
}
.tab:hover {
z-index: 3;
}
.tab::before {
background: var(--l2-background);
}
.selected_view {
background: var(--l1-border);
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
z-index: 2;
}
.selected_view::before {
background: var(--l2-background);
}
}
}
.top-services-content {

View File

@@ -2,9 +2,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Spacing } from '@signozhq/design-tokens';
import { Button, Divider, Drawer, Radio } from 'antd';
import { Button, Drawer } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import type { RadioChangeEvent } from 'antd/lib';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
@@ -55,9 +56,9 @@ function DomainDetails({
const [initialFiltersEndPointStats, setInitialFiltersEndPointStats] =
useState<IBuilderQuery['filters']>(domainListFilters);
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
setParams({ selectedView: e.target.value });
const handleTabChange = (value: string): void => {
setSelectedView(value as VIEWS);
setParams({ selectedView: value });
};
const handleEndPointChange = (name: string): void => {
@@ -224,38 +225,17 @@ function DomainDetails({
timeRange={modalTimeRange}
/>
<div className="views-tabs-container">
<Radio.Group
className="views-tabs"
<ToggleGroupSimple
type="single"
onChange={handleTabChange}
value={selectedView}
>
<Radio.Button
className={
selectedView === VIEW_TYPES.ALL_ENDPOINTS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.ALL_ENDPOINTS}
>
<div className="view-title">All Endpoints</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.ENDPOINT_STATS
? 'tab selected_view'
: 'tab'
}
value={VIEW_TYPES.ENDPOINT_STATS}
>
<div className="view-title">Endpoint(s) Stats</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.TOP_ERRORS ? 'tab selected_view' : 'tab'
}
value={VIEW_TYPES.TOP_ERRORS}
>
<div className="view-title">Top 10 Errors</div>
</Radio.Button>
</Radio.Group>
size="lg"
items={[
{ value: VIEW_TYPES.ALL_ENDPOINTS, label: 'All Endpoints' },
{ value: VIEW_TYPES.ENDPOINT_STATS, label: 'Endpoint(s) Stats' },
{ value: VIEW_TYPES.TOP_ERRORS, label: 'Top 10 Errors' },
]}
/>
</div>
{selectedView === VIEW_TYPES.ALL_ENDPOINTS && (
<AllEndPoints

View File

@@ -263,7 +263,6 @@ function EndPointDetails({
setSelectedEndPointName={setSelectedEndPointName}
endPointDropDownDataQuery={endPointDropDownDataQuery}
parentContainerDiv=".endpoint-details-filters-container"
dropdownStyle={{ width: 'calc(100% - 36px)' }}
/>
</div>
<div className="endpoint-details-filters-container-search">

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { UseQueryResult } from 'react-query';
import { Select } from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { getFormattedEndPointDropDownData } from 'container/ApiMonitoring/utils';
import { SuccessResponse } from 'types/api';
@@ -22,40 +22,33 @@ function EndPointsDropDown({
selectedEndPointName,
setSelectedEndPointName,
endPointDropDownDataQuery,
parentContainerDiv,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
parentContainerDiv: _parentContainerDiv,
dropdownStyle,
}: EndPointsDropDownProps): JSX.Element {
const { data, isLoading, isFetching } = endPointDropDownDataQuery;
const handleChange = (value: string): void => {
setSelectedEndPointName(value);
const handleChange = (value: string | string[]): void => {
setSelectedEndPointName(value as string);
};
const formattedData = useMemo(
() =>
getFormattedEndPointDropDownData(data?.payload.data.result[0].table.rows),
getFormattedEndPointDropDownData(
data?.payload.data.result[0].table.rows,
) as ComboboxSimpleItem[],
[data?.payload.data.result],
);
return (
<Select
<ComboboxSimple
value={selectedEndPointName || undefined}
placeholder="Select endpoint"
loading={isLoading || isFetching}
style={{ width: '100%' }}
style={{ width: '100%', ...dropdownStyle }}
onChange={handleChange}
options={formattedData}
getPopupContainer={
parentContainerDiv
? (): HTMLElement =>
document.querySelector(parentContainerDiv) as HTMLElement
: (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement
}
dropdownStyle={dropdownStyle}
allowClear
onClear={(): void => {
setSelectedEndPointName('');
}}
items={formattedData}
virtualized
/>
);
}

View File

@@ -21,14 +21,17 @@ import { useResizeObserver } from 'hooks/useDimensions';
import { useNotifications } from 'hooks/useNotifications';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { LegendPosition } from 'lib/uPlotV2/components/types';
import { getStartAndEndTimesInMilliseconds } from 'pages/MessagingQueues/MessagingQueuesUtils';
import { useTimezone } from 'providers/Timezone';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import ErrorState from './ErrorState';
import { prepareStatusCodeBarChartsConfig } from './utils';
import {
getStepIntervalForQuery,
getTracesTimeRangeFromStepInterval,
prepareStatusCodeBarChartsConfig,
} from './utils';
function StatusCodeBarCharts({
endPointStatusCodeBarChartsDataQuery,
@@ -135,6 +138,18 @@ function StatusCodeBarCharts({
[domainName, filters],
);
const activeApiResponse = useMemo(
() =>
currentWidgetInfoIndex === 0
? formattedEndPointStatusCodeBarChartsDataPayload
: formattedEndPointStatusCodeLatencyBarChartsDataPayload,
[
currentWidgetInfoIndex,
formattedEndPointStatusCodeBarChartsDataPayload,
formattedEndPointStatusCodeLatencyBarChartsDataPayload,
],
);
const graphClickHandler = useCallback(
(
xValue: number,
@@ -144,11 +159,14 @@ function StatusCodeBarCharts({
metric?: { [key: string]: string },
queryData?: { queryName: string; inFocusOrNot: boolean },
): void => {
const TWO_AND_HALF_MINUTES_IN_MILLISECONDS = 2.5 * 60 * 1000; // 150,000 milliseconds
const customFilters = getCustomFiltersForBarChart(metric);
const { start, end } = getStartAndEndTimesInMilliseconds(
const stepInterval = getStepIntervalForQuery(
activeApiResponse,
queryData?.queryName,
);
const { start, end } = getTracesTimeRangeFromStepInterval(
xValue,
TWO_AND_HALF_MINUTES_IN_MILLISECONDS,
stepInterval,
);
handleGraphClick({
@@ -171,6 +189,7 @@ function StatusCodeBarCharts({
});
},
[
activeApiResponse,
widget,
navigateToExplorerPages,
navigateToExplorer,

View File

@@ -0,0 +1,68 @@
import {
getMinStepIntervalFromApiResponse,
getStepIntervalForQuery,
getTracesTimeRangeFromStepInterval,
} from '../utils';
describe('StatusCodeBarCharts utils', () => {
describe('getTracesTimeRangeFromStepInterval', () => {
const xValue = 1609459200; // seconds
it('keeps start at click time with a minimum 5 minute end range', () => {
const { start, end } = getTracesTimeRangeFromStepInterval(xValue, 60);
expect(start).toBe(xValue * 1000);
expect(end - start).toBe(5 * 60 * 1000);
expect(end).toBe(xValue * 1000 + 5 * 60 * 1000);
});
it('extends end when step interval is larger than 5 minutes', () => {
const stepInterval = 600; // 10 minutes
const { start, end } = getTracesTimeRangeFromStepInterval(
xValue,
stepInterval,
);
expect(start).toBe(xValue * 1000);
expect(end - start).toBe(10 * 60 * 1000);
expect(end).toBe(xValue * 1000 + 10 * 60 * 1000);
});
});
describe('getMinStepIntervalFromApiResponse', () => {
it('returns 60 when step intervals are missing', () => {
expect(getMinStepIntervalFromApiResponse({} as any)).toBe(60);
});
it('returns the minimum step interval from the response', () => {
const apiResponse = {
data: {
newResult: {
meta: {
stepIntervals: { A: 120, B: 60 },
},
},
},
};
expect(getMinStepIntervalFromApiResponse(apiResponse as any)).toBe(60);
});
});
describe('getStepIntervalForQuery', () => {
it('returns query-specific step interval when available', () => {
const apiResponse = {
data: {
newResult: {
meta: {
stepIntervals: { A: 120, B: 60 },
},
},
},
};
expect(getStepIntervalForQuery(apiResponse as any, 'A')).toBe(120);
expect(getStepIntervalForQuery(apiResponse as any, 'B')).toBe(60);
});
});
});

View File

@@ -13,6 +13,65 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from 'types/api/widgets/getQuery';
import { v4 } from 'uuid';
const DEFAULT_STEP_INTERVAL_SECONDS = 60;
const MIN_TRACES_TIME_RANGE_MINUTES = 5;
export function getMinStepIntervalFromApiResponse(
apiResponse: MetricRangePayloadProps,
): number {
const stepIntervals: ExecStats['stepIntervals'] = get(
apiResponse,
'data.newResult.meta.stepIntervals',
{},
);
const values = Object.values(stepIntervals).filter(
(value): value is number =>
typeof value === 'number' && Number.isFinite(value),
);
if (values.length === 0) {
return DEFAULT_STEP_INTERVAL_SECONDS;
}
return Math.min(...values);
}
export function getStepIntervalForQuery(
apiResponse: MetricRangePayloadProps,
queryName?: string,
): number {
const minStepInterval = getMinStepIntervalFromApiResponse(apiResponse);
if (!queryName) {
return minStepInterval;
}
const stepIntervals: ExecStats['stepIntervals'] = get(
apiResponse,
'data.newResult.meta.stepIntervals',
{},
);
return get(stepIntervals, queryName, minStepInterval) ?? minStepInterval;
}
export function getTracesTimeRangeFromStepInterval(
xValue: number,
stepIntervalSeconds: number,
): { start: number; end: number } {
const rangeMinutes = Math.max(
stepIntervalSeconds / 60,
MIN_TRACES_TIME_RANGE_MINUTES,
);
const rangeMs = rangeMinutes * 60 * 1000;
const start = Math.floor(xValue * 1000);
return {
start,
end: Math.ceil(start + rangeMs),
};
}
export const prepareStatusCodeBarChartsConfig = ({
timezone,
isDarkMode,
@@ -41,7 +100,7 @@ export const prepareStatusCodeBarChartsConfig = ({
'data.newResult.meta.stepIntervals',
{},
);
const minStepInterval = Math.min(...Object.values(stepIntervals));
const minStepInterval = getMinStepIntervalFromApiResponse(apiResponse);
const config = buildBaseConfig({
id: v4(),

View File

@@ -1,8 +1,8 @@
import { memo, useMemo } from 'react';
import { ChevronLeft, ChevronRight } from '@signozhq/icons';
import { Button, Flex, Select } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Button, Flex } from 'antd';
import { DEFAULT_PER_PAGE_OPTIONS, Pagination } from 'hooks/queryPagination';
import { popupContainer } from 'utils/selectPopupContainer';
import { defaultSelectStyle } from './config';
import { Container } from './styles';
@@ -59,20 +59,18 @@ function Controls({
</Button>
{showSizeChanger && (
<Select<Pagination['limit']>
<SelectSimple
style={defaultSelectStyle}
loading={isLoading}
value={countPerPage}
onChange={handleCountItemsPerPageChange}
getPopupContainer={popupContainer}
>
{perPageOptions.map((count) => (
<Select.Option
key={count}
value={count}
>{`${count} / page`}</Select.Option>
))}
</Select>
disabled={isLoading}
value={String(countPerPage)}
onChange={(value): void =>
handleCountItemsPerPageChange(Number(value) as Pagination['limit'])
}
items={perPageOptions.map((count) => ({
value: String(count),
label: `${count} / page`,
}))}
/>
)}
</Container>
);

View File

@@ -61,10 +61,6 @@
margin-left: 16px;
}
&:not(:first-of-type) {
margin-left: 24px !important;
}
[aria-selected='false'] {
.periscope-tab {
color: var(--l2-foreground);

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { Button, Select, Tooltip } from 'antd';
import { Button, Tooltip } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -231,15 +232,18 @@ function AlertThreshold({
<Typography.Text className="sentence-text">
Send a notification when
</Typography.Text>
<Select
<SelectSimple
value={thresholdState.selectedQuery}
onChange={handleSelectedQueryChange}
onChange={(value): void => handleSelectedQueryChange(value as string)}
style={{ width: 80 }}
options={queryNames}
items={queryNames.map((q) => ({
value: String(q.value),
label: q.label as React.ReactNode,
}))}
data-testid="alert-threshold-query-select"
/>
<Typography.Text className="sentence-text">is</Typography.Text>
<Select
<SelectSimple
value={
(normalizeOperator(thresholdState.operator) ??
thresholdState.operator) as AlertThresholdOperator
@@ -247,17 +251,17 @@ function AlertThreshold({
onChange={(value): void => {
setThresholdState({
type: 'SET_OPERATOR',
payload: value,
payload: value as AlertThresholdOperator,
});
}}
style={{ width: 180 }}
options={THRESHOLD_OPERATOR_OPTIONS}
items={THRESHOLD_OPERATOR_OPTIONS}
data-testid="alert-threshold-operator-select"
/>
<Typography.Text className="sentence-text">
the threshold(s)
</Typography.Text>
<Select
<SelectSimple
value={
(normalizeMatchType(thresholdState.matchType) ??
thresholdState.matchType) as AlertThresholdMatchType
@@ -265,11 +269,11 @@ function AlertThreshold({
onChange={(value): void => {
setThresholdState({
type: 'SET_MATCH_TYPE',
payload: value,
payload: value as AlertThresholdMatchType,
});
}}
style={{ width: 180 }}
options={matchTypeOptionsWithTooltips}
items={matchTypeOptionsWithTooltips}
data-testid="alert-threshold-match-type-select"
/>
<Typography.Text className="sentence-text">

View File

@@ -1,8 +1,8 @@
import { useMemo } from 'react';
import { Select } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useAppContext } from 'providers/App/App';
import { useCreateAlertState } from '../context';
import {
@@ -18,19 +18,13 @@ import {
} from '../context/types';
import { normalizeMatchType, normalizeOperator } from '../utils';
import { AnomalyAndThresholdProps } from './types';
import {
getQueryNames,
NotificationChannelsNotFoundContent,
RoutingPolicyBanner,
} from './utils';
import { getQueryNames, RoutingPolicyBanner } from './utils';
function AnomalyThreshold({
channels,
isLoadingChannels,
isErrorChannels,
refreshChannels,
}: AnomalyAndThresholdProps): JSX.Element {
const { user } = useAppContext();
const {
thresholdState,
setThresholdState,
@@ -42,13 +36,14 @@ function AnomalyThreshold({
const queryNames = getQueryNames(currentQuery);
const deviationOptions = useMemo(() => {
const options = [];
for (let i = 1; i <= 7; i++) {
options.push({ label: i.toString(), value: i });
}
return options;
}, []);
const deviationOptions = useMemo(
() =>
Array.from({ length: 7 }, (_, i) => ({
label: (i + 1).toString(),
value: (i + 1).toString(),
})),
[],
);
const updateThreshold = (
id: string,
@@ -71,16 +66,16 @@ function AnomalyThreshold({
<Typography.Text data-testid="notification-text" className="sentence-text">
Send notification when the observed value for
</Typography.Text>
<Select
<SelectSimple
value={thresholdState.selectedQuery}
data-testid="query-select"
testId="query-select"
onChange={(value): void => {
setThresholdState({
type: 'SET_SELECTED_QUERY',
payload: value,
payload: value as string,
});
}}
options={queryNames}
items={queryNames}
/>
<Typography.Text
data-testid="evaluation-window-text"
@@ -88,16 +83,16 @@ function AnomalyThreshold({
>
during the last
</Typography.Text>
<Select
<SelectSimple
value={thresholdState.evaluationWindow}
data-testid="evaluation-window-select"
testId="evaluation-window-select"
onChange={(value): void => {
setThresholdState({
type: 'SET_EVALUATION_WINDOW',
payload: value,
payload: value as string,
});
}}
options={ANOMALY_TIME_DURATION_OPTIONS}
items={ANOMALY_TIME_DURATION_OPTIONS}
/>
</div>
<div className="alert-condition-sentence">
@@ -105,34 +100,34 @@ function AnomalyThreshold({
<Typography.Text data-testid="threshold-text" className="sentence-text">
is
</Typography.Text>
<Select
value={thresholdState.thresholds[0].thresholdValue}
data-testid="threshold-value-select"
<SelectSimple
value={thresholdState.thresholds[0].thresholdValue?.toString()}
testId="threshold-value-select"
onChange={(value): void => {
updateThreshold(
thresholdState.thresholds[0].id,
'thresholdValue',
value.toString(),
(value as string).toString(),
);
}}
options={deviationOptions}
items={deviationOptions}
/>
<Typography.Text data-testid="deviations-text" className="sentence-text">
deviations
</Typography.Text>
<Select
<SelectSimple
value={
(normalizeOperator(thresholdState.operator) ??
thresholdState.operator) as AlertThresholdOperator
}
data-testid="operator-select"
testId="operator-select"
onChange={(value): void => {
setThresholdState({
type: 'SET_OPERATOR',
payload: value,
payload: value as AlertThresholdOperator,
});
}}
options={ANOMALY_THRESHOLD_OPERATOR_OPTIONS}
items={ANOMALY_THRESHOLD_OPERATOR_OPTIONS}
/>
<Typography.Text
data-testid="predicted-data-text"
@@ -140,19 +135,19 @@ function AnomalyThreshold({
>
the predicted data
</Typography.Text>
<Select
<SelectSimple
value={
(normalizeMatchType(thresholdState.matchType) ??
thresholdState.matchType) as AlertThresholdMatchType
}
data-testid="match-type-select"
testId="match-type-select"
onChange={(value): void => {
setThresholdState({
type: 'SET_MATCH_TYPE',
payload: value,
payload: value as AlertThresholdMatchType,
});
}}
options={ANOMALY_THRESHOLD_MATCH_TYPE_OPTIONS}
items={ANOMALY_THRESHOLD_MATCH_TYPE_OPTIONS}
/>
</div>
{/* Sentence 3 */}
@@ -160,16 +155,16 @@ function AnomalyThreshold({
<Typography.Text data-testid="using-the-text" className="sentence-text">
using the
</Typography.Text>
<Select
<SelectSimple
value={thresholdState.algorithm}
data-testid="algorithm-select"
testId="algorithm-select"
onChange={(value): void => {
setThresholdState({
type: 'SET_ALGORITHM',
payload: value,
payload: value as string,
});
}}
options={ANOMALY_ALGORITHM_OPTIONS}
items={ANOMALY_ALGORITHM_OPTIONS}
/>
<Typography.Text
data-testid="algorithm-with-text"
@@ -177,16 +172,16 @@ function AnomalyThreshold({
>
algorithm with
</Typography.Text>
<Select
<SelectSimple
value={thresholdState.seasonality}
data-testid="seasonality-select"
testId="seasonality-select"
onChange={(value): void => {
setThresholdState({
type: 'SET_SEASONALITY',
payload: value,
payload: value as string,
});
}}
options={ANOMALY_SEASONALITY_OPTIONS}
items={ANOMALY_SEASONALITY_OPTIONS}
/>
{notificationSettings.routingPolicies ? (
<>
@@ -196,35 +191,25 @@ function AnomalyThreshold({
>
seasonality to
</Typography.Text>
<Select
<ComboboxSimple
value={thresholdState.thresholds[0].channels}
onChange={(value): void =>
updateThreshold(thresholdState.thresholds[0].id, 'channels', value)
updateThreshold(
thresholdState.thresholds[0].id,
'channels',
value as string[],
)
}
style={{ width: 350 }}
options={channels.map((channel) => ({
items={channels.map((channel) => ({
value: channel.id,
label: channel.name,
}))}
mode="multiple"
multiple
placeholder="Select notification channels"
showSearch
maxTagCount={2}
maxTagPlaceholder={(omittedValues): string =>
`+${omittedValues.length} more`
}
maxTagTextLength={10}
filterOption={(input, option): boolean =>
option?.label?.toLowerCase().includes(input.toLowerCase()) || false
}
status={isErrorChannels ? 'error' : undefined}
disabled={isLoadingChannels}
notFoundContent={
<NotificationChannelsNotFoundContent
user={user}
refreshChannels={refreshChannels}
/>
}
loading={isLoadingChannels}
className={isErrorChannels ? 'error' : undefined}
emptyPlaceholder="No channels found"
/>
</>
) : (

View File

@@ -1,14 +1,14 @@
import { useMemo, useState } from 'react';
import { Button, Input, Select, Tooltip } from 'antd';
import { Button, Input, Tooltip } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import { CircleX, Trash } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
import { useCreateAlertState } from '../context';
import { AlertThresholdOperator } from '../context/types';
import { normalizeOperator } from '../utils';
import { ThresholdItemProps } from './types';
import { NotificationChannelsNotFoundContent } from './utils';
function ThresholdItem({
threshold,
@@ -18,36 +18,38 @@ function ThresholdItem({
channels,
units,
isErrorChannels,
refreshChannels,
isLoadingChannels,
}: ThresholdItemProps): JSX.Element {
const { user } = useAppContext();
const { thresholdState, notificationSettings } = useCreateAlertState();
const [showRecoveryThreshold, setShowRecoveryThreshold] = useState(false);
const yAxisUnitSelect = useMemo(() => {
let component = (
<Select
<SelectSimple
placeholder="Unit"
value={threshold.unit ? threshold.unit : null}
onChange={(value): void => updateThreshold(threshold.id, 'unit', value)}
value={threshold.unit ? threshold.unit : undefined}
onChange={(value): void =>
updateThreshold(threshold.id, 'unit', value as string)
}
style={{ width: 150 }}
options={units}
items={units}
disabled={units.length === 0}
data-testid="threshold-unit-select"
testId="threshold-unit-select"
/>
);
if (units.length === 0) {
component = (
<Tooltip trigger="hover" title="No compatible units available">
<Select
<SelectSimple
placeholder="Unit"
value={threshold.unit ? threshold.unit : null}
onChange={(value): void => updateThreshold(threshold.id, 'unit', value)}
value={threshold.unit ? threshold.unit : undefined}
onChange={(value): void =>
updateThreshold(threshold.id, 'unit', value as string)
}
style={{ width: 150 }}
options={units}
items={units}
disabled={units.length === 0}
data-testid="threshold-unit-select"
testId="threshold-unit-select"
/>
</Tooltip>
);
@@ -117,37 +119,22 @@ function ThresholdItem({
{!notificationSettings.routingPolicies && (
<>
<Typography.Text className="sentence-text">send to</Typography.Text>
<Select
<ComboboxSimple
value={threshold.channels}
onChange={(value): void =>
updateThreshold(threshold.id, 'channels', value)
updateThreshold(threshold.id, 'channels', value as string[])
}
data-testid="threshold-notification-channel-select"
testId="threshold-notification-channel-select"
style={{ width: 350 }}
options={channels.map((channel) => ({
items={channels.map((channel) => ({
value: channel.name,
label: channel.name,
'data-testid': `threshold-notification-channel-option-${threshold.label}`,
}))}
mode="multiple"
multiple
placeholder="Select notification channels"
showSearch
maxTagCount={2}
maxTagPlaceholder={(omittedValues): string =>
`+${omittedValues.length} more`
}
maxTagTextLength={10}
filterOption={(input, option): boolean =>
option?.label?.toLowerCase().includes(input.toLowerCase()) || false
}
status={isErrorChannels ? 'error' : undefined}
disabled={isLoadingChannels}
notFoundContent={
<NotificationChannelsNotFoundContent
user={user}
refreshChannels={refreshChannels}
/>
}
loading={isLoadingChannels}
className={isErrorChannels ? 'error' : undefined}
emptyPlaceholder="No channels found"
/>
</>
)}

View File

@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
import type { DefaultOptionType } from 'antd/es/select';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { createMockAlertContextState } from 'container/CreateAlertV2/EvaluationSettings/__tests__/testUtils';
import { getAppContextMockState } from 'container/RoutingPolicies/__tests__/testUtils';
import * as appHooks from 'providers/App/App';
@@ -66,7 +66,7 @@ const mockChannels: Channels[] = [
{ id: TEST_CONSTANTS.CHANNEL_3, name: 'PagerDuty Channel' } as any,
];
const mockUnits: DefaultOptionType[] = [
const mockUnits: ComboboxSimpleItem[] = [
{ label: 'Bytes', value: 'bytes' },
{ label: 'KB', value: 'kb' },
{ label: 'MB', value: 'mb' },

View File

@@ -11,7 +11,7 @@
.alert-condition-tabs {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
@@ -26,19 +26,19 @@
padding: 9px;
box-shadow: none;
border-radius: 0px;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
border-left: 0.5px solid var(--l2-border);
border-bottom: 0.5px solid var(--l2-border);
width: 120px;
height: 36px;
gap: 8px;
&.active-tab {
background-color: var(--l1-background);
background-color: var(--l2-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
background-color: var(--l2-background-hover) !important;
}
}
@@ -54,7 +54,7 @@
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
color: var(--l3-foreground);
}
}
}
@@ -65,10 +65,16 @@
.anomaly-threshold-container {
padding: 24px;
padding-right: 72px;
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
background-color: var(--l2-background);
border: 1px solid var(--l2-border);
width: 100%;
--select-trigger-background-color: var(--l3-background);
--select-trigger-border-color: var(--l3-border);
--combobox-trigger-background-color: var(--l3-background);
--combobox-trigger-border-color: var(--l3-border);
.alert-condition-sentences {
display: flex;
flex-direction: column;
@@ -88,34 +94,6 @@
align-items: center;
gap: 8px;
}
.ant-select {
width: 240px;
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--muted-foreground);
}
}
}
}
@@ -151,9 +129,9 @@
flex-wrap: wrap;
.ant-input {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
height: 32px;
&::placeholder {
@@ -161,28 +139,28 @@
}
&:hover {
border-color: var(--l1-border);
border-color: var(--l3-border);
}
&:focus {
border-color: var(--l1-border);
border-color: var(--l3-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
border-color: var(--l2-border);
}
&:focus {
border-color: var(--l1-border);
border-color: var(--l2-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
@@ -192,21 +170,23 @@
}
.ant-select-selection-item {
color: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-select-arrow {
color: var(--muted-foreground);
color: var(--l2-foreground);
}
}
.icon-btn {
color: var(--muted-foreground);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
border: 1px solid var(--l3-border);
background-color: var(--l3-background);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: none;
}
}
}
@@ -226,8 +206,8 @@
pointer-events: none;
cursor: default;
color: var(--muted-foreground);
background-color: var(--card) !important;
border: 1px solid var(--l1-border);
background-color: var(--l3-background) !important;
border: 1px solid var(--l3-border);
border-radius: 4px;
display: flex;
align-items: center;
@@ -235,9 +215,9 @@
}
.ant-input {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
height: 32px;
&::placeholder {
@@ -245,11 +225,11 @@
}
&:hover {
border-color: var(--l1-border);
border-color: var(--l3-border);
}
&:focus {
border-color: var(--l1-border);
border-color: var(--l2-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
@@ -258,7 +238,7 @@
.add-threshold-btn {
margin-top: 8px;
border: 1px dashed var(--l1-border);
border: 1px dashed var(--l3-border);
color: var(--l2-foreground);
background-color: transparent;
border-radius: 4px;
@@ -267,10 +247,11 @@
display: flex;
align-items: center;
justify-content: center;
box-shadow: none;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
border-color: var(--l2-border);
color: var(--l3-foreground);
}
.anticon {
@@ -339,8 +320,9 @@
min-width: 240px;
width: auto;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
box-shadow: none;
.evaluate-alert-conditions-button-left {
color: var(--l2-foreground);
@@ -358,7 +340,7 @@
.evaluate-alert-conditions-button-right-text {
font-size: 12px;
font-weight: 500;
background-color: var(--l1-border);
background-color: var(--l3-border);
padding: 1px 4px;
}
}

View File

@@ -1,4 +1,4 @@
import type { DefaultOptionType } from 'antd/es/select';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Channels } from 'types/api/channels/getAll';
import {
@@ -23,7 +23,7 @@ export interface ThresholdItemProps {
showRemoveButton: boolean;
channels: Channels[];
isLoadingChannels: boolean;
units: DefaultOptionType[];
units: ComboboxSimpleItem[];
isErrorChannels: boolean;
refreshChannels: () => void;
}

View File

@@ -1,7 +1,7 @@
import { Button, Flex, SelectProps } from 'antd';
import { Button, Flex } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Typography } from '@signozhq/ui/typography';
import type { BaseOptionType, DefaultOptionType } from 'antd/es/select';
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
import { YAxisSource } from 'components/YAxisUnitSelector/types';
import { getYAxisCategories } from 'components/YAxisUnitSelector/utils';
@@ -22,11 +22,11 @@ import { openInNewTab } from 'utils/navigation';
import { ROUTING_POLICIES_ROUTE } from './constants';
import { RoutingPolicyBannerProps } from './types';
export function getQueryNames(currentQuery: Query): BaseOptionType[] {
export function getQueryNames(currentQuery: Query): ComboboxSimpleItem[] {
const involvedQueriesInTraceOperator = getInvolvedQueriesInTraceOperator(
currentQuery.builder.queryTraceOperator,
);
const queryConfig: Record<EQueryType, () => SelectProps['options']> = {
const queryConfig: Record<EQueryType, () => ComboboxSimpleItem[]> = {
[EQueryType.QUERY_BUILDER]: () => [
...(getSelectedQueryOptions(currentQuery.builder.queryData)?.filter(
(option) =>
@@ -52,7 +52,7 @@ export function getCategoryByOptionId(id: string): string | undefined {
export function getCategorySelectOptionByName(
name: string | undefined,
): DefaultOptionType[] {
): ComboboxSimpleItem[] {
if (!name) {
return [];
}
@@ -68,7 +68,6 @@ export function getCategorySelectOptionByName(
?.units.map((unit) => ({
label: unit.name,
value: unit.id,
'data-testid': `threshold-unit-select-option-${unit.id}`,
})) || []
);
}

View File

@@ -3,6 +3,7 @@ import { X } from '@signozhq/icons';
import { useNotifications } from 'hooks/useNotifications';
import { LabelInputState, LabelsInputProps } from './types';
import { Button } from '@signozhq/ui/button';
function LabelsInput({
labels,
@@ -144,14 +145,16 @@ function LabelsInput({
)}
{!isAdding ? (
<button
<Button
className="labels-input__add-button"
type="button"
variant="outlined"
color="secondary"
onClick={handleAddLabelsClick}
data-testid="alert-add-label-button"
>
+ Add labels
</button>
</Button>
) : (
<div className="labels-input__input-container">
<input

View File

@@ -39,9 +39,14 @@
&__input.title {
background-color: transparent;
color: var(--l1-foreground);
background: var(--l2-background);
color: var(--l2-foreground);
width: 100%;
min-width: 300px;
&:hover {
background: var(--l2-background);
}
}
&__input.description {
@@ -49,15 +54,6 @@
background-color: transparent;
color: var(--l2-foreground);
}
.ant-btn {
display: flex;
gap: 4px;
align-items: center;
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
margin-right: 16px;
}
}
.labels-input {
@@ -66,19 +62,7 @@
gap: 8px;
&__add-button {
width: fit-content;
font-size: 13px;
color: var(--l2-foreground);
border: 1px solid var(--l1-border);
background-color: transparent;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
width: 80px;
}
&__existing-labels {
@@ -121,6 +105,7 @@
align-items: center;
background-color: transparent;
border: none;
border: 1px solid var(--l2-border);
}
&__input {

View File

@@ -93,16 +93,16 @@
gap: 8px;
.ant-input {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
background-color: var(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
&:hover {
border-color: var(--l1-border);
border-color: var(--l3-border);
}
&:focus {
border-color: var(--l1-border);
border-color: var(--l3-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Input, Select, Tooltip } from 'antd';
import { Input, Tooltip } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import { Info } from '@signozhq/icons';
@@ -79,8 +80,8 @@ function EvaluationCadence(): JSX.Element {
}
data-testid="evaluation-cadence-duration-input"
/>
<Select
options={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}
<SelectSimple
items={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}
placeholder="Select time unit"
style={{ width: 120 }}
value={advancedOptions.evaluationCadence.default.timeUnit}
@@ -91,7 +92,7 @@ function EvaluationCadence(): JSX.Element {
...advancedOptions.evaluationCadence,
default: {
...advancedOptions.evaluationCadence.default,
timeUnit: value,
timeUnit: value as string,
},
},
})

View File

@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Button, DatePicker, Input, Select } from 'antd';
import { Button, DatePicker, Input } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useCreateAlertState } from 'container/CreateAlertV2/context';
@@ -42,10 +43,6 @@ function EvaluationCadenceDetails({
},
});
const [searchTimezoneString, setSearchTimezoneString] = useState('');
const [occurenceSearchString, setOccurenceSearchString] = useState('');
const [repeatEverySearchString, setRepeatEverySearchString] = useState('');
const tabs = [
{
label: 'Editor',
@@ -93,45 +90,39 @@ function EvaluationCadenceDetails({
<div className="editor-view" data-testid="editor-view">
<div className="select-group">
<Typography.Text>REPEAT EVERY</Typography.Text>
<Select
options={EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS}
value={evaluationCadence.custom.repeatEvery || null}
<ComboboxSimple
items={EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS}
value={evaluationCadence.custom.repeatEvery || undefined}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
repeatEvery: value,
repeatEvery: value as string,
occurence: [],
},
})
}
placeholder="Select repeat every"
showSearch
searchValue={repeatEverySearchString}
onSearch={setRepeatEverySearchString}
/>
</div>
{evaluationCadence.custom.repeatEvery !== 'day' && (
<div className="select-group">
<Typography.Text>ON DAY(S)</Typography.Text>
<Select
options={occurenceOptions}
value={evaluationCadence.custom.occurence || null}
mode="multiple"
<ComboboxSimple
items={occurenceOptions}
value={evaluationCadence.custom.occurence || []}
multiple
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
occurence: value,
occurence: value as string[],
},
})
}
placeholder="Select day(s)"
showSearch
searchValue={occurenceSearchString}
onSearch={setOccurenceSearchString}
/>
</div>
)}
@@ -152,22 +143,19 @@ function EvaluationCadenceDetails({
</div>
<div className="select-group">
<Typography.Text>TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
value={evaluationCadence.custom.timezone || null}
<ComboboxSimple
items={TIMEZONE_DATA}
value={evaluationCadence.custom.timezone || undefined}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
timezone: value,
timezone: value as string,
},
})
}
placeholder="Select timezone"
onSearch={setSearchTimezoneString}
searchValue={searchTimezoneString}
showSearch
/>
</div>
</div>

View File

@@ -1,5 +1,9 @@
.evaluation-cadence-container {
border-bottom: 1px solid var(--l1-border);
--select-trigger-background-color: var(--l3-background);
--select-trigger-border-color: var(--l3-border);
.evaluation-cadence-item {
border-bottom: none !important;
}
@@ -157,24 +161,6 @@
font-weight: 500;
}
.ant-select {
border: 1px solid var(--l1-border);
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
.ant-picker {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);

View File

@@ -1,5 +1,6 @@
import { useMemo } from 'react';
import { Input, Select } from 'antd';
import { Input } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
@@ -19,7 +20,7 @@ function EvaluationWindowDetails({
const currentHourOptions = useMemo(() => {
const options = [];
for (let i = 0; i < 60; i++) {
options.push({ label: i.toString(), value: i });
options.push({ label: i.toString(), value: i.toString() });
}
return options;
}, []);
@@ -27,7 +28,7 @@ function EvaluationWindowDetails({
const currentMonthOptions = useMemo(() => {
const options = [];
for (let i = 1; i <= 31; i++) {
options.push({ label: i.toString(), value: i });
options.push({ label: i.toString(), value: i.toString() });
}
return options;
}, []);
@@ -123,10 +124,10 @@ function EvaluationWindowDetails({
<Typography.Text>{displayText}</Typography.Text>
<div className="select-group">
<Typography.Text>STARTING AT MINUTE</Typography.Text>
<Select
options={currentHourOptions}
value={evaluationWindow.startingAt.number || null}
onChange={handleNumberChange}
<SelectSimple
items={currentHourOptions}
value={evaluationWindow.startingAt.number || undefined}
onChange={(value): void => handleNumberChange(value as string)}
placeholder="Select starting at"
data-testid="evaluation-window-details-starting-at-select"
/>
@@ -151,10 +152,10 @@ function EvaluationWindowDetails({
</div>
<div className="select-group">
<Typography.Text>SELECT TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
value={evaluationWindow.startingAt.timezone || null}
onChange={handleTimezoneChange}
<SelectSimple
items={TIMEZONE_DATA}
value={evaluationWindow.startingAt.timezone || undefined}
onChange={(value): void => handleTimezoneChange(value as string)}
placeholder="Select timezone"
data-testid="evaluation-window-details-timezone-select"
/>
@@ -172,10 +173,10 @@ function EvaluationWindowDetails({
<Typography.Text>{displayText}</Typography.Text>
<div className="select-group">
<Typography.Text>STARTING ON DAY</Typography.Text>
<Select
options={currentMonthOptions}
value={evaluationWindow.startingAt.number || null}
onChange={handleNumberChange}
<SelectSimple
items={currentMonthOptions}
value={evaluationWindow.startingAt.number || undefined}
onChange={(value): void => handleNumberChange(value as string)}
placeholder="Select starting at"
data-testid="evaluation-window-details-starting-at-select"
/>
@@ -189,10 +190,10 @@ function EvaluationWindowDetails({
</div>
<div className="select-group">
<Typography.Text>SELECT TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
value={evaluationWindow.startingAt.timezone || null}
onChange={handleTimezoneChange}
<SelectSimple
items={TIMEZONE_DATA}
value={evaluationWindow.startingAt.timezone || undefined}
onChange={(value): void => handleTimezoneChange(value as string)}
placeholder="Select timezone"
data-testid="evaluation-window-details-timezone-select"
/>
@@ -221,10 +222,10 @@ function EvaluationWindowDetails({
</div>
<div className="select-group time-select-group">
<Typography.Text>UNIT</Typography.Text>
<Select
options={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}
value={evaluationWindow.startingAt.unit || null}
onChange={handleUnitChange}
<SelectSimple
items={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}
value={evaluationWindow.startingAt.unit || undefined}
onChange={(value): void => handleUnitChange(value as string)}
placeholder="Select unit"
data-testid="evaluation-window-details-custom-rolling-window-unit-select"
/>

View File

@@ -1,5 +1,6 @@
import { useCallback, useMemo } from 'react';
import { Select, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Info } from '@signozhq/icons';
@@ -87,16 +88,15 @@ function MultipleNotifications(): JSX.Element {
: 'No grouping fields available';
let input = (
<div>
<Select
options={spaceAggregationOptions}
onChange={onSelectChange}
value={notificationSettings.multipleNotifications}
mode="multiple"
<SelectSimple
items={spaceAggregationOptions}
onChange={(value): void => onSelectChange(value as string[])}
value={notificationSettings.multipleNotifications || []}
multiple
placeholder={placeholder}
disabled={!isMultipleNotificationsEnabled}
aria-disabled={!isMultipleNotificationsEnabled}
maxTagCount={3}
data-testid="multiple-notifications-select"
maxDisplayedPills={3}
testId="multiple-notifications-select"
/>
{isMultipleNotificationsEnabled && (
<Typography.Text className="multiple-notifications-select-description">

View File

@@ -1,4 +1,5 @@
import { Input, Select } from 'antd';
import { Input } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import { useCreateAlertState } from '../context';
@@ -38,31 +39,31 @@ function NotificationSettings(): JSX.Element {
}}
data-testid="repeat-notifications-time-input"
/>
<Select
value={notificationSettings.reNotification.unit || null}
<SelectSimple
value={notificationSettings.reNotification.unit || undefined}
placeholder="Select unit"
disabled={!notificationSettings.reNotification.enabled}
options={RE_NOTIFICATION_TIME_UNIT_OPTIONS}
items={RE_NOTIFICATION_TIME_UNIT_OPTIONS}
onChange={(value): void => {
setNotificationSettings({
type: 'SET_RE_NOTIFICATION',
payload: {
enabled: notificationSettings.reNotification.enabled,
value: notificationSettings.reNotification.value,
unit: value,
unit: value as string,
conditions: notificationSettings.reNotification.conditions,
},
});
}}
data-testid="repeat-notifications-unit-select"
testId="repeat-notifications-unit-select"
/>
<Typography.Text>while</Typography.Text>
<Select
mode="multiple"
value={notificationSettings.reNotification.conditions || null}
<SelectSimple
multiple
value={notificationSettings.reNotification.conditions || []}
placeholder="Select conditions"
disabled={!notificationSettings.reNotification.enabled}
options={RE_NOTIFICATION_CONDITION_OPTIONS}
items={RE_NOTIFICATION_CONDITION_OPTIONS}
onChange={(value): void => {
setNotificationSettings({
type: 'SET_RE_NOTIFICATION',
@@ -70,11 +71,11 @@ function NotificationSettings(): JSX.Element {
enabled: notificationSettings.reNotification.enabled,
value: notificationSettings.reNotification.value,
unit: notificationSettings.reNotification.unit,
conditions: value,
conditions: value as ('firing' | 'nodata')[],
},
});
}}
data-testid="repeat-notifications-conditions-select"
testId="repeat-notifications-conditions-select"
/>
</div>
);

View File

@@ -3,6 +3,9 @@
flex-direction: column;
margin: 0 16px;
--select-trigger-background-color: var(--l3-background);
--select-trigger-border-color: var(--l3-border);
.notification-message-container {
display: flex;
flex-direction: column;
@@ -54,8 +57,8 @@
textarea {
height: 150px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--l3-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: Inter;
@@ -77,8 +80,9 @@
gap: 8px;
.ant-input {
width: 120px;
border: 1px solid var(--l1-border);
width: 190px;
border: 1px solid var(--l3-border);
background-color: var(--l3-background);
}
.ant-select {

View File

@@ -37,11 +37,11 @@
gap: 8px;
&.active-tab {
background-color: var(--l1-background);
background-color: var(--l3-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
background-color: var(--l3-background) !important;
}
}

View File

@@ -11,8 +11,13 @@ import {
} from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@signozhq/ui/dropdown-menu';
import { toast } from '@signozhq/ui/sonner';
import { Dropdown, Skeleton } from 'antd';
import { Skeleton } from 'antd';
import {
RenderErrorResponseDTO,
ZeustypesHostDTO,
@@ -200,10 +205,15 @@ export default function CustomDomainSettings(): JSX.Element {
!workspaceName ? 'workspace-name-hidden' : ''
}`}
>
<Dropdown
trigger={['click']}
disabled={isFetchingHosts}
dropdownRender={(): JSX.Element => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="link" color="none" disabled={isFetchingHosts}>
<Link2 size={12} />
<span>{stripProtocol(activeHost?.url ?? '')}</span>
<ChevronDown size={12} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<div className="workspace-url-dropdown">
<span className="workspace-url-dropdown-header">
All Workspace URLs
@@ -236,14 +246,8 @@ export default function CustomDomainSettings(): JSX.Element {
);
})}
</div>
)}
>
<Button variant="link" color="none">
<Link2 size={12} />
<span>{stripProtocol(activeHost?.url ?? '')}</span>
<ChevronDown size={12} />
</Button>
</Dropdown>
</DropdownMenuContent>
</DropdownMenu>
<span className="custom-domain-card-meta-timezone">
<Clock size={11} />
{timezone.offset}

View File

@@ -1,4 +1,5 @@
import { GetHosts200 } from 'api/generated/services/sigNoz.schemas';
import userEvent from '@testing-library/user-event';
import { rest, server } from 'mocks-server/server';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
@@ -142,12 +143,13 @@ describe('CustomDomainSettings', () => {
});
it('shows all workspace URLs as links in the dropdown', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
render(<CustomDomainSettings />);
await screen.findByText(/custom-host\.test\.cloud/i);
// Open the URL dropdown
fireEvent.click(
await user.click(
screen.getByRole('button', { name: /custom-host\.test\.cloud/i }),
);

View File

@@ -7,7 +7,7 @@ import {
useState,
} from 'react';
import { Color } from '@signozhq/design-tokens';
import { Select } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import CustomSelect from 'components/NewSelect/CustomSelect';
import TextToolTip from 'components/TextToolTip';
@@ -204,10 +204,9 @@ function DynamicVariable({
}
/>
</span>
<Select
<SelectSimple
placeholder="Source"
defaultValue={AttributeSource.ALL_TELEMETRY}
options={sources.map((source) => ({ label: source, value: source }))}
items={sources.map((source) => ({ label: source, value: source }))}
onChange={(value): void => setAttributeSource(value as AttributeSource)}
value={attributeSource || dynamicVariablesSelectedValue?.value}
/>

View File

@@ -5,8 +5,9 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { orange } from '@ant-design/colors';
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Input, Select, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Button, Collapse, Input, Tag } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import cx from 'classnames';
@@ -56,7 +57,11 @@ import { WidgetSelector } from './WidgetSelector';
import './VariableItem.styles.scss';
const { Option } = Select;
const SORT_ITEMS = [
{ value: VariableSortTypeArr[0], label: 'Disabled' },
{ value: VariableSortTypeArr[1], label: 'Ascending' },
{ value: VariableSortTypeArr[2], label: 'Descending' },
];
interface VariableItemProps {
variableData: IDashboardVariable;
@@ -743,19 +748,14 @@ function VariableItem({
<Typography className="typography-variables">Sort Values</Typography>
</LabelContainer>
<Select
defaultActiveFirstOption
defaultValue={VariableSortTypeArr[0]}
<SelectSimple
value={variableSortType}
onChange={(value: TSortVariableValuesType): void =>
setVariableSortType(value)
onChange={(value): void =>
setVariableSortType(value as TSortVariableValuesType)
}
className="sort-input"
>
<Option value={VariableSortTypeArr[0]}>Disabled</Option>
<Option value={VariableSortTypeArr[1]}>Ascending</Option>
<Option value={VariableSortTypeArr[2]}>Descending</Option>
</Select>
items={SORT_ITEMS}
/>
</VariableItemRow>
<VariableItemRow className="multiple-values-section">
<LabelContainer>
@@ -798,10 +798,10 @@ function VariableItem({
<CustomSelect
placeholder="Select a default value"
value={variableDefaultValue}
onChange={(value): void => setVariableDefaultValue(value)}
options={previewValues.map((value) => ({
label: value,
value,
onChange={(value): void => setVariableDefaultValue(value ?? '')}
options={previewValues.map((val) => ({
label: val,
value: val,
}))}
/>
</VariableItemRow>

View File

@@ -98,6 +98,14 @@
.nameIconInput {
display: flex;
--select-trigger-width: 40px;
--select-trigger-background-color: var(--l3-background);
--select-trigger-border-color: var(--l1-border);
[data-slot='select-trigger'] svg {
display: none;
}
}
.dashboardImageInput {
@@ -108,7 +116,7 @@
padding: 6px;
justify-content: center;
align-items: center;
border-radius: 2px 0px 0px 2px;
border-radius: 2var (--l3-border) px 0px 0px 2px;
border: 1px solid var(--l1-border) !important;
background: var(--l3-background) !important;

View File

@@ -1,6 +1,8 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Col, Input, Radio, Select, Space, Tooltip } from 'antd';
import { Col, Input, Space, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import AddTags from 'container/DashboardContainer/DashboardSettings/General/AddTags';
import { useDashboardCursorSyncMode } from 'hooks/dashboard/useDashboardCursorSyncMode';
@@ -21,8 +23,6 @@ import logEvent from 'api/common/logEvent';
import { Events } from 'constants/events';
import { getAbsoluteUrl } from 'utils/basePath';
const { Option } = Select;
function GeneralDashboardSettings(): JSX.Element {
const { dashboardData, setDashboardData } = useDashboardStore();
@@ -130,24 +130,22 @@ function GeneralDashboardSettings(): JSX.Element {
<div>
<Typography className={styles.dashboardName}>Dashboard Name</Typography>
<section className={styles.nameIconInput}>
<Select
defaultActiveFirstOption
<SelectSimple
data-testid="dashboard-image"
suffixIcon={null}
rootClassName={styles.dashboardImageInput}
className={styles.dashboardImageInput}
value={updatedImage}
onChange={(value: string): void => setUpdatedImage(value)}
>
{Base64Icons.map((icon) => (
<Option value={icon} key={icon}>
onChange={(value): void => setUpdatedImage(value as string)}
items={Base64Icons.map((icon) => ({
value: icon,
label: (
<img
src={icon}
alt="dashboard-icon"
className={styles.listItemImage}
/>
</Option>
))}
</Select>
),
}))}
/>
<Input
data-testid="dashboard-name"
className={styles.dashboardNameInput}
@@ -213,18 +211,18 @@ function GeneralDashboardSettings(): JSX.Element {
Sync crosshair and tooltip across all the dashboard panels
</Typography.Text>
</div>
<Radio.Group
<ToggleGroupSimple
type="single"
value={cursorSyncMode}
onChange={(e): void => {
setCursorSyncMode(e.target.value as DashboardCursorSync);
onChange={(value: string): void => {
setCursorSyncMode(value as DashboardCursorSync);
}}
>
<Radio.Button value={DashboardCursorSync.None}>No Sync</Radio.Button>
<Radio.Button value={DashboardCursorSync.Crosshair}>
Crosshair
</Radio.Button>
<Radio.Button value={DashboardCursorSync.Tooltip}>Tooltip</Radio.Button>
</Radio.Group>
items={[
{ value: DashboardCursorSync.None, label: 'No Sync' },
{ value: DashboardCursorSync.Crosshair, label: 'Crosshair' },
{ value: DashboardCursorSync.Tooltip, label: 'Tooltip' },
]}
/>
</div>
{cursorSyncMode === DashboardCursorSync.Tooltip && (
<div className={styles.crossPanelSyncRow}>
@@ -237,21 +235,21 @@ function GeneralDashboardSettings(): JSX.Element {
matching ones highlighted
</Typography.Text>
</div>
<Radio.Group
<ToggleGroupSimple
type="single"
value={syncTooltipFilterMode}
onChange={(e): void => {
onChange={(value: string): void => {
logEvent(Events.TOOLTIP_SYNC_MODE_CHANGED, {
path: getAbsoluteUrl(window.location.pathname),
mode: e.target.value,
mode: value,
});
setSyncTooltipFilterMode(e.target.value as SyncTooltipFilterMode);
setSyncTooltipFilterMode(value as SyncTooltipFilterMode);
}}
>
<Radio.Button value={SyncTooltipFilterMode.All}>All</Radio.Button>
<Radio.Button value={SyncTooltipFilterMode.Filtered}>
Filtered
</Radio.Button>
</Radio.Group>
items={[
{ value: SyncTooltipFilterMode.All, label: 'All' },
{ value: SyncTooltipFilterMode.Filtered, label: 'Filtered' },
]}
/>
</div>
)}
</Col>

View File

@@ -3,7 +3,8 @@ import { useMutation } from 'react-query';
import { useCopyToClipboard } from 'react-use';
import { Checkbox } from '@signozhq/ui/checkbox';
import { toast } from '@signozhq/ui/sonner';
import { Button, Select } from 'antd';
import { Button } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import createPublicDashboardAPI from 'api/dashboard/public/createPublicDashboard';
import revokePublicDashboardAccessAPI from 'api/dashboard/public/revokePublicDashboardAccess';
@@ -270,12 +271,12 @@ function PublicDashboardSetting(): JSX.Element {
Default time range
</Typography.Text>
</div>
<Select
<SelectSimple
placeholder="Select default time range"
options={TIME_RANGE_PRESETS_OPTIONS}
items={TIME_RANGE_PRESETS_OPTIONS}
value={defaultTimeRange}
onChange={handleDefaultTimeRange}
data-testid="default-time-range-select-dropdown"
onChange={(value): void => handleDefaultTimeRange(value as string)}
testId="default-time-range-select-dropdown"
className="default-time-range-select-dropdown"
/>
</div>

View File

@@ -395,8 +395,8 @@ describe('Dynamic Variable Default Behavior', () => {
// Check if the checkbox exists (it should be unchecked initially)
const checkbox = allOptionContainer?.querySelector(
'input[type="checkbox"]',
) as HTMLInputElement;
'[role="checkbox"]',
) as HTMLElement;
expect(checkbox).toBeInTheDocument();
// Should call onValueUpdate with all values (ALL selection)
@@ -516,10 +516,10 @@ describe('Dynamic Variable Default Behavior', () => {
// Check if the checkbox for ALL option is checked
const checkbox = dropdownAllOption.querySelector(
'input[type="checkbox"]',
) as HTMLInputElement;
'[role="checkbox"]',
) as HTMLElement;
expect(checkbox).toBeInTheDocument();
expect(checkbox.checked).toBe(true);
expect(checkbox).toHaveAttribute('data-state', 'checked');
});
});
});

View File

@@ -196,10 +196,10 @@ describe('Panel Management Tests', () => {
expect(allOption).toHaveClass('selected');
const allCheckbox = allOption.querySelector(
'input[type="checkbox"]',
) as HTMLInputElement;
'[role="checkbox"]',
) as HTMLElement;
expect(allCheckbox).toBeInTheDocument();
expect(allCheckbox.checked).toBe(true);
expect(allCheckbox).toHaveAttribute('data-state', 'checked');
});
});

View File

@@ -160,8 +160,8 @@ describe('getChartManagerColumns', () => {
expect(renderFn).toBeDefined();
const { container } = render(renderFn!(null, tableDataSet[1], 1));
const checkbox = container.querySelector('input[type="checkbox"]');
const checkbox = container.querySelector('[role="checkbox"]');
expect(checkbox).toBeInTheDocument();
expect(checkbox).toBeChecked(); // graphVisibilityState[1] is true
expect(checkbox).toHaveAttribute('data-state', 'checked'); // graphVisibilityState[1] is true
});
});

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { CloudDownload } from '@signozhq/icons';
import { Button, Dropdown, MenuProps, Flex } from 'antd';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import { Button, Flex } from 'antd';
import { unparse } from 'papaparse';
import { DownloadProps } from './Download.types';
@@ -67,7 +68,7 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
};
return (
<Dropdown menu={menu} trigger={['click']}>
<DropdownMenuSimple menu={menu}>
<Button
className="download-button"
loading={isLoading || isDownloading}
@@ -79,7 +80,7 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
Download
</Flex>
</Button>
</Dropdown>
</DropdownMenuSimple>
);
}

View File

@@ -2,7 +2,8 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { Button, Divider, Space } from 'antd';
import { Button, Space } from 'antd';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import getNextPrevId from 'api/errors/getNextPrevId';

View File

@@ -75,8 +75,8 @@
gap: 16px;
z-index: 1;
.ant-select-selector {
padding: 0 !important;
.views-dropdown {
--combobox-trigger-border-color: transparent;
}
hr {
@@ -98,29 +98,17 @@
align-items: center;
gap: 8px;
.ant-select-focused {
border-color: transparent !important;
.ant-select-selector {
border-color: transparent !important;
box-shadow: none !important;
}
}
.ant-select-selector {
border: transparent !important;
background-color: transparent !important;
.ant-select-selection-placeholder {
margin-left: 12px;
}
button {
display: flex;
align-items: center;
justify-content: center;
}
}
.hidden {
display: none;
}
button {
.views-save-button {
display: flex;
align-items: center;
justify-content: center;

View File

@@ -1,11 +1,9 @@
import {
CSSProperties,
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useHistory } from 'react-router-dom';
@@ -22,13 +20,12 @@ import { Color } from '@signozhq/design-tokens';
import {
Button,
ColorPicker,
Divider,
Input,
Modal,
RefSelectProps,
Select,
Tooltip,
} from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
@@ -58,7 +55,6 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
import { useSaveView } from 'hooks/saveViews/useSaveView';
import { useUpdateView } from 'hooks/saveViews/useUpdateView';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useErrorNotification from 'hooks/useErrorNotification';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { useNotifications } from 'hooks/useNotifications';
@@ -108,8 +104,6 @@ function ExplorerOptions({
const [color, setColor] = useState(Color.BG_SIENNA_500);
const { notifications } = useNotifications();
const history = useHistory();
const ref = useRef<RefSelectProps>(null);
const isDarkMode = useIsDarkMode();
const [queryToExport, setQueryToExport] = useState<Query | null>(null);
const isLogsExplorer = sourcepage === DataSource.LOGS;
@@ -493,10 +487,16 @@ function ExplorerOptions({
);
};
const handleSelect = (
value: string,
option: { key: string; value: string },
): void => {
const handleSelect = (value: string | string[]): void => {
const selectedId = value as string;
const selectedView = viewsData?.data?.data?.find(
(view) => view.id === selectedId,
);
const option = {
key: selectedId,
value: selectedView?.name ?? '',
};
onMenuItemSelectHandler({
key: option.key,
});
@@ -530,10 +530,6 @@ function ExplorerOptions({
options,
handleOptionsChange,
);
if (ref.current) {
ref.current.blur();
}
};
const removeCurrentViewFromLocalStorage = (): void => {
@@ -639,24 +635,42 @@ function ExplorerOptions({
}
};
// TODO: Remove this and move this to scss file
const dropdownStyle: CSSProperties = useMemo(
() => ({
borderRadius: '4px',
border: isDarkMode
? `1px solid ${Color.BG_SLATE_400}`
: `1px solid ${Color.BG_VANILLA_300}`,
background: isDarkMode
? 'var(--bg-gradient-dark-shadow)'
: 'linear-gradient(139deg, rgba(241, 241, 241, 0.8) 0%, rgba(241, 241, 241, 0.9) 98.68%)',
boxShadow: '4px 10px 16px 2px rgba(0, 0, 0, 0.20)',
backdropFilter: 'blur(20px)',
bottom: '74px',
width: '191px',
}),
[isDarkMode],
const viewSelectItems = useMemo<ComboboxSimpleItem[]>(
() =>
viewsData?.data?.data?.map((view) => {
const extraData = view.extraData !== '' ? JSON.parse(view.extraData) : '';
let bgColor = getRandomColor();
if (extraData !== '') {
bgColor = extraData.color;
}
return {
value: view.id,
label: (
<div className="render-options">
<span
className="dot"
style={{
background: bgColor,
boxShadow: `0px 0px 6px 0px ${bgColor}`,
}}
/>{' '}
{view.name}
</div>
),
displayValue: view.name,
keywords: [view.name],
};
}) ?? [],
[viewsData?.data?.data],
);
const viewSelectValue = useMemo<string | undefined>(() => {
if (!viewName) {
return undefined;
}
return viewsData?.data?.data?.find((view) => view.name === viewName)?.id;
}, [viewName, viewsData?.data?.data]);
const isEditDeleteSupported = allowedRoles.includes(user.role as string);
const [isRecentlyUsedSavedViewSelected, setIsRecentlyUsedSavedViewSelected] =
@@ -735,37 +749,32 @@ function ExplorerOptions({
const CreateAlertButton = useMemo(() => {
if (isOneChartPerQuery) {
const selectLabel = (
<Button
disabled={disabled}
shape="round"
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
);
return (
<Select
<Dropdown
disabled={disabled}
className="multi-alert-button"
placeholder={selectLabel}
value={selectLabel}
suffixIcon={null}
onSelect={(e): void => {
const selectedQuery = splitedQueries.find(
(query) => query.id === (e as unknown as string),
);
if (selectedQuery) {
onCreateAlertsHandler(selectedQuery);
}
trigger={['click']}
overlayClassName="multi-alert-button"
menu={{
items: splitedQueries.map((splittedQuery) => ({
key: splittedQuery.id,
label: getQueryName(splittedQuery),
})),
onClick: ({ key }): void => {
const selectedQuery = splitedQueries.find((query) => query.id === key);
if (selectedQuery) {
onCreateAlertsHandler(selectedQuery);
}
},
}}
>
{splitedQueries.map((splittedQuery) => (
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
{getQueryName(splittedQuery)}
</Select.Option>
))}
</Select>
<Button
disabled={disabled}
shape="round"
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
</Dropdown>
);
}
return (
@@ -788,43 +797,36 @@ function ExplorerOptions({
const AddToDashboardButton = useMemo(() => {
if (isOneChartPerQuery) {
const selectLabel = (
<Button
type="primary"
disabled={disabled}
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
);
return (
<Select
<Dropdown
disabled={disabled}
className="multi-dashboard-button"
placeholder={selectLabel}
value={selectLabel}
suffixIcon={null}
onSelect={(e): void => {
const selectedQuery = splitedQueries.find(
(query) => query.id === (e as unknown as string),
);
if (selectedQuery) {
setQueryToExport(() => {
onAddToDashboard();
return selectedQuery;
});
}
trigger={['click']}
overlayClassName="multi-dashboard-button"
menu={{
items: splitedQueries.map((splittedQuery) => ({
key: splittedQuery.id,
label: getQueryName(splittedQuery),
})),
onClick: ({ key }): void => {
const selectedQuery = splitedQueries.find((query) => query.id === key);
if (selectedQuery) {
setQueryToExport(() => {
onAddToDashboard();
return selectedQuery;
});
}
},
}}
>
{/* eslint-disable-next-line sonarjs/no-identical-functions */}
{splitedQueries.map((splittedQuery) => (
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
{getQueryName(splittedQuery)}
</Select.Option>
))}
</Select>
<Button
type="primary"
disabled={disabled}
shape="round"
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
</Dropdown>
);
}
return (
@@ -898,48 +900,24 @@ function ExplorerOptions({
}}
>
<div className="view-options">
<Select<string, { key: string; value: string }>
showSearch
<ComboboxSimple
placeholder="Select a view"
loading={viewsIsLoading || isRefetching}
value={viewName || undefined}
onSelect={handleSelect}
value={viewSelectValue}
onChange={handleSelect}
style={{
minWidth: 170,
}}
dropdownStyle={dropdownStyle}
className="views-dropdown"
allowClear={false}
ref={ref}
>
{viewsData?.data?.data?.map((view) => {
const extraData =
view.extraData !== '' ? JSON.parse(view.extraData) : '';
let bgColor = getRandomColor();
if (extraData !== '') {
bgColor = extraData.color;
}
return (
<Select.Option key={view.id} value={view.name}>
<div className="render-options">
<span
className="dot"
style={{
background: bgColor,
boxShadow: `0px 0px 6px 0px ${bgColor}`,
}}
/>{' '}
{view.name}
</div>
</Select.Option>
);
})}
</Select>
items={viewSelectItems}
/>
<Button
shape="round"
onClick={handleSaveViewModalToggle}
className={isEditDeleteSupported ? '' : 'hidden'}
className={
isEditDeleteSupported ? 'views-save-button' : 'views-save-button hidden'
}
disabled={viewsIsLoading || isRefetching}
icon={<Disc3 size={16} />}
>

View File

@@ -1,9 +1,11 @@
import { memo, useMemo } from 'react';
import { Select, Spin } from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { IOption } from 'hooks/useResourceAttribute/types';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
import { useOrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/useOrderByFilter';
import { selectStyle } from 'container/QueryBuilder/filters/QueryBuilderSearch/config';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import { uniqWith } from 'lodash-es';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { StringOperators } from 'types/common/queryBuilder';
@@ -15,7 +17,6 @@ function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
generateOptions,
createOptions,
handleChange,
handleSearchKeys,
} = useOrderByFilter({ query, onChange });
const { data, isFetching } = useGetAggregateKeys(
@@ -54,18 +55,42 @@ function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
query.aggregateOperator,
]);
const items: ComboboxSimpleItem[] = useMemo(() => {
const merged: IOption[] = [...selectedValue, ...options];
const unique = uniqWith(merged, (a, b) => a.value === b.value);
return unique.map((opt) => ({
label: opt.label,
displayValue: opt.label,
value: opt.value,
}));
}, [selectedValue, options]);
const value = useMemo(
() => selectedValue.map((item) => item.value),
[selectedValue],
);
const handleComboboxChange = (next: string | string[]): void => {
const values = (next as string[]) || [];
const asOptions: IOption[] = values.map((v) => {
const found = items.find((item) => item.value === v);
return {
label: typeof found?.label === 'string' ? found.label : v,
value: v,
};
});
handleChange(asOptions);
};
return (
<Select
mode="tags"
<ComboboxSimple
multiple
allowCreate
loading={isFetching}
style={selectStyle}
onSearch={handleSearchKeys}
showSearch
showArrow={false}
value={selectedValue}
labelInValue
options={options}
notFoundContent={isFetching ? <Spin size="small" /> : null}
onChange={handleChange}
items={items}
value={value}
onChange={handleComboboxChange}
/>
);
}

View File

@@ -17,7 +17,7 @@ import {
Title,
Wrapper,
} from './styles';
import { filterOptions, getSelectOptions } from './utils';
import { getSelectOptions } from './utils';
function ExportPanelContainer({
isLoading,
@@ -91,13 +91,13 @@ function ExportPanelContainer({
<SelectWrapper direction="horizontal">
<DashboardSelect
placeholder="Select Dashboard"
options={options}
showSearch
items={options}
loading={isDashboardLoading}
disabled={isDashboardLoading}
value={dashboardId}
onSelect={handleSelect}
filterOption={filterOptions}
style={
isDashboardLoading ? { pointerEvents: 'none', opacity: 0.5 } : undefined
}
value={dashboardId || ''}
onChange={(value): void => handleSelect(value as string)}
/>
<Button
type="primary"

View File

@@ -1,11 +1,9 @@
import { FunctionComponent } from 'react';
import { Button, Select, SelectProps, Space } from 'antd';
import { Button, Space } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { Typography } from '@signozhq/ui/typography';
import styled from 'styled-components';
export const DashboardSelect: FunctionComponent<SelectProps> = styled(
Select,
)<SelectProps>`
export const DashboardSelect = styled(ComboboxSimple)`
width: 100%;
`;

View File

@@ -1,16 +1,8 @@
import { SelectProps } from 'antd';
import { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Dashboard } from 'types/api/dashboard/getAll';
export const getSelectOptions = (data: Dashboard[]): SelectProps['options'] =>
export const getSelectOptions = (data: Dashboard[]): ComboboxSimpleItem[] =>
data.map(({ id, data }) => ({
label: data.title,
value: id,
}));
export const filterOptions: SelectProps['filterOption'] = (
input,
options,
): boolean =>
(options?.label?.toString() ?? '')
?.toLowerCase()
.includes(input.toLowerCase());

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