Skip to content

Commit a72df07

Browse files
authored
ref(aci): Use updated detector list API for connected automations (#93550)
1 parent f0134a0 commit a72df07

File tree

3 files changed

+102
-92
lines changed

3 files changed

+102
-92
lines changed

static/app/views/automations/hooks/index.tsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import type {
44
DataConditionHandler,
55
DataConditionHandlerGroupType,
66
} from 'sentry/types/workflowEngine/dataConditions';
7-
import type {ApiQueryKey} from 'sentry/utils/queryClient';
8-
import {useApiQueries, useApiQuery} from 'sentry/utils/queryClient';
7+
import type {ApiQueryKey, UseApiQueryOptions} from 'sentry/utils/queryClient';
8+
import {useApiQuery} from 'sentry/utils/queryClient';
99
import useOrganization from 'sentry/utils/useOrganization';
1010

1111
const makeAutomationsQueryKey = ({
@@ -41,12 +41,16 @@ interface UseAutomationsQueryOptions {
4141
query?: string;
4242
sort?: string;
4343
}
44-
export function useAutomationsQuery(options: UseAutomationsQueryOptions = {}) {
44+
export function useAutomationsQuery(
45+
options: UseAutomationsQueryOptions = {},
46+
queryOptions: Partial<UseApiQueryOptions<Automation[]>> = {}
47+
) {
4548
const {slug: orgSlug} = useOrganization();
4649

4750
return useApiQuery<Automation[]>(makeAutomationsQueryKey({orgSlug, ...options}), {
4851
staleTime: 0,
4952
retry: false,
53+
...queryOptions,
5054
});
5155
}
5256

@@ -79,15 +83,3 @@ export function useAvailableActionsQuery() {
7983
retry: false,
8084
});
8185
}
82-
83-
export function useDetectorQueriesByIds(automationIds: string[]) {
84-
const org = useOrganization();
85-
86-
return useApiQueries<Automation>(
87-
automationIds.map(id => makeAutomationQueryKey(org.slug, id)),
88-
{
89-
staleTime: 0,
90-
retry: false,
91-
}
92-
);
93-
}

static/app/views/detectors/components/connectedAutomationList.tsx

Lines changed: 52 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
import {useEffect, useState} from 'react';
2-
3-
import {Flex} from 'sentry/components/container/flex';
41
import {Button} from 'sentry/components/core/button';
5-
import {ButtonBar} from 'sentry/components/core/button/buttonBar';
62
import LoadingError from 'sentry/components/loadingError';
73
import LoadingIndicator from 'sentry/components/loadingIndicator';
4+
import Pagination from 'sentry/components/pagination';
85
import {ActionCell} from 'sentry/components/workflowEngine/gridCell/actionCell';
96
import AutomationTitleCell from 'sentry/components/workflowEngine/gridCell/automationTitleCell';
107
import {TimeAgoCell} from 'sentry/components/workflowEngine/gridCell/timeAgoCell';
118
import {defineColumns, SimpleTable} from 'sentry/components/workflowEngine/simpleTable';
12-
import {IconChevron} from 'sentry/icons';
139
import {t} from 'sentry/locale';
1410
import type {Automation} from 'sentry/types/workflowEngine/automations';
1511
import type {Detector} from 'sentry/types/workflowEngine/detectors';
1612
import {defined} from 'sentry/utils';
13+
import {useLocation} from 'sentry/utils/useLocation';
14+
import {useNavigate} from 'sentry/utils/useNavigate';
1715
import useOrganization from 'sentry/utils/useOrganization';
18-
import {useDetectorQueriesByIds} from 'sentry/views/automations/hooks';
16+
import {useAutomationsQuery} from 'sentry/views/automations/hooks';
1917
import {getAutomationActions} from 'sentry/views/automations/hooks/utils';
2018
import {makeAutomationDetailsPathname} from 'sentry/views/automations/pathnames';
2119

@@ -34,36 +32,23 @@ export function ConnectedAutomationsList({
3432
}: Props) {
3533
const organization = useOrganization();
3634
const canEdit = connectedAutomationIds && !!toggleConnected;
37-
// TODO: There will eventually be a single api call to fetch a page of automations
38-
const queries = useDetectorQueriesByIds(automationIds);
39-
const [currentPage, setCurrentPage] = useState(0);
40-
const totalPages = Math.ceil(queries.length / AUTOMATIONS_PER_PAGE);
41-
42-
// Reset the page when the automationIds change
43-
useEffect(() => {
44-
setCurrentPage(0);
45-
}, [automationIds]);
46-
47-
const data = queries
48-
.map((query): ConnectedAutomationsData | undefined => {
49-
if (!query.data) {
50-
return undefined;
51-
}
52-
return {
53-
...query.data,
54-
link: makeAutomationDetailsPathname(organization.slug, query.data.id),
55-
connected: canEdit
56-
? {
57-
isConnected: connectedAutomationIds?.has(query.data.id),
58-
toggleConnected: () => toggleConnected?.(query.data.id),
59-
}
60-
: undefined,
61-
};
62-
})
63-
.filter(defined);
64-
65-
const isLoading = queries.some(query => query.isPending);
66-
const isError = queries.some(query => query.isError);
35+
const navigate = useNavigate();
36+
const location = useLocation();
37+
38+
const {
39+
data: automations,
40+
isLoading,
41+
isError,
42+
getResponseHeader,
43+
} = useAutomationsQuery(
44+
{
45+
ids: automationIds,
46+
limit: AUTOMATIONS_PER_PAGE,
47+
cursor:
48+
typeof location.query.cursor === 'string' ? location.query.cursor : undefined,
49+
},
50+
{enabled: automationIds.length > 0}
51+
);
6752

6853
if (isError) {
6954
return <LoadingError />;
@@ -73,52 +58,42 @@ export function ConnectedAutomationsList({
7358
return <LoadingIndicator />;
7459
}
7560

76-
const handlePreviousPage = () => {
77-
setCurrentPage(prev => Math.max(0, prev - 1));
78-
};
79-
const handleNextPage = () => {
80-
setCurrentPage(prev => Math.min(totalPages - 1, prev + 1));
81-
};
82-
83-
const pagination = (
84-
<Flex justify="flex-end">
85-
<ButtonBar merged>
86-
<Button
87-
onClick={handlePreviousPage}
88-
disabled={currentPage === 0}
89-
aria-label={t('Previous page')}
90-
icon={<IconChevron direction="left" />}
91-
size="sm"
92-
/>
93-
<Button
94-
onClick={handleNextPage}
95-
disabled={currentPage === totalPages - 1}
96-
aria-label={t('Next page')}
97-
icon={<IconChevron direction="right" />}
98-
size="sm"
99-
/>
100-
</ButtonBar>
101-
</Flex>
102-
);
103-
104-
if (canEdit) {
105-
return (
106-
<Flex column>
107-
<SimpleTable columns={connectedColumns} data={data} />
108-
{pagination}
109-
</Flex>
110-
);
111-
}
61+
const tableData: ConnectedAutomationsData[] =
62+
automations
63+
?.map(automation => {
64+
return {
65+
...automation,
66+
link: makeAutomationDetailsPathname(organization.slug, automation.id),
67+
connected: canEdit
68+
? {
69+
isConnected: connectedAutomationIds?.has(automation.id),
70+
toggleConnected: () => toggleConnected?.(automation.id),
71+
}
72+
: undefined,
73+
};
74+
})
75+
.filter(defined) ?? [];
11276

11377
return (
114-
<Flex column>
78+
<div>
11579
<SimpleTable
116-
columns={baseColumns}
117-
data={data}
80+
columns={canEdit ? connectedColumns : baseColumns}
81+
data={tableData}
11882
fallback={t('No automations connected')}
11983
/>
120-
{pagination}
121-
</Flex>
84+
<Pagination
85+
onCursor={cursor => {
86+
navigate({
87+
pathname: location.pathname,
88+
query: {
89+
...location.query,
90+
cursor,
91+
},
92+
});
93+
}}
94+
pageLinks={getResponseHeader?.('Link')}
95+
/>
96+
</div>
12297
);
12398
}
12499

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {AutomationFixture} from 'sentry-fixture/automations';
12
import {DetectorFixture, SnubaQueryDataSourceFixture} from 'sentry-fixture/detectors';
23
import {OrganizationFixture} from 'sentry-fixture/organization';
34
import {ProjectFixture} from 'sentry-fixture/project';
@@ -28,6 +29,7 @@ describe('DetectorDetails', function () {
2829
projectId: project.id,
2930
dataSources: [dataSource],
3031
owner: `team:${ownerTeam.id}`,
32+
workflowIds: ['1', '2'], // Add workflow IDs for connected automations
3133
});
3234
const initialRouterConfig = {
3335
location: {
@@ -43,6 +45,14 @@ describe('DetectorDetails', function () {
4345
url: `/organizations/${organization.slug}/detectors/${snubaQueryDetector.id}/`,
4446
body: snubaQueryDetector,
4547
});
48+
MockApiClient.addMockResponse({
49+
url: '/organizations/org-slug/workflows/',
50+
body: [
51+
AutomationFixture({id: '1', name: 'Automation 1'}),
52+
AutomationFixture({id: '2', name: 'Automation 2'}),
53+
],
54+
match: [MockApiClient.matchQuery({id: ['1', '2']})],
55+
});
4656
});
4757

4858
it('renders the detector details and snuba query', async function () {
@@ -63,4 +73,37 @@ describe('DetectorDetails', function () {
6373
// Displays the owner team
6474
expect(screen.getByText(`Assign to #${ownerTeam.slug}`)).toBeInTheDocument();
6575
});
76+
77+
describe('connected automations', function () {
78+
it('displays empty message when no automations are connected', async function () {
79+
MockApiClient.addMockResponse({
80+
url: `/organizations/${organization.slug}/detectors/${snubaQueryDetector.id}/`,
81+
body: {
82+
...snubaQueryDetector,
83+
workflowIds: [],
84+
},
85+
});
86+
render(<DetectorDetails />, {
87+
organization,
88+
initialRouterConfig,
89+
});
90+
expect(await screen.findByText('No automations connected')).toBeInTheDocument();
91+
});
92+
93+
it('displays connected automations', async function () {
94+
render(<DetectorDetails />, {
95+
organization,
96+
initialRouterConfig,
97+
});
98+
99+
// Verify both automations are displayed
100+
expect(await screen.findByText('Automation 1')).toBeInTheDocument();
101+
expect(await screen.findByText('Automation 2')).toBeInTheDocument();
102+
103+
// Verify the table shows the correct columns
104+
expect(screen.getByText('Name')).toBeInTheDocument();
105+
expect(screen.getByText('Last Triggered')).toBeInTheDocument();
106+
expect(screen.getByText('Actions')).toBeInTheDocument();
107+
});
108+
});
66109
});

0 commit comments

Comments
 (0)