mirror of
https://github.com/SigNoz/signoz.git
synced 2026-11-07 17:00:41 +00:00
Compare commits
2 Commits
fix/genera
...
fix/alert-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23168dc322 | ||
|
|
819d66d94d |
@@ -1,18 +1,13 @@
|
||||
import { ConfigProvider, Select } from 'antd';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import { Select } from 'antd';
|
||||
import cx from 'classnames';
|
||||
|
||||
import type { UpdatedWindow } from '../dsl/types';
|
||||
import type { CreatorOption } from '../types';
|
||||
import { UPDATED_OPTIONS } from './constants';
|
||||
import MultiSelectFilter from './MultiSelectFilter';
|
||||
|
||||
import styles from './FilterZone.module.scss';
|
||||
|
||||
// The option checkbox already marks selection, so selected rows stay regular weight.
|
||||
const CREATOR_SELECT_THEME = {
|
||||
components: { Select: { optionSelectedFontWeight: 400 } },
|
||||
};
|
||||
|
||||
interface Props {
|
||||
createdBy: string[];
|
||||
updated: UpdatedWindow;
|
||||
@@ -52,36 +47,16 @@ function FilterChips({
|
||||
|
||||
return (
|
||||
<div className={styles.chips}>
|
||||
<ConfigProvider theme={CREATOR_SELECT_THEME}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
showSearch
|
||||
allowClear
|
||||
className={cx(styles.select, styles.selectWide)}
|
||||
placeholder="Created by"
|
||||
value={createdBy}
|
||||
options={creatorOptionsData}
|
||||
optionFilterProp="label"
|
||||
maxTagCount={1}
|
||||
menuItemSelectedIcon={null}
|
||||
data-testid={`${testIdPrefix}-filter-created-by`}
|
||||
disabled={disabled}
|
||||
onClear={onClearCreatedBy}
|
||||
optionRender={(option): JSX.Element => (
|
||||
<div className={styles.creatorOption}>
|
||||
<div className={styles.creatorCheck}>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
value={createdBy.includes(option.value as string)}
|
||||
/>
|
||||
</div>
|
||||
<span className={styles.creatorLabel}>{option.label}</span>
|
||||
</div>
|
||||
)}
|
||||
onChange={(value): void => onCreatedByChange(value as string[])}
|
||||
onDropdownVisibleChange={runOnClose}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
<MultiSelectFilter
|
||||
value={createdBy}
|
||||
options={creatorOptionsData}
|
||||
placeholder="Created by"
|
||||
testId={`${testIdPrefix}-filter-created-by`}
|
||||
disabled={disabled}
|
||||
onClear={onClearCreatedBy}
|
||||
onChange={onCreatedByChange}
|
||||
onDropdownVisibleChange={runOnClose}
|
||||
/>
|
||||
<Select
|
||||
showSearch
|
||||
className={cx(styles.select, styles.selectNarrow)}
|
||||
|
||||
@@ -86,21 +86,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Created-by option: a presentational checkbox + label (antd handles the toggle).
|
||||
.creatorOption {
|
||||
// Multi-select option: a presentational checkbox + label (antd handles the toggle).
|
||||
.checkOption {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.creatorCheck {
|
||||
.checkOptionBox {
|
||||
// Flex, not block: a line box would shift with the check mark's baseline.
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.creatorLabel {
|
||||
.checkOptionLabel {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { ConfigProvider, Select } from 'antd';
|
||||
import { Checkbox } from '@signozhq/ui/checkbox';
|
||||
import cx from 'classnames';
|
||||
|
||||
import styles from './FilterZone.module.scss';
|
||||
|
||||
// The option checkbox already marks selection, so selected rows stay regular weight.
|
||||
const CHECKBOX_SELECT_THEME = {
|
||||
components: { Select: { optionSelectedFontWeight: 400 } },
|
||||
};
|
||||
|
||||
export interface MultiSelectFilterOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: string[];
|
||||
options: MultiSelectFilterOption[];
|
||||
placeholder: string;
|
||||
testId: string;
|
||||
onChange: (values: string[]) => void;
|
||||
onClear?: () => void;
|
||||
onDropdownVisibleChange?: (open: boolean) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
function MultiSelectFilter({
|
||||
value,
|
||||
options,
|
||||
placeholder,
|
||||
testId,
|
||||
onChange,
|
||||
onClear,
|
||||
onDropdownVisibleChange,
|
||||
disabled = false,
|
||||
}: Props): JSX.Element {
|
||||
return (
|
||||
<ConfigProvider theme={CHECKBOX_SELECT_THEME}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
showSearch
|
||||
allowClear
|
||||
className={cx(styles.select, styles.selectWide)}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
options={options}
|
||||
optionFilterProp="label"
|
||||
maxTagCount={1}
|
||||
menuItemSelectedIcon={null}
|
||||
data-testid={testId}
|
||||
disabled={disabled}
|
||||
onClear={onClear}
|
||||
optionRender={(option): JSX.Element => (
|
||||
<div className={styles.checkOption}>
|
||||
<div className={styles.checkOptionBox}>
|
||||
<Checkbox
|
||||
color="primary"
|
||||
value={value.includes(option.value as string)}
|
||||
/>
|
||||
</div>
|
||||
<span className={styles.checkOptionLabel}>{option.label}</span>
|
||||
</div>
|
||||
)}
|
||||
onChange={(next): void => onChange(next as string[])}
|
||||
onDropdownVisibleChange={onDropdownVisibleChange}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default MultiSelectFilter;
|
||||
@@ -135,9 +135,18 @@ describe('ListAlertRules — search', () => {
|
||||
|
||||
await screen.findByText('High CPU Alert');
|
||||
|
||||
await user.click(screen.getByTestId('alert-rules-filter-states'));
|
||||
await user.click(await screen.findByRole('option', { name: 'Firing' }));
|
||||
await user.click(await screen.findByRole('option', { name: 'Disabled' }));
|
||||
await user.click(
|
||||
within(screen.getByTestId('alert-rules-filter-states')).getByRole(
|
||||
'combobox',
|
||||
),
|
||||
);
|
||||
const dropdown = await waitFor(() => {
|
||||
const element = document.querySelector<HTMLElement>('.ant-select-dropdown');
|
||||
expect(element).not.toBeNull();
|
||||
return element as HTMLElement;
|
||||
});
|
||||
await user.click(within(dropdown).getByText('Firing'));
|
||||
await user.click(within(dropdown).getByText('Disabled'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(lastRequest(requests).getAll('states')).toStrictEqual([
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
.statesSelect {
|
||||
width: 240px;
|
||||
flex: none;
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useCallback } from 'react';
|
||||
import { ComboboxSimple, type ComboboxSimpleItem } from '@signozhq/ui/combobox';
|
||||
import { RuletypesAlertStateDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import MultiSelectFilter, {
|
||||
type MultiSelectFilterOption,
|
||||
} from 'components/ListFilter/FilterZone/MultiSelectFilter';
|
||||
|
||||
import styles from './RuleStatesFilter.module.scss';
|
||||
|
||||
const STATE_ITEMS: ComboboxSimpleItem[] = [
|
||||
const STATE_OPTIONS: MultiSelectFilterOption[] = [
|
||||
{ value: RuletypesAlertStateDTO.firing, label: 'Firing' },
|
||||
{ value: RuletypesAlertStateDTO.pending, label: 'Pending' },
|
||||
{ value: RuletypesAlertStateDTO.recovering, label: 'Recovering' },
|
||||
@@ -22,26 +21,13 @@ function RuleStatesFilter({
|
||||
states,
|
||||
onChange,
|
||||
}: RuleStatesFilterProps): JSX.Element {
|
||||
const handleChange = useCallback(
|
||||
(values: unknown): void => {
|
||||
if (Array.isArray(values)) {
|
||||
onChange(values as string[]);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<ComboboxSimple
|
||||
className={styles.statesSelect}
|
||||
multiple
|
||||
<MultiSelectFilter
|
||||
value={states}
|
||||
onChange={handleChange}
|
||||
placeholder="Filter by state"
|
||||
inputPlaceholder="Select one or more"
|
||||
items={STATE_ITEMS}
|
||||
maxDisplayedPills={2}
|
||||
options={STATE_OPTIONS}
|
||||
placeholder="State"
|
||||
testId="alert-rules-filter-states"
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
openRenameViewPopover,
|
||||
openRowActions,
|
||||
openSaveViewPopover,
|
||||
openStatesFilter,
|
||||
toggleAlert,
|
||||
} from './__story_mockdata__/alertRules.play';
|
||||
|
||||
@@ -179,6 +180,11 @@ export const ColumnPicker: Story = {
|
||||
play: openColumnPicker,
|
||||
};
|
||||
|
||||
/** The State dropdown, open with one state picked. */
|
||||
export const StateFilterOpen: Story = {
|
||||
play: openStatesFilter,
|
||||
};
|
||||
|
||||
/**
|
||||
* Every label badge in the Labels column, held open: each one repeats its own
|
||||
* `key: value` with a copy button. The rules carry two labels apiece, which fit
|
||||
|
||||
@@ -111,6 +111,23 @@ export const openColumnPicker = async ({
|
||||
await screen.findByText(/toggle columns/i);
|
||||
};
|
||||
|
||||
export const openStatesFilter = async ({
|
||||
canvasElement,
|
||||
}: PlayContext): Promise<void> => {
|
||||
const filter = await within(canvasElement).findByTestId(
|
||||
'alert-rules-filter-states',
|
||||
undefined,
|
||||
untilLoaded,
|
||||
);
|
||||
|
||||
await userEvent.click(filter.querySelector('.ant-select-selector') ?? filter);
|
||||
const option = (await screen.findAllByText('Firing')).find((node) =>
|
||||
node.closest('.ant-select-dropdown'),
|
||||
);
|
||||
await userEvent.click(option ?? filter);
|
||||
await screen.findByRole('checkbox', { checked: true });
|
||||
};
|
||||
|
||||
export const toggleAlert = async ({
|
||||
canvasElement,
|
||||
args,
|
||||
|
||||
Reference in New Issue
Block a user