diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index 23e7ea9644b..b9002551720 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -19,6 +19,7 @@ import { ResourceProvider } from 'hooks/useResourceAttribute'; import history from 'lib/history'; import { identity, pick, pickBy } from 'lodash-es'; import posthog from 'posthog-js'; +import AlertRuleProvider from 'providers/Alert'; import { DashboardProvider } from 'providers/Dashboard/Dashboard'; import { QueryBuilderProvider } from 'providers/QueryBuilder'; import { Suspense, useEffect, useState } from 'react'; @@ -236,22 +237,24 @@ function App(): JSX.Element { - - }> - - {routes.map(({ path, component, exact }) => ( - - ))} - - - - - + + + }> + + {routes.map(({ path, component, exact }) => ( + + ))} + + + + + + diff --git a/frontend/src/container/AlertHistory/AlertPopover/AlertPopover.tsx b/frontend/src/container/AlertHistory/AlertPopover/AlertPopover.tsx index 7c230439b6b..83605a61d31 100644 --- a/frontend/src/container/AlertHistory/AlertPopover/AlertPopover.tsx +++ b/frontend/src/container/AlertHistory/AlertPopover/AlertPopover.tsx @@ -25,9 +25,9 @@ function PopoverContent({ const isDarkMode = useIsDarkMode(); return (
- {!!relatedTracesLink && ( + {!!relatedLogsLink && (
@@ -36,9 +36,9 @@ function PopoverContent({
View Logs
)} - {!!relatedLogsLink && ( + {!!relatedTracesLink && (
diff --git a/frontend/src/container/AlertHistory/Statistics/StatsCard/StatsCard.tsx b/frontend/src/container/AlertHistory/Statistics/StatsCard/StatsCard.tsx index 22f15515709..f204579f938 100644 --- a/frontend/src/container/AlertHistory/Statistics/StatsCard/StatsCard.tsx +++ b/frontend/src/container/AlertHistory/Statistics/StatsCard/StatsCard.tsx @@ -1,12 +1,18 @@ import './StatsCard.styles.scss'; import { Color } from '@signozhq/design-tokens'; +import { Tooltip } from 'antd'; +import { QueryParams } from 'constants/query'; import useUrlQuery from 'hooks/useUrlQuery'; import { ArrowDownLeft, ArrowUpRight, Calendar } from 'lucide-react'; import { AlertRuleStats } from 'types/api/alerts/def'; import { calculateChange } from 'utils/calculateChange'; import StatsGraph from './StatsGraph/StatsGraph'; +import { + convertTimestampToLocaleDateString, + extractDayFromTimestamp, +} from './utils'; type ChangePercentageProps = { percentage: number; @@ -78,6 +84,25 @@ function StatsCard({ totalPastCount, ); + const startTime = urlQuery.get(QueryParams.startTime); + const endTime = urlQuery.get(QueryParams.endTime); + + let displayTime = relativeTime; + + if (!displayTime && startTime && endTime) { + const formattedStartDate = extractDayFromTimestamp(startTime); + const formattedEndDate = extractDayFromTimestamp(endTime); + displayTime = `${formattedStartDate} to ${formattedEndDate}`; + } + + if (!displayTime) { + displayTime = ''; + } + const formattedStartTimeForTooltip = convertTimestampToLocaleDateString( + startTime, + ); + const formattedEndTimeForTooltip = convertTimestampToLocaleDateString(endTime); + return (
@@ -86,7 +111,15 @@ function StatsCard({
-
{relativeTime}
+ {relativeTime ? ( +
{displayTime}
+ ) : ( + +
{displayTime}
+
+ )}
diff --git a/frontend/src/container/AlertHistory/Statistics/StatsCard/utils.ts b/frontend/src/container/AlertHistory/Statistics/StatsCard/utils.ts new file mode 100644 index 00000000000..a2584aad37a --- /dev/null +++ b/frontend/src/container/AlertHistory/Statistics/StatsCard/utils.ts @@ -0,0 +1,12 @@ +export const extractDayFromTimestamp = (timestamp: string | null): string => { + if (!timestamp) return ''; + const date = new Date(parseInt(timestamp, 10)); + return date.getDate().toString(); +}; + +export const convertTimestampToLocaleDateString = ( + timestamp: string | null, +): string => { + if (!timestamp) return ''; + return new Date(parseInt(timestamp, 10)).toLocaleString(); +}; diff --git a/frontend/src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsContent.tsx b/frontend/src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsContent.tsx index 39a2f8f27ae..b458871f712 100644 --- a/frontend/src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsContent.tsx +++ b/frontend/src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsContent.tsx @@ -1,9 +1,3 @@ -import { Button } from 'antd'; -import { QueryParams } from 'constants/query'; -import ROUTES from 'constants/routes'; -import useUrlQuery from 'hooks/useUrlQuery'; -import history from 'lib/history'; - import TopContributorsRows from './TopContributorsRows'; import { TopContributorsCardProps } from './types'; @@ -13,34 +7,13 @@ function TopContributorsContent({ }: TopContributorsCardProps): JSX.Element { const isEmpty = !topContributorsData.length; - const urlQuery = useUrlQuery(); - const ruleIdKey = QueryParams.ruleId; - const relativeTimeKey = QueryParams.relativeTime; - - const handleRedirectToOverview = (): void => { - const params = `${ruleIdKey}=${urlQuery.get( - ruleIdKey, - )}&${relativeTimeKey}=${urlQuery.get(relativeTimeKey)}`; - - history.push(`${ROUTES.ALERT_OVERVIEW}?${params}`); - }; - if (isEmpty) { return (
ℹ️
- Add Group By Field To view top - contributors, please add at least one group by field to your query. -
-
- + Top contributors highlight the most frequently triggering group-by + attributes in multi-dimensional alerts
); diff --git a/frontend/src/container/AlertHistory/Timeline/Graph/Graph.tsx b/frontend/src/container/AlertHistory/Timeline/Graph/Graph.tsx index 684ba897f29..a0534691df8 100644 --- a/frontend/src/container/AlertHistory/Timeline/Graph/Graph.tsx +++ b/frontend/src/container/AlertHistory/Timeline/Graph/Graph.tsx @@ -21,19 +21,28 @@ function HorizontalTimelineGraph({ isDarkMode: boolean; data: AlertRuleTimelineGraphResponse[]; }): JSX.Element { - const transformedData: AlignedData = useMemo( - () => - data?.length > 1 - ? [ - data.map((item: AlertRuleTimelineGraphResponse) => item.start / 1000), - data.map( - (item: AlertRuleTimelineGraphResponse) => ALERT_STATUS[item.state], - ), - ] - : [[], []], - - [data], - ); + const transformedData: AlignedData = useMemo(() => { + if (!data?.length) { + return [[], []]; + } + + // add a first and last entry to make sure the graph displays all the data + const FIVE_MINUTES_IN_SECONDS = 300; + + const timestamps = [ + data[0].start / 1000 - FIVE_MINUTES_IN_SECONDS, // 5 minutes before the first entry + ...data.map((item) => item.start / 1000), + data[data.length - 1].end / 1000, // end value of last entry + ]; + + const states = [ + ALERT_STATUS[data[0].state], // Same state as the first entry + ...data.map((item) => ALERT_STATUS[item.state]), + ALERT_STATUS[data[data.length - 1].state], // Same state as the last entry + ]; + + return [timestamps, states]; + }, [data]); const options: uPlot.Options = useMemo( () => ({ diff --git a/frontend/src/container/AlertHistory/Timeline/Graph/constants.ts b/frontend/src/container/AlertHistory/Timeline/Graph/constants.ts index 34a79df4862..b56499a0d0c 100644 --- a/frontend/src/container/AlertHistory/Timeline/Graph/constants.ts +++ b/frontend/src/container/AlertHistory/Timeline/Graph/constants.ts @@ -2,6 +2,7 @@ import { Color } from '@signozhq/design-tokens'; export const ALERT_STATUS: { [key: string]: number } = { firing: 0, + inactive: 1, normal: 1, 'no-data': 2, disabled: 3, diff --git a/frontend/src/container/AlertHistory/Timeline/Table/Table.styles.scss b/frontend/src/container/AlertHistory/Timeline/Table/Table.styles.scss index 807bbfb4be9..9d31e0b0ea7 100644 --- a/frontend/src/container/AlertHistory/Timeline/Table/Table.styles.scss +++ b/frontend/src/container/AlertHistory/Timeline/Table/Table.styles.scss @@ -19,8 +19,11 @@ font-size: 12px; font-weight: 500; padding: 12px 16px 8px !important; - &:last-of-type, - &:nth-last-of-type(2) { + &:last-of-type + // TODO(shaheer): uncomment when we display value column + // , + // &:nth-last-of-type(2) + { text-align: right; } } diff --git a/frontend/src/container/AlertHistory/Timeline/Table/Table.tsx b/frontend/src/container/AlertHistory/Timeline/Table/Table.tsx index 63d0464eb83..f3144b88e61 100644 --- a/frontend/src/container/AlertHistory/Timeline/Table/Table.tsx +++ b/frontend/src/container/AlertHistory/Timeline/Table/Table.tsx @@ -1,23 +1,16 @@ import './Table.styles.scss'; import { Table } from 'antd'; -import { initialFilters } from 'constants/queryBuilder'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { useGetAlertRuleDetailsTimelineTable, useTimelineTable, } from 'pages/AlertDetails/hooks'; -import { useMemo, useState } from 'react'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useQueryClient } from 'react-query'; -import { PayloadProps } from 'types/api/alerts/get'; -import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { timelineTableColumns } from './useTimelineTable'; function TimelineTable(): JSX.Element { - const [filters, setFilters] = useState(initialFilters); - const { isLoading, isRefetching, @@ -25,7 +18,7 @@ function TimelineTable(): JSX.Element { data, isValidRuleId, ruleId, - } = useGetAlertRuleDetailsTimelineTable({ filters }); + } = useGetAlertRuleDetailsTimelineTable(); const { timelineData, totalItems } = useMemo(() => { const response = data?.payload?.data; @@ -39,22 +32,6 @@ function TimelineTable(): JSX.Element { totalItems: totalItems ?? 0, }); - const queryClient = useQueryClient(); - - const { currentUnit, targetUnit } = useMemo(() => { - const alertDetailsQuery = queryClient.getQueryData([ - REACT_QUERY_KEY.ALERT_RULE_DETAILS, - ruleId, - ]) as { - payload: PayloadProps; - }; - const condition = alertDetailsQuery?.payload?.data?.condition; - const { targetUnit } = condition ?? {}; - const { unit: currentUnit } = condition?.compositeQuery ?? {}; - - return { currentUnit, targetUnit }; - }, [queryClient, ruleId]); - const { t } = useTranslation('common'); if (isError || !isValidRuleId || !ruleId) { @@ -65,7 +42,7 @@ function TimelineTable(): JSX.Element {
`${row.fingerprint}-${row.value}-${row.unixMilli}`} - columns={timelineTableColumns(filters, setFilters, currentUnit, targetUnit)} + columns={timelineTableColumns()} dataSource={timelineData} pagination={paginationConfig} size="middle" diff --git a/frontend/src/container/AlertHistory/Timeline/Table/useTimelineTable.tsx b/frontend/src/container/AlertHistory/Timeline/Table/useTimelineTable.tsx index 7dbafb83c65..5a42fcd5bd8 100644 --- a/frontend/src/container/AlertHistory/Timeline/Table/useTimelineTable.tsx +++ b/frontend/src/container/AlertHistory/Timeline/Table/useTimelineTable.tsx @@ -1,91 +1,11 @@ -import { Color } from '@signozhq/design-tokens'; import { ColumnsType } from 'antd/es/table'; -import { QueryParams } from 'constants/query'; -import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover'; -import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch'; -import { useIsDarkMode } from 'hooks/useDarkMode'; -import useUrlQuery from 'hooks/useUrlQuery'; -import { convertValue } from 'lib/getConvertedValue'; -import { Search } from 'lucide-react'; import AlertLabels from 'pages/AlertDetails/AlertHeader/AlertLabels/AlertLabels'; import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState'; -import { useMemo } from 'react'; -import { useQueryClient } from 'react-query'; -import { SuccessResponse } from 'types/api'; import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def'; -import { PayloadProps } from 'types/api/alerts/get'; -import { - IBuilderQuery, - TagFilter, -} from 'types/api/queryBuilder/queryBuilderData'; import { formatEpochTimestamp } from 'utils/timeUtils'; -function LabelFilter({ - filters, - setFilters, -}: { - setFilters: (text: TagFilter) => void; - filters: TagFilter; -}): JSX.Element | null { - const isDarkMode = useIsDarkMode(); - - const queryClient = useQueryClient(); - const urlQuery = useUrlQuery(); - const ruleId = urlQuery.get(QueryParams.ruleId); - - const data = queryClient.getQueryData>([ - REACT_QUERY_KEY.ALERT_RULE_DETAILS, - ruleId, - ]); - - const query = useMemo(() => { - const compositeQueries = data?.payload?.data?.condition.compositeQuery; - const query = compositeQueries?.builderQueries?.A; - - return { - ...query, - filters, - } as IBuilderQuery; - }, [data?.payload?.data?.condition.compositeQuery, filters]); - - if (!data) { - return null; - } - - const handleSearch = (tagFilters: TagFilter): void => { - const tagFiltersLength = tagFilters.items.length; - - if ( - (!tagFiltersLength && (!filters || !filters.items.length)) || - tagFiltersLength === filters?.items.length - ) - return; - - setFilters(tagFilters); - }; - - return ( - - } - /> - ); -} - -export const timelineTableColumns = ( - filters: TagFilter, - setFilters: (text: TagFilter) => void, - currentUnit?: string, - targetUnit?: string, -): ColumnsType => [ +export const timelineTableColumns = (): ColumnsType => [ { title: 'STATE', dataIndex: 'state', @@ -103,7 +23,7 @@ export const timelineTableColumns = ( ), }, { - title: , + title: 'LABELS', dataIndex: 'labels', width: '54.5%', render: (labels, record): JSX.Element => ( @@ -117,22 +37,6 @@ export const timelineTableColumns = ( ), }, - { - title: 'VALUE', - dataIndex: 'value', - width: '14%', - render: (value, record): JSX.Element => ( - -
- {/* convert the value based on y axis and target unit */} - {convertValue(value.toFixed(2), currentUnit, targetUnit)} -
-
- ), - }, { title: 'CREATED AT', dataIndex: 'unixMilli', diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx index 6b6a3bce682..cd4fb97d4f5 100644 --- a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx @@ -66,7 +66,7 @@ function DateTimeSelection({ globalTimeLoading, showResetButton = false, showOldExplorerCTA = false, - defaultRelativeTime = RelativeTimeMap['30min'] as Time, + defaultRelativeTime = RelativeTimeMap['6hr'] as Time, }: Props): JSX.Element { const [formSelector] = Form.useForm(); @@ -469,6 +469,22 @@ function DateTimeSelection({ } const currentRoute = location.pathname; + + // set the default relative time for alert history and overview pages if relative time is not specified + if ( + (!urlQuery.has(QueryParams.startTime) || + !urlQuery.has(QueryParams.endTime)) && + !urlQuery.has(QueryParams.relativeTime) && + (currentRoute === ROUTES.ALERT_OVERVIEW || + currentRoute === ROUTES.ALERT_HISTORY) + ) { + updateTimeInterval(defaultRelativeTime); + urlQuery.set(QueryParams.relativeTime, defaultRelativeTime); + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); + return; + } + const time = getDefaultTime(currentRoute); const currentOptions = getOptions(currentRoute); @@ -710,7 +726,7 @@ DateTimeSelection.defaultProps = { hideShareModal: false, showOldExplorerCTA: false, showResetButton: false, - defaultRelativeTime: RelativeTimeMap['30min'] as Time, + defaultRelativeTime: RelativeTimeMap['6hr'] as Time, }; interface DispatchProps { updateTimeInterval: ( diff --git a/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx index bf4ac624463..183b4d2aaae 100644 --- a/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx +++ b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx @@ -1,7 +1,7 @@ import './ActionButtons.styles.scss'; import { Color } from '@signozhq/design-tokens'; -import { Button, Divider, Dropdown, MenuProps, Switch, Tooltip } from 'antd'; +import { Divider, Dropdown, MenuProps, Switch, Tooltip } from 'antd'; import { QueryParams } from 'constants/query'; import ROUTES from 'constants/routes'; import { useIsDarkMode } from 'hooks/useDarkMode'; @@ -14,65 +14,32 @@ import { useAlertRuleStatusToggle, } from 'pages/AlertDetails/hooks'; import CopyToClipboard from 'periscope/components/CopyToClipboard'; -import React, { useCallback, useState } from 'react'; +import { useAlertRule } from 'providers/Alert'; +import React from 'react'; +import { CSSProperties } from 'styled-components'; import { AlertDef } from 'types/api/alerts/def'; import { AlertHeaderProps } from '../AlertHeader'; -const menuStyle: React.CSSProperties = { - padding: 0, - boxShadow: 'none', - fontSize: 14, +const menuItemStyle: CSSProperties = { + fontSize: '14px', + letterSpacing: '0.14px', }; - -function DropdownMenuRenderer( - menu: React.ReactNode, - handleDelete: () => void, -): React.ReactNode { - return ( -
- {React.cloneElement(menu as React.ReactElement, { - style: menuStyle, - })} - - -
- ); -} - function AlertActionButtons({ ruleId, - state, alertDetails, }: { ruleId: string; - state: string; alertDetails: AlertHeaderProps['alertDetails']; }): JSX.Element { - const [dropdownOpen, setDropdownOpen] = useState(false); - - const { - handleAlertStateToggle, - isAlertRuleEnabled, - } = useAlertRuleStatusToggle({ ruleId, state }); + const { isAlertRuleDisabled } = useAlertRule(); + const { handleAlertStateToggle } = useAlertRuleStatusToggle({ ruleId }); const { handleAlertDuplicate } = useAlertRuleDuplicate({ alertDetails: (alertDetails as unknown) as AlertDef, }); const { handleAlertDelete } = useAlertRuleDelete({ ruleId: Number(ruleId) }); - const handleDeleteWithClose = useCallback(() => { - handleAlertDelete(); - setDropdownOpen(false); - }, [handleAlertDelete]); - const params = useUrlQuery(); const handleRename = React.useCallback(() => { @@ -87,38 +54,46 @@ function AlertActionButtons({ label: 'Rename', icon: , onClick: (): void => handleRename(), + style: menuItemStyle, }, { key: 'duplicate-rule', label: 'Duplicate', icon: , onClick: (): void => handleAlertDuplicate(), + style: menuItemStyle, + }, + { + key: 'delete-rule', + label: 'Delete', + icon: , + onClick: (): void => handleAlertDelete(), + style: { + ...menuItemStyle, + color: Color.BG_CHERRY_400, + }, }, ], - [handleAlertDuplicate, handleRename], + [handleAlertDelete, handleAlertDuplicate, handleRename], ); const isDarkMode = useIsDarkMode(); return (
- + + {isAlertRuleDisabled !== undefined && ( + + )} + - - DropdownMenuRenderer(menu, handleDeleteWithClose) - } - > + ; + disabled: boolean; }; }; function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element { - const { state, alert, id, labels } = alertDetails; + const { state, alert, labels, disabled } = alertDetails; + + const labelsWithoutSeverity = useMemo( + () => + Object.fromEntries( + Object.entries(labels).filter(([key]) => key !== 'severity'), + ), + [labels], + ); + + const { isAlertRuleDisabled, setIsAlertRuleDisabled } = useAlertRule(); + + useEffect(() => { + if (isAlertRuleDisabled === undefined) { + setIsAlertRuleDisabled(disabled); + } + }, [disabled, setIsAlertRuleDisabled, isAlertRuleDisabled]); return (
@@ -23,7 +43,6 @@ function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
{alert}
-
{id}
@@ -34,15 +53,11 @@ function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element { status="firing" timestamp={dayjs().subtract(1, 'd').valueOf()} /> */} - +
- +
); diff --git a/frontend/src/pages/AlertDetails/hooks.tsx b/frontend/src/pages/AlertDetails/hooks.tsx index 78d9bd0038c..fc6219b1953 100644 --- a/frontend/src/pages/AlertDetails/hooks.tsx +++ b/frontend/src/pages/AlertDetails/hooks.tsx @@ -27,8 +27,9 @@ import { History, Table } from 'lucide-react'; import EditRules from 'pages/EditRules'; import { OrderPreferenceItems } from 'pages/Logs/config'; import PaginationInfoText from 'periscope/components/PaginationInfoText/PaginationInfoText'; -import { useCallback, useMemo, useState } from 'react'; -import { useMutation, useQuery } from 'react-query'; +import { useAlertRule } from 'providers/Alert'; +import { useCallback, useMemo } from 'react'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useSelector } from 'react-redux'; import { generatePath, useLocation } from 'react-router-dom'; import { AppState } from 'store/reducers'; @@ -42,7 +43,6 @@ import { AlertRuleTopContributorsPayload, } from 'types/api/alerts/def'; import { PayloadProps } from 'types/api/alerts/get'; -import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { GlobalReducer } from 'types/reducer/globalTime'; import { nanoToMilli } from 'utils/timeUtils'; @@ -251,17 +251,12 @@ type GetAlertRuleDetailsTimelineTableProps = GetAlertRuleDetailsApiProps & { | undefined; }; -export const useGetAlertRuleDetailsTimelineTable = ({ - filters, -}: { - filters: TagFilter; -}): GetAlertRuleDetailsTimelineTableProps => { +export const useGetAlertRuleDetailsTimelineTable = (): GetAlertRuleDetailsTimelineTableProps => { const { ruleId, startTime, endTime, params } = useAlertHistoryQueryParams(); - - const { updatedOrder, getUpdatedOffset } = useMemo( + const { updatedOrder, offset } = useMemo( () => ({ updatedOrder: params.get(urlKey.order) ?? OrderPreferenceItems.ASC, - getUpdatedOffset: params.get(urlKey.offset) ?? '0', + offset: parseInt(params.get(urlKey.offset) ?? '1', 10), }), [params], ); @@ -279,8 +274,7 @@ export const useGetAlertRuleDetailsTimelineTable = ({ endTime, timelineFilter, updatedOrder, - getUpdatedOffset, - JSON.stringify(filters.items), + offset, ], { queryFn: () => @@ -290,8 +284,7 @@ export const useGetAlertRuleDetailsTimelineTable = ({ end: endTime, limit: TIMELINE_TABLE_PAGE_SIZE, order: updatedOrder, - offset: parseInt(getUpdatedOffset, 10), - filters, + offset, ...(timelineFilter && timelineFilter !== TimelineFilter.ALL ? { @@ -327,7 +320,7 @@ export const useTimelineTable = ({ const params = useMemo(() => new URLSearchParams(search), [search]); - const updatedOffset = params.get(urlKey.offset) ?? '0'; + const offset = params.get('offset') ?? '0'; const onChangeHandler: TableProps['onChange'] = useCallback( ( @@ -339,7 +332,7 @@ export const useTimelineTable = ({ ) => { if (!Array.isArray(sorter)) { const { pageSize = 0, current = 0 } = pagination; - const { columnKey = '', order } = sorter; + const { order } = sorter; const updatedOrder = order === 'ascend' ? 'asc' : 'desc'; const params = new URLSearchParams(window.location.search); @@ -347,8 +340,7 @@ export const useTimelineTable = ({ `${pathname}?${createQueryParams({ ...Object.fromEntries(params), order: updatedOrder, - offset: current - 1, - orderParam: columnKey, + offset: current * TIMELINE_TABLE_PAGE_SIZE - TIMELINE_TABLE_PAGE_SIZE, pageSize, })}`, ); @@ -357,10 +349,14 @@ export const useTimelineTable = ({ [pathname], ); + const offsetInt = parseInt(offset, 10); + const pageSize = params.get('pageSize') ?? String(TIMELINE_TABLE_PAGE_SIZE); + const pageSizeInt = parseInt(pageSize, 10); + const paginationConfig: TablePaginationConfig = { - pageSize: TIMELINE_TABLE_PAGE_SIZE, + pageSize: pageSizeInt, showTotal: PaginationInfoText, - current: parseInt(updatedOffset, 10) + 1, + current: offsetInt / TIMELINE_TABLE_PAGE_SIZE + 1, showSizeChanger: false, hideOnSinglePage: true, total: totalItems, @@ -370,20 +366,16 @@ export const useTimelineTable = ({ }; export const useAlertRuleStatusToggle = ({ - state, ruleId, }: { - state: string; ruleId: string; }): { handleAlertStateToggle: (state: boolean) => void; - isAlertRuleEnabled: boolean; } => { + const { isAlertRuleDisabled, setIsAlertRuleDisabled } = useAlertRule(); const { notifications } = useNotifications(); - const isAlertRuleInitiallyEnabled = state !== 'disabled'; - const [isAlertRuleEnabled, setIsAlertRuleEnabled] = useState( - isAlertRuleInitiallyEnabled, - ); + + const queryClient = useQueryClient(); const handleError = useAxiosError(); const { mutate: toggleAlertState } = useMutation( @@ -391,26 +383,29 @@ export const useAlertRuleStatusToggle = ({ patchAlert, { onMutate: () => { - setIsAlertRuleEnabled((prev) => !prev); + setIsAlertRuleDisabled((prev) => !prev); }, onSuccess: () => { notifications.success({ - message: `Alert has been turned ${!isAlertRuleEnabled ? 'on' : 'off'}.`, + message: `Alert has been ${isAlertRuleDisabled ? 'enabled' : 'disabled'}.`, }); }, onError: (error) => { - setIsAlertRuleEnabled(isAlertRuleInitiallyEnabled); + queryClient.refetchQueries([REACT_QUERY_KEY.ALERT_RULE_DETAILS]); handleError(error); }, }, ); - const handleAlertStateToggle = (state: boolean): void => { - const args = { id: parseInt(ruleId, 10), data: { disabled: !state } }; + const handleAlertStateToggle = (): void => { + const args = { + id: parseInt(ruleId, 10), + data: { disabled: !isAlertRuleDisabled }, + }; toggleAlertState(args); }; - return { handleAlertStateToggle, isAlertRuleEnabled }; + return { handleAlertStateToggle }; }; export const useAlertRuleDuplicate = ({ diff --git a/frontend/src/providers/Alert.tsx b/frontend/src/providers/Alert.tsx new file mode 100644 index 00000000000..337eec9ba53 --- /dev/null +++ b/frontend/src/providers/Alert.tsx @@ -0,0 +1,43 @@ +import React, { createContext, useContext, useState } from 'react'; + +interface AlertRuleContextType { + isAlertRuleDisabled: boolean | undefined; + setIsAlertRuleDisabled: React.Dispatch< + React.SetStateAction + >; +} + +const AlertRuleContext = createContext( + undefined, +); + +function AlertRuleProvider({ + children, +}: { + children: React.ReactNode; +}): JSX.Element { + const [isAlertRuleDisabled, setIsAlertRuleDisabled] = useState< + boolean | undefined + >(undefined); + + const value = React.useMemo( + () => ({ isAlertRuleDisabled, setIsAlertRuleDisabled }), + [isAlertRuleDisabled], + ); + + return ( + + {children} + + ); +} + +export const useAlertRule = (): AlertRuleContextType => { + const context = useContext(AlertRuleContext); + if (context === undefined) { + throw new Error('useAlertRule must be used within an AlertRuleProvider'); + } + return context; +}; + +export default AlertRuleProvider;