Skip to content

Commit

Permalink
feat(aci): add automations index page (#85268)
Browse files Browse the repository at this point in the history
  • Loading branch information
ameliahsu authored Feb 18, 2025
1 parent 5f5f036 commit 9967a93
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 13 deletions.
18 changes: 14 additions & 4 deletions static/app/components/workflowEngine/gridCell/actionCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ export type Action = keyof typeof ActionMetadata;

type ActionCellProps = {
actions: Action[];
disabled?: boolean;
};

export function ActionCell({actions}: ActionCellProps) {
export function ActionCell({actions, disabled}: ActionCellProps) {
if (!actions || actions.length === 0) {
return <EmptyCell />;
}
Expand All @@ -35,20 +36,29 @@ export function ActionCell({actions}: ActionCellProps) {
}
const actionsList = actions.map(action => ActionMetadata[action].name).join(', ');
return (
<Flex align="center" gap={space(0.75)}>
<ActionContainer align="center" gap={space(0.75)}>
<IconContainer>
<IconCircledNumber number={actions.length} />
</IconContainer>
<Tooltip title={actionsList}>
<Tooltip title={actionsList} disabled={disabled}>
<ActionsList>{actionsList}</ActionsList>
</Tooltip>
</Flex>
</ActionContainer>
);
}

const ActionContainer = styled(Flex)`
/* overflow: hidden;
white-space: nowrap; */
`;

const ActionsList = styled('span')`
${p => p.theme.tooltipUnderline()};
text-overflow: ellipsis;
display: flex;
overflow: hidden;
white-space: nowrap;
max-width: 100%;
`;

const IconContainer = styled('div')`
Expand Down
16 changes: 8 additions & 8 deletions static/app/components/workflowEngine/useBulkActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import Checkbox from 'sentry/components/checkbox';
import {Flex} from 'sentry/components/container/flex';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Automation} from 'sentry/views/automations/components/automationListRow';
import type {Detector} from 'sentry/views/detectors/components/detectorListRow';

// TODO: Adjust to work for automations once type is available
export function useBulkActions(detectors: Detector[]) {
export function useBulkActions(items: Detector[] | Automation[]) {
const [selectedRows, setSelectedRows] = useState<string[]>([]);
const detectorIds = useMemo(() => detectors.map(detector => detector.id), [detectors]);
const itemIds = useMemo(() => items.map(item => item.id), [items]);

const handleSelect = useCallback((id: string, checked: boolean): void => {
if (checked) {
Expand All @@ -23,17 +23,17 @@ export function useBulkActions(detectors: Detector[]) {
}, []);

const toggleSelectAll = useCallback((): void => {
if (selectedRows.length === detectors.length) {
if (selectedRows.length === items.length) {
setSelectedRows([]);
} else {
setSelectedRows(detectorIds);
setSelectedRows(itemIds);
}
}, [selectedRows, detectors, detectorIds]);
}, [selectedRows, items, itemIds]);

const bulkActionsVisible = useMemo(() => selectedRows.length > 0, [selectedRows]);
const isSelectAllChecked = useMemo(
() => selectedRows.length === detectors.length,
[selectedRows, detectors]
() => selectedRows.length === items.length,
[selectedRows, items]
);

return {
Expand Down
158 changes: 158 additions & 0 deletions static/app/views/automations/components/automationListRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import styled from '@emotion/styled';

import Checkbox from 'sentry/components/checkbox';
import {Flex} from 'sentry/components/container/flex';
import InteractionStateLayer from 'sentry/components/interactionStateLayer';
import {
type Action,
ActionCell,
} from 'sentry/components/workflowEngine/gridCell/actionCell';
import {
ConnectionCell,
type Item,
} from 'sentry/components/workflowEngine/gridCell/connectionCell';
import {TimeAgoCell} from 'sentry/components/workflowEngine/gridCell/timeAgoCell';
import {TitleCell} from 'sentry/components/workflowEngine/gridCell/titleCell';
import {tn} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {AvatarProject} from 'sentry/types/project';

export type Automation = {
actions: Action[];
id: string;
link: string;
monitors: Item[];
name: string;
project: AvatarProject;
details?: string[];
disabled?: boolean;
lastTriggered?: Date;
};

type AutomationListRowProps = Automation & {
handleSelect: (id: string, checked: boolean) => void;
selected: boolean;
};

export function AutomationListRow({
actions,
id,
lastTriggered,
link,
monitors,
name,
project,
details,
handleSelect,
selected,
disabled,
}: AutomationListRowProps) {
return (
<RowWrapper disabled={disabled}>
<InteractionStateLayer />
<Flex justify="space-between">
<StyledCheckbox
checked={selected}
onChange={() => {
handleSelect(id, !selected);
}}
/>
<CellWrapper>
<StyledTitleCell
name={name}
project={project}
link={link}
details={details}
disabled={disabled}
/>
</CellWrapper>
</Flex>
<CellWrapper className="last-triggered">
<TimeAgoCell date={lastTriggered} />
</CellWrapper>
<CellWrapper className="action">
<ActionCell actions={actions} disabled={disabled} />
</CellWrapper>
<CellWrapper className="connected-monitors">
<ConnectionCell
items={monitors}
renderText={count => tn('%s monitor', '%s monitors', count)}
disabled={disabled}
/>
</CellWrapper>
</RowWrapper>
);
}

const StyledCheckbox = styled(Checkbox)<{checked?: boolean}>`
visibility: ${p => (p.checked ? 'visible' : 'hidden')};
align-self: flex-start;
opacity: 1;
`;

const CellWrapper = styled(Flex)`
padding: 0 ${space(2)};
flex: 1;
overflow: hidden;
white-space: nowrap;
`;

const StyledTitleCell = styled(TitleCell)`
padding: ${space(2)};
margin: -${space(2)};
`;

const RowWrapper = styled('div')<{disabled?: boolean}>`
display: grid;
position: relative;
align-items: center;
padding: ${space(2)};
${p =>
p.disabled &&
`
${CellWrapper} {
opacity: 0.6;
}
`}
&:hover {
${StyledCheckbox} {
visibility: visible;
}
}
.last-triggered,
.action,
.connected-monitors {
display: none;
}
@media (min-width: ${p => p.theme.breakpoints.xsmall}) {
grid-template-columns: 2.5fr 1fr;
.action {
display: flex;
}
}
@media (min-width: ${p => p.theme.breakpoints.small}) {
grid-template-columns: 2.5fr 1fr 1fr;
.last-triggered {
display: flex;
}
}
@media (min-width: ${p => p.theme.breakpoints.medium}) {
grid-template-columns: 2.5fr 1fr 1fr 1fr;
.connected-monitors {
display: flex;
}
}
@media (min-width: ${p => p.theme.breakpoints.large}) {
grid-template-columns: 3fr 1fr 1fr 1fr;
}
`;
131 changes: 131 additions & 0 deletions static/app/views/automations/components/automationListTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import styled from '@emotion/styled';

import {Flex} from 'sentry/components/container/flex';
import Panel from 'sentry/components/panels/panel';
import PanelBody from 'sentry/components/panels/panelBody';
import PanelHeader from 'sentry/components/panels/panelHeader';
import {
BulkActions,
useBulkActions,
} from 'sentry/components/workflowEngine/useBulkActions';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {
type Automation,
AutomationListRow,
} from 'sentry/views/automations/components/automationListRow';

type AutomationListTableProps = {
automations: Automation[];
};

function AutomationListTable({automations}: AutomationListTableProps) {
const {
selectedRows,
handleSelect,
isSelectAllChecked,
toggleSelectAll,
bulkActionsVisible,
canDelete,
} = useBulkActions(automations);

return (
<Panel>
<StyledPanelHeader>
<BulkActions
bulkActionsVisible={bulkActionsVisible}
canDelete={canDelete}
isSelectAllChecked={isSelectAllChecked}
toggleSelectAll={toggleSelectAll}
/>
<Flex className="last-triggered">
<HeaderDivider />
<Heading>{t('Last Triggered')}</Heading>
</Flex>
<Flex className="action">
<HeaderDivider />
<Heading>{t('Action')}</Heading>
</Flex>
<Flex className="connected-monitors">
<HeaderDivider />
<Heading>{t('Connected Monitors')}</Heading>
</Flex>
</StyledPanelHeader>
<PanelBody>
{automations.map(automation => (
<AutomationListRow
key={automation.id}
actions={automation.actions}
id={automation.id}
lastTriggered={automation.lastTriggered}
link={automation.link}
monitors={automation.monitors}
name={automation.name}
project={automation.project}
details={automation.details}
handleSelect={handleSelect}
selected={selectedRows.includes(automation.id)}
disabled={automation.disabled}
/>
))}
</PanelBody>
</Panel>
);
}

const HeaderDivider = styled('div')`
background-color: ${p => p.theme.gray200};
width: 1px;
border-radius: ${p => p.theme.borderRadius};
`;

const Heading = styled('div')`
display: flex;
padding: 0 ${space(2)};
color: ${p => p.theme.subText};
align-items: center;
`;

const StyledPanelHeader = styled(PanelHeader)`
justify-content: left;
padding: ${space(0.75)} ${space(2)};
min-height: 40px;
align-items: center;
display: grid;
.last-triggered,
.action,
.connected-monitors {
display: none;
}
@media (min-width: ${p => p.theme.breakpoints.xsmall}) {
grid-template-columns: 2.5fr 1fr;
.action {
display: flex;
}
}
@media (min-width: ${p => p.theme.breakpoints.small}) {
grid-template-columns: 2.5fr 1fr 1fr;
.last-triggered {
display: flex;
}
}
@media (min-width: ${p => p.theme.breakpoints.medium}) {
grid-template-columns: 2.5fr 1fr 1fr 1fr;
.connected-monitors {
display: flex;
}
}
@media (min-width: ${p => p.theme.breakpoints.large}) {
grid-template-columns: 3fr 1fr 1fr 1fr;
}
`;

export default AutomationListTable;
Loading

0 comments on commit 9967a93

Please sign in to comment.