Skip to content

Commit

Permalink
Feat: get alert history data from API (#5718)
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: 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 authored Aug 27, 2024
1 parent a4fb723 commit 91068e3
Show file tree
Hide file tree
Showing 74 changed files with 3,269 additions and 565 deletions.
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;
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);
}
}
5 changes: 5 additions & 0 deletions frontend/src/constants/reactQueryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ export const REACT_QUERY_KEY = {
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
DELETE_DASHBOARD: 'DELETE_DASHBOARD',
LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW',
ALERT_RULE_DETAILS: 'ALERT_RULE_DETAILS',
ALERT_RULE_STATS: 'ALERT_RULE_STATS',
ALERT_RULE_TOP_CONTRIBUTORS: 'ALERT_RULE_TOP_CONTRIBUTORS',
ALERT_RULE_TIMELINE_TABLE: 'ALERT_RULE_TIMELINE_TABLE',
ALERT_RULE_TIMELINE_GRAPH: 'ALERT_RULE_TIMELINE_GRAPH',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.alert-popover {
cursor: pointer;
}
122 changes: 97 additions & 25 deletions frontend/src/container/AlertHistory/AlertPopover/AlertPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,113 @@
import './AlertPopover.styles.scss';

import { Popover } from 'antd';
import LogsIcon from 'assets/AlertHistory/LogsIcon';
import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { DraftingCompass } from 'lucide-react';
import React from 'react';
import { Link } from 'react-router-dom';

type Props = { children: React.ReactNode };
type Props = {
children: React.ReactNode;
relatedTracesLink?: string;
relatedLogsLink?: string;
};

function PopoverContent(): JSX.Element {
function PopoverContent({
relatedTracesLink,
relatedLogsLink,
}: {
relatedTracesLink?: Props['relatedTracesLink'];
relatedLogsLink?: Props['relatedLogsLink'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div className="contributor-row-popover-buttons">
<div className="contributor-row-popover-buttons__button">
<div className="icon">
<LogsIcon />
</div>
<div className="text">View Logs</div>
</div>
<div className="contributor-row-popover-buttons__button">
<div className="icon">
<DraftingCompass size={14} color="var(--text-vanilla-400)" />
</div>
<div className="text">View Traces</div>
</div>
{!!relatedTracesLink && (
<Link
to={`${ROUTES.LOGS_EXPLORER}?${relatedTracesLink}`}
className="contributor-row-popover-buttons__button"
>
<div className="icon">
<LogsIcon />
</div>
<div className="text">View Logs</div>
</Link>
)}
{!!relatedLogsLink && (
<Link
to={`${ROUTES.TRACES_EXPLORER}?${relatedLogsLink}`}
className="contributor-row-popover-buttons__button"
>
<div className="icon">
<DraftingCompass
size={14}
color={isDarkMode ? 'var(--bg-vanilla-400)' : 'var(--text-ink-400'}
/>
</div>
<div className="text">View Traces</div>
</Link>
)}
</div>
);
}
function AlertPopover({ children }: Props): JSX.Element {
PopoverContent.defaultProps = {
relatedTracesLink: '',
relatedLogsLink: '',
};

function AlertPopover({
children,
relatedTracesLink,
relatedLogsLink,
}: Props): JSX.Element {
return (
<Popover
showArrow={false}
placement="bottom"
color="linear-gradient(139deg, rgba(18, 19, 23, 1) 0%, rgba(18, 19, 23, 1) 98.68%)"
destroyTooltipOnHide
content={<PopoverContent />}
trigger="click"
>
{children}
</Popover>
<div className="alert-popover">
<Popover
showArrow={false}
placement="bottom"
color="linear-gradient(139deg, rgba(18, 19, 23, 1) 0%, rgba(18, 19, 23, 1) 98.68%)"
destroyTooltipOnHide
content={
<PopoverContent
relatedTracesLink={relatedTracesLink}
relatedLogsLink={relatedLogsLink}
/>
}
trigger="click"
>
{children}
</Popover>
</div>
);
}

AlertPopover.defaultProps = {
relatedTracesLink: '',
relatedLogsLink: '',
};

type ConditionalAlertPopoverProps = {
relatedTracesLink: string;
relatedLogsLink: string;
children: React.ReactNode;
};
export function ConditionalAlertPopover({
children,
relatedTracesLink,
relatedLogsLink,
}: ConditionalAlertPopoverProps): JSX.Element {
if (relatedTracesLink || relatedLogsLink) {
return (
<AlertPopover
relatedTracesLink={relatedTracesLink}
relatedLogsLink={relatedLogsLink}
>
{children}
</AlertPopover>
);
}
return <div>{children}</div>;
}
export default AlertPopover;
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import './averageResolutionCard.styles.scss';
import { AlertRuleStats } from 'types/api/alerts/def';
import { formatTime } from 'utils/timeUtils';

import { statsData } from '../mocks';
import StatsCard from '../StatsCard/StatsCard';

function AverageResolutionCard(): JSX.Element {
type TotalTriggeredCardProps = {
currentAvgResolutionTime: AlertRuleStats['currentAvgResolutionTime'];
pastAvgResolutionTime: AlertRuleStats['pastAvgResolutionTime'];
timeSeries: AlertRuleStats['currentAvgResolutionTimeSeries']['values'];
};

function AverageResolutionCard({
currentAvgResolutionTime,
pastAvgResolutionTime,
timeSeries,
}: TotalTriggeredCardProps): JSX.Element {
return (
<div className="average-resolution-card">
<StatsCard
totalCurrentCount={statsData.totalCurrentTriggers}
totalPastCount={statsData.totalPastTriggers}
title="Avg. Resolution Time"
/>
</div>
<StatsCard
displayValue={formatTime(currentAvgResolutionTime)}
totalCurrentCount={currentAvgResolutionTime}
totalPastCount={pastAvgResolutionTime}
title="Avg. Resolution Time"
timeSeries={timeSeries}
/>
);
}

Expand Down

This file was deleted.

Loading

0 comments on commit 91068e3

Please sign in to comment.