Skip to content

Commit 7d98746

Browse files
ref(crons): Share checks table with group details (#91988)
Prior to this change the checks table for cron issues was a totally separate implementation from the table in the cron details page. This unifies the two tables <img alt="clipboard.png" width="1815" src="https://i.imgur.com/2aPAb8w.png" />
1 parent bb0af99 commit 7d98746

File tree

7 files changed

+125
-309
lines changed

7 files changed

+125
-309
lines changed

static/app/views/insights/crons/components/checkInRow.tsx renamed to static/app/views/insights/crons/components/checkInCell.tsx

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ import {
1515
import Text from 'sentry/components/text';
1616
import {t, tct} from 'sentry/locale';
1717
import {space} from 'sentry/styles/space';
18+
import type {Project} from 'sentry/types/project';
1819
import {defined} from 'sentry/utils';
1920
import useOrganization from 'sentry/utils/useOrganization';
2021
import {QuickContextHovercard} from 'sentry/views/discover/table/quickContext/quickContextHovercard';
2122
import {ContextType} from 'sentry/views/discover/table/quickContext/utils';
22-
import type {CheckIn, Monitor} from 'sentry/views/insights/crons/types';
23+
import type {CheckIn, CheckInCellKey} from 'sentry/views/insights/crons/types';
2324
import {CheckInStatus} from 'sentry/views/insights/crons/types';
2425
import {statusToText} from 'sentry/views/insights/crons/utils';
2526

2627
import {DEFAULT_CHECKIN_MARGIN, DEFAULT_MAX_RUNTIME} from './monitorForm';
2728

2829
interface CheckInRowProps {
30+
cellKey: CheckInCellKey;
2931
checkIn: CheckIn;
30-
monitor: Monitor;
31-
hasMultiEnv?: boolean;
32+
project: Project;
3233
}
3334

3435
const checkStatusToIndicatorStatus: Record<
@@ -92,7 +93,7 @@ function getCompletionStatus({status, duration}: CheckIn) {
9293
return CompletionStatus.INCOMPLETE;
9394
}
9495

95-
export function CheckInRow({monitor, checkIn, hasMultiEnv}: CheckInRowProps) {
96+
export function CheckInCell({cellKey, project, checkIn}: CheckInRowProps) {
9697
const organization = useOrganization();
9798
const {
9899
status,
@@ -113,11 +114,13 @@ export function CheckInRow({monitor, checkIn, hasMultiEnv}: CheckInRowProps) {
113114
status={checkStatusToIndicatorStatus[status]}
114115
tooltipTitle={tct('Check-in Status: [statusText]', {statusText})}
115116
/>
116-
<Text>{statusText}</Text>
117+
{statusText}
117118
</Status>
118119
);
119120

120-
const expectedTimeColum = expectedTime ? (
121+
const environmentColumn = <div>{environment}</div>;
122+
123+
const expectedAtColumn = expectedTime ? (
121124
<TimestampContainer>
122125
<ExpectedDateTime date={expectedTime} timeZone seconds />
123126
<OffScheduleIndicator checkIn={checkIn} />
@@ -128,17 +131,16 @@ export function CheckInRow({monitor, checkIn, hasMultiEnv}: CheckInRowProps) {
128131

129132
// Missed rows are mostly empty
130133
if (status === CheckInStatus.MISSED) {
131-
return (
132-
<Fragment>
133-
{statusColumn}
134-
{emptyCell}
135-
{emptyCell}
136-
{emptyCell}
137-
{emptyCell}
138-
{hasMultiEnv ? emptyCell : null}
139-
{expectedTimeColum}
140-
</Fragment>
141-
);
134+
switch (cellKey) {
135+
case 'status':
136+
return statusColumn;
137+
case 'environment':
138+
return environmentColumn;
139+
case 'expectedAt':
140+
return expectedAtColumn;
141+
default:
142+
return emptyCell;
143+
}
142144
}
143145

144146
const hadInProgress = !!dateInProgress;
@@ -198,7 +200,7 @@ export function CheckInRow({monitor, checkIn, hasMultiEnv}: CheckInRowProps) {
198200
>
199201
<StyledShortId
200202
shortId={shortId}
201-
avatar={<ProjectBadge project={monitor.project} hideName avatarSize={12} />}
203+
avatar={<ProjectBadge project={project} hideName avatarSize={12} />}
202204
to={`/organizations/${organization.slug}/issues/${id}/`}
203205
/>
204206
</QuickContextHovercard>
@@ -208,17 +210,24 @@ export function CheckInRow({monitor, checkIn, hasMultiEnv}: CheckInRowProps) {
208210
emptyCell
209211
);
210212

211-
return (
212-
<Fragment>
213-
{statusColumn}
214-
{startedColumn}
215-
{completedColumn}
216-
{durationColumn}
217-
{groupsColumn}
218-
{hasMultiEnv ? <div>{environment}</div> : null}
219-
{expectedTimeColum}
220-
</Fragment>
221-
);
213+
switch (cellKey) {
214+
case 'status':
215+
return statusColumn;
216+
case 'started':
217+
return startedColumn;
218+
case 'completed':
219+
return completedColumn;
220+
case 'duration':
221+
return durationColumn;
222+
case 'issues':
223+
return groupsColumn;
224+
case 'environment':
225+
return environmentColumn;
226+
case 'expectedAt':
227+
return expectedAtColumn;
228+
default:
229+
return emptyCell;
230+
}
222231
}
223232

224233
interface OffScheduleIndicatorProps {
Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
import {Fragment} from 'react';
2-
import styled from '@emotion/styled';
32

43
import {SectionHeading} from 'sentry/components/charts/styles';
54
import LoadingError from 'sentry/components/loadingError';
65
import Pagination from 'sentry/components/pagination';
7-
import {PanelTable} from 'sentry/components/panels/panelTable';
8-
import Placeholder from 'sentry/components/placeholder';
96
import {t} from 'sentry/locale';
10-
import {space} from 'sentry/styles/space';
117
import {useLocation} from 'sentry/utils/useLocation';
128
import useOrganization from 'sentry/utils/useOrganization';
139
import type {Monitor, MonitorEnvironment} from 'sentry/views/insights/crons/types';
1410
import {useMonitorCheckIns} from 'sentry/views/insights/crons/utils/useMonitorCheckIns';
1511

16-
import {CheckInRow} from './checkInRow';
12+
import {MonitorCheckInsGrid} from './monitorCheckInsGrid';
1713

1814
type Props = {
1915
monitor: Monitor;
@@ -47,49 +43,16 @@ export function MonitorCheckIns({monitor, monitorEnvs}: Props) {
4743

4844
const hasMultiEnv = monitorEnvs.length > 1;
4945

50-
const headers = [
51-
t('Status'),
52-
t('Started'),
53-
t('Completed'),
54-
t('Duration'),
55-
t('Issues'),
56-
...(hasMultiEnv ? [t('Environment')] : []),
57-
t('Expected At'),
58-
];
59-
6046
return (
6147
<Fragment>
6248
<SectionHeading>{t('Recent Check-Ins')}</SectionHeading>
63-
<PanelTable
64-
headers={headers}
65-
isEmpty={!isPending && checkInList.length === 0}
66-
emptyMessage={t('No check-ins have been recorded for this time period.')}
67-
>
68-
{isPending
69-
? [...new Array(PER_PAGE)].map((_, i) => (
70-
<RowPlaceholder key={i}>
71-
<Placeholder height="2rem" />
72-
</RowPlaceholder>
73-
))
74-
: checkInList.map(checkIn => (
75-
<CheckInRow
76-
key={checkIn.id}
77-
monitor={monitor}
78-
checkIn={checkIn}
79-
hasMultiEnv={hasMultiEnv}
80-
/>
81-
))}
82-
</PanelTable>
49+
<MonitorCheckInsGrid
50+
checkIns={checkInList ?? []}
51+
isLoading={isPending}
52+
hasMultiEnv={hasMultiEnv}
53+
project={monitor.project}
54+
/>
8355
<Pagination pageLinks={getResponseHeader?.('Link')} />
8456
</Fragment>
8557
);
8658
}
87-
88-
const RowPlaceholder = styled('div')`
89-
grid-column: 1 / -1;
90-
padding: ${space(1)};
91-
92-
&:not(:last-child) {
93-
border-bottom: solid 1px ${p => p.theme.innerBorder};
94-
}
95-
`;

static/app/views/insights/crons/components/checkInRow.spec.tsx renamed to static/app/views/insights/crons/components/monitorCheckInsGrid.spec.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import {CheckInFixture} from 'sentry-fixture/checkIn';
2-
import {MonitorFixture} from 'sentry-fixture/monitor';
2+
import {ProjectFixture} from 'sentry-fixture/project';
33

44
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
55
import {textWithMarkupMatcher} from 'sentry-test/utils';
66

77
import {CheckInStatus} from 'sentry/views/insights/crons/types';
88

9-
import {CheckInRow} from './checkInRow';
9+
import {MonitorCheckInsGrid} from './monitorCheckInsGrid';
1010

1111
describe('CheckInRow', () => {
12-
const monitor = MonitorFixture();
12+
const project = ProjectFixture();
1313

1414
it('represents a simple Missed check-in', function () {
1515
const checkIn = CheckInFixture({
1616
status: CheckInStatus.MISSED,
1717
duration: null,
1818
});
1919

20-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
20+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
2121

2222
expect(screen.getByText('Missed')).toBeInTheDocument();
2323
expect(screen.getByText('Jan 1, 2025 12:00:00 AM UTC')).toBeInTheDocument();
@@ -28,7 +28,7 @@ describe('CheckInRow', () => {
2828
status: CheckInStatus.OK,
2929
});
3030

31-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
31+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
3232

3333
expect(screen.getByText('Okay')).toBeInTheDocument();
3434
expect(screen.getByText('Jan 1, 2025 12:00:01 AM UTC')).toBeInTheDocument();
@@ -49,7 +49,7 @@ describe('CheckInRow', () => {
4949
duration: null,
5050
});
5151

52-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
52+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
5353

5454
expect(screen.getAllByText('In Progress')).toHaveLength(2);
5555
});
@@ -60,7 +60,7 @@ describe('CheckInRow', () => {
6060
environment: 'prod',
6161
});
6262

63-
render(<CheckInRow monitor={monitor} checkIn={checkIn} hasMultiEnv />);
63+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} hasMultiEnv />);
6464

6565
expect(screen.getByText('prod')).toBeInTheDocument();
6666
});
@@ -72,7 +72,7 @@ describe('CheckInRow', () => {
7272
dateInProgress: null,
7373
});
7474

75-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
75+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
7676

7777
const notSent = screen.getByText('Not Sent');
7878
expect(notSent).toBeInTheDocument();
@@ -92,7 +92,7 @@ describe('CheckInRow', () => {
9292
duration: null,
9393
});
9494

95-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
95+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
9696

9797
const incomplete = screen.getByText('Incomplete');
9898
expect(incomplete).toBeInTheDocument();
@@ -113,7 +113,7 @@ describe('CheckInRow', () => {
113113
duration: 12 * 60 * 1000,
114114
});
115115

116-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
116+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
117117

118118
const overrunBadge = screen.getByText(textWithMarkupMatcher('2min late'));
119119
expect(overrunBadge).toBeInTheDocument();
@@ -135,7 +135,7 @@ describe('CheckInRow', () => {
135135
expectedTime: '2025-01-02T00:00:00Z',
136136
});
137137

138-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
138+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
139139

140140
const earlyBadge = screen.getByText(textWithMarkupMatcher('Early'));
141141
expect(earlyBadge).toBeInTheDocument();
@@ -158,7 +158,7 @@ describe('CheckInRow', () => {
158158
duration: 12 * 60 * 1000,
159159
});
160160

161-
render(<CheckInRow monitor={monitor} checkIn={checkIn} />);
161+
render(<MonitorCheckInsGrid project={project} checkIns={[checkIn]} />);
162162

163163
const earlyBadge = screen.getByText(textWithMarkupMatcher('Early'));
164164
expect(earlyBadge).toBeInTheDocument();
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type {GridColumnOrder} from 'sentry/components/gridEditable';
2+
import GridEditable from 'sentry/components/gridEditable';
3+
import {t} from 'sentry/locale';
4+
import type {Project} from 'sentry/types/project';
5+
import type {CheckIn, CheckInCellKey} from 'sentry/views/insights/crons/types';
6+
7+
import {CheckInCell} from './checkInCell';
8+
9+
type Props = {
10+
checkIns: CheckIn[];
11+
project: Project;
12+
hasMultiEnv?: boolean;
13+
isLoading?: boolean;
14+
};
15+
16+
export function MonitorCheckInsGrid({checkIns, isLoading, project, hasMultiEnv}: Props) {
17+
const envColumn: Array<GridColumnOrder<CheckInCellKey>> = hasMultiEnv
18+
? [{key: 'environment', width: 120, name: t('Environment')}]
19+
: [];
20+
21+
return (
22+
<GridEditable<CheckIn, CheckInCellKey>
23+
isLoading={isLoading}
24+
emptyMessage={t('No check-ins have been recorded for this time period.')}
25+
data={checkIns}
26+
columnOrder={[
27+
{key: 'status', width: 120, name: t('Status')},
28+
{key: 'started', width: 200, name: t('Started')},
29+
{key: 'completed', width: 240, name: t('Completed')},
30+
{key: 'duration', width: 150, name: t('Duration')},
31+
{key: 'issues', width: 160, name: t('Issues')},
32+
...envColumn,
33+
{key: 'expectedAt', width: 240, name: t('Expected At')},
34+
]}
35+
columnSortBy={[]}
36+
grid={{
37+
renderBodyCell: (column, checkIn) => (
38+
<CheckInCell cellKey={column.key} project={project} checkIn={checkIn} />
39+
),
40+
}}
41+
/>
42+
);
43+
}

static/app/views/insights/crons/types.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ export interface CheckIn {
176176
environment: string;
177177
/**
178178
* What was the monitors nextCheckIn value when this check-in occured, this
179-
* is when we expected the check-in to happen.
179+
* is when we expected the check-in to happen. May be null for the very first check-in.
180180
*/
181-
expectedTime: string;
181+
expectedTime: string | null;
182182
/**
183183
* Check-in GUID
184184
*/
@@ -322,3 +322,12 @@ export interface CheckinProcessingError {
322322
errors: ProcessingError[];
323323
id: string;
324324
}
325+
326+
export type CheckInCellKey =
327+
| 'status'
328+
| 'started'
329+
| 'completed'
330+
| 'duration'
331+
| 'issues'
332+
| 'environment'
333+
| 'expectedAt';

0 commit comments

Comments
 (0)