Skip to content

Commit 99ca862

Browse files
authored
feat(aci): set up detector Ongoing Issues (#96604)
<img width="856" height="694" alt="Screenshot 2025-07-29 at 9 47 43 AM" src="https://github.com/user-attachments/assets/606673d9-f62a-4c71-8a35-bfdef1ee1bc3" /> extracted time selector for metric detector chart to the details component so that it could be shared between the chart and table
1 parent ec21956 commit 99ca862

File tree

11 files changed

+185
-35
lines changed

11 files changed

+185
-35
lines changed

static/app/components/workflowEngine/ui/section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
33
import {Flex} from 'sentry/components/core/layout';
44

55
type SectionProps = {
6-
title: string;
6+
title: React.ReactNode;
77
children?: React.ReactNode;
88
description?: string;
99
};

static/app/views/detectors/components/details/common/ongoingIssues.tsx

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,80 @@
1+
import {LinkButton} from 'sentry/components/core/button/linkButton';
2+
import {Flex} from 'sentry/components/core/layout';
3+
import EmptyStateWarning from 'sentry/components/emptyStateWarning';
14
import ErrorBoundary from 'sentry/components/errorBoundary';
2-
import {SimpleTable} from 'sentry/components/tables/simpleTable';
5+
import GroupList from 'sentry/components/issues/groupList';
6+
import Panel from 'sentry/components/panels/panel';
7+
import PanelBody from 'sentry/components/panels/panelBody';
38
import Section from 'sentry/components/workflowEngine/ui/section';
49
import {t} from 'sentry/locale';
10+
import {getUtcDateString} from 'sentry/utils/dates';
11+
import useOrganization from 'sentry/utils/useOrganization';
12+
import usePageFilters from 'sentry/utils/usePageFilters';
13+
14+
interface Props {
15+
detectorId: string;
16+
query?: Record<string, any>;
17+
}
18+
19+
function EmptyMessage() {
20+
return (
21+
<Panel>
22+
<PanelBody>
23+
<EmptyStateWarning small withIcon={false}>
24+
{t('No ongoing issues found for this monitor')}
25+
</EmptyStateWarning>
26+
</PanelBody>
27+
</Panel>
28+
);
29+
}
30+
31+
export function DetectorDetailsOngoingIssues({detectorId, query}: Props) {
32+
const organization = useOrganization();
33+
34+
const {selection} = usePageFilters();
35+
const {start, end, period} = selection.datetime;
36+
const timeProps =
37+
start && end
38+
? {
39+
start: getUtcDateString(start),
40+
end: getUtcDateString(end),
41+
}
42+
: {
43+
statsPeriod: period,
44+
};
45+
46+
const queryParams = {
47+
...(query || timeProps),
48+
query: `is:unresolved detector:${detectorId}`,
49+
limit: 5,
50+
};
51+
52+
const issueSearch = {
53+
pathname: `/organizations/${organization.slug}/issues/`,
54+
query: queryParams,
55+
};
556

6-
export function DetectorDetailsOngoingIssues() {
757
return (
8-
<Section title={t('Ongoing Issues')}>
9-
{/* TODO: Implement fetching and replace with GroupList */}
58+
<Section
59+
title={
60+
<Flex justify={'between'} align="center">
61+
{t('Ongoing Issues')}
62+
<LinkButton size="xs" to={issueSearch}>
63+
{t('View All')}
64+
</LinkButton>
65+
</Flex>
66+
}
67+
>
1068
<ErrorBoundary mini>
11-
<SimpleTable>
12-
<SimpleTable.Header>
13-
<SimpleTable.HeaderCell>{t('Issue')}</SimpleTable.HeaderCell>
14-
</SimpleTable.Header>
15-
<SimpleTable.Empty>{t('Not yet implemented')}</SimpleTable.Empty>
16-
</SimpleTable>
69+
<GroupList
70+
endpointPath={`/organizations/${organization.slug}/issues/`}
71+
queryParams={queryParams}
72+
canSelectGroups={false}
73+
withPagination={false}
74+
withChart={false}
75+
renderEmptyMessage={EmptyMessage}
76+
source="detector-details"
77+
/>
1778
</ErrorBoundary>
1879
</Section>
1980
);

static/app/views/detectors/components/details/error/index.spec.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {ErrorDetectorFixture} from 'sentry-fixture/detectors';
2+
import {GroupFixture} from 'sentry-fixture/group';
23
import {ProjectFixture} from 'sentry-fixture/project';
34

45
import {render, screen} from 'sentry-test/reactTestingLibrary';
@@ -17,6 +18,16 @@ describe('ErrorDetectorDetails', function () {
1718
method: 'GET',
1819
body: ProjectFixture(),
1920
});
21+
MockApiClient.addMockResponse({
22+
url: '/organizations/org-slug/users/',
23+
method: 'GET',
24+
body: [],
25+
});
26+
MockApiClient.addMockResponse({
27+
url: '/organizations/org-slug/issues/?limit=5&query=is%3Aunresolved%20detector%3A2&statsPeriod=14d',
28+
method: 'GET',
29+
body: [GroupFixture()],
30+
});
2031
});
2132

2233
describe('Resolve section', function () {

static/app/views/detectors/components/details/error/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {ExternalLink, Link} from 'sentry/components/core/link';
22
import {Text} from 'sentry/components/core/text';
3+
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
34
import Placeholder from 'sentry/components/placeholder';
45
import DetailLayout from 'sentry/components/workflowEngine/layout/detail';
56
import Section from 'sentry/components/workflowEngine/ui/section';
@@ -69,7 +70,8 @@ export function ErrorDetectorDetails({detector, project}: ErrorDetectorDetailsPr
6970
<DetectorDetailsHeader detector={detector} project={project} />
7071
<DetailLayout.Body>
7172
<DetailLayout.Main>
72-
<DetectorDetailsOngoingIssues />
73+
<DatePageFilter />
74+
<DetectorDetailsOngoingIssues detectorId={detector.id} />
7375
<DetectorDetailsAutomations detector={detector} />
7476
</DetailLayout.Main>
7577
<DetailLayout.Sidebar>

static/app/views/detectors/components/details/fallback.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import DetailLayout from 'sentry/components/workflowEngine/layout/detail';
22
import type {Project} from 'sentry/types/project';
33
import type {Detector} from 'sentry/types/workflowEngine/detectors';
4+
import {getUtcDateString} from 'sentry/utils/dates';
5+
import usePageFilters from 'sentry/utils/usePageFilters';
46
import {DetectorDetailsAssignee} from 'sentry/views/detectors/components/details/common/assignee';
57
import {DetectorDetailsAutomations} from 'sentry/views/detectors/components/details/common/automations';
68
import {DetectorExtraDetails} from 'sentry/views/detectors/components/details/common/extraDetails';
@@ -16,12 +18,24 @@ export function FallbackDetectorDetails({
1618
detector,
1719
project,
1820
}: FallbackDetectorDetailsProps) {
21+
const {selection} = usePageFilters();
22+
const {start, end, period} = selection.datetime;
23+
const timeProps =
24+
start && end
25+
? {
26+
start: getUtcDateString(start),
27+
end: getUtcDateString(end),
28+
}
29+
: {
30+
statsPeriod: period,
31+
};
32+
1933
return (
2034
<DetailLayout>
2135
<DetectorDetailsHeader detector={detector} project={project} />
2236
<DetailLayout.Body>
2337
<DetailLayout.Main>
24-
<DetectorDetailsOngoingIssues />
38+
<DetectorDetailsOngoingIssues detectorId={detector.id} query={timeProps} />
2539
<DetectorDetailsAutomations detector={detector} />
2640
</DetailLayout.Main>
2741
<DetailLayout.Sidebar>

static/app/views/detectors/components/details/metric/chart.tsx

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
11
import styled from '@emotion/styled';
22

3-
import {CompactSelect} from 'sentry/components/core/compactSelect';
43
import {Flex} from 'sentry/components/core/layout';
54
import type {MetricDetector} from 'sentry/types/workflowEngine/detectors';
6-
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
5+
import type {TimePeriod} from 'sentry/views/alerts/rules/metric/types';
76
import {MetricDetectorChart} from 'sentry/views/detectors/components/forms/metric/metricDetectorChart';
87
import {getDetectorDataset} from 'sentry/views/detectors/components/forms/metric/metricFormData';
9-
import {useTimePeriodSelection} from 'sentry/views/detectors/hooks/useTimePeriodSelection';
108

119
interface MetricDetectorDetailsChartProps {
1210
detector: MetricDetector;
11+
statsPeriod: TimePeriod;
1312
}
1413

15-
export function MetricDetectorDetailsChart({detector}: MetricDetectorDetailsChartProps) {
14+
export function MetricDetectorDetailsChart({
15+
detector,
16+
statsPeriod,
17+
}: MetricDetectorDetailsChartProps) {
1618
const dataSource = detector.dataSources[0];
1719
const snubaQuery = dataSource.queryObj?.snubaQuery;
1820

19-
const {selectedTimePeriod, setSelectedTimePeriod, timePeriodOptions} =
20-
useTimePeriodSelection({
21-
dataset: snubaQuery?.dataset ?? Dataset.ERRORS,
22-
interval: snubaQuery?.timeWindow,
23-
});
24-
2521
if (!snubaQuery) {
2622
// Unlikely, helps narrow types
2723
return null;
@@ -39,12 +35,6 @@ export function MetricDetectorDetailsChart({detector}: MetricDetectorDetailsChar
3935

4036
return (
4137
<Flex direction="column" gap="xl">
42-
<CompactSelect
43-
size="sm"
44-
options={timePeriodOptions}
45-
value={selectedTimePeriod}
46-
onChange={opt => setSelectedTimePeriod(opt.value)}
47-
/>
4838
<ChartContainer>
4939
<ChartContainerBody>
5040
<MetricDetectorChart
@@ -56,7 +46,7 @@ export function MetricDetectorDetailsChart({detector}: MetricDetectorDetailsChar
5646
projectId={detector.projectId}
5747
conditions={conditions}
5848
detectionType={detectionType}
59-
statsPeriod={selectedTimePeriod}
49+
statsPeriod={statsPeriod}
6050
comparisonDelta={comparisonDelta}
6151
/>
6252
</ChartContainerBody>

static/app/views/detectors/components/details/metric/index.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
1+
import {CompactSelect} from 'sentry/components/core/compactSelect';
12
import DetailLayout from 'sentry/components/workflowEngine/layout/detail';
23
import type {Project} from 'sentry/types/project';
34
import type {MetricDetector} from 'sentry/types/workflowEngine/detectors';
5+
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
46
import {DetectorDetailsAutomations} from 'sentry/views/detectors/components/details/common/automations';
57
import {DetectorDetailsHeader} from 'sentry/views/detectors/components/details/common/header';
68
import {DetectorDetailsOngoingIssues} from 'sentry/views/detectors/components/details/common/ongoingIssues';
79
import {MetricDetectorDetailsChart} from 'sentry/views/detectors/components/details/metric/chart';
810
import {MetricDetectorDetailsSidebar} from 'sentry/views/detectors/components/details/metric/sidebar';
11+
import {useTimePeriodSelection} from 'sentry/views/detectors/hooks/useTimePeriodSelection';
912

1013
type MetricDetectorDetailsProps = {
1114
detector: MetricDetector;
1215
project: Project;
1316
};
1417

1518
export function MetricDetectorDetails({detector, project}: MetricDetectorDetailsProps) {
19+
const dataSource = detector.dataSources[0];
20+
const snubaQuery = dataSource.queryObj?.snubaQuery;
21+
22+
const {selectedTimePeriod, setSelectedTimePeriod, timePeriodOptions} =
23+
useTimePeriodSelection({
24+
dataset: snubaQuery?.dataset ?? Dataset.ERRORS,
25+
interval: snubaQuery?.timeWindow,
26+
});
27+
1628
return (
1729
<DetailLayout>
1830
<DetectorDetailsHeader detector={detector} project={project} />
1931
<DetailLayout.Body>
2032
<DetailLayout.Main>
21-
<MetricDetectorDetailsChart detector={detector} />
22-
<DetectorDetailsOngoingIssues />
33+
<CompactSelect
34+
size="sm"
35+
options={timePeriodOptions}
36+
value={selectedTimePeriod}
37+
onChange={opt => setSelectedTimePeriod(opt.value)}
38+
/>
39+
<MetricDetectorDetailsChart
40+
detector={detector}
41+
statsPeriod={selectedTimePeriod}
42+
/>
43+
<DetectorDetailsOngoingIssues
44+
detectorId={detector.id}
45+
query={{statsPeriod: selectedTimePeriod}}
46+
/>
2347
<DetectorDetailsAutomations detector={detector} />
2448
</DetailLayout.Main>
2549
<DetailLayout.Sidebar>

static/app/views/detectors/components/details/uptime/index.spec.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {AutomationFixture} from 'sentry-fixture/automations';
22
import {UptimeDetectorFixture} from 'sentry-fixture/detectors';
3+
import {GroupFixture} from 'sentry-fixture/group';
34
import {ProjectFixture} from 'sentry-fixture/project';
45

56
import {render, screen} from 'sentry-test/reactTestingLibrary';
@@ -18,6 +19,14 @@ describe('UptimeDetectorDetails', function () {
1819
method: 'GET',
1920
body: [AutomationFixture()],
2021
});
22+
MockApiClient.addMockResponse({
23+
url: `/organizations/org-slug/users/`,
24+
body: [],
25+
});
26+
MockApiClient.addMockResponse({
27+
url: '/organizations/org-slug/issues/?limit=5&query=is%3Aunresolved%20detector%3A3&statsPeriod=14d',
28+
body: [GroupFixture()],
29+
});
2130
});
2231

2332
it('displays correct detector details', async function () {

static/app/views/detectors/components/details/uptime/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {KeyValueTableRow} from 'sentry/components/keyValueTable';
2+
import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
23
import DetailLayout from 'sentry/components/workflowEngine/layout/detail';
34
import Section from 'sentry/components/workflowEngine/ui/section';
45
import {t} from 'sentry/locale';
@@ -24,7 +25,8 @@ export function UptimeDetectorDetails({detector, project}: UptimeDetectorDetails
2425
<DetectorDetailsHeader detector={detector} project={project} />
2526
<DetailLayout.Body>
2627
<DetailLayout.Main>
27-
<DetectorDetailsOngoingIssues />
28+
<DatePageFilter />
29+
<DetectorDetailsOngoingIssues detectorId={detector.id} />
2830
<DetectorDetailsAutomations detector={detector} />
2931
</DetailLayout.Main>
3032
<DetailLayout.Sidebar>

static/app/views/detectors/detail.spec.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import {
33
MetricDetectorFixture,
44
SnubaQueryDataSourceFixture,
55
} from 'sentry-fixture/detectors';
6+
import {GroupFixture} from 'sentry-fixture/group';
67
import {OrganizationFixture} from 'sentry-fixture/organization';
78
import {ProjectFixture} from 'sentry-fixture/project';
89
import {TeamFixture} from 'sentry-fixture/team';
910
import {UserFixture} from 'sentry-fixture/user';
1011

11-
import {render, screen} from 'sentry-test/reactTestingLibrary';
12+
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
1213

1314
import ProjectsStore from 'sentry/stores/projectsStore';
1415
import TeamStore from 'sentry/stores/teamStore';
@@ -57,6 +58,10 @@ describe('DetectorDetails', function () {
5758
],
5859
match: [MockApiClient.matchQuery({id: ['1', '2']})],
5960
});
61+
MockApiClient.addMockResponse({
62+
url: `/organizations/${organization.slug}/users/`,
63+
body: [],
64+
});
6065
MockApiClient.addMockResponse({
6166
url: `/organizations/${organization.slug}/users/1/`,
6267
body: UserFixture(),
@@ -70,6 +75,10 @@ describe('DetectorDetails', function () {
7075
],
7176
},
7277
});
78+
MockApiClient.addMockResponse({
79+
url: '/organizations/org-slug/issues/?limit=5&query=is%3Aunresolved%20detector%3A1&statsPeriod=9998m',
80+
body: [GroupFixture()],
81+
});
7382
});
7483

7584
it('renders the detector details and snuba query', async function () {
@@ -122,5 +131,30 @@ describe('DetectorDetails', function () {
122131
expect(screen.getByText('Last Triggered')).toBeInTheDocument();
123132
expect(screen.getByText('Actions')).toBeInTheDocument();
124133
});
134+
135+
it('displays ongoing issues for the detector', async function () {
136+
const {router} = render(<DetectorDetails />, {
137+
organization,
138+
initialRouterConfig,
139+
});
140+
141+
// Verify ongoing issues section is displayed
142+
expect(await screen.findByText('RequestError')).toBeInTheDocument();
143+
144+
// Verify the View All button links to the issues page with the correct query
145+
const viewAllButton = screen.getByRole('button', {name: 'View All'});
146+
147+
await userEvent.click(viewAllButton);
148+
149+
// Check that navigation occurred to the issues page with the detector query
150+
expect(router.location.pathname).toBe(
151+
`/organizations/${organization.slug}/issues/`
152+
);
153+
expect(router.location.query).toEqual(
154+
expect.objectContaining({
155+
query: `is:unresolved detector:${snubaQueryDetector.id}`,
156+
})
157+
);
158+
});
125159
});
126160
});

0 commit comments

Comments
 (0)