Skip to content

Commit

Permalink
Feat/alert history remaining feats (#5825)
Browse files Browse the repository at this point in the history
* fix: add switch case for inactive state to alert state component

* feat: add API enabled label search similar to Query Builder

* feat: add reset button to date and time picker

* feat: add vertical timeline chart using static data

* chore: use Colors instead of hex + dummy data for 90 days

* fix: label search light mode UI

* fix: remove placeholder logic, and display vertical charts if more than 1 day

* chore: extract dayjs manipulate types to a  constant

* fix: hide the overflow of top contributors card

* fix: throw instead of return error to prevent breaking alert history page in case of error

* chore: temporarily comment alert history vertical charts

* chore: calculate start and end times from relative time and remove query params (#5828)

* chore: calculate start and end times from relative time and remove query params

* fix: hide reset button if selected time is 30m
  • Loading branch information
ahmadshaheer authored Sep 2, 2024
1 parent 86d707e commit 7548199
Show file tree
Hide file tree
Showing 19 changed files with 413 additions and 245 deletions.
3 changes: 3 additions & 0 deletions frontend/src/api/alerts/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ const get = async (
payload: response.data,
};
} catch (error) {
if (window.location.href.includes('alerts/history')) {
throw error as AxiosError;
}
return ErrorResponseHandler(error as AxiosError);
}
};
Expand Down
26 changes: 1 addition & 25 deletions frontend/src/components/AlertDetailsFilters/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,9 @@ import './Filters.styles.scss';
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';

export function Filters(): JSX.Element {
// const urlQuery = useUrlQuery();
// const history = useHistory();
// const relativeTime = urlQuery.get(QueryParams.relativeTime);

// const handleFiltersReset = (): void => {
// urlQuery.set(QueryParams.relativeTime, RelativeTimeMap['30min']);
// urlQuery.delete(QueryParams.startTime);
// urlQuery.delete(QueryParams.endTime);
// history.replace({
// pathname: history.location.pathname,
// search: `?${urlQuery.toString()}`,
// });
// };
return (
<div className="filters">
{/* TODO(shaheer): re-enable reset button after fixing the issue w.r.t. updated timeInterval not updating in time picker */}
{/* {relativeTime !== RelativeTimeMap['30min'] && (
<Button
type="default"
className="reset-button"
onClick={handleFiltersReset}
icon={<Undo size={14} />}
>
Reset
</Button>
)} */}
<DateTimeSelector showAutoRefresh={false} hideShareModal />
<DateTimeSelector showAutoRefresh={false} hideShareModal showResetButton />
</div>
);
}
1 change: 0 additions & 1 deletion frontend/src/components/TabsAndFilters/TabsAndFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Filters } from 'components/AlertDetailsFilters/Filters';
import { Tabs } from './Tabs/Tabs';

function TabsAndFilters(): JSX.Element {
// TODO(shaheer): make it a reusable component inside periscope
return (
<div className="tabs-and-filters">
<Tabs />
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/constants/global.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { ManipulateType } from 'dayjs';

const MAX_RPS_LIMIT = 100;
export { MAX_RPS_LIMIT };

export const LEGEND = 'legend';

export const DAYJS_MANIPULATE_TYPES: { [key: string]: ManipulateType } = {
DAY: 'day',
WEEK: 'week',
MONTH: 'month',
YEAR: 'year',
HOUR: 'hour',
MINUTE: 'minute',
SECOND: 'second',
MILLISECOND: 'millisecond',
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.top-contributors-card {
width: 56.6%;
overflow: hidden;

&--view-all {
width: auto;
Expand Down
81 changes: 78 additions & 3 deletions frontend/src/container/AlertHistory/Timeline/Graph/Graph.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable consistent-return */
/* eslint-disable react/jsx-props-no-spreading */
import { Color } from '@signozhq/design-tokens';
import Uplot from 'components/Uplot';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
import { useMemo, useRef } from 'react';
import { AlertRuleTimelineGraphResponse } from 'types/api/alerts/def';
Expand Down Expand Up @@ -76,6 +75,75 @@ function HorizontalTimelineGraph({
return <Uplot data={transformedData} options={options} />;
}

const transformVerticalTimelineGraph = (data: any[]): any => [
data.map((item: { timestamp: any }) => item.timestamp),
Array(data.length).fill(0),
Array(data.length).fill(10),
Array(data.length).fill([0, 1, 2, 3, 4, 5]),
data.map((item: { value: number }) => {
const count = Math.floor(item.value / 10);
return [...Array(count).fill(1), 2];
}),
];

const datatest: any[] = [];
const now = Math.floor(Date.now() / 1000); // current timestamp in seconds
const oneDay = 24 * 60 * 60; // one day in seconds

for (let i = 0; i < 90; i++) {
const timestamp = now - i * oneDay;
const startOfDay = timestamp - (timestamp % oneDay);
datatest.push({
timestamp: startOfDay,
value: Math.floor(Math.random() * 30) + 1,
});
}

function VerticalTimelineGraph({
isDarkMode,
width,
}: {
width: number;
isDarkMode: boolean;
}): JSX.Element {
const transformedData = useMemo(
() => transformVerticalTimelineGraph(datatest),
[],
);

const options: uPlot.Options = useMemo(
() => ({
width,
height: 90,
plugins: [heatmapPlugin()],
cursor: { show: false },
legend: {
show: false,
},
axes: [
{
gap: 10,
stroke: isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400,
},
{ show: false },
],
series: [
{},
{
paths: (): null => null,
points: { show: false },
},
{
paths: (): null => null,
points: { show: false },
},
],
}),
[isDarkMode, width],
);
return <Uplot data={transformedData} options={options} />;
}

function Graph({ type, data }: Props): JSX.Element | null {
const graphRef = useRef<HTMLDivElement>(null);

Expand All @@ -94,7 +162,14 @@ function Graph({ type, data }: Props): JSX.Element | null {
</div>
);
}
return null;
return (
<div ref={graphRef}>
<VerticalTimelineGraph
isDarkMode={isDarkMode}
width={containerDimensions.width}
/>
</div>
);
}

export default Graph;
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import '../Graph/Graph.styles.scss';

import { BarChartOutlined } from '@ant-design/icons';
import { QueryParams } from 'constants/query';
import useUrlQuery from 'hooks/useUrlQuery';
import { useGetAlertRuleDetailsTimelineGraphData } from 'pages/AlertDetails/hooks';
import DataStateRenderer from 'periscope/components/DataStateRenderer/DataStateRenderer';
import { useEffect, useState } from 'react';

import Graph from '../Graph/Graph';

Expand All @@ -27,44 +24,41 @@ function GraphWrapper({
ruleId,
} = useGetAlertRuleDetailsTimelineGraphData();

const startTime = urlQuery.get(QueryParams.startTime);
// TODO(shaheer): uncomment when the API is ready for
// const { startTime } = useAlertHistoryQueryParams();

const [isPlaceholder, setIsPlaceholder] = useState(false);
// const [isVerticalGraph, setIsVerticalGraph] = useState(false);

useEffect(() => {
if (startTime) {
const startTimeDate = new Date(startTime);
const now = new Date();
const twentyFourHoursAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
// useEffect(() => {
// const checkVerticalGraph = (): void => {
// if (startTime) {
// const startTimeDate = dayjs(Number(startTime));
// const twentyFourHoursAgo = dayjs().subtract(
// HORIZONTAL_GRAPH_HOURS_THRESHOLD,
// DAYJS_MANIPULATE_TYPES.HOUR,
// );

if (startTimeDate < twentyFourHoursAgo) {
setIsPlaceholder(true);
} else {
setIsPlaceholder(false);
}
}
}, [startTime]);
// setIsVerticalGraph(startTimeDate.isBefore(twentyFourHoursAgo));
// }
// };

// checkVerticalGraph();
// }, [startTime]);

return (
<div className="timeline-graph">
<div className="timeline-graph__title">
{totalCurrentTriggers} triggers in {relativeTime}
</div>
<div className="timeline-graph__chart">
{isPlaceholder ? (
<div className="chart-placeholder">
<BarChartOutlined className="chart-icon" />
</div>
) : (
<DataStateRenderer
isLoading={isLoading}
isError={isError || !isValidRuleId || !ruleId}
isRefetching={isRefetching}
data={data?.payload?.data || null}
>
{(data): JSX.Element => <Graph type="horizontal" data={data} />}
</DataStateRenderer>
)}
<DataStateRenderer
isLoading={isLoading}
isError={isError || !isValidRuleId || !ruleId}
isRefetching={isRefetching}
data={data?.payload?.data || null}
>
{(data): JSX.Element => <Graph type="horizontal" data={data} />}
</DataStateRenderer>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
.timeline-table {
border-top: 1px solid var(--text-slate-500);

border-radius: 6px;
overflow: hidden;
margin-top: 4px;

min-height: 600px;
.ant-table {
background: var(--bg-ink-500);
&-cell {
Expand Down Expand Up @@ -88,6 +87,11 @@
}
}
}
.alert-history-label-search {
.ant-select-selector {
border: none;
}
}
}

.lightMode {
Expand All @@ -106,12 +110,12 @@
border-color: var(--bg-vanilla-300);
}
}
.label-filter {
&,
& input {
.alert-history-label-search {
.ant-select-selector {
background: var(--bg-vanilla-200);
}
}

.alert-rule {
&-value,
&-created-at {
Expand Down
22 changes: 8 additions & 14 deletions frontend/src/container/AlertHistory/Timeline/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './Table.styles.scss';

import { Table } from 'antd';
import { initialFilters } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import {
useGetAlertRuleDetailsTimelineTable,
Expand All @@ -10,18 +11,21 @@ import { useMemo, useState } 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<TagFilter>(initialFilters);

const {
isLoading,
isRefetching,
isError,
data,
isValidRuleId,
ruleId,
} = useGetAlertRuleDetailsTimelineTable();
} = useGetAlertRuleDetailsTimelineTable({ filters });

const { timelineData, totalItems } = useMemo(() => {
const response = data?.payload?.data;
Expand All @@ -31,20 +35,10 @@ function TimelineTable(): JSX.Element {
};
}, [data?.payload?.data]);

const [searchText, setSearchText] = useState('');
const { paginationConfig, onChangeHandler } = useTimelineTable({
totalItems: totalItems ?? 0,
});

const visibleTimelineData = useMemo(() => {
if (searchText === '') {
return timelineData;
}
return timelineData?.filter((data) =>
JSON.stringify(data.labels).toLowerCase().includes(searchText.toLowerCase()),
);
}, [searchText, timelineData]);

const queryClient = useQueryClient();

const { currentUnit, targetUnit } = useMemo(() => {
Expand All @@ -70,9 +64,9 @@ function TimelineTable(): JSX.Element {
return (
<div className="timeline-table">
<Table
rowKey={(row): string => `${row.fingerprint}-${row.value}`}
columns={timelineTableColumns(setSearchText, currentUnit, targetUnit)}
dataSource={visibleTimelineData}
rowKey={(row): string => `${row.fingerprint}-${row.value}-${row.unixMilli}`}
columns={timelineTableColumns(filters, setFilters, currentUnit, targetUnit)}
dataSource={timelineData}
pagination={paginationConfig}
size="middle"
onChange={onChangeHandler}
Expand Down
Loading

0 comments on commit 7548199

Please sign in to comment.