Skip to content

Commit 226cf89

Browse files
authored
feat(aci): add bulk actions to automation list table (#97315)
implemented enable/disable/delete bulk actions for automation list table. checkboxes/action buttons will not appear if the user does not have `alerts:write` https://github.com/user-attachments/assets/1824026f-35e3-42d7-8eae-decb84711be3 no animations yet
1 parent 4cf5d0e commit 226cf89

File tree

7 files changed

+747
-26
lines changed

7 files changed

+747
-26
lines changed

static/app/components/workflowEngine/gridCell/automationTitleCell.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default function AutomationTitleCell({automation}: Props) {
1414
<TitleCell
1515
name={automation.name}
1616
link={makeAutomationDetailsPathname(organization.slug, automation.id)}
17+
systemCreated={!automation.createdBy}
1718
disabled={!automation.enabled}
1819
/>
1920
);
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import {Fragment, useCallback, useState} from 'react';
2+
import styled from '@emotion/styled';
3+
4+
import {openConfirmModal} from 'sentry/components/confirm';
5+
import {Alert} from 'sentry/components/core/alert';
6+
import {Button} from 'sentry/components/core/button';
7+
import {Checkbox} from 'sentry/components/core/checkbox';
8+
import {Flex} from 'sentry/components/core/layout';
9+
import {SimpleTable} from 'sentry/components/tables/simpleTable';
10+
import {t, tct, tn} from 'sentry/locale';
11+
import {decodeScalar} from 'sentry/utils/queryString';
12+
import useLocationQuery from 'sentry/utils/url/useLocationQuery';
13+
import usePageFilters from 'sentry/utils/usePageFilters';
14+
import {
15+
useDeleteAutomationsMutation,
16+
useUpdateAutomationsMutation,
17+
} from 'sentry/views/automations/hooks';
18+
19+
interface AutomationsTableActionsProps {
20+
allResultsVisible: boolean;
21+
canDisable: boolean;
22+
canEnable: boolean;
23+
pageSelected: boolean;
24+
queryCount: string;
25+
selected: Set<string>;
26+
togglePageSelected: (pageSelected: boolean) => void;
27+
}
28+
29+
export function AutomationsTableActions({
30+
selected,
31+
pageSelected,
32+
togglePageSelected,
33+
queryCount,
34+
allResultsVisible,
35+
canEnable,
36+
canDisable,
37+
}: AutomationsTableActionsProps) {
38+
const [allInQuerySelected, setAllInQuerySelected] = useState(false);
39+
const anySelected = selected.size > 0;
40+
41+
const {selection} = usePageFilters();
42+
const {query} = useLocationQuery({
43+
fields: {
44+
query: decodeScalar,
45+
},
46+
});
47+
48+
const {mutateAsync: deleteAutomations, isPending: isDeleting} =
49+
useDeleteAutomationsMutation();
50+
const {mutateAsync: updateAutomations, isPending: isUpdating} =
51+
useUpdateAutomationsMutation();
52+
53+
const getEnableConfirmMessage = useCallback(() => {
54+
if (allInQuerySelected) {
55+
return tct(
56+
'Are you sure you want to enable all [queryCount] automations that match the search?',
57+
{
58+
queryCount,
59+
}
60+
);
61+
}
62+
return tn(
63+
`Are you sure you want to enable this %s automation?`,
64+
`Are you sure you want to enable these %s automations?`,
65+
selected.size
66+
);
67+
}, [allInQuerySelected, queryCount, selected.size]);
68+
69+
const getDisableConfirmMessage = useCallback(() => {
70+
if (allInQuerySelected) {
71+
return tct(
72+
'Are you sure you want to disable all [queryCount] automations that match the search?',
73+
{
74+
queryCount,
75+
}
76+
);
77+
}
78+
return tn(
79+
`Are you sure you want to disable this %s automation?`,
80+
`Are you sure you want to disable these %s automations?`,
81+
selected.size
82+
);
83+
}, [allInQuerySelected, queryCount, selected.size]);
84+
85+
const handleUpdate = useCallback(
86+
({enabled}: {enabled: boolean}) => {
87+
openConfirmModal({
88+
message: enabled ? getEnableConfirmMessage() : getDisableConfirmMessage(),
89+
confirmText: enabled ? t('Enable') : t('Disable'),
90+
priority: 'danger',
91+
onConfirm: async () => {
92+
if (allInQuerySelected) {
93+
await updateAutomations({enabled, query, projects: selection.projects});
94+
} else {
95+
await updateAutomations({enabled, ids: Array.from(selected)});
96+
}
97+
togglePageSelected(false);
98+
},
99+
});
100+
},
101+
[
102+
selected,
103+
allInQuerySelected,
104+
updateAutomations,
105+
getEnableConfirmMessage,
106+
getDisableConfirmMessage,
107+
togglePageSelected,
108+
selection.projects,
109+
query,
110+
]
111+
);
112+
113+
const getDeleteConfirmMessage = useCallback(() => {
114+
if (allInQuerySelected) {
115+
return tct(
116+
'Are you sure you want to delete all [queryCount] automations that match the search?',
117+
{
118+
queryCount,
119+
}
120+
);
121+
}
122+
return tn(
123+
`Are you sure you want to delete this %s automation?`,
124+
`Are you sure you want to delete these %s automations?`,
125+
selected.size
126+
);
127+
}, [allInQuerySelected, queryCount, selected.size]);
128+
129+
const handleDelete = useCallback(() => {
130+
openConfirmModal({
131+
message: getDeleteConfirmMessage(),
132+
confirmText: t('Delete'),
133+
priority: 'danger',
134+
onConfirm: async () => {
135+
if (allInQuerySelected) {
136+
await deleteAutomations({query, projects: selection.projects});
137+
} else {
138+
await deleteAutomations({ids: Array.from(selected)});
139+
}
140+
togglePageSelected(false);
141+
},
142+
});
143+
}, [
144+
selected,
145+
allInQuerySelected,
146+
deleteAutomations,
147+
getDeleteConfirmMessage,
148+
togglePageSelected,
149+
selection.projects,
150+
query,
151+
]);
152+
153+
return (
154+
<Fragment>
155+
<SimpleTable.Header>
156+
<ActionsBarWrapper>
157+
<Checkbox
158+
checked={pageSelected || (anySelected ? 'indeterminate' : false)}
159+
onChange={s => {
160+
togglePageSelected(s.target.checked);
161+
setAllInQuerySelected(false);
162+
}}
163+
/>
164+
{canEnable && (
165+
<Button
166+
size="xs"
167+
onClick={() => handleUpdate({enabled: true})}
168+
disabled={isUpdating}
169+
>
170+
{t('Enable')}
171+
</Button>
172+
)}
173+
{canDisable && (
174+
<Button
175+
size="xs"
176+
onClick={() => handleUpdate({enabled: false})}
177+
disabled={isUpdating}
178+
>
179+
{t('Disable')}
180+
</Button>
181+
)}
182+
<Button
183+
size="xs"
184+
priority="danger"
185+
onClick={handleDelete}
186+
disabled={isDeleting}
187+
>
188+
{t('Delete')}
189+
</Button>
190+
</ActionsBarWrapper>
191+
</SimpleTable.Header>
192+
{pageSelected && !allResultsVisible && (
193+
<FullWidthAlert type="warning" showIcon={false}>
194+
<Flex justify="center" wrap="wrap" gap="md">
195+
{allInQuerySelected ? (
196+
tct('Selected all [count] automations that match this search query.', {
197+
count: queryCount,
198+
})
199+
) : (
200+
<Fragment>
201+
{tn(
202+
'%s automation on this page selected.',
203+
'%s automations on this page selected.',
204+
selected.size
205+
)}
206+
<Button priority={'link'} onClick={() => setAllInQuerySelected(true)}>
207+
{tct('Select all [count] automations that match this search query.', {
208+
count: queryCount,
209+
})}
210+
</Button>
211+
</Fragment>
212+
)}
213+
</Flex>
214+
</FullWidthAlert>
215+
)}
216+
</Fragment>
217+
);
218+
}
219+
220+
const FullWidthAlert = styled(Alert)`
221+
grid-column: 1 / -1;
222+
`;
223+
224+
const ActionsBarWrapper = styled('div')`
225+
display: flex;
226+
align-items: center;
227+
gap: ${p => p.theme.space.md};
228+
padding: 0 ${p => p.theme.space.xl};
229+
width: 100%;
230+
grid-column: 1 / -1;
231+
`;

0 commit comments

Comments
 (0)