Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Meta, StoryObj } from "@storybook/react";

import { OccurrenceChart } from ".";
import { actions } from "../../actions";

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta<typeof OccurrenceChart> = {
title: "Errors/NewErrorCard/OccurrenceChart",
component: OccurrenceChart,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen"
}
};

export default meta;

// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
type Story = StoryObj<typeof meta>;

export const Default: Story = {
play: () => {
window.setTimeout(() => {
window.postMessage({
type: "digma",
action: actions.SET_ERROR_TIME_SERIES_DATA,
payload: {
dailyOccurrence: [
{
date: "2024-10-17T00:00:00",
value: 15
},
{
date: "2024-10-18T00:00:00",
value: 10
},
{
date: "2024-10-19T00:00:00",
value: 5
},
{
date: "2024-10-20T00:00:00",
value: 20
},
{
date: "2024-10-21T00:00:00",
value: 25
},
{
date: "2024-10-22T00:00:00",
value: 30
},
{
date: "2024-10-23T00:00:00",
value: 35
},
{
date: "2024-10-24T00:00:00",
value: 40
},
{
date: "2024-10-25T00:00:00",
value: 45
},
{
date: "2024-10-26T00:00:00",
value: 50
}
]
}
});
}, 500);
}
};
169 changes: 169 additions & 0 deletions src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { format } from "date-fns";
import { useEffect, useMemo, useState } from "react";
import {
Bar,
BarChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis
} from "recharts";
import { useTheme } from "styled-components";
import {
DataFetcherConfiguration,
useFetchData
} from "../../../../hooks/useFetchData";
import { useConfigSelector } from "../../../../store/config/useConfigSelector";
import { isNumber } from "../../../../typeGuards/isNumber";
import { actions } from "../../actions";
import * as s from "./styles";
import {
ErrorOccurrenceRecord,
GetErrorTimeSeriesDataPayload,
HorizontalCoordinatesGeneratorProps,
OccurrenceChartProps,
SetErrorTimeSeriesDataPayload
} from "./types";

const MAX_BAR_WIDTH = 32;

export const OccurrenceChart = ({
errorId,
spanCodeObjectId,
service
}: OccurrenceChartProps) => {
const theme = useTheme();
const [chartData, setChartData] = useState<
SetErrorTimeSeriesDataPayload | undefined
>();

const { environment } = useConfigSelector();
const environmentId = environment?.id;

const tickLabelStyles: React.SVGProps<SVGTextElement> = {
fill: theme.colors.v3.text.secondary,
opacity: 0.5
};

const YAxisTickLabelStyles: React.SVGProps<SVGTextElement> = {
...tickLabelStyles,
fontSize: theme.typographies.footNote.fontSize,
fontWeight: theme.typographies.footNote.fontWeight.regular
};

const XAxisTickLabelStyles: React.SVGProps<SVGTextElement> = {
...tickLabelStyles,
fontSize: theme.typographies.captionOne.fontSize,
fontWeight: theme.typographies.captionOne.fontWeight.regular
};

const dataFetcherConfiguration: DataFetcherConfiguration = useMemo(
() => ({
requestAction: actions.GET_ERROR_TIME_SERIES_DATA,
responseAction: actions.SET_ERROR_TIME_SERIES_DATA,
refreshOnPayloadChange: true
}),
[]
);

const payload: GetErrorTimeSeriesDataPayload = useMemo(
() => ({
errorId,
scope: {
spanCodeObjectId,
service,
environment: environmentId ?? ""
}
}),
[errorId, spanCodeObjectId, service, environmentId]
);

const { data } = useFetchData<
GetErrorTimeSeriesDataPayload,
SetErrorTimeSeriesDataPayload
>(dataFetcherConfiguration, payload);

useEffect(() => {
if (data && data.errorId === errorId) {
setChartData(data);
}
}, [data, errorId]);

return (
<s.HistogramContainer>
<s.HistogramHeader>
<s.HistogramTitle>Occurrence over time</s.HistogramTitle>
</s.HistogramHeader>
{chartData?.dailyOccurrence && (
<ResponsiveContainer width={"100%"} height={"100%"}>
<BarChart data={chartData.dailyOccurrence} maxBarSize={MAX_BAR_WIDTH}>
<CartesianGrid
vertical={false}
stroke={theme.colors.v3.stroke.tertiary}
horizontalCoordinatesGenerator={({
offset
}: HorizontalCoordinatesGeneratorProps) => {
if (!offset.height || !isNumber(offset.top)) {
return [];
}
let linesCount = 4;
const lines = [];
const maxTickTopOffset = offset.height + offset.top;
const interval = Math.floor(offset.height / linesCount);
let top = maxTickTopOffset - interval;

while (linesCount) {
lines.push(top);
linesCount--;
top -= interval;
}

return lines;
}}
/>
<XAxis
dataKey={"date"}
axisLine={{ stroke: theme.colors.v3.stroke.tertiary }}
tickLine={false}
tick={XAxisTickLabelStyles}
tickFormatter={(x: string) => format(new Date(x), "MM/dd")}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={YAxisTickLabelStyles}
allowDecimals={false}
/>
<Bar
isAnimationActive={false}
dataKey={"value"}
fill={theme.colors.v3.status.backgroundHigh}
/>
<Tooltip
cursor={false}
content={(x) => {
const payload = x.payload;

if (!payload || payload.length === 0) {
return;
}

const { date, value } = payload[0]
.payload as ErrorOccurrenceRecord;

return (
<s.TooltipContainer>
<span>Occurrences: {value}</span>
<span>{format(new Date(date), "MM/dd/yyyy")}</span>
</s.TooltipContainer>
);
}}
isAnimationActive={false}
/>
</BarChart>
</ResponsiveContainer>
)}
</s.HistogramContainer>
);
};
34 changes: 34 additions & 0 deletions src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styled from "styled-components";
import { bodySemiboldTypography } from "../../../common/App/typographies";
import { Tooltip } from "../../../common/v3/Tooltip/styles";

export const HEIGHT = 208; // in pixels

export const HistogramContainer = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
box-sizing: border-box;
width: 100%;
height: ${HEIGHT}px;
background: ${({ theme }) => theme.colors.v3.surface.primary};
padding: 12px 0 12px 12px;
border-radius: 4px;
`;

export const HistogramHeader = styled.div`
height: 28px;
display: flex;
align-items: center;
`;

export const HistogramTitle = styled.span`
${bodySemiboldTypography}
color: ${({ theme }) => theme.colors.v3.text.primary};
`;

export const TooltipContainer = styled(Tooltip)`
display: flex;
flex-direction: column;
gap: 8px;
`;
30 changes: 30 additions & 0 deletions src/components/Errors/NewErrorCard/OccurrenceChart/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ChartOffset } from "recharts/types/util/types";

export interface OccurrenceChartProps {
errorId: string;
spanCodeObjectId: string;
service: string;
}

export interface GetErrorTimeSeriesDataPayload {
errorId: string;
scope: {
spanCodeObjectId: string;
service: string;
environment: string;
};
}

export interface ErrorOccurrenceRecord {
date: string;
value: number;
}

export interface SetErrorTimeSeriesDataPayload {
errorId: string;
dailyOccurrence: ErrorOccurrenceRecord[];
}

export interface HorizontalCoordinatesGeneratorProps {
offset: ChartOffset;
}
Loading
Loading