From f9e4183b1c107d4f4b6fb14fa2503849c49eced3 Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:03:31 -0500 Subject: [PATCH] Implement `AreaChartWidget` --- sampleDurationTimeSeries.json | 112 +++++++++ .../areaChartWidget/areaChartWidget.spec.tsx | 87 +++++++ .../areaChartWidget.stories.tsx | 234 ++++++++++++++++++ .../areaChartWidget/areaChartWidget.tsx | 78 ++++++ .../areaChartWidgetVisualization.tsx | 215 ++++++++++++++++ .../sampleLatencyTimeSeries.json | 205 +++++++++++++++ .../sampleSpanDurationTimeSeries.json | 205 +++++++++++++++ 7 files changed, 1136 insertions(+) create mode 100644 sampleDurationTimeSeries.json create mode 100644 static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.spec.tsx create mode 100644 static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.stories.tsx create mode 100644 static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.tsx create mode 100644 static/app/views/dashboards/widgets/areaChartWidget/areaChartWidgetVisualization.tsx create mode 100644 static/app/views/dashboards/widgets/areaChartWidget/sampleLatencyTimeSeries.json create mode 100644 static/app/views/dashboards/widgets/areaChartWidget/sampleSpanDurationTimeSeries.json diff --git a/sampleDurationTimeSeries.json b/sampleDurationTimeSeries.json new file mode 100644 index 00000000000000..0714c525203711 --- /dev/null +++ b/sampleDurationTimeSeries.json @@ -0,0 +1,112 @@ +[ + { + "field": "avg(span.duration)", + "data": [ + [1733781600, [{"count": 194.32090152293875}]], + [1733783400, [{"count": 147.62552997973896}]], + [1733785200, [{"count": 167.3125896486525}]], + [1733787000, [{"count": 154.9546418433016}]], + [1733788800, [{"count": 177.75841778182115}]], + [1733790600, [{"count": 149.62071539059102}]], + [1733792400, [{"count": 148.37426947953236}]], + [1733794200, [{"count": 151.02028675214447}]], + [1733796000, [{"count": 143.50677477334426}]], + [1733797800, [{"count": 142.88535217056204}]], + [1733799600, [{"count": 146.46517983479544}]], + [1733801400, [{"count": 147.25420642999308}]], + [1733803200, [{"count": 146.49359730424217}]], + [1733805000, [{"count": 142.00870233797465}]], + [1733806800, [{"count": 157.48923328095663}]], + [1733808600, [{"count": 144.61466487144975}]], + [1733810400, [{"count": 156.85201565358486}]], + [1733812200, [{"count": 148.59436417696972}]], + [1733814000, [{"count": 155.72779310291295}]], + [1733815800, [{"count": 148.23792988918535}]], + [1733817600, [{"count": 155.66213671702405}]], + [1733819400, [{"count": 161.74766100502546}]], + [1733821200, [{"count": 171.07824116698762}]], + [1733823000, [{"count": 164.33299771174853}]], + [1733824800, [{"count": 192.93421512847078}]], + [1733826600, [{"count": 180.4593461868112}]], + [1733828400, [{"count": 172.99345240577236}]], + [1733830200, [{"count": 163.5327323114427}]], + [1733832000, [{"count": 182.60959861686507}]], + [1733833800, [{"count": 173.0523860965734}]], + [1733835600, [{"count": 177.17056856145237}]], + [1733837400, [{"count": 169.14652081980321}]], + [1733839200, [{"count": 164.87272365787706}]], + [1733841000, [{"count": 180.67025513785254}]], + [1733842800, [{"count": 206.55308274312813}]], + [1733844600, [{"count": 254.2190236395339}]], + [1733846400, [{"count": 191.45799652669942}]], + [1733848200, [{"count": 168.08625846646902}]], + [1733850000, [{"count": 171.19012303216394}]], + [1733851800, [{"count": 165.4369895365753}]], + [1733853600, [{"count": 179.14458584719685}]], + [1733855400, [{"count": 162.45116280726685}]], + [1733857200, [{"count": 173.7653410018822}]], + [1733859000, [{"count": 158.8766740686512}]], + [1733860800, [{"count": 165.27304423302115}]], + [1733862600, [{"count": 155.74659761848613}]], + [1733864400, [{"count": 164.0378545765358}]], + [1733866200, [{"count": 160.07243369487554}]], + [1733868000, [{"count": 161.39470890181587}]], + [1733869800, [{"count": 0}]] + ] + }, + { + "field": "avg(messaging.message.receive.latency)", + "data": [ + [1733781600, [{"count": 15.797387473297285}]], + [1733783400, [{"count": 6.098785205112891}]], + [1733785200, [{"count": 10.023521061981072}]], + [1733787000, [{"count": 6.303244308149576}]], + [1733788800, [{"count": 11.352363343199878}]], + [1733790600, [{"count": 4.845476325747717}]], + [1733792400, [{"count": 11.251521855070287}]], + [1733794200, [{"count": 4.425860645618586}]], + [1733796000, [{"count": 9.989218687466126}]], + [1733797800, [{"count": 4.025772756170272}]], + [1733799600, [{"count": 9.17541579241641}]], + [1733801400, [{"count": 4.2380217489924865}]], + [1733803200, [{"count": 8.027566877217474}]], + [1733805000, [{"count": 5.295137636393576}]], + [1733806800, [{"count": 8.656494836655359}]], + [1733808600, [{"count": 5.475662654003712}]], + [1733810400, [{"count": 9.793632841698757}]], + [1733812200, [{"count": 5.591866790430932}]], + [1733814000, [{"count": 9.187478698931766}]], + [1733815800, [{"count": 3.779925204205954}]], + [1733817600, [{"count": 10.315176397597504}]], + [1733819400, [{"count": 5.101678176894567}]], + [1733821200, [{"count": 10.405819741300553}]], + [1733823000, [{"count": 5.157673142672812}]], + [1733824800, [{"count": 13.454678120116997}]], + [1733826600, [{"count": 5.633257152519796}]], + [1733828400, [{"count": 12.38331484233643}]], + [1733830200, [{"count": 6.7168414807525245}]], + [1733832000, [{"count": 10.82394699109634}]], + [1733833800, [{"count": 6.649313439563898}]], + [1733835600, [{"count": 10.957238674362912}]], + [1733837400, [{"count": 7.891612896606848}]], + [1733839200, [{"count": 9.83684972309017}]], + [1733841000, [{"count": 4.472633330313572}]], + [1733842800, [{"count": 13.886404361521333}]], + [1733844600, [{"count": 9.212753388080626}]], + [1733846400, [{"count": 12.213060522650725}]], + [1733848200, [{"count": 5.308362659314872}]], + [1733850000, [{"count": 12.167955277129803}]], + [1733851800, [{"count": 5.95843470061378}]], + [1733853600, [{"count": 11.623431372484815}]], + [1733855400, [{"count": 7.133246343117738}]], + [1733857200, [{"count": 9.91670999332601}]], + [1733859000, [{"count": 8.220551566043556}]], + [1733860800, [{"count": 10.644324473372116}]], + [1733862600, [{"count": 6.380262999155083}]], + [1733864400, [{"count": 11.611059631291011}]], + [1733866200, [{"count": 4.830427051517974}]], + [1733868000, [{"count": 14.018863061817425}]], + [1733869800, [{"count": 0}]] + ] + } +] diff --git a/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.spec.tsx b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.spec.tsx new file mode 100644 index 00000000000000..5975cb6041941b --- /dev/null +++ b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.spec.tsx @@ -0,0 +1,87 @@ +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; + +import {AreaChartWidget} from './areaChartWidget'; +import sampleDurationTimeSeries from './sampleDurationTimeSeries.json'; + +describe('AreaChartWidget', () => { + describe('Layout', () => { + it('Renders', () => { + render( + + ); + }); + }); + + describe('Visualization', () => { + it('Explains missing data', () => { + render(); + + expect(screen.getByText('No Data')).toBeInTheDocument(); + }); + }); + + describe('State', () => { + it('Shows a loading placeholder', () => { + render(); + + expect(screen.getByTestId('loading-indicator')).toBeInTheDocument(); + }); + + it('Loading state takes precedence over error state', () => { + render( + + ); + + expect(screen.getByTestId('loading-indicator')).toBeInTheDocument(); + }); + + it('Shows an error message', () => { + render(); + + expect(screen.getByText('Error: Uh oh')).toBeInTheDocument(); + }); + + it('Shows a retry button', async () => { + const onRetry = jest.fn(); + + render(); + + await userEvent.click(screen.getByRole('button', {name: 'Retry'})); + expect(onRetry).toHaveBeenCalledTimes(1); + }); + + it('Hides other actions if there is an error and a retry handler', () => { + const onRetry = jest.fn(); + + render( + + ); + + expect(screen.getByRole('button', {name: 'Retry'})).toBeInTheDocument(); + expect( + screen.queryByRole('link', {name: 'Open in Discover'}) + ).not.toBeInTheDocument(); + }); + }); +}); diff --git a/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.stories.tsx b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.stories.tsx new file mode 100644 index 00000000000000..eae96d30624839 --- /dev/null +++ b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.stories.tsx @@ -0,0 +1,234 @@ +import {Fragment} from 'react'; +import {useTheme} from '@emotion/react'; +import styled from '@emotion/styled'; +import moment from 'moment-timezone'; + +import JSXNode from 'sentry/components/stories/jsxNode'; +import SideBySide from 'sentry/components/stories/sideBySide'; +import SizingWindow from 'sentry/components/stories/sizingWindow'; +import storyBook from 'sentry/stories/storyBook'; +import type {DateString} from 'sentry/types/core'; +import usePageFilters from 'sentry/utils/usePageFilters'; + +import type {Release, TimeseriesData} from '../common/types'; + +import {AreaChartWidget} from './areaChartWidget'; +import sampleLatencyTimeSeries from './sampleLatencyTimeSeries.json'; +import sampleSpanDurationTimeSeries from './sampleSpanDurationTimeSeries.json'; + +export default storyBook(AreaChartWidget, story => { + story('Getting Started', () => { + return ( + +

+ is a Dashboard Widget Component. It displays + a timeseries chart with multiple timeseries, and the timeseries are stacked. + Each timeseries is shown using a solid block of color. This chart is used to + visualize multiple timeseries that represent parts of something. For example, a + chart that shows time spent in the app broken down by component. In all other + ways, it behaves like , though it doesn't + support features like "Previous Period Data". +

+

+ NOTE: This chart is not appropriate for showing a single timeseries! + You should use instead. +

+
+ ); + }); + + story('Visualization', () => { + const {selection} = usePageFilters(); + const {datetime} = selection; + const {start, end} = datetime; + + const latencyTimeSeries = toTimeSeriesSelection( + sampleLatencyTimeSeries as unknown as TimeseriesData, + start, + end + ); + + const spanDurationTimeSeries = toTimeSeriesSelection( + sampleSpanDurationTimeSeries as unknown as TimeseriesData, + start, + end + ); + + return ( + +

+ The visualization of a stacked area chart. It + has some bells and whistles including automatic axes labels, and a hover + tooltip. Like other widgets, it automatically fills the parent element. +

+ + + +
+ ); + }); + + story('State', () => { + return ( + +

+ supports the usual loading and error states. + The loading state shows a spinner. The error state shows a message, and an + optional "Retry" button. +

+ + + + + + + + + + + + + {}} + /> + + +
+ ); + }); + + story('Colors', () => { + const theme = useTheme(); + + return ( + +

+ You can control the color of each timeseries by setting the color{' '} + attribute to a string that contains a valid hex color code. +

+ + + + +
+ ); + }); + + story('Releases', () => { + const releases = [ + { + version: 'ui@0.1.2', + timestamp: sampleLatencyTimeSeries.data.at(2)?.timestamp, + }, + { + version: 'ui@0.1.3', + timestamp: sampleLatencyTimeSeries.data.at(20)?.timestamp, + }, + ].filter(hasTimestamp); + + return ( + +

+ supports the releases prop. If + passed in, the widget will plot every release as a vertical line that overlays + the chart data. Clicking on a release line will open the release details page. +

+ + + + +
+ ); + }); +}); + +const MediumWidget = styled('div')` + width: 420px; + height: 250px; +`; + +const SmallWidget = styled('div')` + width: 360px; + height: 160px; +`; + +const SmallSizingWindow = styled(SizingWindow)` + width: 50%; + height: 300px; +`; + +function toTimeSeriesSelection( + timeSeries: TimeseriesData, + start: DateString | null, + end: DateString | null +): TimeseriesData { + return { + ...timeSeries, + data: timeSeries.data.filter(datum => { + if (start && moment(datum.timestamp).isBefore(moment.utc(start))) { + return false; + } + + if (end && moment(datum.timestamp).isAfter(moment.utc(end))) { + return false; + } + + return true; + }), + }; +} + +function hasTimestamp(release: Partial): release is Release { + return Boolean(release?.timestamp); +} diff --git a/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.tsx b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.tsx new file mode 100644 index 00000000000000..71d3b76c4f4826 --- /dev/null +++ b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidget.tsx @@ -0,0 +1,78 @@ +import styled from '@emotion/styled'; + +import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask'; +import LoadingIndicator from 'sentry/components/loadingIndicator'; +import {defined} from 'sentry/utils'; +import { + AreaChartWidgetVisualization, + type AreaChartWidgetVisualizationProps, +} from 'sentry/views/dashboards/widgets/areaChartWidget/areaChartWidgetVisualization'; +import { + WidgetFrame, + type WidgetFrameProps, +} from 'sentry/views/dashboards/widgets/common/widgetFrame'; + +import {MISSING_DATA_MESSAGE, X_GUTTER, Y_GUTTER} from '../common/settings'; +import type {StateProps} from '../common/types'; + +export interface AreaChartWidgetProps + extends StateProps, + Omit, + Partial {} + +export function AreaChartWidget(props: AreaChartWidgetProps) { + const {timeseries} = props; + + if (props.isLoading) { + return ( + + + + + + + ); + } + + let parsingError: string | undefined = undefined; + + if (!defined(timeseries)) { + parsingError = MISSING_DATA_MESSAGE; + } + + const error = props.error ?? parsingError; + + return ( + + {defined(timeseries) && ( + + + + )} + + ); +} + +const AreaChartWrapper = styled('div')` + flex-grow: 1; + padding: 0 ${X_GUTTER} ${Y_GUTTER} ${X_GUTTER}; +`; + +const LoadingPlaceholder = styled('div')` + position: absolute; + inset: 0; + + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidgetVisualization.tsx b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidgetVisualization.tsx new file mode 100644 index 00000000000000..1358e6de70291e --- /dev/null +++ b/static/app/views/dashboards/widgets/areaChartWidget/areaChartWidgetVisualization.tsx @@ -0,0 +1,215 @@ +import {useRef} from 'react'; +import {useNavigate} from 'react-router-dom'; +import {useTheme} from '@emotion/react'; +import type { + TooltipFormatterCallback, + TopLevelFormatterParams, +} from 'echarts/types/dist/shared'; +import type EChartsReactCore from 'echarts-for-react/lib/core'; + +import BaseChart from 'sentry/components/charts/baseChart'; +import {getFormatter} from 'sentry/components/charts/components/tooltip'; +import AreaSeries from 'sentry/components/charts/series/areaSeries'; +import LineSeries from 'sentry/components/charts/series/lineSeries'; +import {useChartZoom} from 'sentry/components/charts/useChartZoom'; +import {isChartHovered} from 'sentry/components/charts/utils'; +import type {Series} from 'sentry/types/echarts'; +import {defined} from 'sentry/utils'; +import normalizeUrl from 'sentry/utils/url/normalizeUrl'; +import useOrganization from 'sentry/utils/useOrganization'; +import usePageFilters from 'sentry/utils/usePageFilters'; + +import {useWidgetSyncContext} from '../../contexts/widgetSyncContext'; +import {formatChartValue} from '../common/formatChartValue'; +import {ReleaseSeries} from '../common/releaseSeries'; +import type {Meta, Release, TimeseriesData} from '../common/types'; + +export interface AreaChartWidgetVisualizationProps { + timeseries: TimeseriesData[]; + meta?: Meta; + releases?: Release[]; +} + +export function AreaChartWidgetVisualization(props: AreaChartWidgetVisualizationProps) { + const chartRef = useRef(null); + const {register: registerWithWidgetSyncContext} = useWidgetSyncContext(); + + const pageFilters = usePageFilters(); + const {start, end, period, utc} = pageFilters.selection.datetime; + const {meta} = props; + + const theme = useTheme(); + const organization = useOrganization(); + const navigate = useNavigate(); + + let releaseSeries: Series | undefined = undefined; + if (props.releases) { + const onClick = (release: Release) => { + navigate( + normalizeUrl({ + pathname: `/organizations/${ + organization.slug + }/releases/${encodeURIComponent(release.version)}/`, + }) + ); + }; + + releaseSeries = ReleaseSeries(theme, props.releases, onClick, utc ?? false); + } + + const chartZoomProps = useChartZoom({ + saveOnZoom: true, + }); + + // TODO: There's a TypeScript indexing error here. This _could_ in theory be + // `undefined`. We need to guard against this in the parent component, and + // show an error. + const firstSeries = props.timeseries[0]; + + // TODO: Raise error if attempting to plot series of different types or units + const firstSeriesField = firstSeries?.field; + const type = meta?.fields?.[firstSeriesField] ?? 'number'; + const unit = meta?.units?.[firstSeriesField] ?? undefined; + + const formatter: TooltipFormatterCallback = ( + params, + asyncTicket + ) => { + // Only show the tooltip of the current chart. Otherwise, all tooltips + // in the chart group appear. + if (!isChartHovered(chartRef?.current)) { + return ''; + } + + let deDupedParams = params; + + if (Array.isArray(params)) { + // We split each series into a complete and incomplete series, and they + // have the same name. The two series overlap at one point on the chart, + // to create a continuous line. This code prevents both series from + // showing up on the tooltip + const uniqueSeries = new Set(); + + deDupedParams = params.filter(param => { + // Filter null values from tooltip + if (param.value[1] === null) { + return false; + } + + if (uniqueSeries.has(param.seriesName)) { + return false; + } + + uniqueSeries.add(param.seriesName); + return true; + }); + } + + return getFormatter({ + isGroupedByDate: true, + showTimeInTooltip: true, + valueFormatter: value => { + return formatChartValue(value, type, unit); + }, + truncate: true, + utc: utc ?? false, + })(deDupedParams, asyncTicket); + }; + + let visibleSeriesCount = props.timeseries.length; + if (releaseSeries) { + visibleSeriesCount += 1; + } + + const showLegend = visibleSeriesCount > 1; + + return ( + { + chartRef.current = e; + + if (e?.getEchartsInstance) { + registerWithWidgetSyncContext(e.getEchartsInstance()); + } + }} + autoHeightResize + series={[ + ...props.timeseries.map(timeserie => { + return AreaSeries({ + name: timeserie.field, + color: timeserie.color, + stack: 'area', + animation: false, + areaStyle: { + color: timeserie.color, + opacity: 1.0, + }, + data: timeserie.data.map(datum => { + return [datum.timestamp, datum.value]; + }), + }); + }), + releaseSeries && + LineSeries({ + ...releaseSeries, + name: releaseSeries.seriesName, + data: [], + }), + ].filter(defined)} + grid={{ + left: 0, + top: showLegend ? 25 : 10, + right: 4, + bottom: 0, + containLabel: true, + }} + legend={ + showLegend + ? { + top: 0, + left: 0, + } + : undefined + } + tooltip={{ + trigger: 'axis', + axisPointer: { + type: 'cross', + }, + formatter, + }} + xAxis={{ + axisLabel: { + padding: [0, 10, 0, 10], + width: 60, + }, + splitNumber: 0, + }} + yAxis={{ + axisLabel: { + formatter(value: number) { + return formatChartValue(value, type, unit); + }, + }, + axisPointer: { + type: 'line', + snap: false, + lineStyle: { + type: 'solid', + width: 0.5, + }, + label: { + show: false, + }, + }, + }} + {...chartZoomProps} + isGroupedByDate + useMultilineDate + start={start ? new Date(start) : undefined} + end={end ? new Date(end) : undefined} + period={period} + utc={utc ?? undefined} + /> + ); +} diff --git a/static/app/views/dashboards/widgets/areaChartWidget/sampleLatencyTimeSeries.json b/static/app/views/dashboards/widgets/areaChartWidget/sampleLatencyTimeSeries.json new file mode 100644 index 00000000000000..f862de4e76249e --- /dev/null +++ b/static/app/views/dashboards/widgets/areaChartWidget/sampleLatencyTimeSeries.json @@ -0,0 +1,205 @@ +{ + "field": "avg(messaging.message.receive.latency)", + "data": [ + { + "timestamp": "2024-12-09T22:00:00Z", + "value": 15.797387473297285 + }, + { + "timestamp": "2024-12-09T22:30:00Z", + "value": 6.098785205112891 + }, + { + "timestamp": "2024-12-09T23:00:00Z", + "value": 10.023521061981072 + }, + { + "timestamp": "2024-12-09T23:30:00Z", + "value": 6.303244308149576 + }, + { + "timestamp": "2024-12-10T00:00:00Z", + "value": 11.352363343199878 + }, + { + "timestamp": "2024-12-10T00:30:00Z", + "value": 4.845476325747717 + }, + { + "timestamp": "2024-12-10T01:00:00Z", + "value": 11.251521855070287 + }, + { + "timestamp": "2024-12-10T01:30:00Z", + "value": 4.425860645618586 + }, + { + "timestamp": "2024-12-10T02:00:00Z", + "value": 9.989218687466126 + }, + { + "timestamp": "2024-12-10T02:30:00Z", + "value": 4.025772756170272 + }, + { + "timestamp": "2024-12-10T03:00:00Z", + "value": 9.17541579241641 + }, + { + "timestamp": "2024-12-10T03:30:00Z", + "value": 4.2380217489924865 + }, + { + "timestamp": "2024-12-10T04:00:00Z", + "value": 8.027566877217474 + }, + { + "timestamp": "2024-12-10T04:30:00Z", + "value": 5.295137636393576 + }, + { + "timestamp": "2024-12-10T05:00:00Z", + "value": 8.656494836655359 + }, + { + "timestamp": "2024-12-10T05:30:00Z", + "value": 5.475662654003712 + }, + { + "timestamp": "2024-12-10T06:00:00Z", + "value": 9.793632841698757 + }, + { + "timestamp": "2024-12-10T06:30:00Z", + "value": 5.591866790430932 + }, + { + "timestamp": "2024-12-10T07:00:00Z", + "value": 9.187478698931766 + }, + { + "timestamp": "2024-12-10T07:30:00Z", + "value": 3.779925204205954 + }, + { + "timestamp": "2024-12-10T08:00:00Z", + "value": 10.315176397597504 + }, + { + "timestamp": "2024-12-10T08:30:00Z", + "value": 5.101678176894567 + }, + { + "timestamp": "2024-12-10T09:00:00Z", + "value": 10.405819741300553 + }, + { + "timestamp": "2024-12-10T09:30:00Z", + "value": 5.157673142672812 + }, + { + "timestamp": "2024-12-10T10:00:00Z", + "value": 13.454678120116997 + }, + { + "timestamp": "2024-12-10T10:30:00Z", + "value": 5.633257152519796 + }, + { + "timestamp": "2024-12-10T11:00:00Z", + "value": 12.38331484233643 + }, + { + "timestamp": "2024-12-10T11:30:00Z", + "value": 6.7168414807525245 + }, + { + "timestamp": "2024-12-10T12:00:00Z", + "value": 10.82394699109634 + }, + { + "timestamp": "2024-12-10T12:30:00Z", + "value": 6.649313439563898 + }, + { + "timestamp": "2024-12-10T13:00:00Z", + "value": 10.957238674362912 + }, + { + "timestamp": "2024-12-10T13:30:00Z", + "value": 7.891612896606848 + }, + { + "timestamp": "2024-12-10T14:00:00Z", + "value": 9.83684972309017 + }, + { + "timestamp": "2024-12-10T14:30:00Z", + "value": 4.472633330313572 + }, + { + "timestamp": "2024-12-10T15:00:00Z", + "value": 13.886404361521333 + }, + { + "timestamp": "2024-12-10T15:30:00Z", + "value": 9.212753388080626 + }, + { + "timestamp": "2024-12-10T16:00:00Z", + "value": 12.213060522650725 + }, + { + "timestamp": "2024-12-10T16:30:00Z", + "value": 5.308362659314872 + }, + { + "timestamp": "2024-12-10T17:00:00Z", + "value": 12.167955277129803 + }, + { + "timestamp": "2024-12-10T17:30:00Z", + "value": 5.95843470061378 + }, + { + "timestamp": "2024-12-10T18:00:00Z", + "value": 11.623431372484815 + }, + { + "timestamp": "2024-12-10T18:30:00Z", + "value": 7.133246343117738 + }, + { + "timestamp": "2024-12-10T19:00:00Z", + "value": 9.91670999332601 + }, + { + "timestamp": "2024-12-10T19:30:00Z", + "value": 8.220551566043556 + }, + { + "timestamp": "2024-12-10T20:00:00Z", + "value": 10.644324473372116 + }, + { + "timestamp": "2024-12-10T20:30:00Z", + "value": 6.380262999155083 + }, + { + "timestamp": "2024-12-10T21:00:00Z", + "value": 11.611059631291011 + }, + { + "timestamp": "2024-12-10T21:30:00Z", + "value": 4.830427051517974 + }, + { + "timestamp": "2024-12-10T22:00:00Z", + "value": 14.018863061817425 + }, + { + "timestamp": "2024-12-10T22:30:00Z", + "value": 0 + } + ] +} diff --git a/static/app/views/dashboards/widgets/areaChartWidget/sampleSpanDurationTimeSeries.json b/static/app/views/dashboards/widgets/areaChartWidget/sampleSpanDurationTimeSeries.json new file mode 100644 index 00000000000000..815c94d3eeac81 --- /dev/null +++ b/static/app/views/dashboards/widgets/areaChartWidget/sampleSpanDurationTimeSeries.json @@ -0,0 +1,205 @@ +{ + "field": "avg(span.duration)", + "data": [ + { + "timestamp": "2024-12-09T22:00:00Z", + "value": 97.16045076146938 + }, + { + "timestamp": "2024-12-09T22:30:00Z", + "value": 73.81276498986948 + }, + { + "timestamp": "2024-12-09T23:00:00Z", + "value": 83.65629482432625 + }, + { + "timestamp": "2024-12-09T23:30:00Z", + "value": 77.4773209216508 + }, + { + "timestamp": "2024-12-10T00:00:00Z", + "value": 88.87920889091058 + }, + { + "timestamp": "2024-12-10T00:30:00Z", + "value": 74.81035769529551 + }, + { + "timestamp": "2024-12-10T01:00:00Z", + "value": 74.18713473976618 + }, + { + "timestamp": "2024-12-10T01:30:00Z", + "value": 75.51014337607224 + }, + { + "timestamp": "2024-12-10T02:00:00Z", + "value": 71.75338738667213 + }, + { + "timestamp": "2024-12-10T02:30:00Z", + "value": 71.44267608528102 + }, + { + "timestamp": "2024-12-10T03:00:00Z", + "value": 73.23258991739772 + }, + { + "timestamp": "2024-12-10T03:30:00Z", + "value": 73.62710321499654 + }, + { + "timestamp": "2024-12-10T04:00:00Z", + "value": 73.24679865212109 + }, + { + "timestamp": "2024-12-10T04:30:00Z", + "value": 71.00435116898733 + }, + { + "timestamp": "2024-12-10T05:00:00Z", + "value": 78.74461664047831 + }, + { + "timestamp": "2024-12-10T05:30:00Z", + "value": 72.30733243572487 + }, + { + "timestamp": "2024-12-10T06:00:00Z", + "value": 78.42600782679243 + }, + { + "timestamp": "2024-12-10T06:30:00Z", + "value": 74.29718208848486 + }, + { + "timestamp": "2024-12-10T07:00:00Z", + "value": 77.86389655145648 + }, + { + "timestamp": "2024-12-10T07:30:00Z", + "value": 74.11896494459268 + }, + { + "timestamp": "2024-12-10T08:00:00Z", + "value": 77.83106835851203 + }, + { + "timestamp": "2024-12-10T08:30:00Z", + "value": 80.87383050251273 + }, + { + "timestamp": "2024-12-10T09:00:00Z", + "value": 85.53912058349381 + }, + { + "timestamp": "2024-12-10T09:30:00Z", + "value": 82.16649885587427 + }, + { + "timestamp": "2024-12-10T10:00:00Z", + "value": 96.46710756423539 + }, + { + "timestamp": "2024-12-10T10:30:00Z", + "value": 90.2296730934056 + }, + { + "timestamp": "2024-12-10T11:00:00Z", + "value": 86.49672620288618 + }, + { + "timestamp": "2024-12-10T11:30:00Z", + "value": 81.76636615572134 + }, + { + "timestamp": "2024-12-10T12:00:00Z", + "value": 91.30479930843254 + }, + { + "timestamp": "2024-12-10T12:30:00Z", + "value": 86.5261930482867 + }, + { + "timestamp": "2024-12-10T13:00:00Z", + "value": 88.58528428072619 + }, + { + "timestamp": "2024-12-10T13:30:00Z", + "value": 84.57326040990161 + }, + { + "timestamp": "2024-12-10T14:00:00Z", + "value": 82.43636182893853 + }, + { + "timestamp": "2024-12-10T14:30:00Z", + "value": 90.33512756892627 + }, + { + "timestamp": "2024-12-10T15:00:00Z", + "value": 103.27654137156406 + }, + { + "timestamp": "2024-12-10T15:30:00Z", + "value": 127.10951181976695 + }, + { + "timestamp": "2024-12-10T16:00:00Z", + "value": 95.72899826334971 + }, + { + "timestamp": "2024-12-10T16:30:00Z", + "value": 84.04312923323451 + }, + { + "timestamp": "2024-12-10T17:00:00Z", + "value": 85.59506151608197 + }, + { + "timestamp": "2024-12-10T17:30:00Z", + "value": 82.71849476828766 + }, + { + "timestamp": "2024-12-10T18:00:00Z", + "value": 89.57229292359843 + }, + { + "timestamp": "2024-12-10T18:30:00Z", + "value": 81.22558140363343 + }, + { + "timestamp": "2024-12-10T19:00:00Z", + "value": 86.8826705009411 + }, + { + "timestamp": "2024-12-10T19:30:00Z", + "value": 79.4383370343256 + }, + { + "timestamp": "2024-12-10T20:00:00Z", + "value": 82.63652211651058 + }, + { + "timestamp": "2024-12-10T20:30:00Z", + "value": 77.87329880924307 + }, + { + "timestamp": "2024-12-10T21:00:00Z", + "value": 82.0189272882679 + }, + { + "timestamp": "2024-12-10T21:30:00Z", + "value": 80.03621684743777 + }, + { + "timestamp": "2024-12-10T22:00:00Z", + "value": 80.69735445090794 + }, + { + "timestamp": "2024-12-10T22:30:00Z", + "value": 0 + } + ] +}