diff --git a/static/app/components/group/inboxBadges/shortId.tsx b/static/app/components/group/inboxBadges/shortId.tsx index 88f5873beead92..6c4ef3a912c1a1 100644 --- a/static/app/components/group/inboxBadges/shortId.tsx +++ b/static/app/components/group/inboxBadges/shortId.tsx @@ -21,7 +21,7 @@ function ShortId({shortId, avatar}: Props) { export default ShortId; -const Wrapper = styled('div')` +export const Wrapper = styled('div')` display: flex; align-items: center; white-space: nowrap; diff --git a/static/app/components/workflowEngine/gridCell/automationTitleCell.tsx b/static/app/components/workflowEngine/gridCell/automationTitleCell.tsx deleted file mode 100644 index cf86ed86d6c711..00000000000000 --- a/static/app/components/workflowEngine/gridCell/automationTitleCell.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import {css} from '@emotion/react'; -import styled from '@emotion/styled'; - -import ProjectBadge from 'sentry/components/idBadge/projectBadge'; -import Link from 'sentry/components/links/link'; -import {space} from 'sentry/styles/space'; -import type {AvatarProject} from 'sentry/types/project'; - -export type AutomationTitleCellProps = { - id: string; - name: string; - project: AvatarProject; -}; - -export function AutomationTitleCell({id, name, project}: AutomationTitleCellProps) { - return ( -
- - {name} - - -
- ); -} - -const StyledProjectBadge = styled(ProjectBadge)` - color: ${p => p.theme.subText}; -`; - -const Name = styled('strong')` - color: ${p => p.theme.textColor}; -`; - -const TitleWrapper = styled(Link)` - display: flex; - flex-direction: column; - gap: ${space(0.5)}; - - &:hover ${Name} { - color: ${p => p.theme.textColor}; - text-decoration: underline; - } -`; diff --git a/static/app/components/workflowEngine/gridCell/monitorsCell.tsx b/static/app/components/workflowEngine/gridCell/connectionCell.tsx similarity index 85% rename from static/app/components/workflowEngine/gridCell/monitorsCell.tsx rename to static/app/components/workflowEngine/gridCell/connectionCell.tsx index 54cba1e1ba712d..a394882625f7e6 100644 --- a/static/app/components/workflowEngine/gridCell/monitorsCell.tsx +++ b/static/app/components/workflowEngine/gridCell/connectionCell.tsx @@ -6,32 +6,32 @@ import {Hovercard} from 'sentry/components/hovercard'; import ProjectBadge from 'sentry/components/idBadge/projectBadge'; import Link from 'sentry/components/links/link'; import {EmptyCell} from 'sentry/components/workflowEngine/gridCell/emptyCell'; -import {tn} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {AvatarProject} from 'sentry/types/project'; -export type Monitor = { - id: string; +export type Item = { + link: string; name: string; project: AvatarProject; description?: string; }; -type MonitorsCellProps = { - monitors: Monitor[]; +export type ConnectionCellProps = { + items: Item[]; + renderText: (count: number) => string; }; -export function MonitorsCell({monitors}: MonitorsCellProps) { - if (monitors.length === 0) { +export function ConnectionCell({items, renderText}: ConnectionCellProps) { + if (items.length === 0) { return ; } return (
( - + body={items.map(({name, project, description, link}, index) => ( + {index > 0 && } - + {name} ))} > - {tn('%s monitor', '%s monitors', monitors.length)} + {renderText(items.length)}
); diff --git a/static/app/components/workflowEngine/gridCell/index.spec.tsx b/static/app/components/workflowEngine/gridCell/index.spec.tsx index c685625a1e8abf..333ea5df3fd53a 100644 --- a/static/app/components/workflowEngine/gridCell/index.spec.tsx +++ b/static/app/components/workflowEngine/gridCell/index.spec.tsx @@ -3,7 +3,8 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {TooltipContext} from 'sentry/components/tooltip'; import {ActionCell} from './actionCell'; -import {MonitorsCell} from './monitorsCell'; +import {ConnectionCell} from './connectionCell'; +import {NumberCell} from './numberCell'; import {TimeAgoCell} from './timeAgoCell'; describe('Action Cell Component', function () { @@ -38,31 +39,38 @@ describe('Time Ago Cell Component', function () { }); }); -const renderMonitorCell = () => { +const renderConnectionCell = (renderText: (count: number) => string) => { render( - ); }; -describe('Monitors Cell Component', function () { - it('renders children and context values', function () { - renderMonitorCell(); +describe('Connection Cell Component', function () { + it('renders monitors', function () { + renderConnectionCell(count => count + ' monitor'); const text = screen.getByText('1 monitor'); expect(text).toBeInTheDocument(); }); + it('renders automations', function () { + renderConnectionCell(count => count + ' automation'); + const text = screen.getByText('1 automation'); + expect(text).toBeInTheDocument(); + }); + it('renders hovercard', async function () { - renderMonitorCell(); + renderConnectionCell(count => count + ' monitor'); const span = screen.getByText('1 monitor'); expect(span).toBeInTheDocument(); @@ -72,3 +80,12 @@ describe('Monitors Cell Component', function () { expect(await screen.findByText('transaction.duration')).toBeInTheDocument(); }); }); + +describe('Number Cell Component', function () { + it('renders', () => { + render(); + + const text = screen.getByText('3'); + expect(text).toBeInTheDocument(); + }); +}); diff --git a/static/app/components/workflowEngine/gridCell/index.stories.tsx b/static/app/components/workflowEngine/gridCell/index.stories.tsx index b878c44ac756c1..1b93badc395379 100644 --- a/static/app/components/workflowEngine/gridCell/index.stories.tsx +++ b/static/app/components/workflowEngine/gridCell/index.stories.tsx @@ -7,103 +7,130 @@ import { ActionCell, } from 'sentry/components/workflowEngine/gridCell/actionCell'; import { - AutomationTitleCell, - type AutomationTitleCellProps, -} from 'sentry/components/workflowEngine/gridCell/automationTitleCell'; -import type {Monitor} from 'sentry/components/workflowEngine/gridCell/monitorsCell'; -import {MonitorsCell} from 'sentry/components/workflowEngine/gridCell/monitorsCell'; + ConnectionCell, + type ConnectionCellProps, +} from 'sentry/components/workflowEngine/gridCell/connectionCell'; +import {NumberCell} from 'sentry/components/workflowEngine/gridCell/numberCell'; import {TimeAgoCell} from 'sentry/components/workflowEngine/gridCell/timeAgoCell'; +import { + TitleCell, + type TitleCellProps, +} from 'sentry/components/workflowEngine/gridCell/titleCell'; +import {tn} from 'sentry/locale'; import storyBook from 'sentry/stories/storyBook'; type ExampleAutomation = { action: Action[]; - automation: AutomationTitleCellProps; - monitors: Monitor[]; + linkedItems: ConnectionCellProps; + openIssues: number; timeAgo: Date | null; + title: TitleCellProps; }; export default storyBook('Grid Cell Components', story => { const data: ExampleAutomation[] = [ { - automation: { - id: '1', + title: { name: 'Slack suggested assignees', project: {slug: 'sentry', platform: 'python'}, + link: '/monitors/1', }, action: ['slack'], timeAgo: new Date(), - monitors: [ - { - name: 'my monitor', - id: 'abc123', - project: {slug: 'ngrok-luver', platform: 'ruby'}, - }, - ], + linkedItems: { + items: [ + { + name: 'my monitor', + project: {slug: 'ngrok-luver', platform: 'ruby'}, + link: 'monitors/abc123', + }, + ], + renderText: count => tn('%s monitor', '%s monitors', count), + }, + openIssues: 3, }, { - automation: { - id: '2', + title: { name: 'Send Discord notification', - project: {slug: 'javascript', platform: 'javascript'}, + project: { + slug: 'javascript', + platform: 'javascript', + }, + details: ['transaction.duration', '2s warn, 2.5s critical threshold'], + link: '/monitors/2', }, action: ['discord'], timeAgo: new Date(Date.now() - 2 * 60 * 60 * 1000), - monitors: [ - { - name: '/endpoint', - id: 'def456', - project: {slug: 'javascript', platform: 'javascript'}, - description: 'transaction.duration', - }, - { - name: '/checkout', - id: 'ghi789', - project: {slug: 'javascript', platform: 'javascript'}, - description: 'transaction.duration', - }, - ], + linkedItems: { + items: [ + { + name: '/endpoint', + project: {slug: 'javascript', platform: 'javascript'}, + description: 'transaction.duration', + link: 'monitors/def456', + }, + { + name: '/checkout', + project: {slug: 'javascript', platform: 'javascript'}, + description: 'transaction.duration', + link: 'monitors/ghi789', + }, + ], + renderText: count => tn('%s monitor', '%s monitors', count), + }, + openIssues: 1, }, { - automation: { - id: '3', + title: { name: 'Email suggested assignees', project: {slug: 'javascript', platform: 'javascript'}, + details: ['Every hour'], + link: '/monitors/3', }, action: ['email'], timeAgo: new Date(Date.now() - 25 * 60 * 60 * 1000), - monitors: [ - { - name: 'test monitor', - id: 'jkl012', - project: {slug: 'bruh', platform: 'android'}, - description: 'transaction.duration', - }, - { - name: 'test python monitor', - id: 'mno345', - project: {slug: 'bruh.py', platform: 'python'}, - }, - { - name: 'test swift monitor', - id: 'pqr678', - project: {slug: 'bruh.swift', platform: 'swift'}, - }, - ], + linkedItems: { + items: [ + { + name: 'test automation', + project: {slug: 'bruh', platform: 'android'}, + description: 'transaction.duration', + link: 'automations/jkl012', + }, + { + name: 'test python automation', + project: {slug: 'bruh.py', platform: 'python'}, + link: 'automations/mno345', + }, + { + name: 'test swift automation', + project: {slug: 'bruh.swift', platform: 'swift'}, + link: 'automations/pqr678', + }, + ], + renderText: count => tn('%s automation', '%s automations', count), + }, + openIssues: 0, }, { - automation: { - id: '4', + title: { name: 'Send notification', project: {slug: 'android', platform: 'android'}, + link: '/monitors/4', + disabled: true, }, action: ['slack', 'discord', 'email'], timeAgo: null, - monitors: [], + linkedItems: { + items: [], + renderText: count => tn('%s automation', '%s automations', count), + }, + openIssues: 0, }, ]; - const automationTitleTable: Array> = [ - {key: 'automation', name: 'Name', width: 200}, + const TitleTable: Array> = [ + {key: 'title', name: 'Name', width: 200}, ]; const actionTable: Array> = [ @@ -114,8 +141,12 @@ export default storyBook('Grid Cell Components', story => { {key: 'timeAgo', name: 'Last Triggered', width: 200}, ]; - const monitorsTable: Array> = [ - {key: 'monitors', name: 'Connected Monitors', width: 200}, + const linkedGroupsTable: Array> = [ + {key: 'linkedItems', name: 'Connected Monitors/Automations', width: 200}, + ]; + + const openIssuesTable: Array> = [ + {key: 'openIssues', name: 'Open Issues', width: 200}, ]; const renderHeadCell = (column: GridColumnOrder) => column.name; @@ -125,30 +156,39 @@ export default storyBook('Grid Cell Components', story => { dataRow: ExampleAutomation ) => { switch (column.key) { - case 'automation': + case 'title': return ( - ); case 'action': return ; case 'timeAgo': return ; - case 'monitors': - return ; + case 'linkedItems': + return ( + + ); + case 'openIssues': + return ; default: return null; } }; - story('AutomationTitleCell', () => ( + story('TitleCell', () => ( { )); - story('MonitorsCell', () => ( + story('ConnectionCell', () => ( + + + + )); + + story('NumberCell', () => ( ; + } + return ( + + } + /> + + + {t('Last seen')} + + + + ); +} + +const IssueWrapper = styled(Link)<{disabled: boolean}>` + color: ${p => (p.disabled ? p.theme.disabled : p.theme.textColor)}; + font-size: ${p => p.theme.fontSizeMedium}; + + ${p => + !p.disabled && + ` + &:hover { + color: ${p.theme.textColor}; + } + `} +`; + +const LastSeenWrapper = styled(Flex)` + color: ${p => p.theme.subText}; +`; diff --git a/static/app/components/workflowEngine/gridCell/numberCell.tsx b/static/app/components/workflowEngine/gridCell/numberCell.tsx new file mode 100644 index 00000000000000..da19abb2f351ee --- /dev/null +++ b/static/app/components/workflowEngine/gridCell/numberCell.tsx @@ -0,0 +1,7 @@ +type NumberCellProps = { + number: number; +}; + +export function NumberCell({number}: NumberCellProps) { + return
{number}
; +} diff --git a/static/app/components/workflowEngine/gridCell/titleCell.tsx b/static/app/components/workflowEngine/gridCell/titleCell.tsx new file mode 100644 index 00000000000000..0221a6993d1860 --- /dev/null +++ b/static/app/components/workflowEngine/gridCell/titleCell.tsx @@ -0,0 +1,108 @@ +import {Fragment} from 'react'; +import {css} from '@emotion/react'; +import styled from '@emotion/styled'; + +import ProjectBadge from 'sentry/components/idBadge/projectBadge'; +import Link from 'sentry/components/links/link'; +import {space} from 'sentry/styles/space'; +import type {AvatarProject} from 'sentry/types/project'; + +export type TitleCellProps = { + link: string; + name: string; + project: AvatarProject; + details?: string[]; + disabled?: boolean; +}; + +export function TitleCell({ + name, + project, + details, + link, + disabled = false, +}: TitleCellProps) { + return ( +
+ + + {name} + {disabled && — Disabled} + + + + {details?.map((detail, index) => ( + + + {detail} + + ))} + + +
+ ); +} + +const Name = styled('div')<{disabled: boolean}>` + color: ${p => p.theme.textColor}; + display: flex; + flex-direction: row; + gap: ${space(0.5)}; + + ${p => + p.disabled && + ` + color: ${p.theme.disabled}; + `} +`; + +const TitleWrapper = styled(Link)<{disabled: boolean}>` + display: flex; + flex-direction: column; + gap: ${space(0.5)}; + + ${p => + !p.disabled && + ` + &:hover ${Name} { + color: ${p.theme.textColor}; + text-decoration: underline; + } + `} +`; + +const DetailsWrapper = styled('div')` + display: inline-grid; + grid-auto-flow: column dense; + gap: ${space(0.75)}; + justify-content: start; + align-items: center; + color: ${p => p.theme.subText}; + min-width: 500px; + white-space: nowrap; + line-height: 1.2; + + @media (min-width: ${p => p.theme.breakpoints.xlarge}) { + line-height: 1; + } +`; + +const StyledProjectBadge = styled(ProjectBadge)` + color: ${p => p.theme.subText}; +`; + +const Separator = styled('span')` + height: 10px; + width: 1px; + background-color: ${p => p.theme.innerBorder}; + border-radius: 1px; +`;