Skip to content

Commit

Permalink
alert rule history skeleton using static data (#5688)
Browse files Browse the repository at this point in the history
* feat: alert history basic tabs and fitlers UI

* feat: route based tabs for alert history and overview and improve the UI to match designs

* feat: top contributors UI using static data

* feat: avg. resolution time and total triggered stats card UI using static data

* feat: tabs component

* feat: timeline tabs and filters

* feat: overall status graph UI using dummy data with graph placeholder

* feat: timeline table and pagination UI using dummy data

* fix: bugfix in reset tabs

* feat: add popover to go to logs/traces to top contributors and timeline table

* chore: remove comments

* chore: rename AlertIcon to AlertState

* fix: add cursor pointer to timeline table rows

* feat: add parent tabs to alert history

* chore: add icon to the configure tab

* fix: display popover on hovering the more button in see more component

* fix: wrap key value label

* feat: alert rule history enable/disable toggle UI

* Feat: get alert history data from API (#5718)

* feat: alert history basic tabs and fitlers UI

* feat: route based tabs for alert history and overview and improve the UI to match designs

* feat: data state renderer component

* feat: get total triggered and avg. resolution cards data from API

* fix: hide stats card if we get NaN

* chore: improve rule stats types

* feat: get top contributors data from API

* feat: get timeline table data from API

* fix: properly render change percentage indicator

* feat: total triggered and avg resolution empty states

* fix: fix stats height issue that would cause short border-right in empty case

* feat: top contributors empty state

* fix: fix table and graph borders

* feat: build alert timeline labels filter and handle client side filtering

* fix: select the first tab on clicking reset

* feat: set param and send in payload on clicking timeline filter tabs

* Feat: alert history timeline remaining subtasks except graphs (#5720)

* feat: alert history basic tabs and fitlers UI

* feat: route based tabs for alert history and overview and improve the UI to match designs

* feat: implement timeline table sorting

* chore: add initial count to see more and alert labels

* chore: move PaginationInfoText component to /periscope

* chore: implement top contributor rows using Ant Table

* feat: top contributors view all

* fix: hide border for last row and prevent layout shift in top contributors by specifying height

* feat: properly display duration in average resolution time

* fix: properly display normal alert rule state

* feat: add/remove view all top contributors param to url on opening/closing view all

* feat: calculate start and end time from relative time and add/remove param to url

* fix: fix console warnings

* fix: enable timeline table query only if start and end times exist

* feat: handle enable/disable alert rule toggle request

* chore: replace string values with constants

* fix: hide stats card if only past data is available + remove unnecessary states from AlertState

* fix: redirect configure alert rule to alert overview tab

* fix: display total triggers in timeline chart wrapper based on API response data

* fix: choosing the same relative time doesn't udpate start and end time

* Feat: total triggered and avg. resolution time graph (#5750)

* feat: alert history basic tabs and fitlers UI

* feat: route based tabs for alert history and overview and improve the UI to match designs

* feat: handle enable/disable alert rule toggle request

* feat: stats card line chart

* fix: overall improvements to stats card graph

* fix: overall UI improvements to match the Figma screens

* chore: remove duplicate hook

* fix: make the changes w.r.t timeline table API changes to prevent breaking the page

* fix: update stats card null check based on updated API response

* feat: stats card no previous data UI

* feat: redirect to 404 page if rule id is invalid

* chore: improve alert enable toggle success toast message

* feat: get top contributors row and timeline table row related logs and traces links from API

* feat: get total items from API and make pagination work

* feat: implement timeline filters based on API response

* fix: in case of current and target units, convert the value unit in timeline table

* fix: timeline table y axis unit null check

* fix: hide stats card graph if only a single entry is there in timeseries

* chore: redirect alert from all alerts to overview tab

* fix: prevent adding extra unnecessary params on clicking alerts top level tabs

* chore: use conditional alert popover in timeline table and import the scss file

* fix: prevent infinity if we receive totalPastTriggers as '0'

* fix: improve UI to be pixel perfect based on figma designs

* fix: fix the incorrect change direction

* fix: add height to top contributors row

* feat: alert history light mode

* fix: remove the extra padding from alert overview query builder tabs

* chore: overall improvements

* chore: remove mock file

* fix: overall improvements

* fix: add dark mode support for top contributors empty state

* chore: improve timeline chart placeholder bg in light mode

* Feat: alert history horizontal timeline chart (#5773)

* feat: timeline horizontal chart

* fix: remove the labels from horizontal timeline chart

* chore: add null check to timeline chart

* chore: hide cursor from timeline chart

* fix: fix the blank container being displayed in loading state
  • Loading branch information
ahmadshaheer committed Aug 27, 2024
1 parent bd9990d commit 7960a7b
Show file tree
Hide file tree
Showing 85 changed files with 4,250 additions and 206 deletions.
4 changes: 2 additions & 2 deletions frontend/src/AppRoutes/pageComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ export const CreateNewAlerts = Loadable(
);

export const AlertHistory = Loadable(
() => import(/* webpackChunkName: "Alert History" */ 'pages/AlertDetails'),
() => import(/* webpackChunkName: "Alert History" */ 'pages/AlertList'),
);

export const AlertOverview = Loadable(
() => import(/* webpackChunkName: "Alert Overview" */ 'pages/AlertDetails'),
() => import(/* webpackChunkName: "Alert Overview" */ 'pages/AlertList'),
);

export const CreateAlertChannelAlerts = Loadable(
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/alerts/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const patch = async (
payload: response.data.data,
};
} catch (error) {
if (window.location.href.includes('alerts/history')) {
throw error as AxiosError;
}

return ErrorResponseHandler(error as AxiosError);
}
};
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/api/alerts/ruleStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AlertRuleStatsPayload } from 'types/api/alerts/def';
import { RuleStatsProps } from 'types/api/alerts/ruleStats';

const ruleStats = async (
props: RuleStatsProps,
): Promise<SuccessResponse<AlertRuleStatsPayload> | ErrorResponse> => {
try {
const response = await axios.post(`/rules/${props.id}/history/stats`, {
start: props.start,
end: props.end,
});

return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

export default ruleStats;
33 changes: 33 additions & 0 deletions frontend/src/api/alerts/timelineGraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AlertRuleTimelineGraphResponsePayload } from 'types/api/alerts/def';
import { GetTimelineGraphRequestProps } from 'types/api/alerts/timelineGraph';

const timelineGraph = async (
props: GetTimelineGraphRequestProps,
): Promise<
SuccessResponse<AlertRuleTimelineGraphResponsePayload> | ErrorResponse
> => {
try {
const response = await axios.post(
`/rules/${props.id}/history/overall_status`,
{
start: props.start,
end: props.end,
},
);

return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

export default timelineGraph;
36 changes: 36 additions & 0 deletions frontend/src/api/alerts/timelineTable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AlertRuleTimelineTableResponsePayload } from 'types/api/alerts/def';
import { GetTimelineTableRequestProps } from 'types/api/alerts/timelineTable';

const timelineTable = async (
props: GetTimelineTableRequestProps,
): Promise<
SuccessResponse<AlertRuleTimelineTableResponsePayload> | ErrorResponse
> => {
try {
const response = await axios.post(`/rules/${props.id}/history/timeline`, {
start: props.start,
end: props.end,
offset: props.offset,
limit: props.limit,
order: props.order,
state: props.state,
// TODO(shaheer): implement filters
filters: props.filters,
});

return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

export default timelineTable;
33 changes: 33 additions & 0 deletions frontend/src/api/alerts/topContributors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AlertRuleTopContributorsPayload } from 'types/api/alerts/def';
import { TopContributorsProps } from 'types/api/alerts/topContributors';

const topContributors = async (
props: TopContributorsProps,
): Promise<
SuccessResponse<AlertRuleTopContributorsPayload> | ErrorResponse
> => {
try {
const response = await axios.post(
`/rules/${props.id}/history/top_contributors`,
{
start: props.start,
end: props.end,
},
);

return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

export default topContributors;
41 changes: 41 additions & 0 deletions frontend/src/assets/AlertHistory/ConfigureIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
interface ConfigureIconProps {
width?: number;
height?: number;
fill?: string;
}

function ConfigureIcon({
width,
height,
fill,
}: ConfigureIconProps): JSX.Element {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill={fill}
>
<path
stroke="#C0C1C3"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.333"
d="M9.71 4.745a.576.576 0 000 .806l.922.922a.576.576 0 00.806 0l2.171-2.171a3.455 3.455 0 01-4.572 4.572l-3.98 3.98a1.222 1.222 0 11-1.727-1.728l3.98-3.98a3.455 3.455 0 014.572-4.572L9.717 4.739l-.006.006z"
/>
<path
stroke="#C0C1C3"
strokeLinecap="round"
strokeWidth="1.333"
d="M4 7L2.527 5.566a1.333 1.333 0 01-.013-1.898l.81-.81a1.333 1.333 0 011.991.119L5.333 3m5.417 7.988l1.179 1.178m0 0l-.138.138a.833.833 0 00.387 1.397v0a.833.833 0 00.792-.219l.446-.446a.833.833 0 00.176-.917v0a.833.833 0 00-1.355-.261l-.308.308z"
/>
</svg>
);
}

ConfigureIcon.defaultProps = {
width: 16,
height: 16,
fill: 'none',
};
export default ConfigureIcon;
65 changes: 65 additions & 0 deletions frontend/src/assets/AlertHistory/LogsIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
interface LogsIconProps {
width?: number;
height?: number;
fill?: string;
strokeColor?: string;
strokeWidth?: number;
}

function LogsIcon({
width,
height,
fill,
strokeColor,
strokeWidth,
}: LogsIconProps): JSX.Element {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
fill={fill}
>
<path
stroke={strokeColor}
strokeWidth={strokeWidth}
d="M2.917 3.208v7.875"
/>
<ellipse
cx="6.417"
cy="3.208"
stroke={strokeColor}
strokeWidth={strokeWidth}
rx="3.5"
ry="1.458"
/>
<ellipse cx="6.417" cy="3.165" fill={strokeColor} rx="0.875" ry="0.365" />
<path
stroke={strokeColor}
strokeWidth={strokeWidth}
d="M9.917 11.083c0 .645-1.567 1.167-3.5 1.167s-3.5-.522-3.5-1.167"
/>
<path
stroke={strokeColor}
strokeLinecap="round"
strokeWidth={strokeWidth}
d="M5.25 6.417v1.117c0 .028.02.053.049.057l1.652.276A.058.058 0 017 7.924v1.993"
/>
<path
stroke={strokeColor}
strokeWidth={strokeWidth}
d="M9.917 3.208v3.103c0 .046.05.074.089.05L12.182 5a.058.058 0 01.088.035l.264 1.059a.058.058 0 01-.013.053l-2.59 2.877a.058.058 0 00-.014.04v2.018"
/>
</svg>
);
}

LogsIcon.defaultProps = {
width: 14,
height: 14,
fill: 'none',
strokeColor: '#C0C1C3',
strokeWidth: 1.167,
};

export default LogsIcon;
9 changes: 6 additions & 3 deletions frontend/src/components/AlertDetailsFilters/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './filters.styles.scss';

import { Button } from 'antd';
import { QueryParams } from 'constants/query';
import { RelativeTimeMap } from 'container/TopNav/DateTimeSelection/config';
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
import useUrlQuery from 'hooks/useUrlQuery';
import { Undo } from 'lucide-react';
Expand All @@ -10,18 +11,20 @@ import { useHistory } from 'react-router-dom';
export function Filters(): JSX.Element {
const urlQuery = useUrlQuery();
const history = useHistory();
const relativeTime = urlQuery.get(QueryParams.relativeTime);

const handleFiltersReset = (): void => {
urlQuery.delete(QueryParams.relativeTime);

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">
{urlQuery.has(QueryParams.relativeTime) && (
{relativeTime !== RelativeTimeMap['30min'] && (
<Button
type="default"
className="reset-button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@
background: var(--Ink-300, #16181d);
border: 1px solid var(--Slate-400, #1d212d);
}

.lightMode {
.reset-button {
background: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
}
41 changes: 41 additions & 0 deletions frontend/src/components/TabsAndFilters/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import './tabs.styles.scss';

import { Radio } from 'antd';
import { RadioChangeEvent } from 'antd/lib';
import { History, Table } from 'lucide-react';
import { useState } from 'react';

import { ALERT_TABS } from '../constants';

export function Tabs(): JSX.Element {
const [selectedTab, setSelectedTab] = useState('overview');

const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedTab(e.target.value);
};

return (
<Radio.Group className="tabs" onChange={handleTabChange} value={selectedTab}>
<Radio.Button
className={
selectedTab === ALERT_TABS.OVERVIEW ? 'selected_view tab' : 'tab'
}
value={ALERT_TABS.OVERVIEW}
>
<div className="tab-title">
<Table size={14} />
Overview
</div>
</Radio.Button>
<Radio.Button
className={selectedTab === ALERT_TABS.HISTORY ? 'selected_view tab' : 'tab'}
value={ALERT_TABS.HISTORY}
>
<div className="tab-title">
<History size={14} />
History
</div>
</Radio.Button>
</Radio.Group>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.alert-icon {
.tab-title {
display: flex;
gap: 4px;
align-items: center;
}
16 changes: 16 additions & 0 deletions frontend/src/components/TabsAndFilters/TabsAndFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import './tabsAndFilters.styles.scss';

import { Filters } from './Filters/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 />
<Filters />
</div>
);
}

export default TabsAndFilters;
5 changes: 5 additions & 0 deletions frontend/src/components/TabsAndFilters/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const ALERT_TABS = {
OVERVIEW: 'OVERVIEW',
HISTORY: 'HISTORY',
ACTIVITY: 'ACTIVITY',
} as const;
Loading

0 comments on commit 7960a7b

Please sign in to comment.