mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 23:47:12 +00:00
chore: add evaluation cadence component for alerts v2 (#9131)
This commit is contained in:
parent
84ae5b4ca9
commit
a16ab114f5
@ -0,0 +1,51 @@
|
||||
import './styles.scss';
|
||||
|
||||
import { Switch, Tooltip, Typography } from 'antd';
|
||||
import { Info } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { IAdvancedOptionItemProps } from '../types';
|
||||
|
||||
function AdvancedOptionItem({
|
||||
title,
|
||||
description,
|
||||
input,
|
||||
tooltipText,
|
||||
onToggle,
|
||||
}: IAdvancedOptionItemProps): JSX.Element {
|
||||
const [showInput, setShowInput] = useState<boolean>(false);
|
||||
|
||||
const handleOnToggle = (): void => {
|
||||
onToggle?.();
|
||||
setShowInput((currentShowInput) => !currentShowInput);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="advanced-option-item">
|
||||
<div className="advanced-option-item-left-content">
|
||||
<Typography.Text className="advanced-option-item-title">
|
||||
{title}
|
||||
{tooltipText && (
|
||||
<Tooltip title={tooltipText}>
|
||||
<Info data-testid="tooltip-icon" size={16} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Typography.Text>
|
||||
<Typography.Text className="advanced-option-item-description">
|
||||
{description}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="advanced-option-item-right-content">
|
||||
<div
|
||||
className="advanced-option-item-input"
|
||||
style={{ display: showInput ? 'block' : 'none' }}
|
||||
>
|
||||
{input}
|
||||
</div>
|
||||
<Switch onChange={handleOnToggle} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AdvancedOptionItem;
|
||||
@ -0,0 +1,3 @@
|
||||
import AdvancedOptionItem from './AdvancedOptionItem';
|
||||
|
||||
export default AdvancedOptionItem;
|
||||
@ -0,0 +1,250 @@
|
||||
.advanced-option-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
|
||||
.advanced-option-item-left-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
.advanced-option-item-title {
|
||||
color: var(--bg-vanilla-300);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.advanced-option-item-description {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.advanced-option-item-input {
|
||||
margin-top: 16px;
|
||||
|
||||
.ant-input {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
height: 32px;
|
||||
|
||||
&::placeholder {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
height: 32px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-option-item-right-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
|
||||
.advanced-option-item-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.ant-input {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-vanilla-300);
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-ink-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
height: 32px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
font-family: 'Space Mono';
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-option-item-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background-color: var(--bg-ink-200);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-400);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.advanced-option-item {
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.advanced-option-item-left-content {
|
||||
.advanced-option-item-title {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
.advanced-option-item-description {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.advanced-option-item-input {
|
||||
.ant-input {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-ink-300);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-ink-300);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-option-item-right-content {
|
||||
.advanced-option-item-input-group {
|
||||
.ant-input {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-ink-300);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--bg-ink-300);
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-option-item-button {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
import { Button, Typography } from 'antd';
|
||||
import { useCreateAlertState } from 'container/CreateAlertV2/context';
|
||||
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
|
||||
import { IEditCustomScheduleProps } from 'container/CreateAlertV2/EvaluationSettings/types';
|
||||
import { Calendar1, Edit, Trash } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
function EditCustomSchedule({
|
||||
setIsEvaluationCadenceDetailsVisible,
|
||||
setIsPreviewVisible,
|
||||
}: IEditCustomScheduleProps): 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>
|
||||
{advancedOptions.evaluationCadence.custom.repeatEvery !== 'day' && (
|
||||
<>
|
||||
<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 handleEdit = (): void => {
|
||||
setIsEvaluationCadenceDetailsVisible(true);
|
||||
};
|
||||
|
||||
const handlePreview = (): void => {
|
||||
setIsPreviewVisible(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={handleEdit}>
|
||||
<Edit size={12} />
|
||||
<Typography.Text>Edit custom schedule</Typography.Text>
|
||||
</Button>
|
||||
<Button type="default" onClick={handlePreview}>
|
||||
<Calendar1 size={12} />
|
||||
<Typography.Text>Preview</Typography.Text>
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="discard-button"
|
||||
type="default"
|
||||
onClick={handleDiscard}
|
||||
>
|
||||
<Trash size={12} />
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditCustomSchedule;
|
||||
@ -0,0 +1,134 @@
|
||||
import './styles.scss';
|
||||
import '../AdvancedOptionItem/styles.scss';
|
||||
|
||||
import { Button, Input, Select, Tooltip, Typography } from 'antd';
|
||||
import { Info, Plus } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useCreateAlertState } from '../../context';
|
||||
import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
|
||||
import EditCustomSchedule from './EditCustomSchedule';
|
||||
import EvaluationCadenceDetails from './EvaluationCadenceDetails';
|
||||
import EvaluationCadencePreview from './EvaluationCadencePreview';
|
||||
|
||||
function EvaluationCadence(): JSX.Element {
|
||||
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
|
||||
|
||||
const [
|
||||
isEvaluationCadenceDetailsVisible,
|
||||
setIsEvaluationCadenceDetailsVisible,
|
||||
] = useState(false);
|
||||
const [
|
||||
isCustomScheduleButtonVisible,
|
||||
setIsCustomScheduleButtonVisible,
|
||||
] = useState(true);
|
||||
const [
|
||||
isEvaluationCadencePreviewVisible,
|
||||
setIsEvaluationCadencePreviewVisible,
|
||||
] = useState(false);
|
||||
const [isEditCustomScheduleVisible, setIsEditCustomScheduleVisible] = useState(
|
||||
() => advancedOptions.evaluationCadence.mode !== 'default',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsEditCustomScheduleVisible(
|
||||
advancedOptions.evaluationCadence.mode !== 'default',
|
||||
);
|
||||
}, [advancedOptions.evaluationCadence.mode]);
|
||||
|
||||
const showCustomSchedule = (): void => {
|
||||
setIsEvaluationCadenceDetailsVisible(true);
|
||||
setIsCustomScheduleButtonVisible(false);
|
||||
};
|
||||
|
||||
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">
|
||||
How often to check
|
||||
<Tooltip title="Controls how frequently the alert evaluates your conditions. For most alerts, 1-5 minutes is sufficient.">
|
||||
<Info data-testid="evaluation-cadence-tooltip-icon" size={16} />
|
||||
</Tooltip>
|
||||
</Typography.Text>
|
||||
<Typography.Text className="advanced-option-item-description">
|
||||
How frequently this alert checks your data. Default: Every 1 minute
|
||||
</Typography.Text>
|
||||
</div>
|
||||
{isCustomScheduleButtonVisible && (
|
||||
<div
|
||||
className="advanced-option-item-right-content"
|
||||
data-testid="evaluation-cadence-input-group"
|
||||
>
|
||||
<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>
|
||||
{isEditCustomScheduleVisible && (
|
||||
<EditCustomSchedule
|
||||
setIsEvaluationCadenceDetailsVisible={setIsEvaluationCadenceDetailsVisible}
|
||||
setIsPreviewVisible={setIsEvaluationCadencePreviewVisible}
|
||||
/>
|
||||
)}
|
||||
{isEvaluationCadenceDetailsVisible && (
|
||||
<EvaluationCadenceDetails
|
||||
isOpen={isEvaluationCadenceDetailsVisible}
|
||||
setIsOpen={setIsEvaluationCadenceDetailsVisible}
|
||||
setIsCustomScheduleButtonVisible={setIsCustomScheduleButtonVisible}
|
||||
/>
|
||||
)}
|
||||
{isEvaluationCadencePreviewVisible && (
|
||||
<EvaluationCadencePreview
|
||||
isOpen={isEvaluationCadencePreviewVisible}
|
||||
setIsOpen={setIsEvaluationCadencePreviewVisible}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EvaluationCadence;
|
||||
@ -0,0 +1,347 @@
|
||||
import { Button, DatePicker, Select, Typography } from 'antd';
|
||||
import TextArea from 'antd/lib/input/TextArea';
|
||||
import classNames from 'classnames';
|
||||
import { useCreateAlertState } from 'container/CreateAlertV2/context';
|
||||
import { AdvancedOptionsState } from 'container/CreateAlertV2/context/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { Code, Edit3Icon } from 'lucide-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
EVALUATION_CADENCE_REPEAT_EVERY_MONTH_OPTIONS,
|
||||
EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS,
|
||||
EVALUATION_CADENCE_REPEAT_EVERY_WEEK_OPTIONS,
|
||||
TIMEZONE_DATA,
|
||||
} from '../constants';
|
||||
import TimeInput from '../TimeInput';
|
||||
import { IEvaluationCadenceDetailsProps } from '../types';
|
||||
import {
|
||||
buildAlertScheduleFromCustomSchedule,
|
||||
buildAlertScheduleFromRRule,
|
||||
isValidRRule,
|
||||
} from '../utils';
|
||||
import { ScheduleList } from './EvaluationCadencePreview';
|
||||
|
||||
function EvaluationCadenceDetails({
|
||||
setIsOpen,
|
||||
setIsCustomScheduleButtonVisible,
|
||||
}: IEvaluationCadenceDetailsProps): JSX.Element {
|
||||
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
|
||||
const [evaluationCadence, setEvaluationCadence] = useState<
|
||||
AdvancedOptionsState['evaluationCadence']
|
||||
>({
|
||||
...advancedOptions.evaluationCadence,
|
||||
mode: 'custom',
|
||||
custom: {
|
||||
...advancedOptions.evaluationCadence.custom,
|
||||
startAt: dayjs().format('HH:mm:ss'),
|
||||
},
|
||||
rrule: {
|
||||
...advancedOptions.evaluationCadence.rrule,
|
||||
startAt: dayjs().format('HH:mm:ss'),
|
||||
},
|
||||
});
|
||||
|
||||
const [searchTimezoneString, setSearchTimezoneString] = useState('');
|
||||
const [occurenceSearchString, setOccurenceSearchString] = useState('');
|
||||
const [repeatEverySearchString, setRepeatEverySearchString] = useState('');
|
||||
|
||||
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;
|
||||
|
||||
useEffect(() => {
|
||||
if (!evaluationCadence.custom.occurence.length) {
|
||||
const today = new Date();
|
||||
const dayOfWeek = today.getDay();
|
||||
const dayOfMonth = today.getDate();
|
||||
|
||||
const occurence =
|
||||
evaluationCadence.custom.repeatEvery === 'week'
|
||||
? EVALUATION_CADENCE_REPEAT_EVERY_WEEK_OPTIONS[dayOfWeek].value
|
||||
: dayOfMonth.toString();
|
||||
|
||||
setEvaluationCadence({
|
||||
...evaluationCadence,
|
||||
custom: {
|
||||
...evaluationCadence.custom,
|
||||
occurence: [occurence],
|
||||
},
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [evaluationCadence.custom.repeatEvery]);
|
||||
|
||||
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"
|
||||
showSearch
|
||||
searchValue={repeatEverySearchString}
|
||||
onSearch={setRepeatEverySearchString}
|
||||
/>
|
||||
</div>
|
||||
{evaluationCadence.custom.repeatEvery !== 'day' && (
|
||||
<div className="select-group">
|
||||
<Typography.Text>ON DAY(S)</Typography.Text>
|
||||
<Select
|
||||
options={occurenceOptions}
|
||||
value={evaluationCadence.custom.occurence || null}
|
||||
mode="multiple"
|
||||
onChange={(value): void =>
|
||||
setEvaluationCadence({
|
||||
...evaluationCadence,
|
||||
custom: {
|
||||
...evaluationCadence.custom,
|
||||
occurence: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
placeholder="Select day(s)"
|
||||
showSearch
|
||||
searchValue={occurenceSearchString}
|
||||
onSearch={setOccurenceSearchString}
|
||||
/>
|
||||
</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"
|
||||
onSearch={setSearchTimezoneString}
|
||||
searchValue={searchTimezoneString}
|
||||
showSearch
|
||||
/>
|
||||
</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);
|
||||
setIsCustomScheduleButtonVisible(true);
|
||||
};
|
||||
|
||||
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') {
|
||||
if (evaluationCadence.custom.repeatEvery === 'day') {
|
||||
return (
|
||||
!evaluationCadence.custom.repeatEvery ||
|
||||
!evaluationCadence.custom.startAt ||
|
||||
!evaluationCadence.custom.timezone
|
||||
);
|
||||
}
|
||||
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,
|
||||
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">
|
||||
<ScheduleList
|
||||
schedule={schedule}
|
||||
currentTimezone={evaluationCadence.custom.timezone}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EvaluationCadenceDetails;
|
||||
@ -0,0 +1,118 @@
|
||||
import { Modal, Typography } from 'antd';
|
||||
import { Calendar, Info } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useCreateAlertState } from '../../context';
|
||||
import { TIMEZONE_DATA } from '../constants';
|
||||
import { IEvaluationCadencePreviewProps, IScheduleListProps } from '../types';
|
||||
import {
|
||||
buildAlertScheduleFromCustomSchedule,
|
||||
buildAlertScheduleFromRRule,
|
||||
} from '../utils';
|
||||
|
||||
export function ScheduleList({
|
||||
schedule,
|
||||
currentTimezone,
|
||||
}: IScheduleListProps): JSX.Element {
|
||||
if (schedule && schedule.length > 0) {
|
||||
return (
|
||||
<div className="schedule-preview" data-testid="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">
|
||||
{
|
||||
TIMEZONE_DATA.find((timezone) => timezone.value === currentTimezone)
|
||||
?.label
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="no-schedule" data-testid="no-schedule">
|
||||
<Info size={32} />
|
||||
<Typography.Text>
|
||||
Please fill the relevant information to generate a schedule
|
||||
</Typography.Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EvaluationCadencePreview({
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
}: IEvaluationCadencePreviewProps): JSX.Element {
|
||||
const { advancedOptions } = useCreateAlertState();
|
||||
|
||||
const schedule = useMemo(() => {
|
||||
if (advancedOptions.evaluationCadence.mode === 'rrule') {
|
||||
return buildAlertScheduleFromRRule(
|
||||
advancedOptions.evaluationCadence.rrule.rrule,
|
||||
advancedOptions.evaluationCadence.rrule.date,
|
||||
advancedOptions.evaluationCadence.rrule.startAt,
|
||||
15,
|
||||
);
|
||||
}
|
||||
return buildAlertScheduleFromCustomSchedule(
|
||||
advancedOptions.evaluationCadence.custom.repeatEvery,
|
||||
advancedOptions.evaluationCadence.custom.occurence,
|
||||
advancedOptions.evaluationCadence.custom.startAt,
|
||||
15,
|
||||
);
|
||||
}, [advancedOptions.evaluationCadence]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
onCancel={(): void => setIsOpen(false)}
|
||||
footer={null}
|
||||
className="evaluation-cadence-preview-modal"
|
||||
width={800}
|
||||
centered
|
||||
>
|
||||
<div className="evaluation-cadence-details evaluation-cadence-preview">
|
||||
<div className="evaluation-cadence-details-content">
|
||||
<div className="evaluation-cadence-details-content-row">
|
||||
<ScheduleList
|
||||
schedule={schedule}
|
||||
currentTimezone={advancedOptions.evaluationCadence.custom.timezone}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default EvaluationCadencePreview;
|
||||
@ -0,0 +1,5 @@
|
||||
import './styles.scss';
|
||||
|
||||
import EvaluationCadence from './EvaluationCadence';
|
||||
|
||||
export default EvaluationCadence;
|
||||
@ -0,0 +1,700 @@
|
||||
.evaluation-cadence-container {
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
.evaluation-cadence-item {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.edit-custom-schedule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 13px;
|
||||
|
||||
.highlight {
|
||||
background-color: var(--bg-slate-500);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-weight: 500;
|
||||
margin: 0 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-group {
|
||||
.ant-btn {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-cadence-details {
|
||||
margin: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
|
||||
.evaluation-cadence-details-title {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding-left: 16px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.query-section-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.query-section-query-actions {
|
||||
display: flex;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
flex-direction: row;
|
||||
border-bottom: none;
|
||||
margin-bottom: -1px;
|
||||
|
||||
.explorer-view-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
border: none;
|
||||
padding: 9px;
|
||||
box-shadow: none;
|
||||
border-radius: 0px;
|
||||
border-left: 0.5px solid var(--bg-slate-400);
|
||||
border-bottom: 0.5px solid var(--bg-slate-400);
|
||||
width: 120px;
|
||||
height: 36px;
|
||||
|
||||
gap: 8px;
|
||||
|
||||
&.active-tab {
|
||||
background-color: var(--bg-ink-500);
|
||||
border-bottom: none;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-ink-500) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--bg-ink-300);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
border-left: 1px solid transparent !important;
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-cadence-details-content {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
border-top: 1px solid var(--bg-slate-500);
|
||||
padding: 16px;
|
||||
|
||||
.evaluation-cadence-details-content-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
height: 500px;
|
||||
overflow-y: scroll;
|
||||
padding-right: 16px;
|
||||
|
||||
.editor-view,
|
||||
.rrule-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
textarea {
|
||||
height: 200px;
|
||||
background: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
border-radius: 4px;
|
||||
color: var(--bg-vanilla-400) !important;
|
||||
font-family: 'Space Mono';
|
||||
font-size: 14px;
|
||||
|
||||
&::placeholder {
|
||||
font-family: 'Space Mono';
|
||||
color: var(--bg-vanilla-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.select-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-picker {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
|
||||
.ant-picker-input {
|
||||
background-color: var(--bg-ink-300);
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.no-schedule {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
height: 100%;
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.schedule-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
.schedule-preview-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0;
|
||||
background-color: var(--bg-ink-400);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
|
||||
.schedule-preview-title {
|
||||
color: var(--bg-vanilla-300);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-top: 8px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-slate-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-slate-200);
|
||||
}
|
||||
|
||||
.schedule-preview-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
|
||||
.schedule-preview-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 20px;
|
||||
|
||||
.schedule-preview-timeline-line {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background-color: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
|
||||
.schedule-preview-date {
|
||||
color: var(--bg-vanilla-300);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.schedule-preview-separator {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
border-top: 1px dashed var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.schedule-preview-timezone {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-picker-date-panel {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-300);
|
||||
}
|
||||
|
||||
.ant-picker-date-panel-layout {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-300);
|
||||
}
|
||||
|
||||
.ant-picker-date-panel-header {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-300);
|
||||
}
|
||||
|
||||
// Custom modal styles for preview
|
||||
.evaluation-cadence-preview-modal {
|
||||
.ant-modal-content {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.ant-modal-header {
|
||||
background-color: var(--bg-ink-400);
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
padding: 16px 20px;
|
||||
|
||||
.ant-modal-title {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-close {
|
||||
color: var(--bg-vanilla-400);
|
||||
top: 16px;
|
||||
right: 20px;
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding: 0;
|
||||
background-color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.evaluation-cadence-details {
|
||||
border: none;
|
||||
margin: 0;
|
||||
|
||||
.evaluation-cadence-details-content {
|
||||
border-top: none;
|
||||
padding: 0;
|
||||
|
||||
.evaluation-cadence-details-content-row {
|
||||
height: auto;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-slate-400);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-slate-300);
|
||||
}
|
||||
|
||||
.schedule-preview {
|
||||
.schedule-preview-header {
|
||||
background-color: var(--bg-ink-400);
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
padding: 12px 16px;
|
||||
margin: -12px -12px 16px -12px;
|
||||
|
||||
.schedule-preview-title {
|
||||
color: var(--bg-vanilla-300);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-list {
|
||||
.schedule-preview-item {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid var(--bg-slate-500);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.schedule-preview-timeline {
|
||||
.schedule-preview-timeline-line {
|
||||
width: 2px;
|
||||
height: 24px;
|
||||
background-color: var(--bg-robin-500);
|
||||
border-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-content {
|
||||
.schedule-preview-date {
|
||||
color: var(--bg-vanilla-300);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.schedule-preview-timezone {
|
||||
background-color: var(--bg-slate-500);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-schedule {
|
||||
min-height: 300px;
|
||||
padding: 40px 12px;
|
||||
|
||||
svg {
|
||||
color: var(--bg-slate-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light mode styles
|
||||
.lightMode {
|
||||
.evaluation-cadence-container {
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.edit-custom-schedule {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
.highlight {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn-group {
|
||||
.ant-btn {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-cadence-details {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.evaluation-cadence-details-title {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.query-section-tabs {
|
||||
.query-section-query-actions {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
background: var(--bg-vanilla-300);
|
||||
|
||||
.explorer-view-option {
|
||||
border-left: 0.5px solid var(--bg-vanilla-300);
|
||||
border-bottom: 0.5px solid var(--bg-vanilla-300);
|
||||
|
||||
&.active-tab {
|
||||
background-color: var(--bg-vanilla-100);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-vanilla-100) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-cadence-details-content {
|
||||
border-top: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.evaluation-cadence-details-content-row {
|
||||
.editor-view,
|
||||
.rrule-view {
|
||||
textarea {
|
||||
background: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400) !important;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--bg-ink-400) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.select-group {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-picker {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-picker-input {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-schedule {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.schedule-preview {
|
||||
.schedule-preview-header {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.schedule-preview-title {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-list {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-vanilla-400);
|
||||
}
|
||||
|
||||
.schedule-preview-item {
|
||||
.schedule-preview-timeline {
|
||||
.schedule-preview-timeline-line {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-content {
|
||||
.schedule-preview-date {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
.schedule-preview-separator {
|
||||
border-top: 1px dashed var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.schedule-preview-timezone {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-picker-date-panel {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-picker-date-panel-layout {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-picker-date-panel-header {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
// Light mode styles for preview modal
|
||||
.evaluation-cadence-preview-modal {
|
||||
.ant-modal-content {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-modal-header {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-modal-title {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-close {
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
&:hover {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
}
|
||||
|
||||
.evaluation-cadence-details {
|
||||
.evaluation-cadence-details-content {
|
||||
.evaluation-cadence-details-content-row {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-vanilla-400);
|
||||
}
|
||||
|
||||
.schedule-preview {
|
||||
.schedule-preview-header {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.schedule-preview-title {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-list {
|
||||
.schedule-preview-item {
|
||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.schedule-preview-timeline {
|
||||
.schedule-preview-timeline-line {
|
||||
background-color: var(--bg-robin-500);
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-preview-content {
|
||||
.schedule-preview-date {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
.schedule-preview-separator {
|
||||
border-top: 1px dashed var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.schedule-preview-timezone {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-schedule {
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
svg {
|
||||
color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import AdvancedOptionItem from '../AdvancedOptionItem/AdvancedOptionItem';
|
||||
|
||||
const TEST_INPUT_PLACEHOLDER = 'Test input';
|
||||
const TEST_TITLE = 'Test Title';
|
||||
const TEST_DESCRIPTION = 'Test Description';
|
||||
const TEST_VALUE = 'test value';
|
||||
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, description and switch', () => {
|
||||
render(
|
||||
<AdvancedOptionItem
|
||||
title={defaultProps.title}
|
||||
description={defaultProps.description}
|
||||
input={defaultProps.input}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(TEST_TITLE)).toBeInTheDocument();
|
||||
expect(screen.getByText(TEST_DESCRIPTION)).toBeInTheDocument();
|
||||
|
||||
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}
|
||||
/>,
|
||||
);
|
||||
|
||||
const inputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(inputElement).toBeInTheDocument();
|
||||
expect(inputElement).not.toBeVisible();
|
||||
});
|
||||
|
||||
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 initialInputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(initialInputElement).toBeInTheDocument();
|
||||
expect(initialInputElement).not.toBeVisible();
|
||||
|
||||
const switchElement = screen.getByRole('switch');
|
||||
await user.click(switchElement);
|
||||
|
||||
expect(switchElement).toBeChecked();
|
||||
const visibleInputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(visibleInputElement).toBeInTheDocument();
|
||||
expect(visibleInputElement).toBeVisible();
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
const initialInputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(initialInputElement).toBeInTheDocument();
|
||||
expect(initialInputElement).not.toBeVisible();
|
||||
|
||||
// First toggle on
|
||||
await user.click(switchElement);
|
||||
const inputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(inputElement).toBeInTheDocument();
|
||||
expect(inputElement).toBeVisible();
|
||||
|
||||
// Then toggle off - input should be hidden but still in DOM
|
||||
await user.click(switchElement);
|
||||
const hiddenInputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(hiddenInputElement).toBeInTheDocument();
|
||||
expect(hiddenInputElement).not.toBeVisible();
|
||||
});
|
||||
|
||||
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 - input should still be in DOM but hidden
|
||||
await user.click(switchElement);
|
||||
const hiddenInputElement = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(hiddenInputElement).toBeInTheDocument();
|
||||
expect(hiddenInputElement).not.toBeVisible();
|
||||
|
||||
// Toggle back on - input should maintain its previous state
|
||||
await user.click(switchElement);
|
||||
const inputElementAgain = screen.getByTestId(TEST_INPUT_TEST_ID);
|
||||
expect(inputElementAgain).toHaveValue(TEST_VALUE); // State preserved!
|
||||
});
|
||||
|
||||
it('should not render tooltip icon if tooltipText is not provided', () => {
|
||||
render(
|
||||
<AdvancedOptionItem
|
||||
title={defaultProps.title}
|
||||
description={defaultProps.description}
|
||||
input={defaultProps.input}
|
||||
/>,
|
||||
);
|
||||
|
||||
const tooltipIcon = screen.queryByTestId('tooltip-icon');
|
||||
expect(tooltipIcon).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render tooltip icon if tooltipText is provided', () => {
|
||||
render(
|
||||
<AdvancedOptionItem
|
||||
title={defaultProps.title}
|
||||
description={defaultProps.description}
|
||||
input={defaultProps.input}
|
||||
tooltipText="mock tooltip text"
|
||||
/>,
|
||||
);
|
||||
const tooltipIcon = screen.getByTestId('tooltip-icon');
|
||||
expect(tooltipIcon).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,155 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import * as alertState from 'container/CreateAlertV2/context';
|
||||
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
|
||||
|
||||
import { TIMEZONE_DATA } from '../constants';
|
||||
import EditCustomSchedule from '../EvaluationCadence/EditCustomSchedule';
|
||||
import { createMockAlertContextState } from './testUtils';
|
||||
|
||||
const mockSetAdvancedOptions = jest.fn();
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
|
||||
createMockAlertContextState({
|
||||
setAdvancedOptions: mockSetAdvancedOptions,
|
||||
}),
|
||||
);
|
||||
|
||||
const mockSetIsEvaluationCadenceDetailsVisible = jest.fn();
|
||||
const mockSetIsPreviewVisible = jest.fn();
|
||||
|
||||
const EDIT_CUSTOM_SCHEDULE_TEST_ID = '.edit-custom-schedule';
|
||||
|
||||
describe('EditCustomSchedule', () => {
|
||||
it('should render the correct display text for custom mode with daily occurrence', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
custom: {
|
||||
repeatEvery: 'day',
|
||||
startAt: '00:00:00',
|
||||
occurence: [],
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<EditCustomSchedule
|
||||
setIsEvaluationCadenceDetailsVisible={
|
||||
mockSetIsEvaluationCadenceDetailsVisible
|
||||
}
|
||||
setIsPreviewVisible={mockSetIsPreviewVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Use textContent to verify the complete text across multiple Typography components
|
||||
const container = screen
|
||||
.getByText('Every')
|
||||
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
|
||||
expect(container).toHaveTextContent('EveryDayat00:00:00');
|
||||
});
|
||||
|
||||
it('should render the correct display text for custom mode with weekly occurrence', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
custom: {
|
||||
repeatEvery: 'week',
|
||||
startAt: '00:00:00',
|
||||
occurence: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<EditCustomSchedule
|
||||
setIsEvaluationCadenceDetailsVisible={
|
||||
mockSetIsEvaluationCadenceDetailsVisible
|
||||
}
|
||||
setIsPreviewVisible={mockSetIsPreviewVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
const container = screen
|
||||
.getByText('Every')
|
||||
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
|
||||
expect(container).toHaveTextContent(
|
||||
'EveryWeekonMonday, Tuesday, Wednesday, Thursday, Fridayat00:00:00',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the correct display text for custom mode with monthly occurrence', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
custom: {
|
||||
repeatEvery: 'month',
|
||||
startAt: '00:00:00',
|
||||
occurence: ['1'],
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<EditCustomSchedule
|
||||
setIsEvaluationCadenceDetailsVisible={
|
||||
mockSetIsEvaluationCadenceDetailsVisible
|
||||
}
|
||||
setIsPreviewVisible={mockSetIsPreviewVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
const container = screen
|
||||
.getByText('Every')
|
||||
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
|
||||
expect(container).toHaveTextContent('EveryMonthon1at00:00:00');
|
||||
});
|
||||
|
||||
it('edit custom schedule action works correctly', () => {
|
||||
render(
|
||||
<EditCustomSchedule
|
||||
setIsEvaluationCadenceDetailsVisible={
|
||||
mockSetIsEvaluationCadenceDetailsVisible
|
||||
}
|
||||
setIsPreviewVisible={mockSetIsPreviewVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Edit custom schedule'));
|
||||
expect(mockSetIsEvaluationCadenceDetailsVisible).toHaveBeenCalledWith(true);
|
||||
expect(mockSetIsPreviewVisible).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('preview custom schedule action works correctly', () => {
|
||||
render(
|
||||
<EditCustomSchedule
|
||||
setIsEvaluationCadenceDetailsVisible={
|
||||
mockSetIsEvaluationCadenceDetailsVisible
|
||||
}
|
||||
setIsPreviewVisible={mockSetIsPreviewVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Preview'));
|
||||
expect(mockSetIsPreviewVisible).toHaveBeenCalledWith(true);
|
||||
expect(mockSetIsEvaluationCadenceDetailsVisible).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,162 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import * as alertState from 'container/CreateAlertV2/context';
|
||||
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
|
||||
|
||||
import { TIMEZONE_DATA } from '../constants';
|
||||
import EvaluationCadence from '../EvaluationCadence';
|
||||
import { createMockAlertContextState } from './testUtils';
|
||||
|
||||
jest.mock('../EvaluationCadence/EditCustomSchedule', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
setIsPreviewVisible,
|
||||
}: {
|
||||
setIsPreviewVisible: (isPreviewVisible: boolean) => void;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="edit-custom-schedule">
|
||||
<div>EditCustomSchedule</div>
|
||||
<button type="button" onClick={(): void => setIsPreviewVisible(true)}>
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
jest.mock('../EvaluationCadence/EvaluationCadenceDetails', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => (
|
||||
<div data-testid="evaluation-cadence-details">EvaluationCadenceDetails</div>
|
||||
),
|
||||
}));
|
||||
jest.mock('../EvaluationCadence/EvaluationCadencePreview', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => (
|
||||
<div data-testid="evaluation-cadence-preview">EvaluationCadencePreview</div>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockSetAdvancedOptions = jest.fn();
|
||||
const EVALUATION_CADENCE_DETAILS_TEST_ID = 'evaluation-cadence-details';
|
||||
const ADD_CUSTOM_SCHEDULE_TEXT = 'Add custom schedule';
|
||||
const EVALUATION_CADENCE_PREVIEW_TEST_ID = 'evaluation-cadence-preview';
|
||||
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
|
||||
createMockAlertContextState({
|
||||
setAdvancedOptions: mockSetAdvancedOptions,
|
||||
}),
|
||||
);
|
||||
|
||||
const EVALUATION_CADENCE_INPUT_GROUP = 'evaluation-cadence-input-group';
|
||||
|
||||
describe('EvaluationCadence', () => {
|
||||
it('should render the title, description, tooltip and input group with default values', () => {
|
||||
render(<EvaluationCadence />);
|
||||
expect(screen.getByText('How often to check')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'How frequently this alert checks your data. Default: Every 1 minute',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('evaluation-cadence-tooltip-icon'),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId(EVALUATION_CADENCE_INPUT_GROUP),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText('Enter time')).toHaveValue(1);
|
||||
expect(screen.getByText('Minutes')).toBeInTheDocument();
|
||||
expect(screen.getByText(ADD_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should hide the input group when add custom schedule button is clicked', () => {
|
||||
render(<EvaluationCadence />);
|
||||
|
||||
expect(
|
||||
screen.getByTestId(EVALUATION_CADENCE_INPUT_GROUP),
|
||||
).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByText(ADD_CUSTOM_SCHEDULE_TEXT));
|
||||
|
||||
expect(
|
||||
screen.queryByTestId(EVALUATION_CADENCE_INPUT_GROUP),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId(EVALUATION_CADENCE_DETAILS_TEST_ID),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show the edit custom schedule component in default mode', () => {
|
||||
render(<EvaluationCadence />);
|
||||
expect(screen.queryByTestId('edit-custom-schedule')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the custom schedule text when the mode is custom with selected values', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
custom: {
|
||||
repeatEvery: 'day',
|
||||
startAt: '00:00:00',
|
||||
occurence: [],
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(<EvaluationCadence />);
|
||||
expect(screen.getByTestId('edit-custom-schedule')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show evaluation cadence details component in default mode', () => {
|
||||
render(<EvaluationCadence />);
|
||||
expect(
|
||||
screen.queryByTestId(EVALUATION_CADENCE_DETAILS_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show evaluation cadence details component when clicked on add custom schedule button', () => {
|
||||
render(<EvaluationCadence />);
|
||||
|
||||
expect(
|
||||
screen.queryByTestId(EVALUATION_CADENCE_DETAILS_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByText(ADD_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText(ADD_CUSTOM_SCHEDULE_TEXT));
|
||||
expect(
|
||||
screen.getByTestId(EVALUATION_CADENCE_DETAILS_TEST_ID),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show evaluation cadence preview component in default mode', () => {
|
||||
render(<EvaluationCadence />);
|
||||
expect(
|
||||
screen.queryByTestId(EVALUATION_CADENCE_PREVIEW_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show evaluation cadence preview component when clicked on preview button in custom mode', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(<EvaluationCadence />);
|
||||
expect(
|
||||
screen.queryByTestId(EVALUATION_CADENCE_PREVIEW_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText('Preview'));
|
||||
expect(
|
||||
screen.getByTestId(EVALUATION_CADENCE_PREVIEW_TEST_ID),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,316 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import * as alertState from 'container/CreateAlertV2/context';
|
||||
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
|
||||
import { AdvancedOptionsState } from 'container/CreateAlertV2/context/types';
|
||||
|
||||
import EvaluationCadenceDetails from '../EvaluationCadence/EvaluationCadenceDetails';
|
||||
import { createMockAlertContextState } from './testUtils';
|
||||
|
||||
const ENTER_RRULE_PLACEHOLDER = 'Enter RRule';
|
||||
|
||||
jest.mock('dayjs', () => {
|
||||
const actualDayjs = jest.requireActual('dayjs');
|
||||
const mockDayjs = (date?: any): any => {
|
||||
if (date) {
|
||||
return actualDayjs(date);
|
||||
}
|
||||
// 21 Jan 2025
|
||||
return actualDayjs('2025-01-21T16:31:36.982Z');
|
||||
};
|
||||
Object.keys(actualDayjs).forEach((key) => {
|
||||
if (typeof (actualDayjs as any)[key] === 'function') {
|
||||
(mockDayjs as any)[key] = (actualDayjs as any)[key];
|
||||
}
|
||||
});
|
||||
(mockDayjs as any).tz = {
|
||||
guess: (): string => 'Asia/Saigon',
|
||||
};
|
||||
return mockDayjs;
|
||||
});
|
||||
|
||||
const INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE: AdvancedOptionsState = {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
},
|
||||
};
|
||||
|
||||
const mockSetAdvancedOptions = jest.fn();
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE,
|
||||
setAdvancedOptions: mockSetAdvancedOptions,
|
||||
}),
|
||||
);
|
||||
|
||||
const mockSetIsOpen = jest.fn();
|
||||
const mockSetIsCustomScheduleButtonVisible = jest.fn();
|
||||
|
||||
const SCHEDULE_PREVIEW_TEST_ID = 'schedule-preview';
|
||||
const NO_SCHEDULE_TEST_ID = 'no-schedule';
|
||||
const EDITOR_VIEW_TEST_ID = 'editor-view';
|
||||
const RULE_VIEW_TEST_ID = 'rrule-view';
|
||||
const SAVE_CUSTOM_SCHEDULE_TEXT = 'Save Custom Schedule';
|
||||
|
||||
describe('EvaluationCadenceDetails', () => {
|
||||
it('should render the evaluation cadence details component with editor mode in daily occurence by default', () => {
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('Add Custom Schedule')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId(EDITOR_VIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('rrule-view')).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('REPEAT EVERY')).toBeInTheDocument();
|
||||
expect(screen.getByText('AT')).toBeInTheDocument();
|
||||
expect(screen.getByText('TIMEZONE')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId(SCHEDULE_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Discard')).toBeInTheDocument();
|
||||
expect(screen.getByText(SAVE_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('when switching to rrule mode, the rrule view should be rendered with no schedule preview', () => {
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByText('RRule'));
|
||||
expect(screen.getByTestId(RULE_VIEW_TEST_ID)).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.queryByTestId(SCHEDULE_PREVIEW_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId(NO_SCHEDULE_TEST_ID)).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('STARTING ON')).toBeInTheDocument();
|
||||
expect(screen.getByText('AT')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByPlaceholderText(ENTER_RRULE_PLACEHOLDER),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Discard')).toBeInTheDocument();
|
||||
expect(screen.getByText(SAVE_CUSTOM_SCHEDULE_TEXT)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('when showing weekly occurence, the occurence options should be rendered', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence,
|
||||
custom: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence
|
||||
.custom,
|
||||
repeatEvery: 'week',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Verify that the "ON DAY(S)" section is rendered for weekly occurrence
|
||||
expect(screen.getByText('ON DAY(S)')).toBeInTheDocument();
|
||||
|
||||
// Verify that the schedule preview is shown as today is selected by default
|
||||
expect(screen.getByTestId(SCHEDULE_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(NO_SCHEDULE_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render schedule preview in weekly occurence when days are selected', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence,
|
||||
custom: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence
|
||||
.custom,
|
||||
repeatEvery: 'week',
|
||||
occurence: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Verify that the schedule preview is shown because days are selected
|
||||
expect(screen.getByTestId(SCHEDULE_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(NO_SCHEDULE_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('when showing monthly occurence, the occurence options should be rendered', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence,
|
||||
custom: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence
|
||||
.custom,
|
||||
repeatEvery: 'month',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Verify that the "ON DAY(S)" section is rendered for monthly occurrence
|
||||
expect(screen.getByText('ON DAY(S)')).toBeInTheDocument();
|
||||
|
||||
// Verify that the schedule preview is shown as today is selected by default
|
||||
expect(screen.getByTestId(SCHEDULE_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(NO_SCHEDULE_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('render schedule preview in monthly occurence when days are selected', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence,
|
||||
custom: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence
|
||||
.custom,
|
||||
repeatEvery: 'month',
|
||||
occurence: ['1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Verify that the schedule preview is shown because days are selected
|
||||
expect(screen.getByTestId(SCHEDULE_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(NO_SCHEDULE_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('discard action works correctly', () => {
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByText('Discard'));
|
||||
expect(mockSetIsOpen).toHaveBeenCalledWith(false);
|
||||
expect(mockSetIsCustomScheduleButtonVisible).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('save custom schedule action works correctly', () => {
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByText(SAVE_CUSTOM_SCHEDULE_TEXT));
|
||||
expect(mockSetAdvancedOptions).toHaveBeenCalledTimes(2);
|
||||
expect(mockSetAdvancedOptions).toHaveBeenCalledWith({
|
||||
type: 'SET_EVALUATION_CADENCE',
|
||||
payload: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence,
|
||||
custom: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE_WITH_CUSTOM_SCHEDULE.evaluationCadence
|
||||
.custom,
|
||||
// today selected by default
|
||||
occurence: [new Date().getDate().toString()],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockSetAdvancedOptions).toHaveBeenCalledWith({
|
||||
type: 'SET_EVALUATION_CADENCE_MODE',
|
||||
payload: 'custom',
|
||||
});
|
||||
});
|
||||
|
||||
describe('alert context mock state verification', () => {
|
||||
it('should set the evaluation cadence tab to rrule from custom', () => {
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Switch to RRule tab
|
||||
fireEvent.click(screen.getByText('RRule'));
|
||||
expect(screen.getByTestId(RULE_VIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(EDITOR_VIEW_TEST_ID)).not.toBeInTheDocument();
|
||||
|
||||
// Type in the text box
|
||||
expect(screen.getByPlaceholderText(ENTER_RRULE_PLACEHOLDER)).toHaveValue('');
|
||||
fireEvent.change(screen.getByPlaceholderText(ENTER_RRULE_PLACEHOLDER), {
|
||||
target: { value: 'RRULE:FREQ=DAILY' },
|
||||
});
|
||||
// Ensure text box content is updated
|
||||
expect(screen.getByPlaceholderText(ENTER_RRULE_PLACEHOLDER)).toHaveValue(
|
||||
'RRULE:FREQ=DAILY',
|
||||
);
|
||||
});
|
||||
|
||||
it('ensure rrule content is not modified by previous test', () => {
|
||||
render(
|
||||
<EvaluationCadenceDetails
|
||||
isOpen
|
||||
setIsOpen={mockSetIsOpen}
|
||||
setIsCustomScheduleButtonVisible={mockSetIsCustomScheduleButtonVisible}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Switch to RRule tab
|
||||
fireEvent.click(screen.getByText('RRule'));
|
||||
expect(screen.getByTestId(RULE_VIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(EDITOR_VIEW_TEST_ID)).not.toBeInTheDocument();
|
||||
|
||||
// Verify text box content
|
||||
expect(screen.getByPlaceholderText(ENTER_RRULE_PLACEHOLDER)).toHaveValue('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,88 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import * as alertState from 'container/CreateAlertV2/context';
|
||||
import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/constants';
|
||||
|
||||
import { TIMEZONE_DATA } from '../constants';
|
||||
import EvaluationCadencePreview, {
|
||||
ScheduleList,
|
||||
} from '../EvaluationCadence/EvaluationCadencePreview';
|
||||
import { createMockAlertContextState } from './testUtils';
|
||||
|
||||
jest
|
||||
.spyOn(alertState, 'useCreateAlertState')
|
||||
.mockReturnValue(createMockAlertContextState());
|
||||
|
||||
const mockSetIsOpen = jest.fn();
|
||||
|
||||
describe('EvaluationCadencePreview', () => {
|
||||
it('should render list of dates when schedule is generated', () => {
|
||||
render(<EvaluationCadencePreview isOpen setIsOpen={mockSetIsOpen} />);
|
||||
expect(screen.getByTestId('schedule-preview')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render empty state when no schedule is generated', () => {
|
||||
jest.spyOn(alertState, 'useCreateAlertState').mockReturnValueOnce(
|
||||
createMockAlertContextState({
|
||||
advancedOptions: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
evaluationCadence: {
|
||||
...INITIAL_ADVANCED_OPTIONS_STATE.evaluationCadence,
|
||||
mode: 'custom',
|
||||
custom: {
|
||||
repeatEvery: 'week',
|
||||
startAt: '00:00:00',
|
||||
occurence: [],
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
render(<EvaluationCadencePreview isOpen setIsOpen={mockSetIsOpen} />);
|
||||
expect(screen.getByTestId('no-schedule')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ScheduleList', () => {
|
||||
const schedule = [
|
||||
new Date('2024-01-15T00:00:00Z'),
|
||||
new Date('2024-01-16T00:00:00Z'),
|
||||
new Date('2024-01-17T00:00:00Z'),
|
||||
new Date('2024-01-18T00:00:00Z'),
|
||||
new Date('2024-01-19T00:00:00Z'),
|
||||
];
|
||||
it('should render list of dates when schedule is generated', () => {
|
||||
render(
|
||||
<ScheduleList
|
||||
schedule={schedule}
|
||||
currentTimezone={TIMEZONE_DATA[0].value}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.queryByText(
|
||||
'Please fill the relevant information to generate a schedule',
|
||||
),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
// Verify all dates are rendered correctly
|
||||
schedule.forEach((date) => {
|
||||
const dateString = date.toLocaleDateString('en-US', {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
const timeString = date.toLocaleTimeString('en-US', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
const combinedString = `${dateString}, ${timeString}`;
|
||||
expect(screen.getByText(combinedString)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Verify timezone is rendered correctly with each date
|
||||
const timezoneElements = screen.getAllByText(TIMEZONE_DATA[0].label);
|
||||
expect(timezoneElements).toHaveLength(schedule.length);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,24 @@
|
||||
import {
|
||||
INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
INITIAL_ALERT_STATE,
|
||||
INITIAL_ALERT_THRESHOLD_STATE,
|
||||
INITIAL_EVALUATION_WINDOW_STATE,
|
||||
} from 'container/CreateAlertV2/context/constants';
|
||||
import { ICreateAlertContextProps } from 'container/CreateAlertV2/context/types';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
export const createMockAlertContextState = (
|
||||
overrides?: Partial<ICreateAlertContextProps>,
|
||||
): ICreateAlertContextProps => ({
|
||||
alertState: INITIAL_ALERT_STATE,
|
||||
setAlertState: jest.fn(),
|
||||
alertType: AlertTypes.METRICS_BASED_ALERT,
|
||||
setAlertType: jest.fn(),
|
||||
thresholdState: INITIAL_ALERT_THRESHOLD_STATE,
|
||||
setThresholdState: jest.fn(),
|
||||
advancedOptions: INITIAL_ADVANCED_OPTIONS_STATE,
|
||||
setAdvancedOptions: jest.fn(),
|
||||
evaluationWindow: INITIAL_EVALUATION_WINDOW_STATE,
|
||||
setEvaluationWindow: jest.fn(),
|
||||
...overrides,
|
||||
});
|
||||
@ -23,6 +23,7 @@ export const EVALUATION_WINDOW_TIMEFRAME = {
|
||||
};
|
||||
|
||||
export const EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS = [
|
||||
{ label: 'DAY', value: 'day' },
|
||||
{ label: 'WEEK', value: 'week' },
|
||||
{ label: 'MONTH', value: 'month' },
|
||||
];
|
||||
|
||||
@ -0,0 +1,389 @@
|
||||
.evaluation-settings-container {
|
||||
margin: 16px;
|
||||
|
||||
.evaluate-alert-conditions-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
background-color: var(--bg-ink-400);
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
margin-bottom: 16px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.evaluate-alert-conditions-separator {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
border-top: 1px dashed var(--bg-slate-400);
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-options-container {
|
||||
.ant-collapse {
|
||||
.ant-collapse-item {
|
||||
.ant-collapse-header {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
|
||||
.ant-collapse-header-text {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Inter;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
.ant-collapse-content-box {
|
||||
background-color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-popover-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ant-popover-content {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
border-radius: 4px;
|
||||
padding: 0;
|
||||
margin: 10px;
|
||||
|
||||
.ant-popover-inner {
|
||||
background-color: var(--bg-ink-400);
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
.evaluation-window-popover {
|
||||
min-width: 500px;
|
||||
|
||||
.evaluation-window-content {
|
||||
display: flex;
|
||||
|
||||
.evaluation-window-content-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border-right: 1px solid var(--bg-slate-400);
|
||||
padding: 12px 16px;
|
||||
min-width: 250px;
|
||||
min-height: 300px;
|
||||
|
||||
.evaluation-window-content-item-label {
|
||||
color: var(--bg-slate-50);
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.evaluation-window-content-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.evaluation-window-content-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 0 -16px;
|
||||
padding: 4px 16px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--bg-slate-500);
|
||||
border-left: 2px solid var(--bg-robin-500);
|
||||
.ant-typography {
|
||||
font-weight: 500;
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--bg-slate-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selection-content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
width: 400px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-window-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
background-color: var(--bg-ink-300);
|
||||
border-top: 1px solid var(--bg-slate-400);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
background-color: var(--bg-ink-200);
|
||||
border: 1px solid var(--bg-slate-200);
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-window-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
width: 400px;
|
||||
min-height: 300px;
|
||||
padding: 16px;
|
||||
|
||||
.select-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-slate-50);
|
||||
font-size: 11px;
|
||||
line-height: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.time-select-group {
|
||||
.ant-input-group {
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
|
||||
.ant-select {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
width: 60%;
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
height: 32px;
|
||||
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input {
|
||||
background-color: var(--bg-ink-300);
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
color: var(--bg-vanilla-100);
|
||||
height: 32px;
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.evaluation-settings-container {
|
||||
.evaluate-alert-conditions-container {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.evaluate-alert-conditions-separator {
|
||||
border-top: 1px dashed var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.evaluate-alert-conditions-button-left {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.evaluate-alert-conditions-button-right {
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
.evaluate-alert-conditions-button-right-text {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-options-container {
|
||||
.ant-collapse {
|
||||
.ant-collapse-item {
|
||||
.ant-collapse-header {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-collapse-header-text {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-content {
|
||||
.ant-collapse-content-box {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-popover-content {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.ant-popover-inner {
|
||||
background-color: var(--bg-vanilla-200);
|
||||
|
||||
.evaluation-window-popover {
|
||||
.evaluation-window-content {
|
||||
.evaluation-window-content-item {
|
||||
border-right: 1px solid var(--bg-vanilla-300);
|
||||
|
||||
.evaluation-window-content-item-label {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
|
||||
.evaluation-window-content-list {
|
||||
.evaluation-window-content-list-item {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border-left: 2px solid var(--bg-robin-500);
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selection-content {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-window-footer {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border-top: 1px solid var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.evaluation-window-details {
|
||||
.select-group {
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-300);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-typography {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
|
||||
.ant-select-selector {
|
||||
background-color: var(--bg-vanilla-300);
|
||||
border: 1px solid var(--bg-vanilla-300);
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--bg-vanilla-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,8 @@ export interface IAdvancedOptionItemProps {
|
||||
title: string;
|
||||
description: string;
|
||||
input: JSX.Element;
|
||||
tooltipText?: string;
|
||||
onToggle?: () => void;
|
||||
}
|
||||
|
||||
export enum RollingWindowTimeframes {
|
||||
@ -42,6 +44,12 @@ export interface IEvaluationWindowDetailsProps {
|
||||
export interface IEvaluationCadenceDetailsProps {
|
||||
isOpen: boolean;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
setIsCustomScheduleButtonVisible: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export interface IEvaluationCadencePreviewProps {
|
||||
isOpen: boolean;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export interface TimeInputProps {
|
||||
@ -51,3 +59,13 @@ export interface TimeInputProps {
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface IEditCustomScheduleProps {
|
||||
setIsEvaluationCadenceDetailsVisible: (isOpen: boolean) => void;
|
||||
setIsPreviewVisible: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export interface IScheduleListProps {
|
||||
schedule: Date[] | null;
|
||||
currentTimezone: string;
|
||||
}
|
||||
|
||||
@ -94,14 +94,14 @@ export const INITIAL_ADVANCED_OPTIONS_STATE: AdvancedOptionsState = {
|
||||
timeUnit: UniversalYAxisUnit.MINUTES,
|
||||
},
|
||||
custom: {
|
||||
repeatEvery: 'week',
|
||||
startAt: '00:00:00',
|
||||
repeatEvery: 'day',
|
||||
startAt: dayjs().format('HH:mm:ss'),
|
||||
occurence: [],
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
},
|
||||
rrule: {
|
||||
date: dayjs(),
|
||||
startAt: '00:00:00',
|
||||
startAt: dayjs().format('HH:mm:ss'),
|
||||
rrule: '',
|
||||
},
|
||||
},
|
||||
@ -111,7 +111,7 @@ export const INITIAL_EVALUATION_WINDOW_STATE: EvaluationWindowState = {
|
||||
windowType: 'rolling',
|
||||
timeframe: '5m0s',
|
||||
startingAt: {
|
||||
time: '00:00:00',
|
||||
time: dayjs().format('HH:mm:ss'),
|
||||
number: '1',
|
||||
timezone: TIMEZONE_DATA[0].value,
|
||||
unit: UniversalYAxisUnit.MINUTES,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user