Skip to content

feat(aci): Setup detector details #91834

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions static/app/types/workflowEngine/dataConditions.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
import type {Action} from './actions';

interface SnubaQuery {
aggregate: string;
dataset: string;
id: string;
query: string;
timeWindow: number;
environment?: string;
}

export interface DataSource {
id: string;
snubaQuery: SnubaQuery;
status: number;
subscription?: string;
}

export enum DataConditionType {
// operators
EQUAL = 'eq',
Expand Down Expand Up @@ -68,12 +52,19 @@ export enum DataConditionGroupLogicType {
NONE = 'none',
}

export const enum DetectorPriorityLevel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this enum is unused so I’ve deleted it in:

Please don’t add unused code that is needed in the future because it is indistinguishable from dead code that can be deleted - try to add code at the time when it is used instead.

HIGH = 75,
MEDIUM = 50,
LOW = 25,
}

export interface DataCondition {
comparison: any;
comparison_type: DataConditionType;
id: string;
condition_result?: any;
}

export interface DataConditionGroup {
conditions: DataCondition[];
id: string;
Expand Down
38 changes: 32 additions & 6 deletions static/app/types/workflowEngine/detectors.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@
import type {
DataConditionGroup,
DataSource,
} from 'sentry/types/workflowEngine/dataConditions';
import type {DataConditionGroup} from 'sentry/types/workflowEngine/dataConditions';

interface SnubaQuery {
aggregate: string;
dataset: string;
id: string;
query: string;
/**
* Time window in seconds
*/
timeWindow: number;
environment?: string;
}

interface QueryObject {
id: string;
snubaQuery: SnubaQuery;
status: number;
subscription: string;
}

export interface SnubaQueryDataSource {
id: string;
organizationId: string;
queryObj: QueryObject;
sourceId: string;
type: 'snuba_query_subscription';
}

export type DataSource = SnubaQueryDataSource;

export type DetectorType =
| 'crons'
Expand All @@ -13,9 +39,9 @@ export type DetectorType =
| 'uptime';

interface NewDetector {
conditionGroup: DataConditionGroup;
config: Record<string, unknown>;
dataCondition: DataConditionGroup;
dataSource: DataSource;
dataSources: DataSource[];
disabled: boolean;
name: string;
projectId: string;
Expand Down
1 change: 1 addition & 0 deletions static/app/utils/useParams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type ParamKeys =
| 'codeId'
| 'dataExportId'
| 'dashboardId'
| 'detectorId'
| 'docIntegrationSlug'
| 'eventId'
| 'fineTuneType'
Expand Down
38 changes: 33 additions & 5 deletions static/app/views/detectors/components/detailsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,45 @@ import {Flex} from 'sentry/components/container/flex';
import {Container} from 'sentry/components/workflowEngine/ui/container';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Detector, SnubaQueryDataSource} from 'sentry/types/workflowEngine/detectors';
import {getExactDuration} from 'sentry/utils/duration/getExactDuration';

interface DetailsPanelProps {
detector: Detector;
}

function SnubaQueryDetails({dataSource}: {dataSource: SnubaQueryDataSource}) {
return (
<Container>
<Flex column gap={space(0.5)}>
<Heading>{t('Query:')}</Heading>
<Query>
<Label>{t('visualize:')}</Label>{' '}
<Value>{dataSource.queryObj.snubaQuery.aggregate}</Value>
<Label>{t('where:')}</Label>{' '}
<Value>{dataSource.queryObj.snubaQuery.query}</Value>
</Query>
</Flex>
<Flex gap={space(0.5)}>
<Heading>{t('Threshold:')}</Heading>
<Value>{getExactDuration(dataSource.queryObj.snubaQuery.timeWindow, true)}</Value>
</Flex>
</Container>
);
}

function DetailsPanel({detector}: DetailsPanelProps) {
if (detector.dataSources[0]?.type === 'snuba_query_subscription') {
return <SnubaQueryDetails dataSource={detector.dataSources[0]} />;
}

// TODO: Make component flexible for different alert types
function DetailsPanel() {
return (
<Container>
<Flex column gap={space(0.5)}>
<Heading>{t('Query:')}</Heading>
<Query>
<Label>{t('visualize:')}</Label> <Value>{t('p75')}</Value>
<Label>{t('where:')}</Label> <Value>{t('device.name is "Chrome"')}</Value>
<Label>{t('grouped by:')}</Label> <Value>{t('release')}</Value>
<Label>{t('visualize:')}</Label> <Value>placeholder</Value>
<Label>{t('where:')}</Label> <Value>placeholder</Value>
</Query>
</Flex>
<Heading>{t('Threshold:')}</Heading>
Expand Down
64 changes: 64 additions & 0 deletions static/app/views/detectors/detail.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {DetectorDataSourceFixture, DetectorFixture} from 'sentry-fixture/detectors';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';

import {render, screen} from 'sentry-test/reactTestingLibrary';

import ProjectsStore from 'sentry/stores/projectsStore';
import DetectorDetails from 'sentry/views/detectors/detail';

describe('DetectorDetails', function () {
const organization = OrganizationFixture({features: ['workflow-engine-ui']});
const project = ProjectFixture();
const defaultDataSource = DetectorDataSourceFixture();
const snubaQueryDetector = DetectorFixture({
projectId: project.id,
dataSources: [
DetectorDataSourceFixture({
queryObj: {
...defaultDataSource.queryObj,
snubaQuery: {
...defaultDataSource.queryObj.snubaQuery,
query: 'test',
environment: 'test-environment',
},
},
}),
],
});
const initialRouterConfig = {
location: {
pathname: `/organizations/${organization.slug}/issues/detectors/${snubaQueryDetector.id}/`,
},
route: '/organizations/:orgId/issues/detectors/:detectorId/',
};

beforeEach(() => {
ProjectsStore.loadInitialData([project]);
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/detectors/${snubaQueryDetector.id}/`,
body: snubaQueryDetector,
});
});

it('renders the detector name and snuba query', async function () {
render(<DetectorDetails />, {
organization,
initialRouterConfig,
});

expect(
await screen.findByRole('heading', {name: snubaQueryDetector.name})
).toBeInTheDocument();
// Displays the snuba query
expect(
screen.getByText(snubaQueryDetector.dataSources[0]!.queryObj.snubaQuery.query)
).toBeInTheDocument();
// Displays the environment
expect(
screen.getByText(
snubaQueryDetector.dataSources[0]!.queryObj.snubaQuery.environment!
)
).toBeInTheDocument();
});
});
67 changes: 50 additions & 17 deletions static/app/views/detectors/detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {Button} from 'sentry/components/core/button';
import {LinkButton} from 'sentry/components/core/button/linkButton';
import {DateTime} from 'sentry/components/dateTime';
import {KeyValueTable, KeyValueTableRow} from 'sentry/components/keyValueTable';
import LoadingError from 'sentry/components/loadingError';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
import TimeSince from 'sentry/components/timeSince';
import {ActionsProvider} from 'sentry/components/workflowEngine/layout/actions';
Expand All @@ -16,14 +18,19 @@ import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/use
import {IconArrow, IconEdit} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {Detector} from 'sentry/types/workflowEngine/detectors';
import getDuration from 'sentry/utils/duration/getDuration';
import useOrganization from 'sentry/utils/useOrganization';
import {useParams} from 'sentry/utils/useParams';
import useProjects from 'sentry/utils/useProjects';
import {ConnectedAutomationsList} from 'sentry/views/detectors/components/connectedAutomationList';
import DetailsPanel from 'sentry/views/detectors/components/detailsPanel';
import IssuesList from 'sentry/views/detectors/components/issuesList';
import {useDetectorQuery} from 'sentry/views/detectors/hooks';
import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames';
import {
makeMonitorBasePathname,
makeMonitorDetailsPathname,
} from 'sentry/views/detectors/pathnames';

type Priority = {
sensitivity: string;
Expand All @@ -35,22 +42,40 @@ const priorities: Priority[] = [
{sensitivity: 'high', threshold: 10},
];

export default function DetectorDetail() {
function getDetectorEnvironment(detector: Detector) {
return detector.dataSources.find(ds => ds.queryObj.snubaQuery.environment)?.queryObj
.snubaQuery.environment;
}

export default function DetectorDetails() {
const organization = useOrganization();
useWorkflowEngineFeatureGate({redirect: true});
const {detectorId} = useParams();
if (!detectorId) {
throw new Error(`Unable to find detector.`);
const params = useParams<{detectorId: string}>();
const {projects} = useProjects();

const {
data: detector,
isPending,
isError,
refetch,
} = useDetectorQuery(params.detectorId);
const project = projects.find(p => p.id === detector?.projectId);

if (isPending) {
return <LoadingIndicator />;
}

if (isError || !project) {
return <LoadingError onRetry={refetch} />;
}
const {data: detector} = useDetectorQuery(detectorId);

return (
<SentryDocumentTitle title={detector?.name} noSuffix>
<SentryDocumentTitle title={detector.name} noSuffix>
<BreadcrumbsProvider
crumb={{label: t('Monitors'), to: makeMonitorBasePathname(organization.slug)}}
>
<ActionsProvider actions={<Actions />}>
<DetailLayout project={{slug: 'project-slug', platform: 'javascript-astro'}}>
<ActionsProvider actions={<Actions detector={detector} />}>
<DetailLayout project={project}>
<DetailLayout.Main>
{/* TODO: Add chart here */}
<Section title={t('Ongoing Issues')}>
Expand All @@ -63,7 +88,7 @@ export default function DetectorDetail() {
</DetailLayout.Main>
<DetailLayout.Sidebar>
<Section title={t('Detect')}>
<DetailsPanel />
<DetailsPanel detector={detector} />
</Section>
<Section title={t('Assign')}>
{t('Assign to %s', 'admin@sentry.io')}
Expand All @@ -88,15 +113,17 @@ export default function DetectorDetail() {
<KeyValueTable>
<KeyValueTableRow
keyName={t('Date created')}
value={<DateTime date={new Date()} dateOnly year />}
value={<DateTime date={detector.dateCreated} dateOnly year />}
/>
<KeyValueTableRow keyName={t('Created by')} value="Jane Doe" />
<KeyValueTableRow keyName={t('Created by')} value="placeholder" />
<KeyValueTableRow
keyName={t('Last modified')}
value={<TimeSince date={new Date()} />}
value={<TimeSince date={detector.dateUpdated} />}
/>
<KeyValueTableRow
keyName={t('Environment')}
value={getDetectorEnvironment(detector)}
/>
<KeyValueTableRow keyName={t('Team')} value="Platform" />
<KeyValueTableRow keyName={t('Environment')} value="prod" />
</KeyValueTable>
</Section>
</DetailLayout.Sidebar>
Expand All @@ -107,7 +134,8 @@ export default function DetectorDetail() {
);
}

function Actions() {
function Actions({detector}: {detector: Detector}) {
const organization = useOrganization();
const disable = () => {
window.alert('disable');
};
Expand All @@ -116,7 +144,12 @@ function Actions() {
<Button onClick={disable} size="sm">
{t('Disable')}
</Button>
<LinkButton to="edit" priority="primary" icon={<IconEdit />} size="sm">
<LinkButton
to={`${makeMonitorDetailsPathname(organization.slug, detector.id)}edit/`}
priority="primary"
icon={<IconEdit />}
size="sm"
>
{t('Edit')}
</LinkButton>
</Fragment>
Expand Down
6 changes: 3 additions & 3 deletions tests/js/fixtures/dataConditions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
DataConditionType,
} from 'sentry/types/workflowEngine/dataConditions';

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

export function DataConditionGroupFixture(
params: Partial<DataConditionGroup>
params: Partial<DataConditionGroup> = {}
): DataConditionGroup {
return {
conditions: [DataConditionFixture({})],
conditions: [DataConditionFixture()],
id: '1',
logicType: DataConditionGroupLogicType.ANY,
actions: [],
Expand Down
Loading
Loading