Compare commits

..

5 Commits

Author SHA1 Message Date
amlannandy
96c90440f3 chore: fix CI 2025-09-17 18:00:05 +07:00
amlannandy
4e93d8df57 chore: add evaluation window popover for alerts 2025-09-17 15:35:48 +07:00
primus-bot[bot]
c9568be5d8 chore(release): bump to v0.95.0 (#9112)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-09-17 12:55:46 +05:30
Srikanth Chekuri
1c257f3e14 chore: populate default zero queries for metrics (#9103) 2025-09-17 07:05:38 +00:00
Amlan Kumar Nandy
ff8ac96d37 chore: fix edit alerts page crashing (#9025) 2025-09-17 06:14:25 +00:00
16 changed files with 12 additions and 1602 deletions

View File

@@ -1,5 +1,4 @@
import './styles.scss';
import '../EvaluationSettings/styles.scss';
import { Button, Tooltip } from 'antd';
import classNames from 'classnames';
@@ -7,16 +6,13 @@ import { Activity, ChartLine } from 'lucide-react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { useCreateAlertState } from '../context';
import AdvancedOptions from '../EvaluationSettings/AdvancedOptions';
import Stepper from '../Stepper';
import { showCondensedLayout } from '../utils';
import AlertThreshold from './AlertThreshold';
import AnomalyThreshold from './AnomalyThreshold';
import { ANOMALY_TAB_TOOLTIP, THRESHOLD_TAB_TOOLTIP } from './constants';
function AlertCondition(): JSX.Element {
const { alertType, setAlertType } = useCreateAlertState();
const showCondensedLayoutFlag = showCondensedLayout();
const showMultipleTabs =
alertType === AlertTypes.ANOMALY_BASED_ALERT ||
@@ -79,11 +75,6 @@ function AlertCondition(): JSX.Element {
</div>
{alertType !== AlertTypes.ANOMALY_BASED_ALERT && <AlertThreshold />}
{alertType === AlertTypes.ANOMALY_BASED_ALERT && <AnomalyThreshold />}
{showCondensedLayoutFlag ? (
<div className="condensed-advanced-options-container">
<AdvancedOptions />
</div>
) : null}
</div>
);
}

View File

@@ -1,9 +1,7 @@
import './styles.scss';
import '../EvaluationSettings/styles.scss';
import { Button, Select, Typography } from 'antd';
import getAllChannels from 'api/channels/getAll';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Plus } from 'lucide-react';
import { useQuery } from 'react-query';
@@ -19,8 +17,6 @@ import {
THRESHOLD_MATCH_TYPE_OPTIONS,
THRESHOLD_OPERATOR_OPTIONS,
} from '../context/constants';
import EvaluationSettings from '../EvaluationSettings/EvaluationSettings';
import { showCondensedLayout } from '../utils';
import ThresholdItem from './ThresholdItem';
import { UpdateThreshold } from './types';
import {
@@ -41,7 +37,6 @@ function AlertThreshold(): JSX.Element {
>(['getChannels'], {
queryFn: () => getAllChannels(),
});
const showCondensedLayoutFlag = showCondensedLayout();
const channels = data?.data || [];
const { currentQuery } = useQueryBuilder();
@@ -86,18 +81,8 @@ function AlertThreshold(): JSX.Element {
});
};
const evaluationWindowContext = showCondensedLayoutFlag ? (
<EvaluationSettings />
) : (
<strong>Evaluation Window.</strong>
);
return (
<div
className={classNames('alert-threshold-container', {
'condensed-alert-threshold-container': showCondensedLayoutFlag,
})}
>
<div className="alert-threshold-container">
{/* Main condition sentence */}
<div className="alert-condition-sentences">
<div className="alert-condition-sentence">
@@ -143,7 +128,7 @@ function AlertThreshold(): JSX.Element {
options={THRESHOLD_MATCH_TYPE_OPTIONS}
/>
<Typography.Text className="sentence-text">
during the {evaluationWindowContext}
during the <strong>Evaluation Window.</strong>
</Typography.Text>
</div>
</div>

View File

@@ -7,6 +7,11 @@ import { Channels } from 'types/api/channels/getAll';
import ThresholdItem from '../ThresholdItem';
import { ThresholdItemProps } from '../types';
// Mock the enableRecoveryThreshold utility
jest.mock('../../utils', () => ({
enableRecoveryThreshold: jest.fn(() => true),
}));
const TEST_CONSTANTS = {
THRESHOLD_ID: 'test-threshold-1',
CRITICAL_LABEL: 'CRITICAL',

View File

@@ -84,9 +84,6 @@
color: var(--text-vanilla-400);
font-size: 14px;
line-height: 1.5;
display: flex;
align-items: center;
gap: 8px;
}
.ant-select {
@@ -278,43 +275,3 @@
}
}
}
.condensed-alert-threshold-container,
.condensed-anomaly-threshold-container {
width: 100%;
}
.condensed-advanced-options-container {
margin-top: 16px;
width: fit-parent;
}
.condensed-evaluation-settings-container {
.ant-btn {
display: flex;
align-items: center;
width: 240px;
justify-content: space-between;
background-color: var(--bg-ink-300);
border: 1px solid var(--bg-slate-400);
.evaluate-alert-conditions-button-left {
color: var(--bg-vanilla-400);
font-size: 12px;
}
.evaluate-alert-conditions-button-right {
display: flex;
align-items: center;
color: var(--bg-vanilla-400);
gap: 8px;
.evaluate-alert-conditions-button-right-text {
font-size: 12px;
font-weight: 500;
background-color: var(--bg-slate-400);
padding: 1px 4px;
}
}
}
}

View File

@@ -7,9 +7,7 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
import AlertCondition from './AlertCondition';
import { CreateAlertProvider } from './context';
import CreateAlertHeader from './CreateAlertHeader';
import EvaluationSettings from './EvaluationSettings';
import QuerySection from './QuerySection';
import { showCondensedLayout } from './utils';
function CreateAlertV2({
initialQuery = initialQueriesMap.metrics,
@@ -18,17 +16,14 @@ function CreateAlertV2({
}): JSX.Element {
useShareBuilderUrl({ defaultValue: initialQuery });
const showCondensedLayoutFlag = showCondensedLayout();
return (
<CreateAlertProvider>
<div className="create-alert-v2-container">
<div className="create-alert-v2-container">
<CreateAlertProvider>
<CreateAlertHeader />
<QuerySection />
<AlertCondition />
{!showCondensedLayoutFlag ? <EvaluationSettings /> : null}
</div>
</CreateAlertProvider>
</CreateAlertProvider>
</div>
);
}

View File

@@ -1,35 +0,0 @@
import { Switch, Typography } from 'antd';
import { useState } from 'react';
import { IAdvancedOptionItemProps } from './types';
function AdvancedOptionItem({
title,
description,
input,
}: IAdvancedOptionItemProps): JSX.Element {
const [showInput, setShowInput] = useState<boolean>(false);
const onToggle = (): void => {
setShowInput((currentShowInput) => !currentShowInput);
};
return (
<div className="advanced-option-item">
<div className="advanced-option-item-left-content">
<Typography.Text className="advanced-option-item-title">
{title}
</Typography.Text>
<Typography.Text className="advanced-option-item-description">
{description}
</Typography.Text>
{showInput && <div className="advanced-option-item-input">{input}</div>}
</div>
<div className="advanced-option-item-right-content">
<Switch onChange={onToggle} />
</div>
</div>
);
}
export default AdvancedOptionItem;

View File

@@ -1,123 +0,0 @@
import { Collapse, Input, Select } from 'antd';
import { Y_AXIS_CATEGORIES } from 'components/YAxisUnitSelector/constants';
import { useCreateAlertState } from '../context';
import AdvancedOptionItem from './AdvancedOptionItem';
import EvaluationCadence from './EvaluationCadence';
function AdvancedOptions(): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
const timeOptions = Y_AXIS_CATEGORIES.find(
(category) => category.name === 'Time',
)?.units.map((unit) => ({ label: unit.name, value: unit.id }));
return (
<div className="advanced-options-container">
<Collapse bordered={false}>
<Collapse.Panel header="ADVANCED OPTIONS" key="1">
<EvaluationCadence />
<AdvancedOptionItem
title="Send a notification if data is missing"
description="If data is missing for this alert rule for a certain time period, notify in the default notification channel."
input={
<Input.Group>
<Input
placeholder="Enter tolerance limit..."
type="number"
style={{ width: 240 }}
onChange={(e): void =>
setAdvancedOptions({
type: 'SET_SEND_NOTIFICATION_IF_DATA_IS_MISSING',
payload: {
toleranceLimit: Number(e.target.value),
timeUnit: advancedOptions.sendNotificationIfDataIsMissing.timeUnit,
},
})
}
value={advancedOptions.sendNotificationIfDataIsMissing.toleranceLimit}
/>
<Select
style={{ width: 120 }}
options={timeOptions}
placeholder="Select time unit"
onChange={(value): void =>
setAdvancedOptions({
type: 'SET_SEND_NOTIFICATION_IF_DATA_IS_MISSING',
payload: {
toleranceLimit:
advancedOptions.sendNotificationIfDataIsMissing.toleranceLimit,
timeUnit: value as string,
},
})
}
value={advancedOptions.sendNotificationIfDataIsMissing.timeUnit}
/>
</Input.Group>
}
/>
<AdvancedOptionItem
title="Enforce minimum datapoints"
description="Run alert evaluation only when there are minimum of pre-defined number of data points in each result group"
input={
<Input
placeholder="Enter minimum datapoints..."
style={{ width: 360 }}
type="number"
onChange={(e): void =>
setAdvancedOptions({
type: 'SET_ENFORCE_MINIMUM_DATAPOINTS',
payload: {
minimumDatapoints: Number(e.target.value),
},
})
}
value={advancedOptions.enforceMinimumDatapoints.minimumDatapoints}
/>
}
/>
<AdvancedOptionItem
title="Delay evaluation"
description="Delay the evaluation of newer groups to prevent noisy alerts."
input={
<Input.Group>
<Input
placeholder="Enter delay..."
style={{ width: 240 }}
type="number"
onChange={(e): void =>
setAdvancedOptions({
type: 'SET_DELAY_EVALUATION',
payload: {
delay: Number(e.target.value),
timeUnit: advancedOptions.delayEvaluation.timeUnit,
},
})
}
value={advancedOptions.delayEvaluation.delay}
/>
<Select
style={{ width: 120 }}
options={timeOptions}
placeholder="Select time unit"
onChange={(value): void =>
setAdvancedOptions({
type: 'SET_DELAY_EVALUATION',
payload: {
delay: advancedOptions.delayEvaluation.delay,
timeUnit: value as string,
},
})
}
value={advancedOptions.delayEvaluation.timeUnit}
/>
</Input.Group>
}
/>
</Collapse.Panel>
</Collapse>
</div>
);
}
export default AdvancedOptions;

View File

@@ -1,543 +0,0 @@
import { Button, DatePicker, Input, Select, Typography } from 'antd';
import TextArea from 'antd/lib/input/TextArea';
import classNames from 'classnames';
import {
Calendar,
Calendar1,
Code,
Edit,
Edit3Icon,
Info,
Plus,
X,
} from 'lucide-react';
import { useMemo, useState } from 'react';
import { useCreateAlertState } from '../context';
import {
ADVANCED_OPTIONS_TIME_UNIT_OPTIONS,
INITIAL_ADVANCED_OPTIONS_STATE,
} from '../context/constants';
import { AdvancedOptionsState } from '../context/types';
import {
EVALUATION_CADENCE_REPEAT_EVERY_MONTH_OPTIONS,
EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS,
EVALUATION_CADENCE_REPEAT_EVERY_WEEK_OPTIONS,
} from './constants';
import TimeInput from './TimeInput';
import { IEvaluationCadenceDetailsProps } from './types';
import {
buildAlertScheduleFromCustomSchedule,
buildAlertScheduleFromRRule,
isValidRRule,
TIMEZONE_DATA,
} from './utils';
export function EvaluationCadenceDetails({
setIsOpen,
}: IEvaluationCadenceDetailsProps): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
const [evaluationCadence, setEvaluationCadence] = useState<
AdvancedOptionsState['evaluationCadence']
>({
...advancedOptions.evaluationCadence,
});
const tabs = [
{
label: 'Editor',
icon: <Edit3Icon size={14} />,
value: 'editor',
},
{
label: 'RRule',
icon: <Code size={14} />,
value: 'rrule',
},
];
const [activeTab, setActiveTab] = useState<'editor' | 'rrule'>(() =>
evaluationCadence.mode === 'custom' ? 'editor' : 'rrule',
);
const occurenceOptions =
evaluationCadence.custom.repeatEvery === 'week'
? EVALUATION_CADENCE_REPEAT_EVERY_WEEK_OPTIONS
: EVALUATION_CADENCE_REPEAT_EVERY_MONTH_OPTIONS;
const EditorView = (
<div className="editor-view" data-testid="editor-view">
<div className="select-group">
<Typography.Text>REPEAT EVERY</Typography.Text>
<Select
options={EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS}
value={evaluationCadence.custom.repeatEvery || null}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
repeatEvery: value,
occurence: [],
},
})
}
placeholder="Select repeat every"
/>
</div>
<div className="select-group">
<Typography.Text>ON DAY(S)</Typography.Text>
<Select
options={occurenceOptions}
value={evaluationCadence.custom.occurence || null}
mode="multiple"
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
occurence: value,
},
})
}
placeholder="Select day(s)"
/>
</div>
<div className="select-group">
<Typography.Text>AT</Typography.Text>
<TimeInput
value={evaluationCadence.custom.startAt}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
startAt: value,
},
})
}
/>
</div>
<div className="select-group">
<Typography.Text>TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
value={evaluationCadence.custom.timezone || null}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
custom: {
...evaluationCadence.custom,
timezone: value,
},
})
}
placeholder="Select timezone"
/>
</div>
</div>
);
const RRuleView = (
<div className="rrule-view" data-testid="rrule-view">
<div className="select-group">
<Typography.Text>STARTING ON</Typography.Text>
<DatePicker
value={evaluationCadence.rrule.date}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
rrule: {
...evaluationCadence.rrule,
date: value,
},
})
}
placeholder="Select date"
/>
</div>
<div className="select-group">
<Typography.Text>AT</Typography.Text>
<TimeInput
value={evaluationCadence.rrule.startAt}
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
rrule: {
...evaluationCadence.rrule,
startAt: value,
},
})
}
/>
</div>
<TextArea
value={evaluationCadence.rrule.rrule}
placeholder="Enter RRule"
onChange={(value): void =>
setEvaluationCadence({
...evaluationCadence,
rrule: {
...evaluationCadence.rrule,
rrule: value.target.value,
},
})
}
/>
</div>
);
const handleDiscard = (): void => {
setIsOpen(false);
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE_MODE',
payload: 'default',
});
};
const handleSaveCustomSchedule = (): void => {
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE',
payload: {
...advancedOptions.evaluationCadence,
custom: evaluationCadence.custom,
rrule: evaluationCadence.rrule,
},
});
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE_MODE',
payload: evaluationCadence.mode,
});
setIsOpen(false);
};
const disableSaveButton = useMemo(() => {
if (activeTab === 'editor') {
return (
!evaluationCadence.custom.repeatEvery ||
!evaluationCadence.custom.occurence.length ||
!evaluationCadence.custom.startAt ||
!evaluationCadence.custom.timezone
);
}
return (
!evaluationCadence.rrule.rrule ||
!evaluationCadence.rrule.date ||
!evaluationCadence.rrule.startAt ||
!isValidRRule(evaluationCadence.rrule.rrule)
);
}, [evaluationCadence, activeTab]);
const schedule = useMemo(() => {
if (activeTab === 'rrule') {
return buildAlertScheduleFromRRule(
evaluationCadence.rrule.rrule,
evaluationCadence.rrule.date,
evaluationCadence.rrule.startAt,
15,
);
}
return buildAlertScheduleFromCustomSchedule(
evaluationCadence.custom.repeatEvery,
evaluationCadence.custom.occurence,
evaluationCadence.custom.startAt,
evaluationCadence.custom.timezone,
15,
);
}, [evaluationCadence, activeTab]);
const handleChangeTab = (tab: 'editor' | 'rrule'): void => {
setActiveTab(tab);
const mode = tab === 'editor' ? 'custom' : 'rrule';
setEvaluationCadence({
...evaluationCadence,
mode,
});
};
return (
<div className="evaluation-cadence-details">
<Typography.Text className="evaluation-cadence-details-title">
Add Custom Schedule
</Typography.Text>
<div className="evaluation-cadence-details-content">
<div className="evaluation-cadence-details-content-row">
<div className="query-section-tabs">
<div className="query-section-query-actions">
{tabs.map((tab) => (
<Button
key={tab.value}
className={classNames('list-view-tab', 'explorer-view-option', {
'active-tab': activeTab === tab.value,
})}
onClick={(): void => {
handleChangeTab(tab.value as 'editor' | 'rrule');
}}
>
{tab.icon}
{tab.label}
</Button>
))}
</div>
</div>
{activeTab === 'editor' && EditorView}
{activeTab === 'rrule' && RRuleView}
<div className="buttons-row">
<Button type="default" onClick={handleDiscard}>
Discard
</Button>
<Button
type="primary"
onClick={handleSaveCustomSchedule}
disabled={disableSaveButton}
>
Save Custom Schedule
</Button>
</div>
</div>
<div className="evaluation-cadence-details-content-row">
{schedule ? (
<div className="schedule-preview">
<div className="schedule-preview-header">
<Calendar size={16} />
<Typography.Text className="schedule-preview-title">
Schedule Preview
</Typography.Text>
</div>
<div className="schedule-preview-list">
{schedule.map((date) => (
<div key={date.toISOString()} className="schedule-preview-item">
<div className="schedule-preview-timeline">
<div className="schedule-preview-timeline-line" />
</div>
<div className="schedule-preview-content">
<div className="schedule-preview-date">
{date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
})}
,{' '}
{date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})}
</div>
<div className="schedule-preview-separator" />
<div className="schedule-preview-timezone">
UTC {date.getTimezoneOffset() <= 0 ? '+' : '-'}{' '}
{Math.abs(Math.floor(date.getTimezoneOffset() / 60))}:
{String(Math.abs(date.getTimezoneOffset() % 60)).padStart(2, '0')}
</div>
</div>
</div>
))}
</div>
</div>
) : (
<div className="no-schedule">
<Info size={32} />
<Typography.Text>
Please fill the relevant information to generate a schedule
</Typography.Text>
</div>
)}
</div>
</div>
</div>
);
}
function EditCustomSchedule({
setIsEvaluationCadenceDetailsVisible,
}: {
setIsEvaluationCadenceDetailsVisible: (isOpen: boolean) => void;
}): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
const displayText = useMemo(() => {
if (advancedOptions.evaluationCadence.mode === 'custom') {
return (
<Typography.Text>
<Typography.Text>Every</Typography.Text>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.repeatEvery
.charAt(0)
.toUpperCase() +
advancedOptions.evaluationCadence.custom.repeatEvery.slice(1)}
</Typography.Text>
<Typography.Text>on</Typography.Text>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.occurence
.map(
(occurence) => occurence.charAt(0).toUpperCase() + occurence.slice(1),
)
.join(', ')}
</Typography.Text>
<Typography.Text>at</Typography.Text>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.startAt}
</Typography.Text>
</Typography.Text>
);
}
return (
<Typography.Text>
<Typography.Text>Starting on</Typography.Text>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.rrule.date?.format('DD/MM/YYYY')}
</Typography.Text>
<Typography.Text>at</Typography.Text>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.rrule.startAt}
</Typography.Text>
</Typography.Text>
);
}, [advancedOptions.evaluationCadence]);
const handlePreviewAndEdit = (): void => {
setIsEvaluationCadenceDetailsVisible(true);
};
const handleDiscard = (): void => {
setIsEvaluationCadenceDetailsVisible(false);
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE',
payload: INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
});
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE_MODE',
payload: 'default',
});
};
return (
<div className="edit-custom-schedule">
{displayText}
<div className="button-row">
<Button.Group>
<Button type="default" onClick={handlePreviewAndEdit}>
<Edit size={12} />
<Typography.Text>Edit custom schedule</Typography.Text>
</Button>
<Button type="default" onClick={handlePreviewAndEdit}>
<Calendar1 size={12} />
<Typography.Text>Preview</Typography.Text>
</Button>
<Button
data-testid="discard-button"
type="default"
onClick={handleDiscard}
>
<X size={12} />
</Button>
</Button.Group>
</div>
</div>
);
}
function EvaluationCadence(): JSX.Element {
const [
isEvaluationCadenceDetailsVisible,
setIsEvaluationCadenceDetailsVisible,
] = useState(false);
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
const showCustomScheduleButton = useMemo(
() =>
!isEvaluationCadenceDetailsVisible &&
advancedOptions.evaluationCadence.mode === 'default',
[isEvaluationCadenceDetailsVisible, advancedOptions.evaluationCadence.mode],
);
const showCustomSchedule = (): void => {
setIsEvaluationCadenceDetailsVisible(true);
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE_MODE',
payload: 'custom',
});
};
return (
<div className="evaluation-cadence-container">
<div className="advanced-option-item evaluation-cadence-item">
<div className="advanced-option-item-left-content">
<Typography.Text className="advanced-option-item-title">
Evaluation cadence
</Typography.Text>
<Typography.Text className="advanced-option-item-description">
Customize when this Alert Rule will run. By default, it runs every 60
seconds (1 minute).
</Typography.Text>
</div>
{showCustomScheduleButton && (
<div className="advanced-option-item-right-content">
<Input.Group className="advanced-option-item-input-group">
<Input
type="number"
placeholder="Enter time"
style={{ width: 180 }}
value={advancedOptions.evaluationCadence.default.value}
onChange={(value): void =>
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE',
payload: {
...advancedOptions.evaluationCadence,
default: {
...advancedOptions.evaluationCadence.default,
value: Number(value.target.value),
},
},
})
}
/>
<Select
options={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}
placeholder="Select time unit"
style={{ width: 120 }}
value={advancedOptions.evaluationCadence.default.timeUnit}
onChange={(value): void =>
setAdvancedOptions({
type: 'SET_EVALUATION_CADENCE',
payload: {
...advancedOptions.evaluationCadence,
default: {
...advancedOptions.evaluationCadence.default,
timeUnit: value,
},
},
})
}
/>
</Input.Group>
<Button
className="advanced-option-item-button"
onClick={showCustomSchedule}
>
<Plus size={12} />
<Typography.Text>Add custom schedule</Typography.Text>
</Button>
</div>
)}
</div>
{!isEvaluationCadenceDetailsVisible &&
advancedOptions.evaluationCadence.mode !== 'default' && (
<EditCustomSchedule
setIsEvaluationCadenceDetailsVisible={
setIsEvaluationCadenceDetailsVisible
}
/>
)}
{isEvaluationCadenceDetailsVisible && (
<EvaluationCadenceDetails
isOpen={isEvaluationCadenceDetailsVisible}
setIsOpen={setIsEvaluationCadenceDetailsVisible}
/>
)}
</div>
);
}
export default EvaluationCadence;

View File

@@ -1,85 +0,0 @@
import './styles.scss';
import { Button, Popover, Typography } from 'antd';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from 'react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { useCreateAlertState } from '../context';
import Stepper from '../Stepper';
import { showCondensedLayout } from '../utils';
import AdvancedOptions from './AdvancedOptions';
import EvaluationWindowPopover from './EvaluationWindowPopover';
import { getEvaluationWindowTypeText, getTimeframeText } from './utils';
function EvaluationSettings(): JSX.Element {
const {
alertType,
evaluationWindow,
setEvaluationWindow,
} = useCreateAlertState();
const [
isEvaluationWindowPopoverOpen,
setIsEvaluationWindowPopoverOpen,
] = useState(false);
const showCondensedLayoutFlag = showCondensedLayout();
const popoverContent = (
<Popover
open={isEvaluationWindowPopoverOpen}
onOpenChange={(visibility: boolean): void => {
setIsEvaluationWindowPopoverOpen(visibility);
}}
content={
<EvaluationWindowPopover
evaluationWindow={evaluationWindow}
setEvaluationWindow={setEvaluationWindow}
isOpen={isEvaluationWindowPopoverOpen}
setIsOpen={setIsEvaluationWindowPopoverOpen}
/>
}
trigger="click"
showArrow={false}
>
<Button>
<div className="evaluate-alert-conditions-button-left">
{getTimeframeText(evaluationWindow.windowType, evaluationWindow.timeframe)}
</div>
<div className="evaluate-alert-conditions-button-right">
<div className="evaluate-alert-conditions-button-right-text">
{getEvaluationWindowTypeText(evaluationWindow.windowType)}
</div>
{isEvaluationWindowPopoverOpen ? (
<ChevronUp size={16} />
) : (
<ChevronDown size={16} />
)}
</div>
</Button>
</Popover>
);
if (showCondensedLayoutFlag) {
return (
<div className="condensed-evaluation-settings-container">
{popoverContent}
</div>
);
}
return (
<div className="evaluation-settings-container">
<Stepper stepNumber={3} label="Evaluation settings" />
{alertType !== AlertTypes.ANOMALY_BASED_ALERT && (
<div className="evaluate-alert-conditions-container">
<Typography.Text>Evaluate Alert Conditions over</Typography.Text>
<div className="evaluate-alert-conditions-separator" />
{popoverContent}
</div>
)}
<AdvancedOptions />
</div>
);
}
export default EvaluationSettings;

View File

@@ -1,248 +0,0 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import AdvancedOptionItem from '../AdvancedOptionItem';
const TEST_INPUT_PLACEHOLDER = 'Test input';
const TEST_TITLE = 'Test Title';
const TEST_DESCRIPTION = 'Test Description';
const TEST_VALUE = 'test value';
const FIRST_INPUT_PLACEHOLDER = 'First input';
const TEST_INPUT_TEST_ID = 'test-input';
describe('AdvancedOptionItem', () => {
const mockInput = (
<input
data-testid={TEST_INPUT_TEST_ID}
placeholder={TEST_INPUT_PLACEHOLDER}
/>
);
const defaultProps = {
title: TEST_TITLE,
description: TEST_DESCRIPTION,
input: mockInput,
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should render title and description', () => {
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
expect(screen.getByText(TEST_TITLE)).toBeInTheDocument();
expect(screen.getByText(TEST_DESCRIPTION)).toBeInTheDocument();
});
it('should render switch component', () => {
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
expect(switchElement).toBeInTheDocument();
expect(switchElement).not.toBeChecked();
});
it('should not show input initially', () => {
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
expect(screen.queryByTestId(TEST_INPUT_TEST_ID)).not.toBeInTheDocument();
});
it('should show input when switch is toggled on', async () => {
const user = userEvent.setup();
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
await user.click(switchElement);
expect(switchElement).toBeChecked();
expect(screen.getByTestId(TEST_INPUT_TEST_ID)).toBeInTheDocument();
});
it('should hide input when switch is toggled off', async () => {
const user = userEvent.setup();
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
// First toggle on
await user.click(switchElement);
expect(screen.getByTestId(TEST_INPUT_TEST_ID)).toBeInTheDocument();
// Then toggle off
await user.click(switchElement);
expect(screen.queryByTestId(TEST_INPUT_TEST_ID)).not.toBeInTheDocument();
});
it('should toggle switch state correctly', async () => {
const user = userEvent.setup();
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
// Initial state
expect(switchElement).not.toBeChecked();
// After first click
await user.click(switchElement);
expect(switchElement).toBeChecked();
// After second click
await user.click(switchElement);
expect(switchElement).not.toBeChecked();
});
it('should render input with correct props when visible', async () => {
const user = userEvent.setup();
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
await user.click(switchElement);
const inputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
expect(inputElement).toBeInTheDocument();
expect(inputElement).toHaveAttribute('placeholder', TEST_INPUT_PLACEHOLDER);
});
it('should handle multiple toggle operations', async () => {
const user = userEvent.setup();
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
// Toggle on
await user.click(switchElement);
expect(screen.getByTestId(TEST_INPUT_TEST_ID)).toBeInTheDocument();
// Toggle off
await user.click(switchElement);
expect(screen.queryByTestId(TEST_INPUT_TEST_ID)).not.toBeInTheDocument();
// Toggle on again
await user.click(switchElement);
expect(screen.getByTestId(TEST_INPUT_TEST_ID)).toBeInTheDocument();
});
it('should maintain input state when toggling', async () => {
const user = userEvent.setup();
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={defaultProps.input}
/>,
);
const switchElement = screen.getByRole('switch');
// Toggle on and interact with input
await user.click(switchElement);
const inputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
await user.type(inputElement, TEST_VALUE);
expect(inputElement).toHaveValue(TEST_VALUE);
// Toggle off
await user.click(switchElement);
expect(screen.queryByTestId(TEST_INPUT_TEST_ID)).not.toBeInTheDocument();
// Toggle back on - input should be recreated (fresh state)
await user.click(switchElement);
const inputElementAgain = screen.getByTestId(TEST_INPUT_TEST_ID);
expect(inputElementAgain).toHaveValue(''); // Fresh input, no previous state
});
it('should render with different title and description', () => {
const customTitle = 'Custom Title';
const customDescription = 'Custom Description';
render(
<AdvancedOptionItem
title={customTitle}
description={customDescription}
input={defaultProps.input}
/>,
);
expect(screen.getByText(customTitle)).toBeInTheDocument();
expect(screen.getByText(customDescription)).toBeInTheDocument();
});
it('should render with complex input component', async () => {
const user = userEvent.setup();
const complexInput = (
<div data-testid="complex-input">
<input placeholder={FIRST_INPUT_PLACEHOLDER} />
<select>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
</select>
</div>
);
render(
<AdvancedOptionItem
title={defaultProps.title}
description={defaultProps.description}
input={complexInput}
/>,
);
const switchElement = screen.getByRole('switch');
await user.click(switchElement);
expect(screen.getByTestId('complex-input')).toBeInTheDocument();
expect(
screen.getByPlaceholderText(FIRST_INPUT_PLACEHOLDER),
).toBeInTheDocument();
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
});

View File

@@ -1,193 +0,0 @@
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from 'react-query';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import store from 'store';
import { CreateAlertProvider } from '../../context';
import {
INITIAL_ADVANCED_OPTIONS_STATE,
INITIAL_EVALUATION_WINDOW_STATE,
} from '../../context/constants';
import AdvancedOptions from '../AdvancedOptions';
jest.mock('uplot', () => {
const paths = {
spline: jest.fn(),
bars: jest.fn(),
};
const uplotMock = jest.fn(() => ({
paths,
}));
return {
paths,
default: uplotMock,
};
});
// Mock dayjs timezone
jest.mock('dayjs', () => {
const originalDayjs = jest.requireActual('dayjs');
const mockDayjs = jest.fn((date) => originalDayjs(date));
Object.assign(mockDayjs, originalDayjs);
((mockDayjs as unknown) as { tz: { guess: jest.Mock } }).tz = {
guess: jest.fn(() => 'UTC'),
};
return mockDayjs;
});
// Mock Y_AXIS_CATEGORIES
jest.mock('components/YAxisUnitSelector/constants', () => ({
Y_AXIS_CATEGORIES: [
{
name: 'Time',
units: [
{ name: 'Second', id: 's' },
{ name: 'Minute', id: 'm' },
{ name: 'Hour', id: 'h' },
{ name: 'Day', id: 'd' },
],
},
],
}));
// Mock the context
const mockSetAdvancedOptions = jest.fn();
jest.mock('../../context', () => ({
...jest.requireActual('../../context'),
useCreateAlertState: (): {
advancedOptions: typeof INITIAL_ADVANCED_OPTIONS_STATE;
setAdvancedOptions: jest.Mock;
evaluationWindow: typeof INITIAL_EVALUATION_WINDOW_STATE;
setEvaluationWindow: jest.Mock;
} => ({
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
setAdvancedOptions: mockSetAdvancedOptions,
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
setEvaluationWindow: jest.fn(),
}),
}));
// Mock EvaluationCadence component
jest.mock('../EvaluationCadence', () => ({
__esModule: true,
default: function MockEvaluationCadence(): JSX.Element {
return (
<div data-testid="evaluation-cadence">Evaluation Cadence Component</div>
);
},
}));
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
const TOLERANCE_LIMIT_PLACEHOLDER = 'Enter tolerance limit...';
const renderAdvancedOptions = (): void => {
render(
<QueryClientProvider client={queryClient}>
<Provider store={store}>
<MemoryRouter>
<CreateAlertProvider>
<AdvancedOptions />
</CreateAlertProvider>
</MemoryRouter>
</Provider>
</QueryClientProvider>,
);
};
describe('AdvancedOptions', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const expandAdvancedOptions = async (
user: ReturnType<typeof userEvent.setup>,
): Promise<void> => {
const collapseHeader = screen.getByRole('button');
await user.click(collapseHeader);
await waitFor(() => {
expect(screen.getByTestId('evaluation-cadence')).toBeInTheDocument();
});
};
it('should render and allow expansion of advanced options', async () => {
const user = userEvent.setup();
renderAdvancedOptions();
expect(screen.getByText('ADVANCED OPTIONS')).toBeInTheDocument();
await expandAdvancedOptions(user);
expect(
screen.getByText('Send a notification if data is missing'),
).toBeInTheDocument();
expect(screen.getByText('Enforce minimum datapoints')).toBeInTheDocument();
expect(screen.getByText('Delay evaluation')).toBeInTheDocument();
});
it('should enable advanced option inputs when switches are toggled', async () => {
const user = userEvent.setup();
renderAdvancedOptions();
await expandAdvancedOptions(user);
const switches = screen.getAllByRole('switch');
// Toggle the first switch (send notification)
await user.click(switches[0]);
await waitFor(() => {
expect(
screen.getByPlaceholderText(TOLERANCE_LIMIT_PLACEHOLDER),
).toBeInTheDocument();
});
// Toggle the second switch (minimum datapoints)
await user.click(switches[1]);
await waitFor(() => {
expect(
screen.getByPlaceholderText('Enter minimum datapoints...'),
).toBeInTheDocument();
});
});
it('should update advanced options state when user interacts with inputs', async () => {
const user = userEvent.setup();
renderAdvancedOptions();
await expandAdvancedOptions(user);
// Enable send notification option
const switches = screen.getAllByRole('switch');
await user.click(switches[0]);
// Wait for tolerance input to appear and test interaction
const toleranceInput = await screen.findByPlaceholderText(
TOLERANCE_LIMIT_PLACEHOLDER,
);
await user.clear(toleranceInput);
await user.type(toleranceInput, '10');
const timeUnitSelect = screen.getByRole('combobox');
await user.click(timeUnitSelect);
await waitFor(() => {
expect(screen.getByText('Minute')).toBeInTheDocument();
});
await user.click(screen.getByText('Minute'));
// Verify that the state update function was called (testing behavior, not exact values)
expect(mockSetAdvancedOptions).toHaveBeenCalled();
// Verify the function was called with the expected action types
const { calls } = mockSetAdvancedOptions.mock;
const actionTypes = calls.map((call) => call[0].type);
expect(actionTypes).toContain('SET_SEND_NOTIFICATION_IF_DATA_IS_MISSING');
});
});

View File

@@ -1,210 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
import * as context from '../../context';
import EvaluationCadence, {
EvaluationCadenceDetails,
} from '../EvaluationCadence';
jest.mock('uplot', () => {
const paths = {
spline: jest.fn(),
bars: jest.fn(),
};
const uplotMock = jest.fn(() => ({
paths,
}));
return {
paths,
default: uplotMock,
};
});
const mockSetAdvancedOptions = jest.fn();
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
setAdvancedOptions: mockSetAdvancedOptions,
} as any);
const EDIT_CUSTOM_SCHEDULE_TEXT = 'Edit custom schedule';
const PREVIEW_TEXT = 'Preview';
const EVALUATION_CADENCE_TEXT = 'Evaluation cadence';
const EVALUATION_CADENCE_DESCRIPTION_TEXT =
'Customize when this Alert Rule will run. By default, it runs every 60 seconds (1 minute).';
const ADD_CUSTOM_SCHEDULE_TEXT = 'Add custom schedule';
const SAVE_CUSTOM_SCHEDULE_TEXT = 'Save Custom Schedule';
const DISCARD_TEXT = 'Discard';
describe('EvaluationCadence', () => {
it('should render evaluation cadence component in default mode', () => {
render(<EvaluationCadence />);
expect(screen.getByText(EVALUATION_CADENCE_TEXT)).toBeInTheDocument();
expect(
screen.getByText(EVALUATION_CADENCE_DESCRIPTION_TEXT),
).toBeInTheDocument();
expect(screen.getByText(ADD_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
});
it('should render evaluation cadence component in custom mode', () => {
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
advancedOptions: {
...INITIAL_ADVANCED_OPTIONS_STATE,
evaluationCadence: {
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
mode: 'custom',
},
},
} as any);
render(<EvaluationCadence />);
expect(screen.getByText(EVALUATION_CADENCE_TEXT)).toBeInTheDocument();
expect(
screen.getByText(EVALUATION_CADENCE_DESCRIPTION_TEXT),
).toBeInTheDocument();
expect(screen.getByText(EDIT_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
expect(screen.getByText(PREVIEW_TEXT)).toBeInTheDocument();
});
it('should render evaluation cadence component in rrule mode', () => {
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
advancedOptions: {
...INITIAL_ADVANCED_OPTIONS_STATE,
evaluationCadence: {
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
mode: 'rrule',
},
},
} as any);
render(<EvaluationCadence />);
expect(screen.getByText(EVALUATION_CADENCE_TEXT)).toBeInTheDocument();
expect(
screen.getByText(EVALUATION_CADENCE_DESCRIPTION_TEXT),
).toBeInTheDocument();
expect(screen.getByText(EDIT_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
expect(screen.getByText(PREVIEW_TEXT)).toBeInTheDocument();
});
it('clicking on discard button should reset the evaluation cadence mode to default', async () => {
const user = userEvent.setup();
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
advancedOptions: {
...INITIAL_ADVANCED_OPTIONS_STATE,
evaluationCadence: {
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
mode: 'custom',
},
},
setAdvancedOptions: mockSetAdvancedOptions,
} as any);
render(<EvaluationCadence />);
expect(screen.getByText(EVALUATION_CADENCE_TEXT)).toBeInTheDocument();
expect(
screen.getByText(EVALUATION_CADENCE_DESCRIPTION_TEXT),
).toBeInTheDocument();
expect(screen.getByText(EDIT_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
expect(screen.getByText(PREVIEW_TEXT)).toBeInTheDocument();
const discardButton = screen.getByTestId('discard-button');
await user.click(discardButton);
expect(mockSetAdvancedOptions).toHaveBeenCalledWith({
type: 'SET_EVALUATION_CADENCE_MODE',
payload: 'default',
});
});
it('clicking on preview button should open the preview modal', async () => {
const user = userEvent.setup();
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
advancedOptions: {
...INITIAL_ADVANCED_OPTIONS_STATE,
evaluationCadence: {
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
mode: 'custom',
},
},
setAdvancedOptions: mockSetAdvancedOptions,
} as any);
render(<EvaluationCadence />);
expect(screen.queryByText(SAVE_CUSTOM_SCHEDULE_TEXT)).not.toBeInTheDocument();
expect(screen.queryByText(DISCARD_TEXT)).not.toBeInTheDocument();
const previewButton = screen.getByText(PREVIEW_TEXT);
await user.click(previewButton);
expect(screen.getByText(SAVE_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
expect(screen.getByText(DISCARD_TEXT)).toBeInTheDocument();
});
});
it('clicking on edit custom schedule button should open the edit custom schedule modal', async () => {
const user = userEvent.setup();
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
advancedOptions: {
...INITIAL_ADVANCED_OPTIONS_STATE,
evaluationCadence: {
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
mode: 'custom',
},
},
setAdvancedOptions: mockSetAdvancedOptions,
} as any);
render(<EvaluationCadence />);
expect(screen.queryByText(SAVE_CUSTOM_SCHEDULE_TEXT)).not.toBeInTheDocument();
expect(screen.queryByText(DISCARD_TEXT)).not.toBeInTheDocument();
const editCustomScheduleButton = screen.getByText(EDIT_CUSTOM_SCHEDULE_TEXT);
await user.click(editCustomScheduleButton);
expect(screen.getByText(SAVE_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
expect(screen.getByText(DISCARD_TEXT)).toBeInTheDocument();
});
const mockSetIsOpen = jest.fn();
const RULE_VIEW_TEXT = 'RRule';
const EDITOR_VIEW_TEST_ID = 'editor-view';
const RULE_VIEW_TEST_ID = 'rrule-view';
describe('EvaluationCadenceDetails', () => {
it('should render evaluation cadence details component', () => {
render(<EvaluationCadenceDetails isOpen setIsOpen={mockSetIsOpen} />);
expect(screen.getByText('Add Custom Schedule')).toBeInTheDocument();
expect(screen.getByText(SAVE_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
expect(screen.getByText(DISCARD_TEXT)).toBeInTheDocument();
});
it('should open the editor tab by default', () => {
render(<EvaluationCadenceDetails isOpen setIsOpen={mockSetIsOpen} />);
expect(screen.getByTestId(EDITOR_VIEW_TEST_ID)).toBeInTheDocument();
expect(screen.queryByTestId(RULE_VIEW_TEST_ID)).not.toBeInTheDocument();
});
it('should open the rrule tab when rrule tab is clicked', async () => {
const user = userEvent.setup();
render(<EvaluationCadenceDetails isOpen setIsOpen={mockSetIsOpen} />);
expect(screen.getByTestId(EDITOR_VIEW_TEST_ID)).toBeInTheDocument();
expect(screen.queryByTestId(RULE_VIEW_TEST_ID)).not.toBeInTheDocument();
const rruleTab = screen.getByText(RULE_VIEW_TEXT);
await user.click(rruleTab);
expect(screen.queryByTestId(EDITOR_VIEW_TEST_ID)).not.toBeInTheDocument();
expect(screen.getByTestId(RULE_VIEW_TEST_ID)).toBeInTheDocument();
});
});

View File

@@ -1,77 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { render, screen } from '@testing-library/react';
import * as context from '../../context';
import { INITIAL_EVALUATION_WINDOW_STATE } from '../../context/constants';
import EvaluationSettings from '../EvaluationSettings';
const mockSetEvaluationWindow = jest.fn();
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
setEvaluationWindow: mockSetEvaluationWindow,
} as any);
jest.mock('uplot', () => {
const paths = {
spline: jest.fn(),
bars: jest.fn(),
};
const uplotMock = jest.fn(() => ({
paths,
}));
return {
paths,
default: uplotMock,
};
});
jest.mock(
'../AdvancedOptions',
() =>
function MockAdvancedOptions(): JSX.Element {
return <div data-testid="advanced-options">Advanced Options</div>;
},
);
describe('EvaluationSettings', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should render evaluation settings container', () => {
render(<EvaluationSettings />);
expect(screen.getByText('Evaluation settings')).toBeInTheDocument();
});
it('should render evaluation alert conditions text', () => {
render(<EvaluationSettings />);
expect(
screen.getByText('Evaluate Alert Conditions over'),
).toBeInTheDocument();
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
});
it('should display correct timeframe text for rolling window', () => {
render(<EvaluationSettings />);
expect(screen.getByText('Last 5 minutes')).toBeInTheDocument();
expect(screen.getByText('Rolling')).toBeInTheDocument();
});
it('should display correct timeframe text for cumulative window', () => {
jest.spyOn(context, 'useCreateAlertState').mockReturnValue({
evaluationWindow: {
...INITIAL_EVALUATION_WINDOW_STATE,
windowType: 'cumulative',
timeframe: 'currentDay',
},
} as any);
render(<EvaluationSettings />);
expect(screen.getByText('Current day')).toBeInTheDocument();
expect(screen.getByText('Cumulative')).toBeInTheDocument();
});
});

View File

@@ -191,7 +191,7 @@ describe('utils', () => {
// When date is provided, DTSTART is automatically added to the rrule string
expect(rrulestr).toHaveBeenCalledWith(
expect.stringContaining('DTSTART:20240120T020000Z'),
expect.stringMatching(/DTSTART:20240120T\d{6}Z/),
);
});

View File

@@ -1,3 +0,0 @@
import EvalutationSettings from './EvaluationSettings';
export default EvalutationSettings;

View File

@@ -1,9 +1,3 @@
// UI side feature flag
export const showNewCreateAlertsPage = (): boolean =>
localStorage.getItem('showNewCreateAlertsPage') === 'true';
// UI side FF to switch between the 2 layouts of the create alert page
// Layout 1 - Default layout
// Layout 2 - Condensed layout
export const showCondensedLayout = (): boolean =>
localStorage.getItem('showCondensedLayout') === 'true';