Browse Source

feat(aci): add automation details page (#86413)

https://sentry-jsfzhszon.sentry.dev/automations/details

<img width="1213" alt="Screenshot 2025-03-07 at 1 56 55 PM"
src="https://github.com/user-attachments/assets/88482211-578e-45eb-8fb3-963924db0cfd"
/>

---------

Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com>
mia hsu 1 day ago
parent
commit
fa7d231ce4

+ 47 - 0
static/app/views/automations/components/automationHistoryList.tsx

@@ -0,0 +1,47 @@
+import moment from 'moment-timezone';
+
+import {DateTime} from 'sentry/components/dateTime';
+import Link from 'sentry/components/links/link';
+import {defineColumns, SimpleTable} from 'sentry/components/workflowEngine/simpleTable';
+import {t, tct} from 'sentry/locale';
+import ConfigStore from 'sentry/stores/configStore';
+
+interface AutomationHistoryData {
+  dateSent: Date;
+  groupId: string;
+  monitor: {link: string; name: string};
+}
+
+type Props = {
+  history: AutomationHistoryData[];
+};
+
+const getColumns = (timezone: string) =>
+  defineColumns<AutomationHistoryData>({
+    dateSent: {
+      Header: () =>
+        tct('Time Sent ([timezone])', {timezone: moment.tz(timezone).zoneAbbr()}),
+      Cell: ({value}) => <DateTime date={value} forcedTimezone={timezone} />,
+      width: '1fr',
+    },
+    monitor: {
+      Header: () => t('Monitor'),
+      Cell: ({value}) => <Link to={value.link}>{value.name}</Link>,
+      width: '2fr',
+    },
+    groupId: {
+      Header: () => t('Issue'),
+      Cell: ({value}) => <Link to={`/issues/${value}`}>{`#${value}`}</Link>,
+      width: '2fr',
+    },
+  });
+
+export default function AutomationHistoryList({history}: Props) {
+  const {
+    options: {timezone},
+  } = ConfigStore.get('user');
+
+  const columns = getColumns(timezone);
+
+  return <SimpleTable columns={columns} data={history} />;
+}

+ 78 - 0
static/app/views/automations/components/conditionsPanel.tsx

@@ -0,0 +1,78 @@
+import styled from '@emotion/styled';
+
+import {Flex} from 'sentry/components/container/flex';
+import {tct} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+
+type ConditionsPanelProps = {
+  actions: string[];
+  if_conditions: string[];
+  when_conditions: string[];
+};
+
+function List(list: string[]) {
+  return list.map((item, index) => <ListItem key={index}>{item}</ListItem>);
+}
+
+function ConditionsPanel({
+  actions,
+  if_conditions,
+  when_conditions,
+}: ConditionsPanelProps) {
+  return (
+    <Panel column gap={space(2)}>
+      <Flex column gap={space(1)}>
+        <div>
+          {tct('[when:When] any of the following occur', {
+            when: <Badge />,
+          })}
+        </div>
+        {List(when_conditions)}
+      </Flex>
+      {if_conditions.length > 0 && (
+        <Flex column gap={space(1)}>
+          <div>
+            {tct('[if:If] any of these filters match', {
+              if: <Badge />,
+            })}
+          </div>
+          {List(if_conditions)}
+        </Flex>
+      )}
+      <Flex column gap={space(1)}>
+        <div>
+          {tct('[then:Then] perform these actions', {
+            then: <Badge />,
+          })}
+        </div>
+        {List(actions)}
+      </Flex>
+    </Panel>
+  );
+}
+
+const Panel = styled(Flex)`
+  background-color: ${p => p.theme.backgroundSecondary};
+  border: 1px solid ${p => p.theme.translucentBorder};
+  border-radius: ${p => p.theme.borderRadius};
+  padding: ${space(1.5)};
+`;
+
+const Badge = styled('span')`
+  display: inline-block;
+  background-color: ${p => p.theme.purple300};
+  padding: 0 ${space(0.75)};
+  border-radius: ${p => p.theme.borderRadius};
+  color: ${p => p.theme.white};
+  text-transform: uppercase;
+  text-align: center;
+  font-size: ${p => p.theme.fontSizeMedium};
+  font-weight: ${p => p.theme.fontWeightBold};
+  line-height: 1.5;
+`;
+
+const ListItem = styled('span')`
+  color: ${p => p.theme.subText};
+`;
+
+export default ConditionsPanel;

+ 41 - 0
static/app/views/automations/components/connectedMonitorsList.tsx

@@ -0,0 +1,41 @@
+import {IssueCell} from 'sentry/components/workflowEngine/gridCell/issueCell';
+import {NumberCell} from 'sentry/components/workflowEngine/gridCell/numberCell';
+import {TitleCell} from 'sentry/components/workflowEngine/gridCell/titleCell';
+import {defineColumns, SimpleTable} from 'sentry/components/workflowEngine/simpleTable';
+import {t} from 'sentry/locale';
+import type {Group} from 'sentry/types/group';
+import type {AvatarProject} from 'sentry/types/project';
+
+interface MonitorsData {
+  lastIssue: Group;
+  name: {link: string; name: string; project: AvatarProject};
+  openIssues: number;
+}
+
+type Props = {
+  monitors: MonitorsData[];
+};
+
+const columns = defineColumns<MonitorsData>({
+  name: {
+    Header: () => t('Name'),
+    Cell: ({value}) => (
+      <TitleCell name={value.name} project={value.project} link={value.link} />
+    ),
+    width: '3fr',
+  },
+  lastIssue: {
+    Header: () => t('Last Issue'),
+    Cell: ({value}) => <IssueCell group={value} />,
+    width: '2fr',
+  },
+  openIssues: {
+    Header: () => t('Open Issues'),
+    Cell: ({value}) => <NumberCell number={value} />,
+    width: '1fr',
+  },
+});
+
+export default function ConnectedMonitorsList({monitors}: Props) {
+  return <SimpleTable columns={columns} data={monitors} />;
+}

+ 85 - 5
static/app/views/automations/detail.tsx

@@ -1,14 +1,83 @@
 /* eslint-disable no-alert */
 import {Fragment} from 'react';
+import styled from '@emotion/styled';
 
 import {Button, LinkButton} from 'sentry/components/button';
+import {Flex} from 'sentry/components/container/flex';
+import {DateTime} from 'sentry/components/dateTime';
+import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
+import TimeSince from 'sentry/components/timeSince';
 import {ActionsProvider} from 'sentry/components/workflowEngine/layout/actions';
 import {BreadcrumbsProvider} from 'sentry/components/workflowEngine/layout/breadcrumbs';
 import DetailLayout from 'sentry/components/workflowEngine/layout/detail';
+import Section from 'sentry/components/workflowEngine/ui/section';
 import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/useWorkflowEngineFeatureGate';
 import {IconEdit} from 'sentry/icons';
 import {t} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+import AutomationHistoryList from 'sentry/views/automations/components/automationHistoryList';
+import ConditionsPanel from 'sentry/views/automations/components/conditionsPanel';
+import ConnectedMonitorsList from 'sentry/views/automations/components/connectedMonitorsList';
+
+function HistoryAndConnectedMonitors() {
+  return (
+    <div>
+      <Section title={t('History')}>
+        <AutomationHistoryList history={[]} />
+      </Section>
+      <Section title={t('Connected Monitors')}>
+        <ConnectedMonitorsList monitors={[]} />
+      </Section>
+    </div>
+  );
+}
+
+function Details() {
+  return (
+    <div>
+      <Flex column gap={space(1)}>
+        <SectionHeading>{t('Last Triggered')}</SectionHeading>
+        <span>
+          <TimeSince date={new Date()} />
+        </span>
+      </Flex>
+      <Flex column gap={space(1)}>
+        <SectionHeading>{t('Conditions')}</SectionHeading>
+        <ConditionsPanel
+          when_conditions={[
+            t('An issue escalates'),
+            t('A new event is captured for an issue'),
+          ]}
+          if_conditions={[
+            t('Issue is assigned to no one'),
+            t('Current issue priority is high'),
+          ]}
+          actions={[
+            t(
+              'Notify Suggested Assignees and if none can be found then notify Recently Active Members'
+            ),
+          ]}
+        />
+      </Flex>
+      <Flex column gap={space(1)}>
+        <SectionHeading>{t('Details')}</SectionHeading>
+        <KeyValueTable>
+          <KeyValueTableRow
+            keyName={t('Date created')}
+            value={<DateTime date={new Date()} dateOnly year />}
+          />
+          <KeyValueTableRow keyName={t('Created by')} value="Jane Doe" />
+          <KeyValueTableRow
+            keyName={t('Last modified')}
+            value={<TimeSince date={new Date()} />}
+          />
+          <KeyValueTableRow keyName={t('Team')} value="Platform" />
+        </KeyValueTable>
+      </Flex>
+    </div>
+  );
+}
 
 export default function AutomationDetail() {
   useWorkflowEngineFeatureGate({redirect: true});
@@ -17,9 +86,13 @@ export default function AutomationDetail() {
     <SentryDocumentTitle title={t('Automation')} noSuffix>
       <BreadcrumbsProvider crumb={{label: t('Automations'), to: '/automations'}}>
         <ActionsProvider actions={<Actions />}>
-          <DetailLayout project={{slug: 'project-slug', platform: 'javascript-astro'}}>
-            <DetailLayout.Main>main</DetailLayout.Main>
-            <DetailLayout.Sidebar>sidebar</DetailLayout.Sidebar>
+          <DetailLayout>
+            <DetailLayout.Main>
+              <HistoryAndConnectedMonitors />
+            </DetailLayout.Main>
+            <DetailLayout.Sidebar>
+              <Details />
+            </DetailLayout.Sidebar>
           </DetailLayout>
         </ActionsProvider>
       </BreadcrumbsProvider>
@@ -33,10 +106,17 @@ function Actions() {
   };
   return (
     <Fragment>
-      <Button onClick={disable}>{t('Disable')}</Button>
-      <LinkButton to="/monitors/edit" priority="primary" icon={<IconEdit />}>
+      <Button onClick={disable} size="sm">
+        {t('Disable')}
+      </Button>
+      <LinkButton to="/monitors/edit" priority="primary" icon={<IconEdit />} size="sm">
         {t('Edit')}
       </LinkButton>
     </Fragment>
   );
 }
+
+export const SectionHeading = styled('h4')`
+  font-size: ${p => p.theme.fontSizeMedium};
+  margin: 0;
+`;

+ 23 - 2
static/app/views/detectors/detail.tsx

@@ -3,7 +3,10 @@ import {Fragment} from 'react';
 import styled from '@emotion/styled';
 
 import {Button, LinkButton} from 'sentry/components/button';
+import {DateTime} from 'sentry/components/dateTime';
+import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
+import TimeSince from 'sentry/components/timeSince';
 import {ActionsProvider} from 'sentry/components/workflowEngine/layout/actions';
 import {BreadcrumbsProvider} from 'sentry/components/workflowEngine/layout/breadcrumbs';
 import DetailLayout from 'sentry/components/workflowEngine/layout/detail';
@@ -38,6 +41,7 @@ export default function DetectorDetail() {
             <DetailLayout.Main>
               {/* TODO: Add chart here */}
               <Section title={t('Ongoing Issues')}>
+                {/* TODO: Replace with GroupList */}
                 <IssuesList />
               </Section>
               <Section title={t('Connected Automations')}>
@@ -67,6 +71,21 @@ export default function DetectorDetail() {
               <Section title={t('Resolve')}>
                 {t('Auto-resolve after %s of inactivity', getDuration(3000000))}
               </Section>
+              <Section title={t('Details')}>
+                <KeyValueTable>
+                  <KeyValueTableRow
+                    keyName={t('Date created')}
+                    value={<DateTime date={new Date()} dateOnly year />}
+                  />
+                  <KeyValueTableRow keyName={t('Created by')} value="Jane Doe" />
+                  <KeyValueTableRow
+                    keyName={t('Last modified')}
+                    value={<TimeSince date={new Date()} />}
+                  />
+                  <KeyValueTableRow keyName={t('Team')} value="Platform" />
+                  <KeyValueTableRow keyName={t('Environment')} value="prod" />
+                </KeyValueTable>
+              </Section>
             </DetailLayout.Sidebar>
           </DetailLayout>
         </ActionsProvider>
@@ -81,8 +100,10 @@ function Actions() {
   };
   return (
     <Fragment>
-      <Button onClick={disable}>{t('Disable')}</Button>
-      <LinkButton to="/monitors/edit" priority="primary" icon={<IconEdit />}>
+      <Button onClick={disable} size="sm">
+        {t('Disable')}
+      </Button>
+      <LinkButton to="/monitors/edit" priority="primary" icon={<IconEdit />} size="sm">
         {t('Edit')}
       </LinkButton>
     </Fragment>

+ 1 - 1
static/app/views/detectors/list.tsx

@@ -33,7 +33,7 @@ function TableHeader() {
     <Flex gap={space(2)}>
       <ProjectPageFilter />
       <div style={{flexGrow: 1}}>
-        <SearchBar placeholder={t('Search by name')} />
+        <SearchBar placeholder={t('Search for events, users, tags, and more')} />
       </div>
     </Flex>
   );