Compare commits

..

38 Commits

Author SHA1 Message Date
Jatinderjit Singh
4da4569953 Merge branch 'main' into mute-rules 2026-05-29 06:53:51 +05:30
Jatinderjit Singh
9458928efc fix(alerts): give mute drawer date picker a solid popup background
The DatePicker popup is portaled to <body>, so without a panel-container
background the time-column cells were transparent and the drawer content
bled through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 06:49:35 +05:30
Jatinderjit Singh
f713254f59 fix: link to planned downtime page 2026-05-29 06:40:06 +05:30
Jatinderjit Singh
eb5f0e946a fix(alerts): format mute times in selected timezone
The backend ignores the offset in the formatted startTime/endTime and
re-attaches the input timezone to the raw wall-clock time. So the
formatted offset must match the timezone field, otherwise the muted
window shifts. Mirror PlannedDowntimeForm by formatting in the selected
timezone: the quick-duration popover formats in the guessed tz, and the
custom-window drawer reinterprets the picked time via .tz(tz, true).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 06:29:05 +05:30
Jatinderjit Singh
fdb48834da fix: return correct upcoming status for recurring maintenances 2026-05-29 06:16:23 +05:30
Jatinderjit Singh
e46d26ff78 refactor: drop status filter, keep status badges 2026-05-29 05:56:41 +05:30
Jatinderjit Singh
503f5c3e7b feat(planned-downtime): add status filter and badge
Planned downtimes were rendered as a flat list with no visual cue for
which were active, upcoming, or expired. Add an "Active & Upcoming /
Expired / All" filter (defaulting to Active & Upcoming so expired noise
is hidden) and a status badge on each row. Sort by status (active →
upcoming → expired) then by most recently updated within each group.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 05:42:07 +05:30
Jatinderjit Singh
de2a730557 refactor(alerts): promote Planned Downtime and Routing Policies to top-level tabs
Replace the nested Configuration > {Planned Downtime, Routing Policies}
sub-tab structure with four flat top-level tabs on /alerts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 05:23:48 +05:30
Jatinderjit Singh
79deebe976 fix(planned-downtime): cascade delete associated rules
Deleting a planned maintenance previously failed with a foreign key
error when alert rules were associated with it, forcing users to first
detach every rule. Wrap the delete in a transaction that first removes
rows from planned_maintenance_rule before deleting the maintenance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 05:11:21 +05:30
Jatinderjit Singh
372cdc89e1 refactor(planned-maintenance): simplify IsActive and IsUpcoming 2026-05-29 04:59:59 +05:30
Jatinderjit Singh
bb5e68eef6 feat(planned-maintenance): support indefinite ("forever") fixed downtimes
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 04:59:07 +05:30
Vinicius Lourenço
fe8283e4de refactor(query-builder): removed unused query component (#11486)
Some checks failed
build-staging / go-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
2026-05-28 17:55:55 +00:00
Vinicius Lourenço
4366d6fd96 fix(divider): mismatch on margin/colors (#11488) 2026-05-28 14:57:57 +00:00
primus-bot[bot]
a8f5bdf256 chore(release): bump to v0.126.1 (#11487)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2026-05-28 10:49:48 +00:00
Nityananda Gohain
2f102ee3f5 fix: ensure timestamp is always in ms (#11483)
Co-authored-by: Tushar Vats <tushar@signoz.io>
2026-05-28 10:12:55 +00:00
Manika Malhotra
0a3717e0d8 chore: migrate antd Tag to badge (#11421)
* chore: migrate antd Tag to badge

* fix: test snapshot

* fix: remove accidently added files

* chore: resolve comments, bump ui package version, update snapshot

* refactor: cleanup query chip component

* chore: rename files

* fix: remove committed pnpm_store file
2026-05-28 07:57:26 +00:00
Jatinderjit Singh
a2f4ce934d Merge branch 'main' into mute-rules 2026-05-27 17:39:00 +05:30
Jatinderjit Singh
c2419a8687 fix: refresh disabled-since time in DisabledBanner every 60s 2026-05-27 17:29:11 +05:30
Jatinderjit Singh
7c214be7ad fix: hide muted banner when mute expires 2026-05-27 17:15:35 +05:30
Jatinderjit Singh
27a2a0cf21 refactor: return all active mutes 2026-05-27 16:33:16 +05:30
Jatinderjit Singh
6ec3c0b8e0 fix: handle badge and banner for indefinite mutes 2026-05-27 11:29:34 +05:30
Jatinderjit Singh
fef84df331 feat(alerts): return activeMute in rule API responses
Backend computes the active mute window per rule (joining with planned
maintenance schedules) in ListRules and GetRuleByID, so the frontend no
longer needs a separate downtime-schedules fetch to determine mute state.

- Add ActiveMuteInfo struct to Rule (id, name, description,
  effectiveStartTime, effectiveEndTime); computed by findActiveMuteForRule
- Handlers for ListRules/GetRuleByID now fetch MaintenanceStore schedules
  and pass them to NewRule
- Regenerate OpenAPI spec and frontend types (RuletypesActiveMuteInfoDTO)
- useActiveMute: drop useListDowntimeSchedules, read activeMute from
  useGetRuleByID (cache-hit on detail page; one request on list page)
- useMuteAlertRule: also invalidate rule queries after muting so activeMute
  refreshes without a separate schedules refetch
- ListAlert: remove useListDowntimeSchedules + findActiveMuteForRule,
  read record.activeMute.effectiveEndTime directly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 20:56:47 +05:30
Jatinderjit Singh
98813660ed fix(alerts): send endTime as null for "Forever" mute
Previously, picking Forever set endTime to now + 10 years so the
schedule had a real (very distant) end. Send null instead so the
backend treats the mute as truly indefinite. The generated schema
narrows endTime to string, but the API accepts null — cast at the
call site with a comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
cd37fbfa71 fix(alerts): drop focus after closing mute popover via Esc / outside click
Pressing Escape promotes the most recent input to 'keyboard', so the
trigger button (Mute) showed a :focus-visible outline once the popover
closed. Blur the active element when closing via keyboard or outside
click so no leftover focus ring lingers. Tab-driven focus still shows
the indicator as expected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
fca2da6b15 fix(alerts): close mute popover on outside click and Escape
Since the Popover uses trigger={[]} (controlled-only), antd no longer
attaches its own outside-click / Escape handlers. Add document-level
mousedown and keydown listeners while the popover is open, deferring
attachment by one tick so the click that opened it isn't counted as
an outside click.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
4a2907ad35 fix(alerts): show firing state + muted badge separately in rule list
Match the original handoff: Status column keeps showing the actual
rule state (Firing/OK/Pending/Disabled), and a separate inline
'MUTED · <countdown>' badge renders next to the rule name when an
active mute exists. Both signals stay visible.

- Revert Status.tsx to its original simple form (no muteEndTime).
- Extract the muted badge into MutedBadge.tsx and render it inline
  in the Alert Name column.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
6b04e17f64 fix(alerts): remove 'Muted/Disabled by <user>' from banners
Drop the createdBy/updatedBy attribution from both the muted and
disabled banners. MutedBanner keeps name + manage link;
DisabledBanner keeps the relative time.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
95864ebe58 fix(alerts): disable Mute pill when alert rule is disabled
Muting a disabled rule wouldn't change observable behavior — fires
aren't recorded, so there's nothing to suppress. Disabling the Mute
pill also brings cursor + hover affordances in line with the
non-interactive Active pill while muted: cursor: not-allowed and no
hover background (the SCSS already guards hover with :not(:disabled)).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
a0b9713a4f feat(alerts): add hover background to segmented pills
Surface clickable regions more clearly:
- Inactive pill hover: --bg-ink-200 background (dark) / --bg-vanilla-300
  (light mode).
- Active pill hovers darken slightly to their next shade — robin-600,
  amber-600, slate-200 — for tactile feedback without losing the
  state color.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
bded46ce95 fix(alerts): wrap segmented pill text in span for reliable color
The previous attempt set color on the .pill--active-muted block but
something in the cascade was leaving the bare text node light while
the icon (which had its own explicit rule) went dark. Wrap each pill's
label in <span class="alert-state-segmented__label"> and apply the
muted color to both the icon and label classes — both children now
have explicit color rules that don't depend on inheritance from the
button.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
b38d2963b5 fix(alerts): make muted segmented pill text legible
The base .alert-state-segmented__pill rule was winning over
.alert-state-segmented__pill--active-muted in the cascade for some
build configurations, leaving the "Mute" label rendered in
var(--bg-vanilla-400) (light gray) on amber. Bump specificity by
self-chaining the class (&.alert-state-segmented__pill--active-muted →
.pill.pill--active-muted) so the dark text color always wins, and
apply the color to the icon explicitly so it tracks the label.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
406800a2e5 Revert "fix(alerts): move toggle toast to per-call mutate callbacks"
This reverts commit 7dacb99536ca386a9b74d101f2f84fdd39ce6864.
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
b10f45a59a Revert "fix(alerts): update rule cache directly instead of refetching"
This reverts commit adc9e0ff1162d7dcc1f82ccfd54ae3b1c428cc46.
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
f75d166eb6 fix(alerts): update rule cache directly instead of refetching
invalidateGetRuleByID + refetchQueries caused EditRules to render its
<Spinner /> (isRefetching=true), which unmounted the whole form
subtree. The unmount cascade plus follow-up query resolutions (channels,
event, fields/keys) caused the success toast to briefly disappear and
re-enter from its animation again, visible as a flicker.

Patching the rule already returns the updated rule, so write it into
the query cache via setQueryData. The form sees the new state without
remounting, and the toast stays put.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
81f7694991 fix(alerts): move toggle toast to per-call mutate callbacks
Hook-level onSuccess on useMutation can re-fire when the
MutationObserver re-subscribes during context-driven re-renders (each
context update from setAlertRuleState re-renders all consumers of
useAlertRule, including ActionButtons, which re-instantiates the
mutation observer). Per-call callbacks passed to mutate() in
react-query v3 are guaranteed to run exactly once per mutate() call.

This was visible as the success toast appearing once per follow-up API
response after enable/disable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
3858e70a4c fix(alerts): detach mute popover from segmented control anchor
Popover's Trigger wrapper could intercept clicks on the inner buttons,
causing the disable/enable toggle to fire twice (visible as a flickering
success toast). Render the segmented control standalone and anchor the
Popover to a separate invisible span positioned at the bottom-right of
the wrapper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:03 +05:30
Jatinderjit Singh
9b87348839 fix(alerts): mute popover only opens via Mute pill, not Active/Disable
The Popover wrapper used trigger="click", which fired on any click in
the anchor (including Active and Disable pills). Switch to trigger={[]}
so the popover is controlled exclusively by the Mute handler.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:02 +05:30
Jatinderjit Singh
fda747f81e feat(alerts): add mute action wrapping planned downtime
Adds a Mute action on the alert rule page that creates a Planned
Downtime entry scoped to the rule, with a quick-duration popover and
full scheduler drawer.

- AlertStateSegmented control replaces the Active/Disabled switch with a
  three-state pill (Active / Mute / Disable). Active is disabled while
  muted; un-mute happens via the Planned Downtimes page.
- MutePopover offers quick durations (15m, 1h, 4h, 1d, 1w, Forever) plus
  a Name field.
- MuteSchedulerDrawer exposes the full Planned Downtime form for custom
  windows and recurrence.
- MutedBanner / DisabledBanner render an informative banner under the
  header for the corresponding states.
- Alert rules list shows a muted badge with countdown for each rule that
  has an active downtime.
- Lookup is frontend-only via listDowntimeSchedules + ruleId filter,
  with no backend changes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:51:02 +05:30
300 changed files with 5384 additions and 4217 deletions

View File

@@ -190,7 +190,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.126.0
image: signoz/signoz:v0.126.1
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port

View File

@@ -117,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.126.0
image: signoz/signoz:v0.126.1
ports:
- "8080:8080" # signoz port
volumes:

View File

@@ -181,7 +181,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.126.0}
image: signoz/signoz:${VERSION:-v0.126.1}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -109,7 +109,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.126.0}
image: signoz/signoz:${VERSION:-v0.126.1}
container_name: signoz
ports:
- "8080:8080" # signoz port

View File

@@ -5945,6 +5945,22 @@ components:
- start
- end
type: object
RuletypesActiveMute:
properties:
description:
type: string
end:
format: date-time
nullable: true
type: string
id:
type: string
name:
type: string
start:
format: date-time
type: string
type: object
RuletypesAlertCompositeQuery:
properties:
panelType:
@@ -6226,6 +6242,11 @@ components:
additionalProperties:
type: string
type: object
mutes:
items:
$ref: '#/components/schemas/RuletypesActiveMute'
nullable: true
type: array
notificationSettings:
$ref: '#/components/schemas/RuletypesNotificationSettings'
preferredChannels:

View File

@@ -25,8 +25,7 @@ const BANNED_COMPONENTS = {
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.',
Tag: 'Use @signozhq/ui/badge Bagde instead of antd Tag.',
};
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

@@ -7051,6 +7051,31 @@ export interface RulestatehistorytypesGettableRuleStateWindowDTO {
state: RuletypesAlertStateDTO;
}
export interface RuletypesActiveMuteDTO {
/**
* @type string
*/
description?: string;
/**
* @type string,null
* @format date-time
*/
end?: string | null;
/**
* @type string
*/
id?: string;
/**
* @type string
*/
name?: string;
/**
* @type string
* @format date-time
*/
start?: string;
}
export enum RuletypesPanelTypeDTO {
value = 'value',
table = 'table',
@@ -7419,6 +7444,10 @@ export interface RuletypesRuleDTO {
* @type object
*/
labels?: RuletypesRuleDTOLabels;
/**
* @type array,null
*/
mutes?: RuletypesActiveMuteDTO[] | null;
notificationSettings?: RuletypesNotificationSettingsDTO;
/**
* @type array

View File

@@ -11,7 +11,6 @@
}
.divider {
border-color: var(--l1-border);
margin: 16px 0;
margin-top: 10px;
--divider-color: var(--l1-border);
--divider-margin: 10px 0 16px 0;
}

View File

@@ -14,11 +14,6 @@
.ant-form-item {
margin-bottom: 0;
}
.ant-tag {
margin-right: 0;
background: var(--card);
}
}
.add-tag-container {

View File

@@ -1,11 +1,12 @@
import React, { Dispatch, SetStateAction, useState } from 'react';
import { Check, Plus, X } from '@signozhq/icons';
import { Button, Flex, Tag } from 'antd';
import { Button, Flex } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import Input from 'components/Input';
import './Tags.styles.scss';
import './Badges.styles.scss';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
function Badges({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
const [inputVisible, setInputVisible] = useState<boolean>(false);
@@ -46,14 +47,18 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
return (
<div className="tags-container">
{tags.map<React.ReactNode>((tag) => (
<Tag
<Badge
key={tag}
closable
color="vanilla"
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
>
<span>{tag}</span>
</Tag>
{tag}
</Badge>
))}
{inputVisible && (
@@ -113,4 +118,4 @@ interface AddTagsProps {
setTags: Dispatch<SetStateAction<string[]>>;
}
export default Tags;
export default Badges;

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import type { DefaultOptionType } from 'antd/es/select';
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: ComboboxSimpleItem[];
options: DefaultOptionType[];
} => {
const [searchText, setSearchText] = useState<string>('');
const { isFetching, options } = useGetAllFilters({

View File

@@ -9,8 +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 { Select, Tooltip } from 'antd';
import {
OPERATORS,
QUERY_BUILDER_OPERATORS_BY_TYPES,
@@ -52,6 +51,7 @@ import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
import './ClientSideQBSearch.styles.scss';
import { Badge } from '@signozhq/ui/badge';
export interface AttributeKey {
key: string;
@@ -548,10 +548,14 @@ function ClientSideQBSearch(
return (
<span className="qb-search-bar-tokenised-tags">
<Tag
closable={!searchValue && closable}
onClose={onCloseHandler}
<Badge
color="vanilla"
className={tagDetails?.key?.type || ''}
closable={!searchValue && closable}
onClose={(e): void => {
e.preventDefault();
onCloseHandler();
}}
>
<Tooltip title={chipValue}>
<TypographyText
@@ -567,7 +571,7 @@ function ClientSideQBSearch(
{chipValue}
</TypographyText>
</Tooltip>
</Tag>
</Badge>
</span>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Color } from '@signozhq/design-tokens';
import { Button, Modal, Tag } from 'antd';
import { Button, Modal } from 'antd';
import { CircleAlert, X } from '@signozhq/icons';
import KeyValueLabel from 'periscope/components/KeyValueLabel';
import { useAppContext } from 'providers/App/App';
@@ -9,6 +9,7 @@ import APIError from 'types/api/error';
import ErrorContent from './components/ErrorContent';
import './ErrorModal.styles.scss';
import { Badge } from '@signozhq/ui/badge';
type Props = {
error: APIError;
@@ -45,14 +46,17 @@ function ErrorModal({
return (
<>
{!triggerComponent ? (
<Tag
<span
className="error-modal__trigger"
icon={<CircleAlert size={14} color={Color.BG_CHERRY_500} />}
color="error"
role="button"
tabIndex={0}
onClick={(): void => setVisible(true)}
onKeyDown={undefined}
>
error
</Tag>
<Badge color="error">
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
</Badge>
</span>
) : (
React.cloneElement(triggerComponent, {
onClick: () => setVisible(true),

View File

@@ -1,8 +1,7 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Button, Col, Popover, Row, Space } from 'antd';
import { Button, Col, Popover, Row, Select, 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';
@@ -24,7 +23,11 @@ import { popupContainer } from 'utils/selectPopupContainer';
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
import MenuItemGenerator from './MenuItemGenerator';
import SaveViewWithName from './SaveViewWithName';
import { ExplorerCardHeadContainer, OffSetCol } from './styles';
import {
DropDownOverlay,
ExplorerCardHeadContainer,
OffSetCol,
} from './styles';
import { ExplorerCardProps } from './types';
import { deleteViewHandler } from './utils';
import { Ellipsis, Save, Share2, Trash2 } from '@signozhq/icons';
@@ -153,26 +156,6 @@ 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 && (
@@ -192,12 +175,30 @@ function ExplorerCard({
<Space size="large">
{viewsData?.data.data && viewsData?.data.data.length && (
<Space>
<ComboboxSimple
<Select
getPopupContainer={popupContainer}
loading={isLoading || isRefetching}
showSearch
placeholder="Select a view"
items={viewItems}
dropdownStyle={DropDownOverlay}
dropdownMatchSelectWidth={false}
optionLabelProp="value"
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 && (

View File

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

View File

@@ -44,10 +44,7 @@ function InputWithLabel({
>
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
className="input"
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 { Plus, Trash2, X } from '@signozhq/icons';
import { ChevronDown, 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,6 +15,7 @@ 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';
@@ -253,17 +254,18 @@ function InviteMembersModal({
)}
</div>
<div className="team-member-cell role-cell">
<SelectSimple
<Select
value={row.role || undefined}
onChange={(role): void => updateRole(row.id, role as ROLES)}
className="team-member-role-select"
placeholder="Select roles"
items={[
{ value: 'VIEWER', label: 'Viewer' },
{ value: 'EDITOR', label: 'Editor' },
{ value: 'ADMIN', label: 'Admin' },
]}
/>
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>
</div>
<div className="team-member-cell action-cell">
{rows.length > 1 && (

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

View File

@@ -10,8 +10,7 @@ import {
Loader,
X,
} from '@signozhq/icons';
import { Modal, Spin, Tooltip, Tree, TreeDataNode } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Modal, Select, Spin, Tooltip, Tree, TreeDataNode } from 'antd';
import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
@@ -182,8 +181,8 @@ function AttributeCheckList({
const [filter, setFilter] = useState<AttributesFilters>(AttributesFilters.ALL);
const [treeData, setTreeData] = useState<TreeDataNode[]>([]);
const handleFilterChange = (value: string | string[]): void => {
setFilter(value as AttributesFilters);
const handleFilterChange = (value: AttributesFilters): void => {
setFilter(value);
};
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const history = useHistory();
@@ -238,11 +237,11 @@ function AttributeCheckList({
</div>
) : (
<div className="modal-content">
<SelectSimple
<Select
defaultValue={AttributesFilters.ALL}
className="attribute-select"
onChange={handleFilterChange}
items={[
options={[
{
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 { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import type { DefaultOptionType } from 'antd/es/select';
import { Info } from '@signozhq/icons';
import './MQCommon.styles.scss';
@@ -35,7 +35,7 @@ export function ComingSoon(): JSX.Element {
}
export function SelectMaxTagPlaceholder(
omittedValues: Partial<ComboboxSimpleItem>[],
omittedValues: Partial<DefaultOptionType>[],
): JSX.Element {
return (
<Tooltip title={omittedValues.map(({ value }) => value).join(', ')}>

View File

@@ -18,9 +18,8 @@ import {
RefreshCw,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
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 { Checkbox } from '@signozhq/ui/checkbox';
import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import TextToolTip from 'components/TextToolTip/TextToolTip';

View File

@@ -16,7 +16,6 @@ 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, useMemo, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Select, Spin } from 'antd';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
@@ -13,25 +13,48 @@ 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 [selectOptions, setSelectOptions] = useState<ComboboxSimpleItem[]>([]);
const [searchInput, setSearchInput] = useState('');
const [debouncedInput, setDebouncedInput] = useState('');
const [selectOptions, setSelectOptions] = useState<
{ label: string; value: string }[]
>([]);
const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
// Fetch key suggestions once; ComboboxSimple handles local filtering.
// Fetch key suggestions based on debounced input
const { data, isLoading } = useQuery({
queryKey: ['orderByKeySuggestions', dataSource],
queryKey: ['orderByKeySuggestions', dataSource, debouncedInput],
queryFn: async () => {
const response = await getKeySuggestions({
signal: dataSource,
searchText: '',
searchText: debouncedInput,
});
return response.data;
},
});
useEffect(
() => (): void => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
},
[],
);
// Update options when API data changes
useEffect(() => {
const rawKeys: QueryKeyDataSuggestionsProps[] = data?.data?.keys
@@ -39,33 +62,52 @@ function ListViewOrderBy({
: [];
const keyNames = rawKeys.map((key) => key.name);
const uniqueKeys = [...new Set(['timestamp', ...keyNames])];
const uniqueKeys = [
...new Set(searchInput ? keyNames : ['timestamp', ...keyNames]),
];
const updatedOptions: ComboboxSimpleItem[] = uniqueKeys.flatMap((key) => [
const updatedOptions = uniqueKeys.flatMap((key) => [
{ label: `${key} (desc)`, value: `${key}:desc` },
{ label: `${key} (asc)`, value: `${key}:asc` },
]);
setSelectOptions(updatedOptions);
}, [data]);
}, [data, searchInput]);
const handleChange = useMemo(
() =>
(val: string | string[]): void => {
onChange(val as string);
},
[onChange],
);
// 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);
}
};
return (
<ComboboxSimple
<Select
showSearch
value={value}
onChange={handleChange}
loading={isLoading}
onChange={onChange}
onSearch={handleSearch}
notFoundContent={<Loader isLoading={isLoading} />}
placeholder="Select a field"
style={{ width: 200 }}
items={selectOptions}
emptyPlaceholder="No results found"
options={selectOptions}
filterOption={(input, option): boolean =>
(option?.value ?? '').toLowerCase().includes(input.trim().toLowerCase())
}
/>
);
}

View File

@@ -22,37 +22,6 @@
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;
@@ -111,8 +80,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -132,8 +101,7 @@
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted
var(--query-builder-v2-border-color, var(--l2-border));
border-left: 6px dotted var(--l1-border);
}
/* Horizontal line pointing from vertical to the item */
@@ -146,8 +114,8 @@
height: 1px;
background: repeating-linear-gradient(
to right,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -273,8 +241,7 @@
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted
var(--query-builder-v2-border-color, var(--l2-border));
border-left: 6px dotted var(--l1-border);
}
/* Horizontal line pointing from vertical to the item */
@@ -287,8 +254,8 @@
height: 1px;
background: repeating-linear-gradient(
to right,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -306,16 +273,6 @@
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 {
@@ -325,30 +282,15 @@
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
background: var(
--query-builder-v2-background-color,
var(--l2-background)
);
color: var(--query-builder-v2-color, var(--l2-foreground));
background: var(--l2-background);
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));
}
}
}
}
@@ -381,8 +323,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -453,8 +395,8 @@
width: 1px;
background: repeating-linear-gradient(
to bottom,
var(--query-builder-v2-border-color, var(--l2-border)),
var(--query-builder-v2-border-color, var(--l2-border)) 4px,
var(--l1-border),
var(--l1-border) 4px,
transparent 4px,
transparent 8px
);
@@ -470,7 +412,7 @@
min-width: 120px;
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
@@ -515,7 +457,7 @@
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border)) !important;
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
height: 34px !important;
box-sizing: border-box !important;

View File

@@ -57,7 +57,6 @@
.group-by-filter-container {
min-width: 340px !important;
--combobox-trigger-height: auto;
}
.metrics-aggregation-section-content-item {
@@ -147,3 +146,17 @@
}
}
}
.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,6 +118,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
value={queryAggregation.timeAggregation || ''}
onChange={handleChangeOperator}
operators={operators}
className="metrics-operators-select"
/>
</div>
</div>

View File

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

View File

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

View File

@@ -29,33 +29,32 @@
font-style: normal;
font-weight: var(--font-weight-normal);
color: var(--query-builder-v2-color, var(--l2-foreground));
color: var(--l2-foreground);
}
> button {
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
border-left: none;
min-width: 120px;
height: 36px;
line-height: 36px;
&:first-child {
border-left: 1px solid
var(--query-builder-v2-border-color, var(--l2-border));
border-left: 1px solid var(--l1-border);
}
&::before {
background: var(--query-builder-v2-border-color, var(--l2-border));
background: var(--l1-border);
}
&[data-state='on'] {
color: var(--text-robin-500);
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
display: none;
&::before {
background: var(--query-builder-v2-border-color, var(--l2-border));
background: var(--l1-border);
}
}
}
@@ -66,7 +65,7 @@
height: 30px;
border-radius: 2px;
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border: 1px solid var(--l1-border);
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
@@ -79,13 +78,10 @@
align-items: center;
.having-filter-select-container {
position: relative;
width: 100%;
display: flex;
flex-direction: row;
align-items: flex-start;
background: var(--query-builder-v2-background-color, var(--l2-background));
padding-right: 38px;
align-items: center;
.having-filter-select-editor {
border-radius: 2px;
@@ -110,17 +106,15 @@
}
.cm-content {
border: 1px solid var(--query-builder-v2-border-color, var(--l2-border));
border-left-width: 0px;
border-right-width: 0px;
border-radius: 2px;
border: 1px solid var(--l1-border);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
padding: 0px !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
background-color: var(--l2-background) !important;
&:focus-within {
border-color: var(--query-builder-v2-border-color, var(--l2-border));
border-color: var(--l1-border);
}
}
@@ -224,32 +218,17 @@
}
.cm-line {
min-height: 34px;
line-height: 32px !important;
line-height: 36px !important;
font-family: 'Space Mono', monospace !important;
background-color: var(
--query-builder-v2-background-color,
var(--l2-background)
) !important;
&,
.ͼ1a {
color: var(--query-builder-v2-color, var(--l2-foreground));
}
background-color: var(--l2-background) !important;
::-moz-selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: var(--l3-background) !important;
opacity: 0.5 !important;
}
@@ -258,11 +237,8 @@
}
.chip-decorator {
background: var(
--query-builder-v2-chip-decorator-background-color,
var(--l3-background)
) !important;
color: var(--query-builder-v2-color, var(--l2-foreground)) !important;
background: var(--l3-background) !important;
color: var(--l1-foreground) !important;
border-radius: 4px;
padding: 2px 4px;
margin-right: 4px;
@@ -270,36 +246,24 @@
}
.cm-selectionBackground {
background: var(
--query-builder-v2-selection-background-color,
var(--l3-background)
) !important;
background: 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(--query-builder-v2-border-color, var(--l2-border));
background: var(--query-builder-v2-background-color, var(--l2-background));
height: 100%;
border: 1px solid var(--l2-border);
background: var(--l2-background);
height: 38px;
width: 38px;
border-left: transparent;
border-top-left-radius: 0px;
border-bottom-left-radius: 0px;
}

View File

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

View File

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

@@ -14,7 +14,8 @@ import { Color } from '@signozhq/design-tokens';
import { copilot } from '@uiw/codemirror-theme-copilot';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, { EditorView, keymap, Prec } from '@uiw/react-codemirror';
import { Button, Card, Collapse, Popover, Tag, Tooltip } from 'antd';
import { Button, Card, Collapse, Popover, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { getKeySuggestions } from 'api/querySuggestions/getKeySuggestions';
import { getValueSuggestions } from 'api/querySuggestions/getValueSuggestion';
import cx from 'classnames';
@@ -664,26 +665,26 @@ function QuerySearch({
// Helper function to render a badge for the current context mode
const renderContextBadge = (): JSX.Element => {
if (!editingMode) {
return <Tag>Unknown</Tag>;
return <Badge color="vanilla">Unknown</Badge>;
}
switch (editingMode) {
case 'key':
return <Tag color="blue">Key</Tag>;
return <Badge color="robin">Key</Badge>;
case 'operator':
return <Tag color="purple">Operator</Tag>;
return <Badge color="sakura">Operator</Badge>;
case 'value':
return <Tag color="green">Value</Tag>;
return <Badge color="forest">Value</Badge>;
case 'conjunction':
return <Tag color="orange">Conjunction</Tag>;
return <Badge color="amber">Conjunction</Badge>;
case 'function':
return <Tag color="cyan">Function</Tag>;
return <Badge color="aqua">Function</Badge>;
case 'parenthesis':
return <Tag color="magenta">Parenthesis</Tag>;
return <Badge color="sakura">Parenthesis</Badge>;
case 'bracketList':
return <Tag color="red">Bracket List</Tag>;
return <Badge color="cherry">Bracket List</Badge>;
default:
return <Tag>Unknown</Tag>;
return <Badge color="vanilla">Unknown</Badge>;
}
};
@@ -1304,34 +1305,37 @@ function QuerySearch({
Currently editing: {renderContextBadge()}
{queryContext?.keyToken && (
<span className="triplet-info">
Key: <Tag>{queryContext.keyToken}</Tag>
Key: <Badge color="vanilla">{queryContext.keyToken}</Badge>
</span>
)}
{queryContext?.operatorToken && (
<span className="triplet-info">
Operator: <Tag>{queryContext.operatorToken}</Tag>
Operator: <Badge color="vanilla">{queryContext.operatorToken}</Badge>
</span>
)}
{queryContext?.valueToken && (
<span className="triplet-info">
Value: <Tag>{queryContext.valueToken}</Tag>
Value: <Badge color="vanilla">{queryContext.valueToken}</Badge>
</span>
)}
{queryContext?.currentPair && (
<span className="triplet-info query-pair-info">
Current pair: <Tag color="blue">{queryContext.currentPair.key}</Tag>
<Tag color="purple">{queryContext.currentPair.operator}</Tag>
Current pair: <Badge color="robin">{queryContext.currentPair.key}</Badge>
<Badge color="sakura">{queryContext.currentPair.operator}</Badge>
{queryContext.currentPair.value && (
<Tag color="green">{queryContext.currentPair.value}</Tag>
<Badge color="forest">{queryContext.currentPair.value}</Badge>
)}
<Tag color={queryContext.currentPair.isComplete ? 'success' : 'warning'}>
<Badge
color={queryContext.currentPair.isComplete ? 'success' : 'warning'}
>
{queryContext.currentPair.isComplete ? 'Complete' : 'Incomplete'}
</Tag>
</Badge>
</span>
)}
{queryContext?.queryPairs && queryContext.queryPairs.length > 0 && (
<span className="triplet-info">
Total pairs: <Tag color="blue">{queryContext.queryPairs.length}</Tag>
Total pairs:{' '}
<Badge color="robin">{queryContext.queryPairs.length}</Badge>
</span>
)}
</div>

View File

@@ -11,7 +11,7 @@ import cx from 'classnames';
import { ENTITY_VERSION_V4, ENTITY_VERSION_V5 } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import QBEntityOptions from 'container/QueryBuilder/components/QBEntityOptions/QBEntityOptions';
import { QueryProps } from 'container/QueryBuilder/components/Query/Query.interfaces';
import { QueryProps } from 'container/QueryBuilder/type';
import SpanScopeSelector from 'container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';

View File

@@ -1,10 +1,12 @@
import { CircleAlert, RefreshCw } from '@signozhq/icons';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Select } from 'antd';
import { Checkbox } from '@signozhq/ui/checkbox';
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';
@@ -73,6 +75,7 @@ interface BaseProps {
id?: string;
placeholder?: string;
className?: string;
getPopupContainer?: (trigger: HTMLElement) => HTMLElement;
roles?: AuthtypesRoleDTO[];
loading?: boolean;
isError?: boolean;
@@ -110,13 +113,14 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
});
const roles = externalRoles ?? data?.data ?? [];
const items: ComboboxSimpleItem[] = getRoleOptions(roles);
const options = getRoleOptions(roles);
const {
mode,
id,
placeholder = 'Select role',
className,
getPopupContainer = popupContainer,
loading = internalLoading,
isError = internalError,
error = convertToApiError(internalErrorObj),
@@ -124,47 +128,54 @@ function RolesSelect(props: RolesSelectProps): JSX.Element {
disabled,
} = props;
const emptyPlaceholder = isError
? error?.message || 'Failed to load roles'
: 'No roles available';
const notFoundContent = isError ? (
<ErrorContent error={error} onRefetch={onRefetch} />
) : undefined;
if (mode === 'multiple') {
const { value = [], onChange } = props as MultipleProps;
return (
<>
<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} />}
</>
<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 => (
<div style={{ pointerEvents: 'none' }}>
<Checkbox value={value.includes(option.value as string)}>
{option.label}
</Checkbox>
</div>
)}
getPopupContainer={getPopupContainer}
disabled={disabled}
/>
);
}
const { value, onChange } = props as SingleProps;
const { value, onChange, allowClear = true } = props as SingleProps;
return (
<>
<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} />}
</>
<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}
/>
);
}

View File

@@ -1,36 +1,34 @@
import { Tag, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { getLabelRenderingValue } from './utils';
function TagWithToolTip({
function BadgeWithTooltip({
label,
value,
color,
}: TagWithToolTipProps): JSX.Element {
}: BadgeWithTooltipProps): JSX.Element {
const tooltipTitle =
value && value[label] ? `${label}: ${value[label]}` : label;
return (
<div key={label}>
<Tooltip title={tooltipTitle}>
<Tag className="label-column--tag" color={color}>
<Badge className="label-column--tag" color="vanilla">
{getLabelRenderingValue(label, value && value[label])}
</Tag>
</Badge>
</Tooltip>
</div>
);
}
type TagWithToolTipProps = {
type BadgeWithTooltipProps = {
label: string;
color?: string;
value?: {
[key: string]: string;
};
};
TagWithToolTip.defaultProps = {
BadgeWithTooltip.defaultProps = {
value: undefined,
color: undefined,
};
export default TagWithToolTip;
export default BadgeWithTooltip;

View File

@@ -1,12 +1,13 @@
import { Popover, Tag } from 'antd';
import { Popover } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { LabelColumnProps } from './TableRenderer.types';
import TagWithToolTip from './TagWithToolTip';
import BadgeWithTooltip from './BadgeWithTooltip';
import { getLabelAndValueContent } from './utils';
import './LabelColumn.styles.scss';
function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
function LabelColumn({ labels, value }: LabelColumnProps): JSX.Element {
const newLabels = labels.length > 3 ? labels.slice(0, 3) : labels;
const remainingLabels = labels.length > 3 ? labels.slice(3) : [];
@@ -14,7 +15,7 @@ function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
<div className="label-column">
{newLabels.map(
(label: string): JSX.Element => (
<TagWithToolTip key={label} label={label} color={color} value={value} />
<BadgeWithTooltip key={label} label={label} value={value} />
),
)}
{remainingLabels.length > 0 && (
@@ -26,9 +27,9 @@ function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
{labels.map(
(label: string): JSX.Element => (
<div key={label}>
<Tag className="label-column--tag" color={color}>
<Badge className="label-column--tag" color="vanilla">
{getLabelAndValueContent(label, value && value[label])}
</Tag>
</Badge>
</div>
),
)}
@@ -36,9 +37,9 @@ function LabelColumn({ labels, value, color }: LabelColumnProps): JSX.Element {
}
trigger="hover"
>
<Tag className="label-column--tag" color={color}>
<Badge className="label-column--tag" color="vanilla">
+{remainingLabels.length}
</Tag>
</Badge>
</Popover>
)}
</div>

View File

@@ -1,11 +1,7 @@
import { useMemo } from 'react';
import { SolidAlertTriangle } from '@signozhq/icons';
import {
ComboboxSimple,
ComboboxSimpleGroup,
ComboboxSimpleItem,
} from '@signozhq/ui/combobox';
import { Tooltip } from 'antd';
import { Select, Tooltip } from 'antd';
import type { DefaultOptionType } from 'antd/es/select';
import classNames from 'classnames';
import { UniversalYAxisUnitMappings } from './constants';
@@ -46,53 +42,69 @@ function YAxisUnitSelector({
return '';
}, [initialValue, value, loading]);
const categoriesToRender = useMemo(
() => categoriesOverride || getYAxisCategories(source),
[categoriesOverride, source],
);
const handleSearch = (
searchTerm: string,
currentOption: DefaultOptionType | undefined,
): boolean => {
if (!currentOption?.value) {
return false;
}
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],
);
const search = searchTerm.toLowerCase();
const unitId = currentOption.value.toString().toLowerCase();
const unitLabel = currentOption.children?.toString().toLowerCase() || '';
const handleChange = (val: string | string[]): void => {
onChange(val as UniversalYAxisUnit);
// 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 categoriesToRender = useMemo(() => {
return categoriesOverride || getYAxisCategories(source);
}, [categoriesOverride, source]);
return (
<div
className={classNames('y-axis-unit-selector-component', containerClassName)}
>
<ComboboxSimple
value={universalUnit ?? undefined}
onChange={handleChange}
<Select
showSearch
value={universalUnit}
onChange={onChange}
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,
})}
testId={dataTestId}
groups={groups}
/>
{incompatibleUnitMessage && (
<Tooltip title={incompatibleUnitMessage}>
<SolidAlertTriangle role="img" aria-label="warning" size="md" />
</Tooltip>
)}
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>
</div>
);
}

View File

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

View File

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

View File

@@ -176,14 +176,14 @@
.group-by-label {
display: flex;
padding: 4px 15px;
padding: 6px 15px;
justify-content: center;
align-items: center;
gap: 4px;
flex-shrink: 0;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l2-border);
border: 1px solid var(--l1-border);
border-right: none;
background: var(--l3-background);
@@ -192,15 +192,18 @@
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
line-height: 18px;
letter-spacing: -0.07px;
}
.group-by-select {
width: 100%;
box-sizing: border-box;
--combobox-trigger-border-radius: 0px;
.ant-select-selector {
font-size: 14px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
}
// Add border-bottom to table cells when pagination is not present
@@ -423,13 +426,14 @@
}
.endpoint-details-filters-container-dropdown {
width: 150px;
width: 120px;
border-right: 1px solid var(--l1-border);
height: 36px;
display: flex;
align-items: center;
--combobox-trigger-border-color: transparent;
.ant-select-single {
height: 32px;
}
}
.endpoint-details-filters-container-search {

View File

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

View File

@@ -1,7 +1,8 @@
import { ReactNode } from 'react';
import { Color } from '@signozhq/design-tokens';
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
import { TableColumnType as ColumnType, Tooltip } from 'antd';
import { Progress } from '@signozhq/ui/progress';
import { Badge } from '@signozhq/ui/badge';
import { convertFiltersToExpressionWithExistingQuery } from 'components/QueryBuilderV2/utils';
import {
FiltersType,
@@ -972,13 +973,9 @@ export const getEndPointsColumnsConfig = (
})()}
{isGroupedByAttribute
? text.split(',').map((value) => (
<Tag
key={value}
color={Color.BG_SLATE_100}
className="endpoint-group-tag-item"
>
<Badge key={value} color="vanilla" className="endpoint-group-tag-item">
{value === '' ? '<no-value>' : value}
</Tag>
</Badge>
))
: endPointName}
</div>

View File

@@ -14,8 +14,8 @@ import {
Skeleton,
Table,
TableColumnsType as ColumnsType,
Tag,
} from 'antd';
import { Badge } from '@signozhq/ui/badge';
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
import logEvent from 'api/common/logEvent';
import updateCreditCardApi from 'api/v1/checkout/create';
@@ -434,7 +434,7 @@ export default function BillingContainer(): JSX.Element {
<Flex vertical>
<Typography.Title level={5} style={{ marginTop: 2, fontWeight: 500 }}>
{isCloudUserVal ? t('teams_cloud') : t('teams')}{' '}
{isFreeTrial ? <Tag color="success"> Free Trial </Tag> : ''}
{isFreeTrial ? <Badge color="success"> Free Trial </Badge> : ''}
</Typography.Title>
{!isLoading && !isFetchingBillingData && !showGracePeriodMessage ? (

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Row, Tag } from 'antd';
import { Row } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
@@ -66,13 +67,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
<AlertTypeCard
key={option.selection}
title={option.title}
extra={
option.isBeta ? (
<Tag bordered={false} color="geekblue">
Beta
</Tag>
) : undefined
}
extra={option.isBeta ? <Badge color="robin">Beta</Badge> : undefined}
onClick={(e): void => {
onSelect(option.selection, isModifierKeyPressed(e));
}}

View File

@@ -14,7 +14,7 @@ import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQue
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { AlertListTabs } from 'pages/AlertList/types';
import { GalleryVerticalEnd, Pyramid } from '@signozhq/icons';
import { CalendarClock, GalleryVerticalEnd, Pyramid } from '@signozhq/icons';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
@@ -175,11 +175,21 @@ function CreateRules(): JSX.Element {
{
label: (
<div className="periscope-tab top-level-tab">
<ConfigureIcon width={14} height={14} />
Configuration
<CalendarClock size={14} />
Planned Downtime
</div>
),
key: AlertListTabs.CONFIGURATION,
key: AlertListTabs.PLANNED_DOWNTIME,
children: null,
},
{
label: (
<div className="periscope-tab top-level-tab">
<ConfigureIcon width={14} height={14} />
Routing Policies
</div>
),
key: AlertListTabs.ROUTING_POLICIES,
children: null,
},
];

View File

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

View File

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

View File

@@ -1,14 +1,14 @@
import { useMemo, useState } from 'react';
import { Button, Input, Tooltip } from 'antd';
import { ComboboxSimple } from '@signozhq/ui/combobox';
import { SelectSimple } from '@signozhq/ui/select';
import { Button, Input, Select, Tooltip } from 'antd';
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,38 +18,36 @@ 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 = (
<SelectSimple
<Select
placeholder="Unit"
value={threshold.unit ? threshold.unit : undefined}
onChange={(value): void =>
updateThreshold(threshold.id, 'unit', value as string)
}
value={threshold.unit ? threshold.unit : null}
onChange={(value): void => updateThreshold(threshold.id, 'unit', value)}
style={{ width: 150 }}
items={units}
options={units}
disabled={units.length === 0}
testId="threshold-unit-select"
data-testid="threshold-unit-select"
/>
);
if (units.length === 0) {
component = (
<Tooltip trigger="hover" title="No compatible units available">
<SelectSimple
<Select
placeholder="Unit"
value={threshold.unit ? threshold.unit : undefined}
onChange={(value): void =>
updateThreshold(threshold.id, 'unit', value as string)
}
value={threshold.unit ? threshold.unit : null}
onChange={(value): void => updateThreshold(threshold.id, 'unit', value)}
style={{ width: 150 }}
items={units}
options={units}
disabled={units.length === 0}
testId="threshold-unit-select"
data-testid="threshold-unit-select"
/>
</Tooltip>
);
@@ -119,22 +117,37 @@ function ThresholdItem({
{!notificationSettings.routingPolicies && (
<>
<Typography.Text className="sentence-text">send to</Typography.Text>
<ComboboxSimple
<Select
value={threshold.channels}
onChange={(value): void =>
updateThreshold(threshold.id, 'channels', value as string[])
updateThreshold(threshold.id, 'channels', value)
}
testId="threshold-notification-channel-select"
data-testid="threshold-notification-channel-select"
style={{ width: 350 }}
items={channels.map((channel) => ({
options={channels.map((channel) => ({
value: channel.name,
label: channel.name,
'data-testid': `threshold-notification-channel-option-${threshold.label}`,
}))}
multiple
mode="multiple"
placeholder="Select notification channels"
loading={isLoadingChannels}
className={isErrorChannels ? 'error' : undefined}
emptyPlaceholder="No channels found"
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}
/>
}
/>
</>
)}

View File

@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
import type { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import type { DefaultOptionType } from 'antd/es/select';
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: ComboboxSimpleItem[] = [
const mockUnits: DefaultOptionType[] = [
{ label: 'Bytes', value: 'bytes' },
{ label: 'KB', value: 'kb' },
{ label: 'MB', value: 'mb' },

View File

@@ -4,5 +4,4 @@ export const THRESHOLD_TAB_TOOLTIP =
export const ANOMALY_TAB_TOOLTIP =
'An alert is triggered whenever the metric deviates from an expected pattern.';
export const ROUTING_POLICIES_ROUTE =
'/alerts?tab=Configuration&subTab=routing-policies';
export const ROUTING_POLICIES_ROUTE = '/alerts?tab=RoutingPolicies';

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { Button, Flex } from 'antd';
import { Button, Flex, SelectProps } 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): ComboboxSimpleItem[] {
export function getQueryNames(currentQuery: Query): BaseOptionType[] {
const involvedQueriesInTraceOperator = getInvolvedQueriesInTraceOperator(
currentQuery.builder.queryTraceOperator,
);
const queryConfig: Record<EQueryType, () => ComboboxSimpleItem[]> = {
const queryConfig: Record<EQueryType, () => SelectProps['options']> = {
[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,
): ComboboxSimpleItem[] {
): DefaultOptionType[] {
if (!name) {
return [];
}
@@ -68,6 +68,7 @@ export function getCategorySelectOptionByName(
?.units.map((unit) => ({
label: unit.name,
value: unit.id,
'data-testid': `threshold-unit-select-option-${unit.id}`,
})) || []
);
}

View File

@@ -3,7 +3,6 @@ import { X } from '@signozhq/icons';
import { useNotifications } from 'hooks/useNotifications';
import { LabelInputState, LabelsInputProps } from './types';
import { Button } from '@signozhq/ui/button';
function LabelsInput({
labels,
@@ -145,16 +144,14 @@ 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,14 +39,9 @@
&__input.title {
background-color: transparent;
background: var(--l2-background);
color: var(--l2-foreground);
color: var(--l1-foreground);
width: 100%;
min-width: 300px;
&:hover {
background: var(--l2-background);
}
}
&__input.description {
@@ -54,6 +49,15 @@
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 {
@@ -62,7 +66,19 @@
gap: 8px;
&__add-button {
width: 80px;
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);
}
}
&__existing-labels {
@@ -105,7 +121,6 @@
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(--l3-background);
border: 1px solid var(--l3-border);
color: var(--l2-foreground);
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l3-border);
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l3-border);
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,9 +3,6 @@
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;
@@ -57,8 +54,8 @@
textarea {
height: 150px;
background: var(--l3-background);
border: 1px solid var(--l3-border);
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: Inter;
@@ -80,9 +77,8 @@
gap: 8px;
.ant-input {
width: 190px;
border: 1px solid var(--l3-border);
background-color: var(--l3-background);
width: 120px;
border: 1px solid var(--l1-border);
}
.ant-select {

View File

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

View File

@@ -16,7 +16,8 @@ import {
Plus,
X,
} from '@signozhq/icons';
import { Button, Card, Input, Modal, Popover, Tag, Tooltip } from 'antd';
import { Button, Card, Input, Modal, Popover, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/Integrations/ConfigureIcon';
@@ -506,9 +507,9 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
{(tags?.length || 0) > 0 && (
<div className="dashboard-tags">
{tags?.map((tag) => (
<Tag key={tag} className="tag">
<Badge key={tag} className="tag" color="vanilla">
{tag}
</Tag>
</Badge>
))}
</div>
)}

View File

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

View File

@@ -359,7 +359,7 @@
flex-flow: wrap;
gap: 8px;
.ant-tag {
[data-slot='badge'] {
height: 30px;
color: var(--l1-foreground);
font-family: 'Space Mono';

View File

@@ -5,9 +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 } from 'antd';
import { Badge } from '@signozhq/ui/badge';
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';
@@ -57,11 +57,7 @@ import { WidgetSelector } from './WidgetSelector';
import './VariableItem.styles.scss';
const SORT_ITEMS = [
{ value: VariableSortTypeArr[0], label: 'Disabled' },
{ value: VariableSortTypeArr[1], label: 'Ascending' },
{ value: VariableSortTypeArr[2], label: 'Descending' },
];
const { Option } = Select;
interface VariableItemProps {
variableData: IDashboardVariable;
@@ -547,9 +543,9 @@ function VariableItem({
}}
>
Dynamic
<Tag bordered={false} className="sidenav-beta-tag" color="geekblue">
<Badge color="robin" className="sidenav-beta-tag">
Beta
</Tag>
</Badge>
</Button>
<Button
type="text"
@@ -604,9 +600,9 @@ function VariableItem({
}}
>
Query
<Tag bordered={false} className="sidenav-beta-tag" color="warning">
<Badge color="amber" className="sidenav-beta-tag">
Not Recommended
</Tag>
</Badge>
<div onClick={(e): void => e.stopPropagation()}>
<TextToolTip
text="Learn why we don't recommend"
@@ -738,7 +734,9 @@ function VariableItem({
<Typography style={{ color: orange[5] }}>{errorPreview}</Typography>
) : (
map(previewValues, (value, idx) => (
<Tag key={`${value}${idx}`}>{value.toString()}</Tag>
<Badge key={`${value}${idx}`} color="vanilla">
{value.toString()}
</Badge>
))
)}
</div>
@@ -748,14 +746,19 @@ function VariableItem({
<Typography className="typography-variables">Sort Values</Typography>
</LabelContainer>
<SelectSimple
<Select
defaultActiveFirstOption
defaultValue={VariableSortTypeArr[0]}
value={variableSortType}
onChange={(value): void =>
setVariableSortType(value as TSortVariableValuesType)
onChange={(value: TSortVariableValuesType): void =>
setVariableSortType(value)
}
className="sort-input"
items={SORT_ITEMS}
/>
>
<Option value={VariableSortTypeArr[0]}>Disabled</Option>
<Option value={VariableSortTypeArr[1]}>Ascending</Option>
<Option value={VariableSortTypeArr[2]}>Descending</Option>
</Select>
</VariableItemRow>
<VariableItemRow className="multiple-values-section">
<LabelContainer>
@@ -798,10 +801,10 @@ function VariableItem({
<CustomSelect
placeholder="Select a default value"
value={variableDefaultValue}
onChange={(value): void => setVariableDefaultValue(value ?? '')}
options={previewValues.map((val) => ({
label: val,
value: val,
onChange={(value): void => setVariableDefaultValue(value)}
options={previewValues.map((value) => ({
label: value,
value,
}))}
/>
</VariableItemRow>

View File

@@ -1,4 +1,38 @@
.tags-input {
.badgesContainer {
display: flex;
align-items: center;
flex-flow: wrap;
gap: 6px;
}
.badgeContainer {
color: var(--bg-sienna-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px;
letter-spacing: 0.52px;
height: 24px;
flex-shrink: 0;
border-radius: 50px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
padding: 2px 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.inputContainer {
> div {
margin: 0;
}
padding-left: 0px !important;
padding-right: 0px !important;
}
.tagsInput {
display: flex;
border: none;
padding: 0px;
@@ -8,23 +42,7 @@
flex-shrink: 0;
}
.tag-container {
color: var(--bg-sienna-400);
font-family: 'Space Mono';
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 153.846% */
letter-spacing: 0.52px;
height: 24px;
flex-shrink: 0;
border-radius: 50px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
padding: 2px 8px;
}
.edit-input {
.editInput {
.ant-form-item {
margin-bottom: 0px;
}

View File

@@ -1,10 +1,9 @@
import { Dispatch, SetStateAction, useState } from 'react';
import { Col, Tooltip } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import Input from 'components/Input';
import { InputContainer, NewTagContainer, TagsContainer } from './styles';
import './AddTags.styles.scss';
import styles from './AddBadges.module.scss';
function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
@@ -39,11 +38,11 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
};
return (
<TagsContainer>
<div className={styles.badgesContainer}>
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<Col key={tag} lg={4} className="edit-input">
<Col key={tag} lg={4} className={styles.editInput}>
<Input
size="small"
value={editInputValue}
@@ -60,11 +59,15 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
const isLongTag = tag.length > 20;
const tagElem = (
<NewTagContainer
closable
<Badge
key={tag}
onClose={(): void => handleClose(tag)}
className="tag-container"
color="vanilla"
className={styles.badgeContainer}
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
>
<span
onDoubleClick={(e): void => {
@@ -75,7 +78,7 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
</NewTagContainer>
</Badge>
);
return isLongTag ? (
@@ -87,11 +90,11 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
);
})}
<InputContainer>
<Col className={styles.inputContainer}>
<Input
type="text"
value={inputValue}
rootClassName="tags-input"
rootClassName={styles.tagsInput}
placeholder="Start typing your tag name"
onChangeHandler={(event): void =>
onChangeHandler(event.target.value, setInputValue)
@@ -99,8 +102,8 @@ function AddTags({ tags, setTags }: AddTagsProps): JSX.Element {
onBlurHandler={handleInputConfirm}
onPressEnterHandler={handleInputConfirm}
/>
</InputContainer>
</TagsContainer>
</Col>
</div>
);
}

View File

@@ -1,30 +0,0 @@
import { Col, Tag } from 'antd';
import styled from 'styled-components';
export const TagsContainer = styled.div`
display: flex;
align-items: center;
flex-flow: wrap;
gap: 6px;
`;
export const NewTagContainer = styled(Tag)`
&&& {
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
svg {
margin-right: 0.2rem;
}
}
`;
export const InputContainer = styled(Col)`
> div {
margin: 0;
}
padding-left: 0px !important;
padding-right: 0px !important;
`;

View File

@@ -98,14 +98,6 @@
.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 {
@@ -116,7 +108,7 @@
padding: 6px;
justify-content: center;
align-items: center;
border-radius: 2var (--l3-border) px 0px 0px 2px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border) !important;
background: var(--l3-background) !important;

View File

@@ -1,10 +1,9 @@
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Col, Input, Space, Tooltip } from 'antd';
import { Col, Input, Select, 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 AddTags from 'container/DashboardContainer/DashboardSettings/General/AddBadges';
import { useDashboardCursorSyncMode } from 'hooks/dashboard/useDashboardCursorSyncMode';
import { useSyncTooltipFilterMode } from 'hooks/dashboard/useSyncTooltipFilterMode';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
@@ -23,6 +22,8 @@ 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,22 +131,24 @@ function GeneralDashboardSettings(): JSX.Element {
<div>
<Typography className={styles.dashboardName}>Dashboard Name</Typography>
<section className={styles.nameIconInput}>
<SelectSimple
<Select
defaultActiveFirstOption
data-testid="dashboard-image"
className={styles.dashboardImageInput}
suffixIcon={null}
rootClassName={styles.dashboardImageInput}
value={updatedImage}
onChange={(value): void => setUpdatedImage(value as string)}
items={Base64Icons.map((icon) => ({
value: icon,
label: (
onChange={(value: string): void => setUpdatedImage(value)}
>
{Base64Icons.map((icon) => (
<Option value={icon} key={icon}>
<img
src={icon}
alt="dashboard-icon"
className={styles.listItemImage}
/>
),
}))}
/>
</Option>
))}
</Select>
<Input
data-testid="dashboard-name"
className={styles.dashboardNameInput}

View File

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

View File

@@ -23,6 +23,10 @@
}
}
}
&__divider {
--divider-vertical-margin: 0;
}
}
.hide-update {
@@ -55,12 +59,6 @@
.hidden {
display: none;
}
.ant-divider {
margin: 0;
height: 28px;
border: 1px solid var(--l1-border);
}
}
.explorer-options {
@@ -75,8 +73,8 @@
gap: 16px;
z-index: 1;
.views-dropdown {
--combobox-trigger-border-color: transparent;
.ant-select-selector {
padding: 0 !important;
}
hr {
@@ -98,17 +96,29 @@
align-items: center;
gap: 8px;
button {
display: flex;
align-items: center;
justify-content: center;
.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;
}
}
}
.hidden {
display: none;
}
.views-save-button {
button {
display: flex;
align-items: center;
justify-content: center;

View File

@@ -1,9 +1,11 @@
import {
CSSProperties,
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useHistory } from 'react-router-dom';
@@ -22,9 +24,10 @@ import {
ColorPicker,
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';
@@ -55,6 +58,7 @@ 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';
@@ -104,6 +108,8 @@ 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;
@@ -487,16 +493,10 @@ function ExplorerOptions({
);
};
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 ?? '',
};
const handleSelect = (
value: string,
option: { key: string; value: string },
): void => {
onMenuItemSelectHandler({
key: option.key,
});
@@ -530,6 +530,10 @@ function ExplorerOptions({
options,
handleOptionsChange,
);
if (ref.current) {
ref.current.blur();
}
};
const removeCurrentViewFromLocalStorage = (): void => {
@@ -635,42 +639,24 @@ function ExplorerOptions({
}
};
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],
// 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 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] =
@@ -749,32 +735,37 @@ function ExplorerOptions({
const CreateAlertButton = useMemo(() => {
if (isOneChartPerQuery) {
return (
<Dropdown
const selectLabel = (
<Button
disabled={disabled}
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);
}
},
shape="round"
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
);
return (
<Select
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);
}
}}
>
<Button
disabled={disabled}
shape="round"
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
</Dropdown>
{splitedQueries.map((splittedQuery) => (
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
{getQueryName(splittedQuery)}
</Select.Option>
))}
</Select>
);
}
return (
@@ -797,36 +788,43 @@ function ExplorerOptions({
const AddToDashboardButton = useMemo(() => {
if (isOneChartPerQuery) {
return (
<Dropdown
const selectLabel = (
<Button
type="primary"
disabled={disabled}
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;
});
}
},
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
);
return (
<Select
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;
});
}
}}
>
<Button
type="primary"
disabled={disabled}
shape="round"
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
</Dropdown>
{/* eslint-disable-next-line sonarjs/no-identical-functions */}
{splitedQueries.map((splittedQuery) => (
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
{getQueryName(splittedQuery)}
</Select.Option>
))}
</Select>
);
}
return (
@@ -876,7 +874,9 @@ function ExplorerOptions({
<>
<Divider
type="vertical"
className={isEditDeleteSupported ? '' : 'hidden'}
className={cx('explorer-options-container__divider', {
hidden: !isEditDeleteSupported,
})}
/>
<Tooltip title="Update this view" placement="top">
<Button
@@ -900,24 +900,48 @@ function ExplorerOptions({
}}
>
<div className="view-options">
<ComboboxSimple
<Select<string, { key: string; value: string }>
showSearch
placeholder="Select a view"
loading={viewsIsLoading || isRefetching}
value={viewSelectValue}
onChange={handleSelect}
value={viewName || undefined}
onSelect={handleSelect}
style={{
minWidth: 170,
}}
dropdownStyle={dropdownStyle}
className="views-dropdown"
items={viewSelectItems}
/>
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>
<Button
shape="round"
onClick={handleSaveViewModalToggle}
className={
isEditDeleteSupported ? 'views-save-button' : 'views-save-button hidden'
}
className={isEditDeleteSupported ? '' : 'hidden'}
disabled={viewsIsLoading || isRefetching}
icon={<Disc3 size={16} />}
>

View File

@@ -1,11 +1,9 @@
import { memo, useMemo } from 'react';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { IOption } from 'hooks/useResourceAttribute/types';
import { Select, Spin } from 'antd';
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';
@@ -17,6 +15,7 @@ function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
generateOptions,
createOptions,
handleChange,
handleSearchKeys,
} = useOrderByFilter({ query, onChange });
const { data, isFetching } = useGetAggregateKeys(
@@ -55,42 +54,18 @@ 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 (
<ComboboxSimple
multiple
allowCreate
loading={isFetching}
<Select
mode="tags"
style={selectStyle}
items={items}
value={value}
onChange={handleComboboxChange}
onSearch={handleSearchKeys}
showSearch
showArrow={false}
value={selectedValue}
labelInValue
options={options}
notFoundContent={isFetching ? <Spin size="small" /> : null}
onChange={handleChange}
/>
);
}

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,7 @@ import { useCallback, useEffect, useMemo } from 'react';
import { ArrowLeft, ArrowRight } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import { SelectSimple } from '@signozhq/ui/select';
import { Form } from 'antd';
import { Form, Select } from 'antd';
import { ErrorResponseHandlerForGeneratedAPIs } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { useForgotPassword } from 'api/generated/services/users';
import AuthError from 'components/AuthError/AuthError';
@@ -169,18 +168,15 @@ function ForgotPassword({
name="orgId"
rules={[{ required: true, message: 'Please select your organization' }]}
>
<SelectSimple
<Select
id="orgId"
testId="orgId"
data-testid="orgId"
className="login-form-input login-form-select-no-border"
placeholder="Select your organization"
items={orgs.map((org) => ({
options={orgs.map((org) => ({
value: org.id,
label: org.name || 'default',
}))}
onChange={(value): void => {
form.setFieldValue('orgId', value);
}}
/>
</Form.Item>
</div>

View File

@@ -1,8 +1,7 @@
import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { Form, FormInstance, Input, Select } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Form, FormInstance, Input } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import type { Store } from 'antd/lib/form/interface';
import ROUTES from 'constants/routes';
@@ -96,20 +95,40 @@ function FormAlertChannels({
</Form.Item>
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
<SelectSimple
<Select
disabled={editing}
onChange={(value): void => onTypeChangeHandler(value as ChannelType)}
onChange={onTypeChangeHandler}
value={type}
testId="channel-type-select"
items={[
{ value: 'slack', label: 'Slack' },
{ value: 'webhook', label: 'Webhook' },
{ value: 'pagerduty', label: 'Pagerduty' },
{ value: 'opsgenie', label: 'Opsgenie' },
{ value: 'email', label: 'Email' },
{ value: 'msteams', label: 'Microsoft Teams' },
]}
/>
data-testid="channel-type-select"
>
<Select.Option value="slack" key="slack" data-testid="select-option">
Slack
</Select.Option>
<Select.Option value="webhook" key="webhook" data-testid="select-option">
Webhook
</Select.Option>
<Select.Option
value="pagerduty"
key="pagerduty"
data-testid="select-option"
>
Pagerduty
</Select.Option>
<Select.Option
value="opsgenie"
key="opsgenie"
data-testid="select-option"
>
Opsgenie
</Select.Option>
<Select.Option value="email" key="email" data-testid="select-option">
Email
</Select.Option>
<Select.Option value="msteams" key="msteams" data-testid="select-option">
Microsoft Teams
</Select.Option>
</Select>
</Form.Item>
<Form.Item>{renderSettings()}</Form.Item>

View File

@@ -2,9 +2,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { Plus } from '@signozhq/icons';
import { Button, Flex, Form, Select, Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Button, Flex, Form, Tooltip } from 'antd';
import { SelectSimple } from '@signozhq/ui/select';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
@@ -18,6 +17,7 @@ import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
import { openInNewTab } from 'utils/navigation';
import { popupContainer } from 'utils/selectPopupContainer';
import ChannelSelect from './ChannelSelect';
import LabelSelect from './labels';
@@ -25,12 +25,15 @@ import {
FormContainer,
FormItemMedium,
InputSmall,
SeveritySelect,
StepHeading,
TextareaMedium,
} from './styles';
import './FormAlertRules.styles.scss';
const { Option } = Select;
interface BasicInfoProps {
isNewRule: boolean;
alertDef: AlertDef;
@@ -113,9 +116,10 @@ function BasicInfo({
labelAlign="left"
name={['labels', 'severity']}
>
<SelectSimple
<SeveritySelect
getPopupContainer={popupContainer}
defaultValue="critical"
onChange={(value): void => {
onChange={(value: unknown | string): void => {
const s = (value as string) || 'critical';
setAlertDef({
...alertDef,
@@ -125,14 +129,12 @@ function BasicInfo({
},
});
}}
items={[
{ value: 'critical', label: t('option_critical') },
{ value: 'error', label: t('option_error') },
{ value: 'warning', label: t('option_warning') },
{ value: 'info', label: t('option_info') },
]}
style={{ width: '25%' }}
/>
>
<Option value="critical">{t('option_critical')}</Option>
<Option value="error">{t('option_error')}</Option>
<Option value="warning">{t('option_warning')}</Option>
<Option value="info">{t('option_info')}</Option>
</SeveritySelect>
</Form.Item>
<Form.Item

View File

@@ -1,16 +1,14 @@
import { useMemo } from 'react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { Plus } from '@signozhq/icons';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Select, Spin } from 'antd';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import { useAppContext } from 'providers/App/App';
import { Channels } from 'types/api/channels/getAll';
import APIError from 'types/api/error';
import { StyledCreateChannelOption } from './styles';
const ADD_NEW_CHANNEL_VALUE = 'add-new-channel';
import { StyledCreateChannelOption, StyledSelect } from './styles';
export interface ChannelSelectProps {
disabled?: boolean;
@@ -40,13 +38,12 @@ function ChannelSelect({
const { notifications } = useNotifications();
const handleChange = (value: string | string[]): void => {
const arr = Array.isArray(value) ? value : [value];
if (arr.includes(ADD_NEW_CHANNEL_VALUE)) {
const handleChange = (value: string[]): void => {
if (value.includes('add-new-channel')) {
handleCreateNewChannels();
return;
}
onSelectChannels(arr);
onSelectChannels(value);
};
if (hasError) {
@@ -62,55 +59,57 @@ function ChannelSelect({
user.role,
);
const items = useMemo<ComboboxSimpleItem[]>(() => {
const result: ComboboxSimpleItem[] = [];
const renderOptions = (): ReactNode[] => {
const children: ReactNode[] = [];
if (!isLoading && addNewChannelPermission) {
result.push({
value: ADD_NEW_CHANNEL_VALUE,
label: (
children.push(
<Select.Option key="add-new-channel" value="add-new-channel">
<StyledCreateChannelOption>
<Plus size="md" />
Create a new channel
</StyledCreateChannelOption>
),
displayValue: 'Create a new channel',
});
</Select.Option>,
);
}
if (!isLoading && channels.length > 0) {
channels.forEach((o) => {
result.push({
value: o.name,
label: o.name,
});
});
if (isLoading || channels.length === 0) {
return children;
}
return result;
}, [isLoading, addNewChannelPermission, channels]);
channels.forEach((o) => {
children.push(
<Select.Option key={o.id} value={o.name}>
{o.name}
</Select.Option>,
);
});
return children;
};
return (
<ComboboxSimple
multiple
style={{
width: '100%',
borderRadius: 4,
}}
<StyledSelect
disabled={disabled}
className={hasError ? 'channel-select-error' : undefined}
status={hasError ? 'error' : ''}
mode="multiple"
style={{ width: '100%' }}
placeholder={t('placeholder_channel_select')}
testId="alert-channel-select"
data-testid="alert-channel-select"
value={currentValue}
loading={isLoading}
items={items}
onChange={(value): void => {
// ComboboxSimple has no onOpen callback, so we fire onDropdownOpen
// (the refetch trigger) on the first interaction via onChange.
onDropdownOpen();
handleChange(value);
notFoundContent={isLoading && <Spin size="small" />}
onDropdownVisibleChange={(open): void => {
if (open) {
onDropdownOpen();
}
}}
/>
onChange={(value): void => {
handleChange(value as string[]);
}}
optionLabelProp="label"
>
{renderOptions()}
</StyledSelect>
);
}

View File

@@ -1,5 +1,10 @@
import { Select } from 'antd';
import styled from 'styled-components';
export const StyledSelect = styled(Select)`
border-radius: 4px;
`;
export const StyledCreateChannelOption = styled.div`
color: var(--accent-primary);
display: flex;

View File

@@ -5,7 +5,6 @@
border: none !important;
margin-left: 0px !important;
padding: 0px !important;
border-radius: 0 !important;
.nav-btns {
display: flex;
@@ -20,13 +19,11 @@
}
.ant-btn-default {
border-color: transparent;
border-radius: 0;
}
}
.ant-tabs-tab-active {
.nav-btns {
background: var(--l3-border) !important;
border-radius: 0;
background: var(--l1-border) !important;
}
}
@@ -38,10 +35,10 @@
border-bottom: none !important;
}
.ant-tabs-nav-list {
border: 1px solid var(--l3-border);
border: 1px solid var(--l1-border);
}
.ant-tabs-tab + .ant-tabs-tab {
border-left: 1px solid var(--l3-border) !important;
border-left: 1px solid var(--l1-border) !important;
}
.stage-run-query {
@@ -55,26 +52,8 @@
}
.alert-query-section-container {
border-color: var(--l1-border);
--query-builder-v2-color: var(--l2-foreground);
--query-builder-v2-background-color: var(--l3-background);
--query-builder-v2-border-color: var(--l3-border);
--query-builder-v2-selection-background-color: var(--l2-background);
--query-builder-v2-chip-decorator-background-color: var(--l2-background);
--query-builder-v2-placeholder-color: var(--l3-foreground);
--query-builder-v2-selected-background-color: var(--l3-foreground);
--query-search-background-color: var(--l3-background);
--query-search-background-color-selection: var(--l3-background);
--input-with-label-border-color: var(--l3-border);
--input-with-label-background-color: var(--l3-background);
--input-with-label-color: var(--l2-foreground);
--combobox-pill-background: var(--l2-background);
.ant-card-body {
padding: 0px;
background-color: var(--l2-background);
.alert-tabs {
padding: 0px;

View File

@@ -8,11 +8,3 @@
.rule-unit-selector {
width: 150px;
}
.inline-select {
display: inline-block;
width: 10%;
min-width: 120px;
margin-left: 0.3em;
margin-right: 0.3em;
}

View File

@@ -5,11 +5,12 @@ import {
Form,
InputNumber,
InputNumberProps,
Select,
SelectProps,
Space,
} from 'antd';
import { ComboboxSimple, ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { SelectSimple, SelectSimpleItem } from '@signozhq/ui/select';
import { Typography } from '@signozhq/ui/typography';
import type { DefaultOptionType } from 'antd/es/select';
import {
getCategoryByOptionId,
getCategorySelectOptionByName,
@@ -24,9 +25,15 @@ import {
defaultSeasonality,
} from 'types/api/alerts/def';
import { EQueryType } from 'types/common/dashboard';
import { popupContainer } from 'utils/selectPopupContainer';
import { AlertDetectionTypes } from '.';
import { FormContainer, StepHeading, VerticalLine } from './styles';
import {
FormContainer,
InlineSelect,
StepHeading,
VerticalLine,
} from './styles';
import './RuleOptions.styles.scss';
@@ -42,7 +49,7 @@ function RuleOptions({
const { ruleType } = alertDef;
const handleMatchOptChange = (value: string | string[]): void => {
const handleMatchOptChange = (value: string | unknown): void => {
const m = (value as string) || alertDef.condition?.matchType;
setAlertDef({
...alertDef,
@@ -67,26 +74,13 @@ function RuleOptions({
});
};
const compareOpItems: SelectSimpleItem[] = [
{ value: '1', label: t('option_above') },
{ value: '2', label: t('option_below') },
...(ruleType !== 'anomaly_rule'
? [
{ value: '3', label: t('option_equal') },
{ value: '4', label: t('option_notequal') },
]
: []),
...(ruleType === 'anomaly_rule'
? [{ value: '7', label: t('option_above_below') }]
: []),
];
const renderCompareOps = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultCompareOp}
value={alertDef.condition?.op}
items={compareOpItems}
onChange={(value): void => {
style={{ minWidth: '120px' }}
onChange={(value: string | unknown): void => {
const newOp = (value as string) || '';
setAlertDef({
@@ -97,31 +91,47 @@ function RuleOptions({
},
});
}}
/>
);
>
<Select.Option value="1">{t('option_above')}</Select.Option>
<Select.Option value="2">{t('option_below')}</Select.Option>
const matchOptItems: SelectSimpleItem[] = [
{ value: '1', label: t('option_atleastonce') },
{ value: '2', label: t('option_allthetimes') },
...(ruleType !== 'anomaly_rule'
? [
{ value: '3', label: t('option_onaverage') },
{ value: '4', label: t('option_intotal') },
{ value: '5', label: t('option_last') },
]
: []),
];
{/* hide equal and not eqaul in case of analmoy based alert */}
{ruleType !== 'anomaly_rule' && (
<>
<Select.Option value="3">{t('option_equal')}</Select.Option>
<Select.Option value="4">{t('option_notequal')}</Select.Option>
</>
)}
{/* the value 5 and 6 are reserved for above or equal and below or equal */}
{ruleType === 'anomaly_rule' && (
<Select.Option value="7">{t('option_above_below')}</Select.Option>
)}
</InlineSelect>
);
const renderMatchOpts = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultMatchType}
style={{ minWidth: '130px' }}
value={alertDef.condition?.matchType}
items={matchOptItems}
onChange={handleMatchOptChange}
/>
onChange={(value: string | unknown): void => handleMatchOptChange(value)}
>
<Select.Option value="1">{t('option_atleastonce')}</Select.Option>
<Select.Option value="2">{t('option_allthetimes')}</Select.Option>
{ruleType !== 'anomaly_rule' && (
<>
<Select.Option value="3">{t('option_onaverage')}</Select.Option>
<Select.Option value="4">{t('option_intotal')}</Select.Option>
<Select.Option value="5">{t('option_last')}</Select.Option>
</>
)}
</InlineSelect>
);
const onChangeEvalWindow = (value: string | string[]): void => {
const onChangeEvalWindow = (value: string | unknown): void => {
const ew = (value as string) || alertDef.evalWindow;
setAlertDef({
...alertDef,
@@ -129,7 +139,7 @@ function RuleOptions({
});
};
const onChangeAlgorithm = (value: string | string[]): void => {
const onChangeAlgorithm = (value: string | unknown): void => {
const alg = (value as string) || alertDef.condition.algorithm;
setAlertDef({
...alertDef,
@@ -140,7 +150,7 @@ function RuleOptions({
});
};
const onChangeSeasonality = (value: string | string[]): void => {
const onChangeSeasonality = (value: string | unknown): void => {
const seasonality = (value as string) || alertDef.condition.seasonality;
setAlertDef({
...alertDef,
@@ -160,91 +170,97 @@ function RuleOptions({
});
};
const evalWindowItems: SelectSimpleItem[] = [
{ value: '5m0s', label: t('option_5min') },
{ value: '10m0s', label: t('option_10min') },
{ value: '15m0s', label: t('option_15min') },
{ value: '1h0m0s', label: t('option_60min') },
{ value: '4h0m0s', label: t('option_4hours') },
{ value: '24h0m0s', label: t('option_24hours') },
];
const renderEvalWindows = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultEvalWindow}
style={{ minWidth: '120px' }}
value={alertDef.evalWindow}
items={evalWindowItems}
onChange={onChangeEvalWindow}
/>
>
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
<Select.Option value="1h0m0s">{t('option_60min')}</Select.Option>
<Select.Option value="4h0m0s">{t('option_4hours')}</Select.Option>
<Select.Option value="24h0m0s">{t('option_24hours')}</Select.Option>
</InlineSelect>
);
const promEvalWindowItems: SelectSimpleItem[] = [
{ value: '5m0s', label: t('option_5min') },
{ value: '10m0s', label: t('option_10min') },
{ value: '15m0s', label: t('option_15min') },
];
const renderPromEvalWindows = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultEvalWindow}
style={{ minWidth: '120px' }}
value={alertDef.evalWindow}
items={promEvalWindowItems}
onChange={onChangeEvalWindow}
/>
>
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
</InlineSelect>
);
const renderAlgorithms = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultAlgorithm}
style={{ minWidth: '120px' }}
value={alertDef.condition.algorithm}
items={[{ value: 'standard', label: 'Standard' }]}
onChange={onChangeAlgorithm}
/>
>
<Select.Option value="standard">Standard</Select.Option>
</InlineSelect>
);
const deviationItems: SelectSimpleItem[] = [1, 2, 3, 4, 5, 6, 7].map((n) => ({
value: String(n),
label: String(n),
}));
const renderDeviationOpts = (): JSX.Element => (
<SelectSimple
defaultValue="3"
value={alertDef.condition.target ? String(alertDef.condition.target) : '3'}
items={deviationItems}
onChange={(value): void => {
const n = Number(value);
if (!Number.isNaN(n)) {
onChangeDeviation(n);
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={3}
style={{ minWidth: '120px' }}
value={alertDef.condition.target}
onChange={(value: number | unknown): void => {
if (typeof value === 'number') {
onChangeDeviation(value);
}
}}
/>
>
<Select.Option value={1}>1</Select.Option>
<Select.Option value={2}>2</Select.Option>
<Select.Option value={3}>3</Select.Option>
<Select.Option value={4}>4</Select.Option>
<Select.Option value={5}>5</Select.Option>
<Select.Option value={6}>6</Select.Option>
<Select.Option value={7}>7</Select.Option>
</InlineSelect>
);
const seasonalityItems: SelectSimpleItem[] = [
{ value: 'hourly', label: 'Hourly' },
{ value: 'daily', label: 'Daily' },
{ value: 'weekly', label: 'Weekly' },
];
const renderSeasonality = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultSeasonality}
style={{ minWidth: '120px' }}
value={alertDef.condition.seasonality}
items={seasonalityItems}
onChange={onChangeSeasonality}
/>
>
<Select.Option value="hourly">Hourly</Select.Option>
<Select.Option value="daily">Daily</Select.Option>
<Select.Option value="weekly">Weekly</Select.Option>
</InlineSelect>
);
const renderThresholdRuleOpts = (): JSX.Element => (
<Form.Item>
<Typography.Text>
{t('text_condition1')}
<ComboboxSimple
items={queryOptions}
<InlineSelect
getPopupContainer={popupContainer}
allowClear
showSearch
options={queryOptions}
placeholder={t('selected_query_placeholder')}
value={alertDef.condition.selectedQueryName || ''}
onChange={(value): void => onChangeSelectedQueryName(value as string)}
value={alertDef.condition.selectedQueryName}
onChange={onChangeSelectedQueryName}
/>
<Typography.Text>is</Typography.Text>
{renderCompareOps()} {t('text_condition2')} {renderMatchOpts()}{' '}
@@ -257,11 +273,14 @@ function RuleOptions({
<Form.Item>
<Typography.Text>
{t('text_condition1')}
<ComboboxSimple
items={queryOptions}
<InlineSelect
getPopupContainer={popupContainer}
allowClear
showSearch
options={queryOptions}
placeholder={t('selected_query_placeholder')}
value={alertDef.condition.selectedQueryName || ''}
onChange={(value): void => onChangeSelectedQueryName(value as string)}
value={alertDef.condition.selectedQueryName}
onChange={onChangeSelectedQueryName}
/>
<Typography.Text>is</Typography.Text>
{renderCompareOps()} {t('text_condition2')} {renderMatchOpts()}
@@ -282,7 +301,7 @@ function RuleOptions({
});
};
const onChangeAlertUnit = (value: string | string[]): void => {
const onChangeAlertUnit: SelectProps['onChange'] = (value) => {
setAlertDef({
...alertDef,
condition: {
@@ -292,7 +311,7 @@ function RuleOptions({
});
};
const onChangeFrequency = (value: string | string[]): void => {
const onChangeFrequency = (value: string | unknown): void => {
const freq = (value as string) || alertDef.frequency;
setAlertDef({
...alertDef,
@@ -304,11 +323,14 @@ function RuleOptions({
<Form.Item>
<Typography.Text className="rule-definition">
{t('text_condition1_anomaly')}
<ComboboxSimple
items={queryOptions}
<InlineSelect
getPopupContainer={popupContainer}
allowClear
showSearch
options={queryOptions}
placeholder={t('selected_query_placeholder')}
value={alertDef.condition.selectedQueryName || ''}
onChange={(value): void => onChangeSelectedQueryName(value as string)}
value={alertDef.condition.selectedQueryName}
onChange={onChangeSelectedQueryName}
/>
{t('text_condition3')} {renderEvalWindows()}
<Typography.Text>is</Typography.Text>
@@ -323,26 +345,25 @@ function RuleOptions({
</Form.Item>
);
const frequencyItems: SelectSimpleItem[] = [
{ value: '1m0s', label: t('option_1min') },
{ value: '5m0s', label: t('option_5min') },
{ value: '10m0s', label: t('option_10min') },
{ value: '15m0s', label: t('option_15min') },
{ value: '30m0s', label: t('option_30min') },
{ value: '1h0m0s', label: t('option_60min') },
{ value: '3h0m0s', label: t('option_3hours') },
{ value: '6h0m0s', label: t('option_6hours') },
{ value: '12h0m0s', label: t('option_12hours') },
{ value: '24h0m0s', label: t('option_24hours') },
];
const renderFrequency = (): JSX.Element => (
<SelectSimple
<InlineSelect
getPopupContainer={popupContainer}
defaultValue={defaultFrequency}
style={{ minWidth: '120px' }}
value={alertDef.frequency}
items={frequencyItems}
onChange={onChangeFrequency}
/>
>
<Select.Option value="1m0s">{t('option_1min')}</Select.Option>
<Select.Option value="5m0s">{t('option_5min')}</Select.Option>
<Select.Option value="10m0s">{t('option_10min')}</Select.Option>
<Select.Option value="15m0s">{t('option_15min')}</Select.Option>
<Select.Option value="30m0s">{t('option_30min')}</Select.Option>
<Select.Option value="1h0m0s">{t('option_60min')}</Select.Option>
<Select.Option value="3h0m0s">{t('option_3hours')}</Select.Option>
<Select.Option value="6h0m0s">{t('option_6hours')}</Select.Option>
<Select.Option value="12h0m0s">{t('option_12hours')}</Select.Option>
<Select.Option value="24h0m0s">{t('option_24hours')}</Select.Option>
</InlineSelect>
);
const selectedCategory = getCategoryByOptionId(yAxisUnit);
@@ -354,7 +375,7 @@ function RuleOptions({
return (
<>
<StepHeading>{t('alert_form_step3', { step: step3Label })}</StepHeading>
<FormContainer className="rule-options-container">
<FormContainer>
{queryCategory === EQueryType.PROM && renderPromRuleOptions()}
{queryCategory !== EQueryType.PROM &&
ruleType === AlertDetectionTypes.ANOMALY_DETECTION_ALERT && (
@@ -379,12 +400,15 @@ function RuleOptions({
</Form.Item>
<Form.Item noStyle>
<ComboboxSimple
<Select
className="rule-unit-selector"
items={categorySelectOptions as ComboboxSimpleItem[]}
getPopupContainer={popupContainer}
allowClear
showSearch
options={categorySelectOptions}
placeholder={t('field_unit')}
value={alertDef.condition.targetUnit || ''}
onChange={(value): void => onChangeAlertUnit(value as string)}
value={alertDef.condition.targetUnit}
onChange={onChangeAlertUnit}
/>
</Form.Item>
</Space>
@@ -490,7 +514,7 @@ interface RuleOptionsProps {
alertDef: AlertDef;
setAlertDef: (a: AlertDef) => void;
queryCategory: EQueryType;
queryOptions: ComboboxSimpleItem[];
queryOptions: DefaultOptionType[];
yAxisUnit: string;
}
export default RuleOptions;

View File

@@ -5,8 +5,7 @@ import { useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { BellDot, CircleAlert, ExternalLink, Save } from '@signozhq/icons';
import { Button, FormInstance } from 'antd';
import { ComboboxSimpleItem } from '@signozhq/ui/combobox';
import { Button, FormInstance, SelectProps } from 'antd';
import { ConfirmDialog } from '@signozhq/ui/dialog';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
@@ -209,7 +208,7 @@ function FormAlertRules({
const involvedQueriesInTraceOperator = getInvolvedQueriesInTraceOperator(
currentQuery.builder.queryTraceOperator,
);
const queryConfig: Record<EQueryType, () => ComboboxSimpleItem[]> = {
const queryConfig: Record<EQueryType, () => SelectProps['options']> = {
[EQueryType.QUERY_BUILDER]: () => [
...(getSelectedQueryOptions(currentQuery.builder.queryData)?.filter(
(option) =>

View File

@@ -1,24 +0,0 @@
import { QueryChipContainer, QueryChipItem } from './styles';
import { ILabelRecord } from './types';
interface QueryChipProps {
queryData: ILabelRecord;
onRemove: (id: string) => void;
}
export default function QueryChip({
queryData,
onRemove,
}: QueryChipProps): JSX.Element {
const { key, value } = queryData;
return (
<QueryChipContainer>
<QueryChipItem
closable={key !== 'severity' && key !== 'description'}
onClose={(): void => onRemove(key)}
>
{key}: {value}
</QueryChipItem>
</QueryChipContainer>
);
}

View File

@@ -1,16 +1,14 @@
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CircleAlert, CircleX } from '@signozhq/icons';
import { ConfirmDialog } from '@signozhq/ui/dialog';
import { Typography } from '@signozhq/ui/typography';
import { Button, Input, message } from 'antd';
import { Button, Input, message, Modal } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { map } from 'lodash-es';
import { Labels } from 'types/api/alerts/def';
import { v4 as uuid } from 'uuid';
import QueryChip from './QueryChip';
import { QueryChipItem, SearchContainer } from './styles';
import { Badge } from '@signozhq/ui/badge';
import { QueryChipContainer, QueryChipItem, SearchContainer } from './styles';
import { ILabelRecord } from './types';
import { createQuery, flattenLabels, prepareLabels } from './utils';
@@ -35,7 +33,6 @@ function LabelSelect({
initialValues ? flattenLabels(initialValues) : [],
);
const [step, setStep] = useState<LabelStep>('Idle');
const [showClearConfirm, setShowClearConfirm] = useState(false);
const dispatchChanges = (updatedRecs: ILabelRecord[]): void => {
onSetLabels(prepareLabels(updatedRecs, initialValues));
@@ -123,16 +120,19 @@ function LabelSelect({
};
const handleClearAll = (): void => {
setShowClearConfirm(true);
};
const handleConfirmClear = (): void => {
send('RESET');
dispatchChanges([]);
setStaging([]);
setCurrentVal('');
message.success(t('remove_label_success'));
setShowClearConfirm(false);
Modal.confirm({
title: 'Confirm',
icon: <CircleAlert size="md" />,
content: t('remove_label_confirm'),
onOk() {
send('RESET');
dispatchChanges([]);
setStaging([]);
message.success(t('remove_label_success'));
},
okText: t('button_yes'),
cancelText: t('button_no'),
});
};
const renderPlaceholder = useCallback((): string => {
if (step === 'LabelKey') {
@@ -147,12 +147,24 @@ function LabelSelect({
<SearchContainer isDarkMode={isDarkMode} disabled={false}>
<div style={{ display: 'inline-flex', flexWrap: 'wrap' }}>
{queries.length > 0 &&
map(
queries,
(query): JSX.Element => (
<QueryChip key={query.key} queryData={query} onRemove={handleClose} />
),
)}
map(queries, (query): JSX.Element => {
const isClosable =
query.key !== 'severity' && query.key !== 'description';
return (
<QueryChipContainer key={query.key}>
<Badge
color="vanilla"
closable={isClosable}
onClose={(e): void => {
e.preventDefault();
handleClose(query.key);
}}
>
{query.key}: {query.value}
</Badge>
</QueryChipContainer>
);
})}
</div>
<div>
{map(staging, (item) => (
@@ -187,23 +199,6 @@ function LabelSelect({
/>
) : null}
</div>
<ConfirmDialog
open={showClearConfirm}
onOpenChange={(next): void => {
if (!next) {
setShowClearConfirm(false);
}
}}
title={t('title_confirm')}
titleIcon={<CircleAlert size={14} />}
confirmText={t('button_yes')}
cancelText={t('button_no')}
onConfirm={handleConfirmClear}
onCancel={(): void => setShowClearConfirm(false)}
>
<Typography>{t('remove_label_confirm')}</Typography>
</ConfirmDialog>
</SearchContainer>
);
}

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