Skip to content

Commit 67a8e56

Browse files
authored
feat(aci): Setup detector details (#91834)
1 parent 0d5a55d commit 67a8e56

File tree

8 files changed

+210
-61
lines changed

8 files changed

+210
-61
lines changed

static/app/types/workflowEngine/dataConditions.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,5 @@
11
import type {Action} from './actions';
22

3-
interface SnubaQuery {
4-
aggregate: string;
5-
dataset: string;
6-
id: string;
7-
query: string;
8-
timeWindow: number;
9-
environment?: string;
10-
}
11-
12-
export interface DataSource {
13-
id: string;
14-
snubaQuery: SnubaQuery;
15-
status: number;
16-
subscription?: string;
17-
}
18-
193
export enum DataConditionType {
204
// operators
215
EQUAL = 'eq',
@@ -68,12 +52,19 @@ export enum DataConditionGroupLogicType {
6852
NONE = 'none',
6953
}
7054

55+
export const enum DetectorPriorityLevel {
56+
HIGH = 75,
57+
MEDIUM = 50,
58+
LOW = 25,
59+
}
60+
7161
export interface DataCondition {
7262
comparison: any;
7363
comparison_type: DataConditionType;
7464
id: string;
7565
condition_result?: any;
7666
}
67+
7768
export interface DataConditionGroup {
7869
conditions: DataCondition[];
7970
id: string;

static/app/types/workflowEngine/detectors.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
1-
import type {
2-
DataConditionGroup,
3-
DataSource,
4-
} from 'sentry/types/workflowEngine/dataConditions';
1+
import type {DataConditionGroup} from 'sentry/types/workflowEngine/dataConditions';
2+
3+
interface SnubaQuery {
4+
aggregate: string;
5+
dataset: string;
6+
id: string;
7+
query: string;
8+
/**
9+
* Time window in seconds
10+
*/
11+
timeWindow: number;
12+
environment?: string;
13+
}
14+
15+
interface QueryObject {
16+
id: string;
17+
snubaQuery: SnubaQuery;
18+
status: number;
19+
subscription: string;
20+
}
21+
22+
export interface SnubaQueryDataSource {
23+
id: string;
24+
organizationId: string;
25+
queryObj: QueryObject;
26+
sourceId: string;
27+
type: 'snuba_query_subscription';
28+
}
29+
30+
export type DataSource = SnubaQueryDataSource;
531

632
export type DetectorType =
733
| 'crons'
@@ -13,9 +39,9 @@ export type DetectorType =
1339
| 'uptime';
1440

1541
interface NewDetector {
42+
conditionGroup: DataConditionGroup;
1643
config: Record<string, unknown>;
17-
dataCondition: DataConditionGroup;
18-
dataSource: DataSource;
44+
dataSources: DataSource[];
1945
disabled: boolean;
2046
name: string;
2147
projectId: string;

static/app/utils/useParams.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type ParamKeys =
1818
| 'codeId'
1919
| 'dataExportId'
2020
| 'dashboardId'
21+
| 'detectorId'
2122
| 'docIntegrationSlug'
2223
| 'eventId'
2324
| 'fineTuneType'

static/app/views/detectors/components/detailsPanel.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,45 @@ import {Flex} from 'sentry/components/container/flex';
44
import {Container} from 'sentry/components/workflowEngine/ui/container';
55
import {t} from 'sentry/locale';
66
import {space} from 'sentry/styles/space';
7+
import type {Detector, SnubaQueryDataSource} from 'sentry/types/workflowEngine/detectors';
8+
import {getExactDuration} from 'sentry/utils/duration/getExactDuration';
9+
10+
interface DetailsPanelProps {
11+
detector: Detector;
12+
}
13+
14+
function SnubaQueryDetails({dataSource}: {dataSource: SnubaQueryDataSource}) {
15+
return (
16+
<Container>
17+
<Flex column gap={space(0.5)}>
18+
<Heading>{t('Query:')}</Heading>
19+
<Query>
20+
<Label>{t('visualize:')}</Label>{' '}
21+
<Value>{dataSource.queryObj.snubaQuery.aggregate}</Value>
22+
<Label>{t('where:')}</Label>{' '}
23+
<Value>{dataSource.queryObj.snubaQuery.query}</Value>
24+
</Query>
25+
</Flex>
26+
<Flex gap={space(0.5)}>
27+
<Heading>{t('Threshold:')}</Heading>
28+
<Value>{getExactDuration(dataSource.queryObj.snubaQuery.timeWindow, true)}</Value>
29+
</Flex>
30+
</Container>
31+
);
32+
}
33+
34+
function DetailsPanel({detector}: DetailsPanelProps) {
35+
if (detector.dataSources[0]?.type === 'snuba_query_subscription') {
36+
return <SnubaQueryDetails dataSource={detector.dataSources[0]} />;
37+
}
738

8-
// TODO: Make component flexible for different alert types
9-
function DetailsPanel() {
1039
return (
1140
<Container>
1241
<Flex column gap={space(0.5)}>
1342
<Heading>{t('Query:')}</Heading>
1443
<Query>
15-
<Label>{t('visualize:')}</Label> <Value>{t('p75')}</Value>
16-
<Label>{t('where:')}</Label> <Value>{t('device.name is "Chrome"')}</Value>
17-
<Label>{t('grouped by:')}</Label> <Value>{t('release')}</Value>
44+
<Label>{t('visualize:')}</Label> <Value>placeholder</Value>
45+
<Label>{t('where:')}</Label> <Value>placeholder</Value>
1846
</Query>
1947
</Flex>
2048
<Heading>{t('Threshold:')}</Heading>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {DetectorDataSourceFixture, DetectorFixture} from 'sentry-fixture/detectors';
2+
import {OrganizationFixture} from 'sentry-fixture/organization';
3+
import {ProjectFixture} from 'sentry-fixture/project';
4+
5+
import {render, screen} from 'sentry-test/reactTestingLibrary';
6+
7+
import ProjectsStore from 'sentry/stores/projectsStore';
8+
import DetectorDetails from 'sentry/views/detectors/detail';
9+
10+
describe('DetectorDetails', function () {
11+
const organization = OrganizationFixture({features: ['workflow-engine-ui']});
12+
const project = ProjectFixture();
13+
const defaultDataSource = DetectorDataSourceFixture();
14+
const snubaQueryDetector = DetectorFixture({
15+
projectId: project.id,
16+
dataSources: [
17+
DetectorDataSourceFixture({
18+
queryObj: {
19+
...defaultDataSource.queryObj,
20+
snubaQuery: {
21+
...defaultDataSource.queryObj.snubaQuery,
22+
query: 'test',
23+
environment: 'test-environment',
24+
},
25+
},
26+
}),
27+
],
28+
});
29+
const initialRouterConfig = {
30+
location: {
31+
pathname: `/organizations/${organization.slug}/issues/detectors/${snubaQueryDetector.id}/`,
32+
},
33+
route: '/organizations/:orgId/issues/detectors/:detectorId/',
34+
};
35+
36+
beforeEach(() => {
37+
ProjectsStore.loadInitialData([project]);
38+
MockApiClient.addMockResponse({
39+
url: `/organizations/${organization.slug}/detectors/${snubaQueryDetector.id}/`,
40+
body: snubaQueryDetector,
41+
});
42+
});
43+
44+
it('renders the detector name and snuba query', async function () {
45+
render(<DetectorDetails />, {
46+
organization,
47+
initialRouterConfig,
48+
});
49+
50+
expect(
51+
await screen.findByRole('heading', {name: snubaQueryDetector.name})
52+
).toBeInTheDocument();
53+
// Displays the snuba query
54+
expect(
55+
screen.getByText(snubaQueryDetector.dataSources[0]!.queryObj.snubaQuery.query)
56+
).toBeInTheDocument();
57+
// Displays the environment
58+
expect(
59+
screen.getByText(
60+
snubaQueryDetector.dataSources[0]!.queryObj.snubaQuery.environment!
61+
)
62+
).toBeInTheDocument();
63+
});
64+
});

static/app/views/detectors/detail.tsx

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {Button} from 'sentry/components/core/button';
66
import {LinkButton} from 'sentry/components/core/button/linkButton';
77
import {DateTime} from 'sentry/components/dateTime';
88
import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable';
9+
import LoadingError from 'sentry/components/loadingError';
10+
import LoadingIndicator from 'sentry/components/loadingIndicator';
911
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
1012
import TimeSince from 'sentry/components/timeSince';
1113
import {ActionsProvider} from 'sentry/components/workflowEngine/layout/actions';
@@ -16,14 +18,19 @@ import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/use
1618
import {IconArrow, IconEdit} from 'sentry/icons';
1719
import {t} from 'sentry/locale';
1820
import {space} from 'sentry/styles/space';
21+
import type {Detector} from 'sentry/types/workflowEngine/detectors';
1922
import getDuration from 'sentry/utils/duration/getDuration';
2023
import useOrganization from 'sentry/utils/useOrganization';
2124
import {useParams} from 'sentry/utils/useParams';
25+
import useProjects from 'sentry/utils/useProjects';
2226
import {ConnectedAutomationsList} from 'sentry/views/detectors/components/connectedAutomationList';
2327
import DetailsPanel from 'sentry/views/detectors/components/detailsPanel';
2428
import IssuesList from 'sentry/views/detectors/components/issuesList';
2529
import {useDetectorQuery} from 'sentry/views/detectors/hooks';
26-
import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames';
30+
import {
31+
makeMonitorBasePathname,
32+
makeMonitorDetailsPathname,
33+
} from 'sentry/views/detectors/pathnames';
2734

2835
type Priority = {
2936
sensitivity: string;
@@ -35,22 +42,40 @@ const priorities: Priority[] = [
3542
{sensitivity: 'high', threshold: 10},
3643
];
3744

38-
export default function DetectorDetail() {
45+
function getDetectorEnvironment(detector: Detector) {
46+
return detector.dataSources.find(ds => ds.queryObj.snubaQuery.environment)?.queryObj
47+
.snubaQuery.environment;
48+
}
49+
50+
export default function DetectorDetails() {
3951
const organization = useOrganization();
4052
useWorkflowEngineFeatureGate({redirect: true});
41-
const {detectorId} = useParams();
42-
if (!detectorId) {
43-
throw new Error(`Unable to find detector.`);
53+
const params = useParams<{detectorId: string}>();
54+
const {projects} = useProjects();
55+
56+
const {
57+
data: detector,
58+
isPending,
59+
isError,
60+
refetch,
61+
} = useDetectorQuery(params.detectorId);
62+
const project = projects.find(p => p.id === detector?.projectId);
63+
64+
if (isPending) {
65+
return <LoadingIndicator />;
66+
}
67+
68+
if (isError || !project) {
69+
return <LoadingError onRetry={refetch} />;
4470
}
45-
const {data: detector} = useDetectorQuery(detectorId);
4671

4772
return (
48-
<SentryDocumentTitle title={detector?.name} noSuffix>
73+
<SentryDocumentTitle title={detector.name} noSuffix>
4974
<BreadcrumbsProvider
5075
crumb={{label: t('Monitors'), to: makeMonitorBasePathname(organization.slug)}}
5176
>
52-
<ActionsProvider actions={<Actions />}>
53-
<DetailLayout project={{slug: 'project-slug', platform: 'javascript-astro'}}>
77+
<ActionsProvider actions={<Actions detector={detector} />}>
78+
<DetailLayout project={project}>
5479
<DetailLayout.Main>
5580
{/* TODO: Add chart here */}
5681
<Section title={t('Ongoing Issues')}>
@@ -63,7 +88,7 @@ export default function DetectorDetail() {
6388
</DetailLayout.Main>
6489
<DetailLayout.Sidebar>
6590
<Section title={t('Detect')}>
66-
<DetailsPanel />
91+
<DetailsPanel detector={detector} />
6792
</Section>
6893
<Section title={t('Assign')}>
6994
{t('Assign to %s', 'admin@sentry.io')}
@@ -88,15 +113,17 @@ export default function DetectorDetail() {
88113
<KeyValueTable>
89114
<KeyValueTableRow
90115
keyName={t('Date created')}
91-
value={<DateTime date={new Date()} dateOnly year />}
116+
value={<DateTime date={detector.dateCreated} dateOnly year />}
92117
/>
93-
<KeyValueTableRow keyName={t('Created by')} value="Jane Doe" />
118+
<KeyValueTableRow keyName={t('Created by')} value="placeholder" />
94119
<KeyValueTableRow
95120
keyName={t('Last modified')}
96-
value={<TimeSince date={new Date()} />}
121+
value={<TimeSince date={detector.dateUpdated} />}
122+
/>
123+
<KeyValueTableRow
124+
keyName={t('Environment')}
125+
value={getDetectorEnvironment(detector)}
97126
/>
98-
<KeyValueTableRow keyName={t('Team')} value="Platform" />
99-
<KeyValueTableRow keyName={t('Environment')} value="prod" />
100127
</KeyValueTable>
101128
</Section>
102129
</DetailLayout.Sidebar>
@@ -107,7 +134,8 @@ export default function DetectorDetail() {
107134
);
108135
}
109136

110-
function Actions() {
137+
function Actions({detector}: {detector: Detector}) {
138+
const organization = useOrganization();
111139
const disable = () => {
112140
window.alert('disable');
113141
};
@@ -116,7 +144,12 @@ function Actions() {
116144
<Button onClick={disable} size="sm">
117145
{t('Disable')}
118146
</Button>
119-
<LinkButton to="edit" priority="primary" icon={<IconEdit />} size="sm">
147+
<LinkButton
148+
to={`${makeMonitorDetailsPathname(organization.slug, detector.id)}edit/`}
149+
priority="primary"
150+
icon={<IconEdit />}
151+
size="sm"
152+
>
120153
{t('Edit')}
121154
</LinkButton>
122155
</Fragment>

tests/js/fixtures/dataConditions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
DataConditionType,
88
} from 'sentry/types/workflowEngine/dataConditions';
99

10-
export function DataConditionFixture(params: Partial<DataCondition>): DataCondition {
10+
export function DataConditionFixture(params: Partial<DataCondition> = {}): DataCondition {
1111
return {
1212
comparison_type: DataConditionType.EQUAL,
1313
comparison: '8',
@@ -17,10 +17,10 @@ export function DataConditionFixture(params: Partial<DataCondition>): DataCondit
1717
}
1818

1919
export function DataConditionGroupFixture(
20-
params: Partial<DataConditionGroup>
20+
params: Partial<DataConditionGroup> = {}
2121
): DataConditionGroup {
2222
return {
23-
conditions: [DataConditionFixture({})],
23+
conditions: [DataConditionFixture()],
2424
id: '1',
2525
logicType: DataConditionGroupLogicType.ANY,
2626
actions: [],

0 commit comments

Comments
 (0)