mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 20:38:59 +08:00
Schedule maintainence release changes (#5585)
* feat: schedule maintenance feedback fixes * feat: schedule maintenance feedback fixes * feat: code refactor * feat: code refactor * feat: fixed incorrect payload values from start and endTime * feat: sorted list by updatedAt * feat: removed dependency on BE response prop - kind * feat: fixed timezone switching and adding different timezones
This commit is contained in:
parent
220edd139a
commit
fff9954da2
@ -1,6 +1,7 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import { Dayjs } from 'dayjs';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
|
||||||
import { Recurrence } from './getAllDowntimeSchedules';
|
import { Recurrence } from './getAllDowntimeSchedules';
|
||||||
@ -11,8 +12,8 @@ export interface DowntimeSchedulePayload {
|
|||||||
alertIds: string[];
|
alertIds: string[];
|
||||||
schedule: {
|
schedule: {
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
startTime?: string;
|
startTime?: string | Dayjs;
|
||||||
endTime?: string;
|
endTime?: string | Dayjs;
|
||||||
recurrence?: Recurrence;
|
recurrence?: Recurrence;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import { Option } from 'container/PlannedDowntime/DropdownWithSubMenu/DropdownWithSubMenu';
|
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
|
||||||
export type Recurrence = {
|
export type Recurrence = {
|
||||||
@ -28,6 +28,7 @@ export interface DowntimeSchedules {
|
|||||||
createdBy: string | null;
|
createdBy: string | null;
|
||||||
updatedAt: string | null;
|
updatedAt: string | null;
|
||||||
updatedBy: string | null;
|
updatedBy: string | null;
|
||||||
|
kind: string | null;
|
||||||
}
|
}
|
||||||
export type PayloadProps = { data: DowntimeSchedules[] };
|
export type PayloadProps = { data: DowntimeSchedules[] };
|
||||||
|
|
||||||
|
@ -19,6 +19,5 @@ export enum FeatureKeys {
|
|||||||
OSS = 'OSS',
|
OSS = 'OSS',
|
||||||
ONBOARDING = 'ONBOARDING',
|
ONBOARDING = 'ONBOARDING',
|
||||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||||
PLANNED_MAINTENANCE = 'PLANNED_MAINTENANCE',
|
|
||||||
GATEWAY = 'GATEWAY',
|
GATEWAY = 'GATEWAY',
|
||||||
}
|
}
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
.options {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.option {
|
|
||||||
padding: 8px 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:hover {
|
|
||||||
background-color: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 50%;
|
|
||||||
z-index: 1;
|
|
||||||
background-color: var(--bg-ink-400);
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
max-height: 300px;
|
|
||||||
overflow-y: auto;
|
|
||||||
width: 160px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
.submenu-checkbox {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-submenu {
|
|
||||||
.dropdown-input {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 4.5px 11px;
|
|
||||||
color: rgba(255, 255, 255, 0.85);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.6153846153846154;
|
|
||||||
list-style: none;
|
|
||||||
font-family: Inter;
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 0;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-popover-inner {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-container {
|
|
||||||
position: relative;
|
|
||||||
--arrow-x: 175px;
|
|
||||||
--arrow-y: 266px;
|
|
||||||
width: 350px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 4px;
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.6153846153846154;
|
|
||||||
list-style: none;
|
|
||||||
font-family: Inter;
|
|
||||||
z-index: 1050;
|
|
||||||
overflow: hidden;
|
|
||||||
font-variant: initial;
|
|
||||||
background-color: var(--bg-ink-400);
|
|
||||||
border-radius: 2px;
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-popover {
|
|
||||||
.ant-popover-inner {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
.option {
|
|
||||||
padding: 8px 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option:hover {
|
|
||||||
background-color: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-option-btn {
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
align-self: flex-end;
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-header {
|
|
||||||
color: var(--bg-vanilla-200);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.option:hover {
|
|
||||||
background-color: var(--bg-vanilla-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-container {
|
|
||||||
background-color: var(--bg-vanilla-100);
|
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-submenu {
|
|
||||||
.dropdown-input {
|
|
||||||
color: var(--bg-slate-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.options-container {
|
|
||||||
color: var(--bg-slate-100);
|
|
||||||
background-color: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
.submenu-header {
|
|
||||||
color: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,230 +0,0 @@
|
|||||||
import './DropdownWithSubMenu.styles.scss';
|
|
||||||
|
|
||||||
import { CheckOutlined } from '@ant-design/icons';
|
|
||||||
import { Button, Checkbox, Popover, Typography } from 'antd';
|
|
||||||
import { CheckboxChangeEvent } from 'antd/es/checkbox';
|
|
||||||
import { FormInstance } from 'antd/lib';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { popupContainer } from 'utils/selectPopupContainer';
|
|
||||||
|
|
||||||
import { recurrenceOptions } from '../PlannedDowntimeutils';
|
|
||||||
|
|
||||||
interface SubOption {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Option {
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
submenu?: SubOption[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DropdownProps {
|
|
||||||
options: Option[];
|
|
||||||
form: FormInstance<any>;
|
|
||||||
setRecurrenceOption: React.Dispatch<
|
|
||||||
React.SetStateAction<string | undefined | null>
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DropdownWithSubMenu(props: DropdownProps): JSX.Element {
|
|
||||||
const { options, form, setRecurrenceOption } = props;
|
|
||||||
const [selectedOption, setSelectedOption] = useState<Option | null>(
|
|
||||||
form.getFieldValue('recurrenceSelect')
|
|
||||||
? form.getFieldValue('recurrenceSelect').repeatType
|
|
||||||
: recurrenceOptions.doesNotRepeat,
|
|
||||||
);
|
|
||||||
const [selectedSubMenuOption, setSelectedSubMenuOption] = useState<string[]>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [isSubMenuOpen, setSubMenuIsOpen] = useState(false);
|
|
||||||
const [selectionCompleted, setSelectionCompleted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => setRecurrenceOption?.(selectedOption?.value), [
|
|
||||||
selectedOption,
|
|
||||||
setRecurrenceOption,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleSelectOption = (option: Option): void => {
|
|
||||||
setSelectedOption(option);
|
|
||||||
if (isSubMenuOpen && !option.submenu?.length) {
|
|
||||||
setSubMenuIsOpen(false);
|
|
||||||
setSelectionCompleted(true);
|
|
||||||
} else {
|
|
||||||
setIsOpen(!!option.submenu?.length);
|
|
||||||
setSelectionCompleted(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form.setFieldValue('recurrenceSelect', {
|
|
||||||
repeatType: selectedOption,
|
|
||||||
repeatOn: selectedOption?.value === 'weekly' ? selectedSubMenuOption : [],
|
|
||||||
});
|
|
||||||
}, [form, selectedOption, selectedSubMenuOption]);
|
|
||||||
|
|
||||||
const handleInputChange = (): void => {
|
|
||||||
setIsOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOptionKeyDown = (
|
|
||||||
e: React.KeyboardEvent<HTMLDivElement>,
|
|
||||||
option: Option,
|
|
||||||
): void => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleSelectOption(option);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckboxChange = (
|
|
||||||
event: CheckboxChangeEvent,
|
|
||||||
value: string,
|
|
||||||
): void => {
|
|
||||||
const { checked } = event.target;
|
|
||||||
let selectedSubMenuOptions: string[] = [...(selectedSubMenuOption || [])];
|
|
||||||
if (!checked) {
|
|
||||||
selectedSubMenuOptions = selectedSubMenuOptions.filter(
|
|
||||||
(item) => item !== value,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
selectedSubMenuOptions.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSelectedSubMenuOption(selectedSubMenuOptions);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveOptionClick = (): void => {
|
|
||||||
setSubMenuIsOpen(false);
|
|
||||||
setSelectionCompleted(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectionCompleted) {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
}, [selectionCompleted]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="dropdown-submenu">
|
|
||||||
<Popover
|
|
||||||
getPopupContainer={popupContainer}
|
|
||||||
open={isOpen}
|
|
||||||
onOpenChange={(visible): void => {
|
|
||||||
if (!visible) {
|
|
||||||
setSubMenuIsOpen(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
content={
|
|
||||||
<div className="options-container">
|
|
||||||
<div className="options">
|
|
||||||
{options.map((option) => {
|
|
||||||
if (option.value === recurrenceOptions.weekly.value) {
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
key={option.value}
|
|
||||||
placement="right"
|
|
||||||
arrow={false}
|
|
||||||
trigger="click"
|
|
||||||
rootClassName="submenu-popover"
|
|
||||||
autoAdjustOverflow
|
|
||||||
open={isSubMenuOpen}
|
|
||||||
content={
|
|
||||||
<div className="submenu-container">
|
|
||||||
<Typography.Text className="submenu-header">
|
|
||||||
repeats weekly on
|
|
||||||
</Typography.Text>
|
|
||||||
{option.submenu?.map((subMenuOption) => (
|
|
||||||
<Checkbox
|
|
||||||
onChange={(e): void =>
|
|
||||||
handleCheckboxChange(e, subMenuOption.value)
|
|
||||||
}
|
|
||||||
className="submenu-checkbox"
|
|
||||||
key={subMenuOption.value}
|
|
||||||
>
|
|
||||||
{subMenuOption.label}
|
|
||||||
</Checkbox>
|
|
||||||
))}
|
|
||||||
<Button
|
|
||||||
icon={<CheckOutlined />}
|
|
||||||
type="primary"
|
|
||||||
className="save-option-btn"
|
|
||||||
onClick={handleSaveOptionClick}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
key={option.value}
|
|
||||||
className="option"
|
|
||||||
role="option"
|
|
||||||
aria-selected={selectedOption === option}
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={(): void => {
|
|
||||||
handleSelectOption(option);
|
|
||||||
setSubMenuIsOpen(true);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e): void => handleOptionKeyDown(e, option)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={option.value}
|
|
||||||
className="option"
|
|
||||||
role="option"
|
|
||||||
aria-selected={selectedOption === option}
|
|
||||||
tabIndex={0}
|
|
||||||
onClick={(): void => {
|
|
||||||
handleSelectOption(option);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e): void => handleOptionKeyDown(e, option)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
trigger="click"
|
|
||||||
arrow={false}
|
|
||||||
autoAdjustOverflow
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Select option..."
|
|
||||||
className="dropdown-input"
|
|
||||||
value={selectedOption?.label}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
onClick={(): void => setIsOpen(true)}
|
|
||||||
/>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const recurrenceOption: Option[] = [
|
|
||||||
recurrenceOptions.doesNotRepeat,
|
|
||||||
recurrenceOptions.daily,
|
|
||||||
{
|
|
||||||
...recurrenceOptions.weekly,
|
|
||||||
submenu: [
|
|
||||||
{ label: 'Monday', value: 'monday' },
|
|
||||||
{ label: 'Tuesday', value: 'tuesday' },
|
|
||||||
{ label: 'Wednesday', value: 'wednesday' },
|
|
||||||
{ label: 'Thrusday', value: 'thrusday' },
|
|
||||||
{ label: 'Friday', value: 'friday' },
|
|
||||||
{ label: 'Saturday', value: 'saturday' },
|
|
||||||
{ label: 'Sunday', value: 'sunday' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
recurrenceOptions.monthly,
|
|
||||||
];
|
|
@ -65,6 +65,18 @@
|
|||||||
background: var(--bg-ink-300);
|
background: var(--bg-ink-300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert-rule-form {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: end;
|
||||||
|
|
||||||
|
.alert-rule-info {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--bg-vanilla-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-rule-tags {
|
.alert-rule-tags {
|
||||||
@ -169,7 +181,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-created-at {
|
.schedule-created-at {
|
||||||
border: 1px solid var(--bg-slate-500);
|
border: 1px solid var(--bg-slate-500);
|
||||||
background-color: var(--bg-ink-400);
|
background-color: var(--bg-ink-400);
|
||||||
border-top: 0px;
|
border-top: 0px;
|
||||||
@ -316,7 +328,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-view-modal {
|
.delete-schedule-modal {
|
||||||
width: calc(100% - 30px) !important; /* Adjust the 20px as needed */
|
width: calc(100% - 30px) !important; /* Adjust the 20px as needed */
|
||||||
max-width: 384px;
|
max-width: 384px;
|
||||||
.ant-modal-content {
|
.ant-modal-content {
|
||||||
@ -429,7 +441,7 @@
|
|||||||
color: var(--bg-ink-500);
|
color: var(--bg-ink-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.view-created-at {
|
.schedule-created-at {
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
border: 1px solid var(--bg-vanilla-300);
|
||||||
background-color: var(--bg-vanilla-100);
|
background-color: var(--bg-vanilla-100);
|
||||||
.ant-typography {
|
.ant-typography {
|
||||||
@ -459,7 +471,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-view-modal {
|
.delete-schedule-modal {
|
||||||
.ant-modal-content {
|
.ant-modal-content {
|
||||||
border: 1px solid var(--bg-vanilla-200);
|
border: 1px solid var(--bg-vanilla-200);
|
||||||
background: var(--bg-vanilla-100);
|
background: var(--bg-vanilla-100);
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
import React, { ChangeEvent, useState } from 'react';
|
import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
import { PlannedDowntimeDeleteModal } from './PlannedDowntimeDeleteModal';
|
import { PlannedDowntimeDeleteModal } from './PlannedDowntimeDeleteModal';
|
||||||
@ -48,6 +48,12 @@ export function PlannedDowntime(): JSX.Element {
|
|||||||
[data],
|
[data],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
form.resetFields();
|
||||||
|
}
|
||||||
|
}, [form, isOpen]);
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = React.useState<string | number>('');
|
const [searchValue, setSearchValue] = React.useState<string | number>('');
|
||||||
const [deleteData, setDeleteData] = useState<{ id: number; name: string }>();
|
const [deleteData, setDeleteData] = useState<{ id: number; name: string }>();
|
||||||
const [isEditMode, setEditMode] = useState<boolean>(false);
|
const [isEditMode, setEditMode] = useState<boolean>(false);
|
||||||
@ -128,17 +134,19 @@ export function PlannedDowntime(): JSX.Element {
|
|||||||
setEditMode={setEditMode}
|
setEditMode={setEditMode}
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
/>
|
/>
|
||||||
<PlannedDowntimeForm
|
{isOpen && (
|
||||||
alertOptions={alertOptions || []}
|
<PlannedDowntimeForm
|
||||||
initialValues={initialValues}
|
alertOptions={alertOptions || []}
|
||||||
isError={isError}
|
initialValues={initialValues}
|
||||||
isLoading={isLoading}
|
isError={isError}
|
||||||
isOpen={isOpen}
|
isLoading={isLoading}
|
||||||
setIsOpen={setIsOpen}
|
isOpen={isOpen}
|
||||||
refetchAllSchedules={refetchAllSchedules}
|
setIsOpen={setIsOpen}
|
||||||
isEditMode={isEditMode}
|
refetchAllSchedules={refetchAllSchedules}
|
||||||
form={form}
|
isEditMode={isEditMode}
|
||||||
/>
|
form={form}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PlannedDowntimeDeleteModal
|
<PlannedDowntimeDeleteModal
|
||||||
isDeleteLoading={isDeleteLoading}
|
isDeleteLoading={isDeleteLoading}
|
||||||
isDeleteModalOpen={isDeleteModalOpen}
|
isDeleteModalOpen={isDeleteModalOpen}
|
||||||
|
@ -22,20 +22,20 @@ export function PlannedDowntimeDeleteModal(
|
|||||||
onDeleteHandler,
|
onDeleteHandler,
|
||||||
downtimeSchedule,
|
downtimeSchedule,
|
||||||
} = props;
|
} = props;
|
||||||
const hideDeleteViewModal = (): void => {
|
const hideDeleteScheduleModal = (): void => {
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="delete-view-modal"
|
className="delete-schedule-modal"
|
||||||
title={<span className="title">Delete view</span>}
|
title={<span className="title">Delete Schedule</span>}
|
||||||
open={isDeleteModalOpen}
|
open={isDeleteModalOpen}
|
||||||
closable={false}
|
closable={false}
|
||||||
onCancel={hideDeleteViewModal}
|
onCancel={hideDeleteScheduleModal}
|
||||||
footer={[
|
footer={[
|
||||||
<Button
|
<Button
|
||||||
key="cancel"
|
key="cancel"
|
||||||
onClick={hideDeleteViewModal}
|
onClick={hideDeleteScheduleModal}
|
||||||
className="cancel-btn"
|
className="cancel-btn"
|
||||||
icon={<X size={16} />}
|
icon={<X size={16} />}
|
||||||
>
|
>
|
||||||
@ -48,12 +48,12 @@ export function PlannedDowntimeDeleteModal(
|
|||||||
className="delete-btn"
|
className="delete-btn"
|
||||||
disabled={isDeleteLoading}
|
disabled={isDeleteLoading}
|
||||||
>
|
>
|
||||||
Delete view
|
Delete Schedule
|
||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Typography.Text className="delete-text">
|
<Typography.Text className="delete-text">
|
||||||
{`Are you sure you want to delete - ${downtimeSchedule} view? Deleting a view is irreversible and cannot be undone.`}
|
{`Are you sure you want to delete - ${downtimeSchedule} schedule? Deleting a schedule is irreversible and cannot be undone.`}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable no-nested-ternary */
|
||||||
|
/* eslint-disable sonarjs/no-identical-functions */
|
||||||
import './PlannedDowntime.styles.scss';
|
import './PlannedDowntime.styles.scss';
|
||||||
import 'dayjs/locale/en';
|
import 'dayjs/locale/en';
|
||||||
|
|
||||||
@ -26,25 +28,30 @@ import {
|
|||||||
ModalTitle,
|
ModalTitle,
|
||||||
} from 'container/PipelinePage/PipelineListsView/styles';
|
} from 'container/PipelinePage/PipelineListsView/styles';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import timezone from 'dayjs/plugin/timezone';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { defaultTo, isEmpty } from 'lodash-es';
|
import { defaultTo, isEmpty } from 'lodash-es';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { ALL_TIME_ZONES } from 'utils/timeZoneUtil';
|
import { ALL_TIME_ZONES } from 'utils/timeZoneUtil';
|
||||||
|
|
||||||
import {
|
|
||||||
DropdownWithSubMenu,
|
|
||||||
Option,
|
|
||||||
recurrenceOption,
|
|
||||||
} from './DropdownWithSubMenu/DropdownWithSubMenu';
|
|
||||||
import { AlertRuleTags } from './PlannedDowntimeList';
|
import { AlertRuleTags } from './PlannedDowntimeList';
|
||||||
import {
|
import {
|
||||||
createEditDowntimeSchedule,
|
createEditDowntimeSchedule,
|
||||||
getAlertOptionsFromIds,
|
getAlertOptionsFromIds,
|
||||||
getDurationInfo,
|
getDurationInfo,
|
||||||
|
getEndTime,
|
||||||
|
handleTimeConvertion,
|
||||||
|
isScheduleRecurring,
|
||||||
recurrenceOptions,
|
recurrenceOptions,
|
||||||
|
recurrenceOptionWithSubmenu,
|
||||||
|
recurrenceWeeklyOptions,
|
||||||
} from './PlannedDowntimeutils';
|
} from './PlannedDowntimeutils';
|
||||||
|
|
||||||
dayjs.locale('en');
|
dayjs.locale('en');
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
interface PlannedDowntimeFormData {
|
interface PlannedDowntimeFormData {
|
||||||
name: string;
|
name: string;
|
||||||
startTime: dayjs.Dayjs | string;
|
startTime: dayjs.Dayjs | string;
|
||||||
@ -93,10 +100,19 @@ export function PlannedDowntimeForm(
|
|||||||
>([]);
|
>([]);
|
||||||
const alertRuleFormName = 'alertRules';
|
const alertRuleFormName = 'alertRules';
|
||||||
const [saveLoading, setSaveLoading] = useState(false);
|
const [saveLoading, setSaveLoading] = useState(false);
|
||||||
const [durationUnit, setDurationUnit] = useState<string>('m');
|
const [durationUnit, setDurationUnit] = useState<string>(
|
||||||
const [selectedRecurrenceOption, setSelectedRecurrenceOption] = useState<
|
getDurationInfo(initialValues.schedule?.recurrence?.duration as string)
|
||||||
string | null
|
?.unit || 'm',
|
||||||
>();
|
);
|
||||||
|
|
||||||
|
const [recurrenceType, setRecurrenceType] = useState<string | null>(
|
||||||
|
(initialValues.schedule?.recurrence?.repeatType as string) ||
|
||||||
|
recurrenceOptions.doesNotRepeat.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
const timezoneInitialValue = !isEmpty(initialValues.schedule?.timezone)
|
||||||
|
? (initialValues.schedule?.timezone as string)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
@ -107,9 +123,7 @@ export function PlannedDowntimeForm(
|
|||||||
|
|
||||||
const saveHanlder = useCallback(
|
const saveHanlder = useCallback(
|
||||||
async (values: PlannedDowntimeFormData) => {
|
async (values: PlannedDowntimeFormData) => {
|
||||||
const formatDate = (date: string | dayjs.Dayjs): string | undefined =>
|
const shouldKeepLocalTime = !isEditMode;
|
||||||
!isEmpty(date) ? dayjs(date).format('YYYY-MM-DDTHH:mm:ss[Z]') : undefined;
|
|
||||||
|
|
||||||
const createEditProps: DowntimeScheduleUpdatePayload = {
|
const createEditProps: DowntimeScheduleUpdatePayload = {
|
||||||
data: {
|
data: {
|
||||||
alertIds: values.alertRules
|
alertIds: values.alertRules
|
||||||
@ -117,9 +131,21 @@ export function PlannedDowntimeForm(
|
|||||||
.filter((alert) => alert !== undefined) as string[],
|
.filter((alert) => alert !== undefined) as string[],
|
||||||
name: values.name,
|
name: values.name,
|
||||||
schedule: {
|
schedule: {
|
||||||
startTime: formatDate(values.startTime),
|
startTime: handleTimeConvertion(
|
||||||
|
values.startTime,
|
||||||
|
timezoneInitialValue,
|
||||||
|
values.timezone,
|
||||||
|
shouldKeepLocalTime,
|
||||||
|
),
|
||||||
timezone: values.timezone,
|
timezone: values.timezone,
|
||||||
endTime: formatDate(values.endTime),
|
endTime: values.endTime
|
||||||
|
? handleTimeConvertion(
|
||||||
|
values.endTime,
|
||||||
|
timezoneInitialValue,
|
||||||
|
values.timezone,
|
||||||
|
shouldKeepLocalTime,
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
recurrence: values.recurrence as Recurrence,
|
recurrence: values.recurrence as Recurrence,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -152,25 +178,41 @@ export function PlannedDowntimeForm(
|
|||||||
}
|
}
|
||||||
setSaveLoading(false);
|
setSaveLoading(false);
|
||||||
},
|
},
|
||||||
[initialValues.id, isEditMode, notifications, refetchAllSchedules, setIsOpen],
|
[
|
||||||
|
initialValues.id,
|
||||||
|
isEditMode,
|
||||||
|
notifications,
|
||||||
|
refetchAllSchedules,
|
||||||
|
setIsOpen,
|
||||||
|
timezoneInitialValue,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
const onFinish = async (values: PlannedDowntimeFormData): Promise<void> => {
|
const onFinish = async (values: PlannedDowntimeFormData): Promise<void> => {
|
||||||
const recurrenceData: Recurrence | undefined =
|
const recurrenceData: Recurrence | undefined =
|
||||||
(values?.recurrenceSelect?.repeatType as Option)?.value ===
|
values?.recurrence?.repeatType === recurrenceOptions.doesNotRepeat.value
|
||||||
recurrenceOptions.doesNotRepeat.value
|
|
||||||
? undefined
|
? undefined
|
||||||
: {
|
: {
|
||||||
duration: values.recurrence?.duration
|
duration: values.recurrence?.duration
|
||||||
? `${values.recurrence?.duration}${durationUnit}`
|
? `${values.recurrence?.duration}${durationUnit}`
|
||||||
: undefined,
|
: undefined,
|
||||||
endTime: !isEmpty(values.endTime)
|
endTime: !isEmpty(values.endTime)
|
||||||
? (values.endTime as string)
|
? handleTimeConvertion(
|
||||||
|
values.endTime,
|
||||||
|
timezoneInitialValue,
|
||||||
|
values.timezone,
|
||||||
|
!isEditMode,
|
||||||
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
startTime: values.startTime as string,
|
startTime: handleTimeConvertion(
|
||||||
repeatOn: !values?.recurrenceSelect?.repeatOn?.length
|
values.startTime,
|
||||||
|
timezoneInitialValue,
|
||||||
|
values.timezone,
|
||||||
|
!isEditMode,
|
||||||
|
),
|
||||||
|
repeatOn: !values.recurrence?.repeatOn?.length
|
||||||
? undefined
|
? undefined
|
||||||
: values?.recurrenceSelect?.repeatOn,
|
: values.recurrence?.repeatOn,
|
||||||
repeatType: (values?.recurrenceSelect?.repeatType as Option)?.value,
|
repeatType: values.recurrence?.repeatType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const payloadValues = { ...values, recurrence: recurrenceData };
|
const payloadValues = { ...values, recurrence: recurrenceData };
|
||||||
@ -226,19 +268,15 @@ export function PlannedDowntimeForm(
|
|||||||
initialValues.alertIds || [],
|
initialValues.alertIds || [],
|
||||||
alertOptions,
|
alertOptions,
|
||||||
),
|
),
|
||||||
endTime: initialValues.schedule?.endTime
|
endTime: getEndTime(initialValues) ? dayjs(getEndTime(initialValues)) : '',
|
||||||
? dayjs(initialValues.schedule?.endTime)
|
|
||||||
: '',
|
|
||||||
startTime: initialValues.schedule?.startTime
|
startTime: initialValues.schedule?.startTime
|
||||||
? dayjs(initialValues.schedule?.startTime)
|
? dayjs(initialValues.schedule?.startTime)
|
||||||
: '',
|
: '',
|
||||||
recurrenceSelect: initialValues.schedule?.recurrence
|
|
||||||
? initialValues.schedule?.recurrence
|
|
||||||
: {
|
|
||||||
repeatType: recurrenceOptions.doesNotRepeat,
|
|
||||||
},
|
|
||||||
recurrence: {
|
recurrence: {
|
||||||
...initialValues.schedule?.recurrence,
|
...initialValues.schedule?.recurrence,
|
||||||
|
repeatType: !isScheduleRecurring(initialValues?.schedule)
|
||||||
|
? recurrenceOptions.doesNotRepeat.value
|
||||||
|
: (initialValues.schedule?.recurrence?.repeatType as string),
|
||||||
duration: getDurationInfo(
|
duration: getDurationInfo(
|
||||||
initialValues.schedule?.recurrence?.duration as string,
|
initialValues.schedule?.recurrence?.duration as string,
|
||||||
)?.value,
|
)?.value,
|
||||||
@ -283,101 +321,118 @@ export function PlannedDowntimeForm(
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
className="createForm"
|
className="createForm"
|
||||||
onFinish={onFinish}
|
onFinish={onFinish}
|
||||||
|
onValuesChange={(): void => {
|
||||||
|
setRecurrenceType(form.getFieldValue('recurrence')?.repeatType as string);
|
||||||
|
}}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item label="Name" name="name" rules={formValidationRules}>
|
||||||
label="Name"
|
|
||||||
name="name"
|
|
||||||
required={false}
|
|
||||||
rules={formValidationRules}
|
|
||||||
>
|
|
||||||
<Input placeholder="e.g. Upgrade downtime" />
|
<Input placeholder="e.g. Upgrade downtime" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Starts from"
|
label="Starts from"
|
||||||
name="startTime"
|
name="startTime"
|
||||||
required={false}
|
|
||||||
rules={formValidationRules}
|
rules={formValidationRules}
|
||||||
className="formItemWithBullet"
|
className="formItemWithBullet"
|
||||||
|
getValueProps={(value): any => ({
|
||||||
|
value: value ? dayjs(value).tz(timezoneInitialValue) : undefined,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
format={customFormat}
|
format={(date): string =>
|
||||||
|
dayjs(date).tz(timezoneInitialValue).format(customFormat)
|
||||||
|
}
|
||||||
showTime
|
showTime
|
||||||
renderExtraFooter={datePickerFooter}
|
renderExtraFooter={datePickerFooter}
|
||||||
|
showNow={false}
|
||||||
popupClassName="datePicker"
|
popupClassName="datePicker"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Repeats every"
|
label="Repeats every"
|
||||||
name="recurrenceSelect"
|
name={['recurrence', 'repeatType']}
|
||||||
required={false}
|
|
||||||
rules={formValidationRules}
|
rules={formValidationRules}
|
||||||
>
|
>
|
||||||
<DropdownWithSubMenu
|
<Select
|
||||||
options={recurrenceOption}
|
placeholder="Select option..."
|
||||||
form={form}
|
options={recurrenceOptionWithSubmenu}
|
||||||
setRecurrenceOption={setSelectedRecurrenceOption}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{selectedRecurrenceOption !== recurrenceOptions.doesNotRepeat.value && (
|
{recurrenceType === recurrenceOptions.weekly.value && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Duration"
|
label="Weekly occurernce"
|
||||||
name={['recurrence', 'duration']}
|
name={['recurrence', 'repeatOn']}
|
||||||
required={false}
|
|
||||||
rules={formValidationRules}
|
rules={formValidationRules}
|
||||||
>
|
>
|
||||||
<Input
|
<Select
|
||||||
addonAfter={
|
placeholder="Select option..."
|
||||||
<Select
|
mode="multiple"
|
||||||
defaultValue="m"
|
options={Object.values(recurrenceWeeklyOptions)}
|
||||||
value={
|
|
||||||
getDurationInfo(
|
|
||||||
initialValues.schedule?.recurrence?.duration as string,
|
|
||||||
)?.unit
|
|
||||||
}
|
|
||||||
onChange={(value): void => setDurationUnit(value)}
|
|
||||||
>
|
|
||||||
<Select.Option value="m">Mins</Select.Option>
|
|
||||||
<Select.Option value="h">Hours</Select.Option>
|
|
||||||
</Select>
|
|
||||||
}
|
|
||||||
className="duration-input"
|
|
||||||
type="number"
|
|
||||||
placeholder="Enter duration"
|
|
||||||
min={1}
|
|
||||||
onWheel={(e): void => e.currentTarget.blur()}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}{' '}
|
)}
|
||||||
<Form.Item
|
{recurrenceType &&
|
||||||
label="Timezone"
|
recurrenceType !== recurrenceOptions.doesNotRepeat.value && (
|
||||||
name="timezone"
|
<Form.Item
|
||||||
required={false}
|
label="Duration"
|
||||||
rules={formValidationRules}
|
name={['recurrence', 'duration']}
|
||||||
>
|
rules={formValidationRules}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
addonAfter={
|
||||||
|
<Select
|
||||||
|
defaultValue="m"
|
||||||
|
value={durationUnit}
|
||||||
|
onChange={(value): void => {
|
||||||
|
setDurationUnit(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select.Option value="m">Mins</Select.Option>
|
||||||
|
<Select.Option value="h">Hours</Select.Option>
|
||||||
|
</Select>
|
||||||
|
}
|
||||||
|
className="duration-input"
|
||||||
|
type="number"
|
||||||
|
placeholder="Enter duration"
|
||||||
|
min={1}
|
||||||
|
onWheel={(e): void => e.currentTarget.blur()}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Form.Item label="Timezone" name="timezone" rules={formValidationRules}>
|
||||||
<Select options={timeZoneItems} placeholder="Select timezone" showSearch />
|
<Select options={timeZoneItems} placeholder="Select timezone" showSearch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Ends on"
|
label="Ends on"
|
||||||
name="endTime"
|
name="endTime"
|
||||||
required={false}
|
required={recurrenceType === recurrenceOptions.doesNotRepeat.value}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required:
|
required: recurrenceType === recurrenceOptions.doesNotRepeat.value,
|
||||||
selectedRecurrenceOption === recurrenceOptions.doesNotRepeat.value,
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
className="formItemWithBullet"
|
className="formItemWithBullet"
|
||||||
|
getValueProps={(value): any => ({
|
||||||
|
value: value ? dayjs(value).tz(timezoneInitialValue) : undefined,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
format={customFormat}
|
format={(date): string =>
|
||||||
|
dayjs(date).tz(timezoneInitialValue).format(customFormat)
|
||||||
|
}
|
||||||
showTime
|
showTime
|
||||||
|
showNow={false}
|
||||||
renderExtraFooter={datePickerFooter}
|
renderExtraFooter={datePickerFooter}
|
||||||
popupClassName="datePicker"
|
popupClassName="datePicker"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div>
|
<div>
|
||||||
<Typography style={{ marginBottom: 8 }}>Silence Alerts</Typography>
|
<div className="alert-rule-form">
|
||||||
|
<Typography style={{ marginBottom: 8 }}>Silence Alerts</Typography>
|
||||||
|
<Typography style={{ marginBottom: 8 }} className="alert-rule-info">
|
||||||
|
(Leave empty to silence all alerts)
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
<Form.Item noStyle shouldUpdate>
|
<Form.Item noStyle shouldUpdate>
|
||||||
<AlertRuleTags
|
<AlertRuleTags
|
||||||
closable
|
closable
|
||||||
@ -385,7 +440,7 @@ export function PlannedDowntimeForm(
|
|||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name={alertRuleFormName} rules={formValidationRules}>
|
<Form.Item name={alertRuleFormName}>
|
||||||
<Select
|
<Select
|
||||||
placeholder="Search for alerts rules or groups..."
|
placeholder="Search for alerts rules or groups..."
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
@ -393,7 +448,11 @@ export function PlannedDowntimeForm(
|
|||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
tagRender={noTagRenderer}
|
tagRender={noTagRenderer}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
showSearch
|
||||||
options={alertOptions}
|
options={alertOptions}
|
||||||
|
filterOption={(input, option): boolean =>
|
||||||
|
(option?.label as string)?.toLowerCase()?.includes(input.toLowerCase())
|
||||||
|
}
|
||||||
notFoundContent={
|
notFoundContent={
|
||||||
isLoading ? (
|
isLoading ? (
|
||||||
<span>
|
<span>
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
formatDateTime,
|
formatDateTime,
|
||||||
getAlertOptionsFromIds,
|
getAlertOptionsFromIds,
|
||||||
getDuration,
|
getDuration,
|
||||||
|
getEndTime,
|
||||||
recurrenceInfo,
|
recurrenceInfo,
|
||||||
} from './PlannedDowntimeutils';
|
} from './PlannedDowntimeutils';
|
||||||
|
|
||||||
@ -122,6 +123,7 @@ export function CollapseListContent({
|
|||||||
updated_at,
|
updated_at,
|
||||||
updated_by_name,
|
updated_by_name,
|
||||||
alertOptions,
|
alertOptions,
|
||||||
|
timezone,
|
||||||
}: {
|
}: {
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
created_by_name?: string;
|
created_by_name?: string;
|
||||||
@ -131,6 +133,7 @@ export function CollapseListContent({
|
|||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
updated_by_name?: string;
|
updated_by_name?: string;
|
||||||
alertOptions?: DefaultOptionType[];
|
alertOptions?: DefaultOptionType[];
|
||||||
|
timezone?: string;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const renderItems = (title: string, value: ReactNode): JSX.Element => (
|
const renderItems = (title: string, value: ReactNode): JSX.Element => (
|
||||||
<div className="render-item-collapse-list">
|
<div className="render-item-collapse-list">
|
||||||
@ -180,6 +183,7 @@ export function CollapseListContent({
|
|||||||
'-'
|
'-'
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
|
{renderItems('Timezone', <Typography>{timezone || '-'}</Typography>)}
|
||||||
{renderItems('Repeats', <Typography>{recurrenceInfo(repeats)}</Typography>)}
|
{renderItems('Repeats', <Typography>{recurrenceInfo(repeats)}</Typography>)}
|
||||||
{renderItems(
|
{renderItems(
|
||||||
'Alerts silenced',
|
'Alerts silenced',
|
||||||
@ -220,13 +224,15 @@ export function CustomCollapseList(
|
|||||||
setModalOpen,
|
setModalOpen,
|
||||||
handleDeleteDowntime,
|
handleDeleteDowntime,
|
||||||
setEditMode,
|
setEditMode,
|
||||||
|
kind,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const scheduleTime = schedule?.startTime ? schedule.startTime : createdAt;
|
const scheduleTime = schedule?.startTime ? schedule.startTime : createdAt;
|
||||||
// Combine time and date
|
// Combine time and date
|
||||||
const formattedDateAndTime = `Start time ⎯ ${formatDateTime(
|
const formattedDateAndTime = `Start time ⎯ ${formatDateTime(
|
||||||
defaultTo(scheduleTime, ''),
|
defaultTo(scheduleTime, ''),
|
||||||
)}`;
|
)} ${schedule?.timezone}`;
|
||||||
|
const endTime = getEndTime({ kind, schedule });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -255,15 +261,19 @@ export function CustomCollapseList(
|
|||||||
<CollapseListContent
|
<CollapseListContent
|
||||||
created_at={defaultTo(createdAt, '')}
|
created_at={defaultTo(createdAt, '')}
|
||||||
created_by_name={defaultTo(createdBy, '')}
|
created_by_name={defaultTo(createdBy, '')}
|
||||||
timeframe={[schedule?.startTime, schedule?.endTime]}
|
timeframe={[
|
||||||
|
schedule?.startTime?.toString(),
|
||||||
|
typeof endTime === 'string' ? endTime : endTime?.toString(),
|
||||||
|
]}
|
||||||
repeats={schedule?.recurrence}
|
repeats={schedule?.recurrence}
|
||||||
updated_at={defaultTo(updatedAt, '')}
|
updated_at={defaultTo(updatedAt, '')}
|
||||||
updated_by_name={defaultTo(updatedBy, '')}
|
updated_by_name={defaultTo(updatedBy, '')}
|
||||||
alertOptions={alertOptions}
|
alertOptions={alertOptions}
|
||||||
|
timezone={defaultTo(schedule?.timezone, '')}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<div className="view-created-at">
|
<div className="schedule-created-at">
|
||||||
<CalendarClock size={14} />
|
<CalendarClock size={14} />
|
||||||
<Typography.Text>{formattedDateAndTime}</Typography.Text>
|
<Typography.Text>{formattedDateAndTime}</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
@ -314,6 +324,12 @@ export function PlannedDowntimeList({
|
|||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const tableData = (downtimeSchedules.data?.data?.data || [])
|
const tableData = (downtimeSchedules.data?.data?.data || [])
|
||||||
|
.sort((a, b): number => {
|
||||||
|
if (a?.updatedAt && b?.updatedAt) {
|
||||||
|
return b.updatedAt.localeCompare(a.updatedAt);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
?.filter(
|
?.filter(
|
||||||
(data) =>
|
(data) =>
|
||||||
data?.name?.includes(searchValue.toLocaleString()) ||
|
data?.name?.includes(searchValue.toLocaleString()) ||
|
||||||
|
@ -12,6 +12,7 @@ import updateDowntimeSchedule, {
|
|||||||
} from 'api/plannedDowntime/updateDowntimeSchedule';
|
} from 'api/plannedDowntime/updateDowntimeSchedule';
|
||||||
import { showErrorNotification } from 'components/ExplorerCard/utils';
|
import { showErrorNotification } from 'components/ExplorerCard/utils';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { isEmpty, isEqual } from 'lodash-es';
|
||||||
import { UseMutateAsyncFunction } from 'react-query';
|
import { UseMutateAsyncFunction } from 'react-query';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
|
||||||
@ -42,7 +43,8 @@ export const formatDateTime = (dateTimeString?: string | null): string => {
|
|||||||
if (!dateTimeString) {
|
if (!dateTimeString) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
}
|
}
|
||||||
return dayjs(dateTimeString).format('MMM DD, YYYY h:mm A');
|
|
||||||
|
return dayjs(dateTimeString.slice(0, 19)).format('MMM DD, YYYY h:mm A');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAlertOptionsFromIds = (
|
export const getAlertOptionsFromIds = (
|
||||||
@ -149,6 +151,15 @@ export const recurrenceOptions = {
|
|||||||
monthly: { label: 'Monthly', value: 'monthly' },
|
monthly: { label: 'Monthly', value: 'monthly' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const recurrenceWeeklyOptions = {
|
||||||
|
monday: { label: 'Monday', value: 'monday' },
|
||||||
|
tuesday: { label: 'Tuesday', value: 'tuesday' },
|
||||||
|
wednesday: { label: 'Wednesday', value: 'wednesday' },
|
||||||
|
thursday: { label: 'Thursday', value: 'thursday' },
|
||||||
|
friday: { label: 'Friday', value: 'friday' },
|
||||||
|
saturday: { label: 'Saturday', value: 'saturday' },
|
||||||
|
sunday: { label: 'Sunday', value: 'sunday' },
|
||||||
|
};
|
||||||
interface DurationInfo {
|
interface DurationInfo {
|
||||||
value: number;
|
value: number;
|
||||||
unit: string;
|
unit: string;
|
||||||
@ -160,17 +171,108 @@ export function getDurationInfo(
|
|||||||
if (!durationString) {
|
if (!durationString) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Regular expression to extract value and unit from the duration string
|
|
||||||
const durationRegex = /(\d+)([hms])/;
|
// Regular expressions to extract hours, minutes
|
||||||
// Match the value and unit parts in the duration string
|
const hoursRegex = /(\d+)h/;
|
||||||
const match = durationString.match(durationRegex);
|
const minutesRegex = /(\d+)m/;
|
||||||
if (match && match.length >= 3) {
|
|
||||||
// Extract value and unit from the match
|
// Extract hours, minutes from the duration string
|
||||||
const value = parseInt(match[1], 10);
|
const hoursMatch = durationString.match(hoursRegex);
|
||||||
const unit = match[2];
|
const minutesMatch = durationString.match(minutesRegex);
|
||||||
// Return duration info object
|
|
||||||
return { value, unit };
|
// Convert extracted values to integers, defaulting to 0 if not found
|
||||||
|
const hours = hoursMatch ? parseInt(hoursMatch[1], 10) : 0;
|
||||||
|
const minutes = minutesMatch ? parseInt(minutesMatch[1], 10) : 0;
|
||||||
|
|
||||||
|
// If there are no minutes and only hours, return the hours
|
||||||
|
if (hours > 0 && minutes === 0) {
|
||||||
|
return { value: hours, unit: 'h' };
|
||||||
}
|
}
|
||||||
// If no value or unit part found, return null
|
|
||||||
return null;
|
// Otherwise, calculate the total duration in minutes
|
||||||
|
const totalMinutes = hours * 60 + minutes;
|
||||||
|
return { value: totalMinutes, unit: 'm' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Option {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const recurrenceOptionWithSubmenu: Option[] = [
|
||||||
|
recurrenceOptions.doesNotRepeat,
|
||||||
|
recurrenceOptions.daily,
|
||||||
|
recurrenceOptions.weekly,
|
||||||
|
recurrenceOptions.monthly,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getRecurrenceOptionFromValue = (
|
||||||
|
value?: string | Option | null,
|
||||||
|
): Option | null | undefined => {
|
||||||
|
if (!value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return Object.values(recurrenceOptions).find(
|
||||||
|
(option) => option.value === value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getEndTime = ({
|
||||||
|
kind,
|
||||||
|
schedule,
|
||||||
|
}: Partial<
|
||||||
|
DowntimeSchedules & {
|
||||||
|
editMode: boolean;
|
||||||
|
}
|
||||||
|
>): string | dayjs.Dayjs => {
|
||||||
|
if (kind === 'fixed') {
|
||||||
|
return schedule?.endTime || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return schedule?.recurrence?.endTime || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isScheduleRecurring = (
|
||||||
|
schedule?: DowntimeSchedules['schedule'],
|
||||||
|
): boolean => (schedule ? !isEmpty(schedule?.recurrence) : false);
|
||||||
|
|
||||||
|
function convertUtcOffsetToTimezoneOffset(offsetMinutes: number): string {
|
||||||
|
const sign = offsetMinutes >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offsetMinutes);
|
||||||
|
const hours = String(Math.floor(absOffset / 60)).padStart(2, '0');
|
||||||
|
const minutes = String(absOffset % 60).padStart(2, '0');
|
||||||
|
return `${sign}${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatWithTimezone(
|
||||||
|
dateValue?: string | dayjs.Dayjs,
|
||||||
|
timezone?: string,
|
||||||
|
): string {
|
||||||
|
const parsedDate =
|
||||||
|
typeof dateValue === 'string' ? dateValue : dateValue?.format();
|
||||||
|
console.log('dateValue', parsedDate, 'timezone', timezone);
|
||||||
|
// Get the target timezone offset
|
||||||
|
const targetOffset = convertUtcOffsetToTimezoneOffset(
|
||||||
|
dayjs(dateValue).tz(timezone).utcOffset(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return `${parsedDate?.substring(0, 19)}${targetOffset}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleTimeConvertion(
|
||||||
|
dateValue: string | dayjs.Dayjs,
|
||||||
|
timezoneInit?: string,
|
||||||
|
timezone?: string,
|
||||||
|
shouldKeepLocalTime?: boolean,
|
||||||
|
): string {
|
||||||
|
const timezoneChanged = !isEqual(timezoneInit, timezone);
|
||||||
|
const initialTime = dayjs(dateValue).tz(timezoneInit);
|
||||||
|
|
||||||
|
const formattedTime = formatWithTimezone(initialTime, timezone);
|
||||||
|
return timezoneChanged
|
||||||
|
? formattedTime
|
||||||
|
: dayjs(dateValue).tz(timezone, shouldKeepLocalTime).format();
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import { TabsProps } from 'antd/lib';
|
import { TabsProps } from 'antd/lib';
|
||||||
import { FeatureKeys } from 'constants/features';
|
|
||||||
import AllAlertRules from 'container/ListAlertRules';
|
import AllAlertRules from 'container/ListAlertRules';
|
||||||
import { PlannedDowntime } from 'container/PlannedDowntime/PlannedDowntime';
|
import { PlannedDowntime } from 'container/PlannedDowntime/PlannedDowntime';
|
||||||
import TriggeredAlerts from 'container/TriggeredAlerts';
|
import TriggeredAlerts from 'container/TriggeredAlerts';
|
||||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@ -13,10 +11,6 @@ function AllAlertList(): JSX.Element {
|
|||||||
const urlQuery = useUrlQuery();
|
const urlQuery = useUrlQuery();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
const isPlannedDowntimeEnabled = useFeatureFlags(
|
|
||||||
FeatureKeys.PLANNED_MAINTENANCE,
|
|
||||||
)?.active;
|
|
||||||
|
|
||||||
const tab = urlQuery.get('tab');
|
const tab = urlQuery.get('tab');
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
{ label: 'Alert Rules', key: 'AlertRules', children: <AllAlertRules /> },
|
{ label: 'Alert Rules', key: 'AlertRules', children: <AllAlertRules /> },
|
||||||
@ -26,7 +20,7 @@ function AllAlertList(): JSX.Element {
|
|||||||
children: <TriggeredAlerts />,
|
children: <TriggeredAlerts />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: isPlannedDowntimeEnabled ? 'Configuration' : '',
|
label: 'Configuration',
|
||||||
key: 'Configuration',
|
key: 'Configuration',
|
||||||
children: <PlannedDowntime />,
|
children: <PlannedDowntime />,
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user