diff --git a/static/app/views/automations/components/automationStatsChart.timeRange.spec.tsx b/static/app/views/automations/components/automationStatsChart.timeRange.spec.tsx
new file mode 100644
index 000000000000..cecdccd7a041
--- /dev/null
+++ b/static/app/views/automations/components/automationStatsChart.timeRange.spec.tsx
@@ -0,0 +1,78 @@
+import type {EChartsOption} from 'echarts';
+import {OrganizationFixture} from 'sentry-fixture/organization';
+
+import {render, screen} from 'sentry-test/reactTestingLibrary';
+
+import {AutomationStatsChart} from 'sentry/views/automations/components/automationStatsChart';
+
+let mockChartOptions: EChartsOption | undefined;
+
+jest.mock('echarts-for-react/lib/core', () => {
+ return function MockEcharts({option}: {option: EChartsOption}) {
+ mockChartOptions = option;
+ return null;
+ };
+});
+
+describe('AutomationStatsChart time range', () => {
+ const organization = OrganizationFixture();
+
+ beforeEach(() => {
+ MockApiClient.clearMockResponses();
+ mockChartOptions = undefined;
+ });
+
+ it('uses the time range returned by the workflow stats endpoint on a time axis', async () => {
+ const start = Date.parse('2026-01-13T19:00:00Z');
+ const end = Date.parse('2026-01-13T20:00:00Z');
+ MockApiClient.addMockResponse({
+ url: '/organizations/org-slug/workflows/123/stats/',
+ body: {
+ meta: {dataset: 'workflow', start, end},
+ timeSeries: [
+ {
+ yAxis: 'count()',
+ values: [{timestamp: end, value: 1, incomplete: false}],
+ meta: {interval: 60 * 60 * 1000, valueType: 'integer', valueUnit: null},
+ },
+ ],
+ },
+ });
+
+ render(
+ ,
+ {organization}
+ );
+
+ expect(await screen.findByText('1')).toBeInTheDocument();
+ expect(mockChartOptions).toMatchObject({
+ xAxis: {type: 'time', min: start, max: end},
+ });
+ });
+
+ it('continues to accept the legacy response during deployment', async () => {
+ MockApiClient.addMockResponse({
+ url: '/organizations/org-slug/workflows/123/stats/',
+ body: [{date: '2026-01-13T20:00:00Z', count: 1}],
+ });
+
+ render(
+ ,
+ {organization}
+ );
+
+ expect(await screen.findByText('1')).toBeInTheDocument();
+ });
+});
diff --git a/static/app/views/automations/components/automationStatsChart.tsx b/static/app/views/automations/components/automationStatsChart.tsx
index e47cf6861d0a..4d7a0d4eeaf2 100644
--- a/static/app/views/automations/components/automationStatsChart.tsx
+++ b/static/app/views/automations/components/automationStatsChart.tsx
@@ -1,5 +1,6 @@
import {useTheme} from '@emotion/react';
import styled from '@emotion/styled';
+import {useQuery} from '@tanstack/react-query';
import {Container, Flex} from '@sentry/scraps/layout';
@@ -14,15 +15,53 @@ import {PanelFooter} from 'sentry/components/panels/panelFooter';
import {Placeholder} from 'sentry/components/placeholder';
import {t} from 'sentry/locale';
import type {Automation, AutomationStats} from 'sentry/types/workflowEngine/automations';
-import {getApiUrl} from 'sentry/utils/api/getApiUrl';
-import {getUtcDateString} from 'sentry/utils/dates';
-import {useApiQuery} from 'sentry/utils/queryClient';
+import {apiOptions} from 'sentry/utils/api/apiOptions';
+import {getUtcDateString, getUtcToLocalDateObject} from 'sentry/utils/dates';
import {useOrganization} from 'sentry/utils/useOrganization';
+import type {TimeSeries} from 'sentry/views/dashboards/widgets/common/types';
interface IssueAlertDetailsProps extends DateTimeObject {
automationId: Automation['id'];
}
+type WorkflowStatsResponse = {
+ meta: {
+ dataset: string;
+ end: number;
+ start: number;
+ };
+ timeSeries: TimeSeries[];
+};
+
+type WorkflowStatsApiResponse = AutomationStats[] | WorkflowStatsResponse;
+
+type WorkflowStats = {
+ values: TimeSeries['values'];
+ timeframe?: WorkflowStatsResponse['meta'];
+};
+
+function normalizeWorkflowStatsResponse(
+ response: WorkflowStatsApiResponse | undefined
+): WorkflowStats | undefined {
+ if (!response) {
+ return undefined;
+ }
+
+ if (Array.isArray(response)) {
+ return {
+ values: response.map(({date, count}) => ({
+ timestamp: Date.parse(date),
+ value: count,
+ })),
+ };
+ }
+
+ return {
+ timeframe: response.meta,
+ values: response.timeSeries[0]?.values ?? [],
+ };
+}
+
export function AutomationStatsChart({
automationId,
period,
@@ -34,28 +73,28 @@ export function AutomationStatsChart({
const organization = useOrganization();
const chartZoomProps = useChartZoom({saveOnZoom: true});
const {
- data: fireHistory,
+ data: statsResponse,
isPending,
isError,
- } = useApiQuery(
- [
- getApiUrl('/organizations/$organizationIdOrSlug/workflows/$workflowId/stats/', {
- path: {organizationIdOrSlug: organization.slug, workflowId: automationId},
- }),
+ } = useQuery(
+ apiOptions.as()(
+ '/organizations/$organizationIdOrSlug/workflows/$workflowId/stats/',
{
+ path: {organizationIdOrSlug: organization.slug, workflowId: automationId},
query: {
...(period && {statsPeriod: period}),
start: start ? getUtcDateString(start) : undefined,
end: end ? getUtcDateString(end) : undefined,
utc: utc ? 'true' : undefined,
},
- },
- ],
- {staleTime: 30000}
+ staleTime: 30_000,
+ }
+ )
);
+ const stats = normalizeWorkflowStatsResponse(statsResponse);
const totalAlertsTriggered =
- fireHistory?.reduce((acc, curr) => acc + curr.count, 0) ?? 0;
+ stats?.values.reduce((acc, curr) => acc + (curr.value ?? 0), 0) ?? 0;
return (
@@ -65,10 +104,14 @@ export function AutomationStatsChart({
{isPending && }
{isError && }
- {fireHistory && (
+ {stats && (
({
- name: automation.date,
- value: automation.count,
+ data: stats.values.map(({timestamp, value}) => ({
+ name: timestamp,
+ value: value ?? 0,
})),
emphasis: {
disabled: true,