diff --git a/static/app/types/workflowEngine/detectors.tsx b/static/app/types/workflowEngine/detectors.tsx index 14a77640f78966..da31966964a17a 100644 --- a/static/app/types/workflowEngine/detectors.tsx +++ b/static/app/types/workflowEngine/detectors.tsx @@ -11,7 +11,10 @@ import type { EventTypes, ExtrapolationMode, } from 'sentry/views/alerts/rules/metric/types'; -import type {Assertion, UptimeMonitorMode} from 'sentry/views/alerts/rules/uptime/types'; +import type { + UptimeAssertion, + UptimeMonitorMode, +} from 'sentry/views/alerts/rules/uptime/types'; import type {Monitor, MonitorConfig} from 'sentry/views/insights/crons/types'; /** @@ -59,7 +62,7 @@ export interface UptimeSubscriptionDataSource extends BaseDataSource { * See UptimeSubscriptionSerializer */ queryObj: { - assertion: Assertion | null; + assertion: UptimeAssertion | null; body: string | null; headers: Array<[string, string]>; intervalSeconds: number; @@ -191,7 +194,7 @@ interface UpdateUptimeDataSourcePayload { timeoutMs: number; traceSampling: boolean; url: string; - assertion?: Assertion | null; + assertion?: UptimeAssertion | null; body?: string | null; headers?: Array<[string, string]>; } diff --git a/static/app/views/alerts/rules/uptime/assertionSuggestionCard.spec.tsx b/static/app/views/alerts/rules/uptime/assertionSuggestionCard.spec.tsx index df16f0d197f79f..cb81a06dd3ddbb 100644 --- a/static/app/views/alerts/rules/uptime/assertionSuggestionCard.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertionSuggestionCard.spec.tsx @@ -5,27 +5,27 @@ import { AssertionSuggestionCardPlaceholder, } from 'sentry/views/alerts/rules/uptime/assertionSuggestionCard'; import { - AssertionType, - ComparisonType, - OpType, - type AssertionSuggestion, + UptimeAssertionType, + UptimeComparisonType, + UptimeOpType, + type UptimeAssertionSuggestion, } from 'sentry/views/alerts/rules/uptime/types'; function makeSuggestion( - overrides: Partial = {} -): AssertionSuggestion { + overrides: Partial = {} +): UptimeAssertionSuggestion { return { - assertion_type: AssertionType.STATUS_CODE, - comparison: ComparisonType.EQUALS, + assertion_type: UptimeAssertionType.STATUS_CODE, + comparison: UptimeComparisonType.EQUALS, expected_value: '200', confidence: 0.9, explanation: 'Checks for a healthy response', json_path: null, header_name: null, assertion_json: { - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, id: 'test-1', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ...overrides, @@ -35,8 +35,8 @@ function makeSuggestion( describe('AssertionSuggestionCard', () => { it('renders status_code assertion label', () => { const suggestion = makeSuggestion({ - assertion_type: AssertionType.STATUS_CODE, - comparison: ComparisonType.EQUALS, + assertion_type: UptimeAssertionType.STATUS_CODE, + comparison: UptimeComparisonType.EQUALS, expected_value: '200', }); @@ -48,8 +48,8 @@ describe('AssertionSuggestionCard', () => { it('renders json_path assertion label', () => { const suggestion = makeSuggestion({ - assertion_type: AssertionType.JSON_PATH, - comparison: ComparisonType.EQUALS, + assertion_type: UptimeAssertionType.JSON_PATH, + comparison: UptimeComparisonType.EQUALS, expected_value: 'active', json_path: '$.status', }); @@ -62,8 +62,8 @@ describe('AssertionSuggestionCard', () => { it('renders json_path with always comparison as exists', () => { const suggestion = makeSuggestion({ - assertion_type: AssertionType.JSON_PATH, - comparison: ComparisonType.ALWAYS, + assertion_type: UptimeAssertionType.JSON_PATH, + comparison: UptimeComparisonType.ALWAYS, json_path: '$.data', }); @@ -75,8 +75,8 @@ describe('AssertionSuggestionCard', () => { it('renders header assertion label', () => { const suggestion = makeSuggestion({ - assertion_type: AssertionType.HEADER, - comparison: ComparisonType.EQUALS, + assertion_type: UptimeAssertionType.HEADER, + comparison: UptimeComparisonType.EQUALS, expected_value: 'application/json', header_name: 'Content-Type', }); @@ -89,8 +89,8 @@ describe('AssertionSuggestionCard', () => { it('renders header with always comparison as exists', () => { const suggestion = makeSuggestion({ - assertion_type: AssertionType.HEADER, - comparison: ComparisonType.ALWAYS, + assertion_type: UptimeAssertionType.HEADER, + comparison: UptimeComparisonType.ALWAYS, header_name: 'X-Request-Id', }); diff --git a/static/app/views/alerts/rules/uptime/assertionSuggestionCard.tsx b/static/app/views/alerts/rules/uptime/assertionSuggestionCard.tsx index ebae4f6d08683e..e50ad101822755 100644 --- a/static/app/views/alerts/rules/uptime/assertionSuggestionCard.tsx +++ b/static/app/views/alerts/rules/uptime/assertionSuggestionCard.tsx @@ -7,9 +7,9 @@ import {Tooltip} from '@sentry/scraps/tooltip'; import Placeholder from 'sentry/components/placeholder'; import {t, tct} from 'sentry/locale'; import { - AssertionType, - ComparisonType, - type AssertionSuggestion, + UptimeAssertionType, + UptimeComparisonType, + type UptimeAssertionSuggestion, } from 'sentry/views/alerts/rules/uptime/types'; function getConfidenceBadgeVariant(confidence: number): 'success' | 'warning' | 'danger' { @@ -24,7 +24,7 @@ function getConfidenceBadgeVariant(confidence: number): 'success' | 'warning' | interface AssertionSuggestionCardProps { onApply: () => void; - suggestion: AssertionSuggestion; + suggestion: UptimeAssertionSuggestion; } export function AssertionSuggestionCard({ @@ -33,13 +33,13 @@ export function AssertionSuggestionCard({ }: AssertionSuggestionCardProps) { const getAssertionLabel = () => { switch (suggestion.assertion_type) { - case AssertionType.STATUS_CODE: + case UptimeAssertionType.STATUS_CODE: return tct('Status code [comparison] [value]', { comparison: suggestion.comparison.replace('_', ' '), value: suggestion.expected_value, }); - case AssertionType.JSON_PATH: - if (suggestion.comparison === ComparisonType.ALWAYS) { + case UptimeAssertionType.JSON_PATH: + if (suggestion.comparison === UptimeComparisonType.ALWAYS) { return tct('[path] exists', { path: suggestion.json_path, }); @@ -49,8 +49,8 @@ export function AssertionSuggestionCard({ comparison: suggestion.comparison.replace('_', ' '), value: `"${suggestion.expected_value}"`, }); - case AssertionType.HEADER: - if (suggestion.comparison === ComparisonType.ALWAYS) { + case UptimeAssertionType.HEADER: + if (suggestion.comparison === UptimeComparisonType.ALWAYS) { return tct('Header [name] exists', { name: suggestion.header_name, }); diff --git a/static/app/views/alerts/rules/uptime/assertionSuggestionsButton.tsx b/static/app/views/alerts/rules/uptime/assertionSuggestionsButton.tsx index 0a08781fc9cb8e..8e392670082ab5 100644 --- a/static/app/views/alerts/rules/uptime/assertionSuggestionsButton.tsx +++ b/static/app/views/alerts/rules/uptime/assertionSuggestionsButton.tsx @@ -9,23 +9,23 @@ import {t} from 'sentry/locale'; import {uniqueId} from 'sentry/utils/guid'; import {AssertionSuggestionsDrawerContent} from 'sentry/views/alerts/rules/uptime/assertionSuggestionsDrawerContent'; import { - OpType, - type AndOp, - type Assertion, - type AssertionSuggestion, - type Op, + UptimeOpType, + type UptimeAndOp, + type UptimeAssertion, + type UptimeAssertionSuggestion, + type UptimeOp, } from 'sentry/views/alerts/rules/uptime/types'; /** * Adds an `id` field to an assertion Op for the frontend */ -function addIdToOp(op: Op): Op { +function addIdToOp(op: UptimeOp): UptimeOp { const id = uniqueId(); switch (op.op) { - case OpType.AND: - case OpType.OR: + case UptimeOpType.AND: + case UptimeOpType.OR: return {...op, id, children: op.children.map(addIdToOp)}; - case OpType.NOT: + case UptimeOpType.NOT: return {...op, id, operand: addIdToOp(op.operand)}; default: return {...op, id}; @@ -36,7 +36,7 @@ interface AssertionSuggestionsButtonProps { /** * Returns the current assertion value from the form at call time. */ - getCurrentAssertion: () => Assertion | null; + getCurrentAssertion: () => UptimeAssertion | null; /** * Callback to get the current form data for the test request. */ @@ -50,7 +50,7 @@ interface AssertionSuggestionsButtonProps { /** * Callback when user applies a suggestion */ - onApplySuggestion: (updatedAssertion: Assertion) => void; + onApplySuggestion: (updatedAssertion: UptimeAssertion) => void; /** * Button size */ @@ -70,19 +70,19 @@ export function AssertionSuggestionsButton({ const {openDrawer, isDrawerOpen} = useDrawer(); const handleApplySuggestion = useCallback( - (suggestion: AssertionSuggestion) => { + (suggestion: UptimeAssertionSuggestion) => { const newOp = addIdToOp(suggestion.assertion_json); // Read the current assertion at apply time so we always merge with // the latest form state, preserving any existing assertions. const current = getCurrentAssertion(); - const newRoot: AndOp = current?.root + const newRoot: UptimeAndOp = current?.root ? { ...current.root, children: [...current.root.children, newOp], } : { - op: OpType.AND, + op: UptimeOpType.AND, id: uniqueId(), children: [newOp], }; diff --git a/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.spec.tsx b/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.spec.tsx index 4b3fef3b7c45e6..bee7251ab69b70 100644 --- a/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.spec.tsx @@ -4,9 +4,9 @@ import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import {AssertionSuggestionsDrawerContent} from 'sentry/views/alerts/rules/uptime/assertionSuggestionsDrawerContent'; import { - AssertionType, - ComparisonType, - OpType, + UptimeAssertionType, + UptimeComparisonType, + UptimeOpType, type PreviewCheckPayload, } from 'sentry/views/alerts/rules/uptime/types'; @@ -46,23 +46,23 @@ describe('AssertionSuggestionsDrawerContent', () => { suggested_assertion: null, suggestions: [ { - assertion_type: AssertionType.STATUS_CODE, - comparison: ComparisonType.EQUALS, + assertion_type: UptimeAssertionType.STATUS_CODE, + comparison: UptimeComparisonType.EQUALS, expected_value: '200', confidence: 0.95, explanation: 'HTTP 200 indicates a successful response', json_path: null, header_name: null, assertion_json: { - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, id: 'sug-1', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, }, { - assertion_type: AssertionType.JSON_PATH, - comparison: ComparisonType.EQUALS, + assertion_type: UptimeAssertionType.JSON_PATH, + comparison: UptimeComparisonType.EQUALS, expected_value: 'ok', confidence: 0.8, explanation: 'The status field should be ok', @@ -72,7 +72,7 @@ describe('AssertionSuggestionsDrawerContent', () => { op: 'json_path_match', id: 'sug-2', path: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 'ok', }, }, diff --git a/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.tsx b/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.tsx index 667d8b1e2b4f2f..2f698373043478 100644 --- a/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.tsx +++ b/static/app/views/alerts/rules/uptime/assertionSuggestionsDrawerContent.tsx @@ -16,13 +16,13 @@ import { AssertionSuggestionCardPlaceholder, } from 'sentry/views/alerts/rules/uptime/assertionSuggestionCard'; import type { - AssertionSuggestion, - AssertionSuggestionsResponse, PreviewCheckPayload, + UptimeAssertionSuggestion, + UptimeAssertionSuggestionsResponse, } from 'sentry/views/alerts/rules/uptime/types'; interface AssertionSuggestionsDrawerContentProps { - onApply: (suggestion: AssertionSuggestion) => void; + onApply: (suggestion: UptimeAssertionSuggestion) => void; payload: PreviewCheckPayload; } @@ -40,21 +40,22 @@ export function AssertionSuggestionsDrawerContent({ }: AssertionSuggestionsDrawerContentProps) { const organization = useOrganization(); - const {data, isPending, isError, refetch} = useApiQuery( - [ - getApiUrl('/organizations/$organizationIdOrSlug/uptime-assertion-suggestions/', { - path: {organizationIdOrSlug: organization.slug}, - }), + const {data, isPending, isError, refetch} = + useApiQuery( + [ + getApiUrl('/organizations/$organizationIdOrSlug/uptime-assertion-suggestions/', { + path: {organizationIdOrSlug: organization.slug}, + }), + { + method: 'POST', + data: {...payload}, + }, + ], { - method: 'POST', - data: {...payload}, - }, - ], - { - staleTime: 5 * 60 * 1000, - retry: false, - } - ); + staleTime: 5 * 60 * 1000, + retry: false, + } + ); const suggestions = data?.suggestions ?? null; const isLoading = isPending; diff --git a/static/app/views/alerts/rules/uptime/assertions/addOpButton.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/addOpButton.spec.tsx index 46bf317a081c26..c7e0ee062640cc 100644 --- a/static/app/views/alerts/rules/uptime/assertions/addOpButton.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/addOpButton.spec.tsx @@ -1,6 +1,6 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; -import {ComparisonType, OpType} from 'sentry/views/alerts/rules/uptime/types'; +import {UptimeComparisonType, UptimeOpType} from 'sentry/views/alerts/rules/uptime/types'; import {AddOpButton} from './addOpButton'; @@ -63,8 +63,8 @@ describe('AddOpButton', () => { expect(mockOnAddOp).toHaveBeenCalledWith({ id: expect.any(String), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); }); @@ -79,9 +79,9 @@ describe('AddOpButton', () => { expect(mockOnAddOp).toHaveBeenCalledWith({ id: expect.any(String), - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }); }); @@ -94,10 +94,10 @@ describe('AddOpButton', () => { expect(mockOnAddOp).toHaveBeenCalledWith({ id: expect.any(String), - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: ''}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); }); @@ -112,7 +112,7 @@ describe('AddOpButton', () => { expect(mockOnAddOp).toHaveBeenCalledWith({ id: expect.any(String), - op: OpType.AND, + op: UptimeOpType.AND, children: [], }); }); @@ -128,8 +128,8 @@ describe('AddOpButton', () => { expect(mockOnAddOp).toHaveBeenNthCalledWith(1, { id: expect.any(String), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -143,8 +143,8 @@ describe('AddOpButton', () => { expect(mockOnAddOp).toHaveBeenNthCalledWith(2, { id: expect.any(String), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); diff --git a/static/app/views/alerts/rules/uptime/assertions/addOpButton.tsx b/static/app/views/alerts/rules/uptime/assertions/addOpButton.tsx index 4e55d80c4773ed..285e0982318f3b 100644 --- a/static/app/views/alerts/rules/uptime/assertions/addOpButton.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/addOpButton.tsx @@ -6,20 +6,20 @@ import { import {t} from 'sentry/locale'; import {uniqueId} from 'sentry/utils/guid'; import { - ComparisonType, - OpType, - type AndOp, - type HeaderCheckOp, - type JsonPathOp, - type Op, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeAndOp, + type UptimeHeaderCheckOp, + type UptimeJsonPathOp, + type UptimeOp, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; interface AddOpButtonProps extends Omit { /** * Callback when an operation type is selected */ - onAddOp: (op: Op) => void; + onAddOp: (op: UptimeOp) => void; } export function AddOpButton({onAddOp, ...dropdownProps}: AddOpButtonProps) { @@ -29,10 +29,10 @@ export function AddOpButton({onAddOp, ...dropdownProps}: AddOpButtonProps) { label: t('Status Code'), details: t('Check HTTP response status code'), onAction: () => { - const statusCodeOp: StatusCodeOp = { + const statusCodeOp: UptimeStatusCodeOp = { id: uniqueId(), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; onAddOp(statusCodeOp); @@ -43,11 +43,11 @@ export function AddOpButton({onAddOp, ...dropdownProps}: AddOpButtonProps) { label: t('JSON Path'), details: t('Validate JSON response body content'), onAction: () => { - const jsonPathOp: JsonPathOp = { + const jsonPathOp: UptimeJsonPathOp = { id: uniqueId(), - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }; onAddOp(jsonPathOp); @@ -58,12 +58,12 @@ export function AddOpButton({onAddOp, ...dropdownProps}: AddOpButtonProps) { label: t('Header'), details: t('Check HTTP response header values'), onAction: () => { - const headerOp: HeaderCheckOp = { + const headerOp: UptimeHeaderCheckOp = { id: uniqueId(), - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: ''}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }; onAddOp(headerOp); @@ -74,9 +74,9 @@ export function AddOpButton({onAddOp, ...dropdownProps}: AddOpButtonProps) { label: t('Logical Group'), details: t('Combine multiple assertions with AND/OR logic'), onAction: () => { - const andOp: AndOp = { + const andOp: UptimeAndOp = { id: uniqueId(), - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; onAddOp(andOp); diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.spec.tsx index 076b773726aa70..62d8dc96ebb866 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.spec.tsx @@ -7,16 +7,19 @@ import { makeOrOp, makeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/assertions/testUtils'; -import {ComparisonType, type Assertion} from 'sentry/views/alerts/rules/uptime/types'; +import { + UptimeComparisonType, + type UptimeAssertion, +} from 'sentry/views/alerts/rules/uptime/types'; import {AssertionFailureTree} from './assertionFailureTree'; describe('AssertionFailureTree', () => { it('renders rows in order for a simple assertion', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: makeAndOp({ children: [ - makeStatusCodeOp({operator: {cmp: ComparisonType.EQUALS}, value: 500}), + makeStatusCodeOp({operator: {cmp: UptimeComparisonType.EQUALS}, value: 500}), ], }), }; @@ -35,14 +38,14 @@ describe('AssertionFailureTree', () => { }); it('renders rows in order for nested assertions', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: makeAndOp({ children: [ makeOrOp({ children: [ makeJsonPathOp({ value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'ok'}, }), makeHeaderCheckOp({ diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.tsx index 9733b499947bf3..0fd445e1ddf57d 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree.tsx @@ -5,7 +5,7 @@ import {Container, Flex} from '@sentry/scraps/layout'; import ErrorBoundary from 'sentry/components/errorBoundary'; import {t} from 'sentry/locale'; -import type {Assertion} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAssertion} from 'sentry/views/alerts/rules/uptime/types'; import {Tree} from './models/tree'; import type {Connector} from './models/treeNode'; @@ -70,10 +70,12 @@ function Connector({ ); } -function AssertionFailureTreeContent({assertion}: {assertion: Assertion | string}) { +function AssertionFailureTreeContent({assertion}: {assertion: UptimeAssertion | string}) { const tree = useMemo(() => { const parsedAssertion = - typeof assertion === 'string' ? (JSON.parse(assertion) as Assertion) : assertion; + typeof assertion === 'string' + ? (JSON.parse(assertion) as UptimeAssertion) + : assertion; return Tree.FromAssertion(parsedAssertion); }, [assertion]); @@ -125,7 +127,7 @@ function AssertionFailureTreeContent({assertion}: {assertion: Assertion | string ); } -export function AssertionFailureTree({assertion}: {assertion: Assertion | string}) { +export function AssertionFailureTree({assertion}: {assertion: UptimeAssertion | string}) { return ( diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/andOpTreeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/andOpTreeNode.tsx index d2a941453ec691..764996c8fd74f8 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/andOpTreeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/andOpTreeNode.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {AndOpRow} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/rows/andOpRow'; -import type {AndOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAndOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -export class AndOpTreeNode extends TreeNode { +export class AndOpTreeNode extends TreeNode { printNode(): string { return `AND - ${this.id}`; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/headerCheckOpTreeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/headerCheckOpTreeNode.tsx index 06135cc3e2e2de..8fa37a582bdcff 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/headerCheckOpTreeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/headerCheckOpTreeNode.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {HeaderCheckOpRow} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/rows/headerCheckOpRow'; -import type {HeaderCheckOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeHeaderCheckOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -export class HeaderCheckOpTreeNode extends TreeNode { +export class HeaderCheckOpTreeNode extends TreeNode { printNode(): string { return `HEADER CHECK - ${this.id}`; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/jsonPathOpTreeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/jsonPathOpTreeNode.tsx index ada0f64e4104c1..16e93360b9743b 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/jsonPathOpTreeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/jsonPathOpTreeNode.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {JsonPathOpRow} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/rows/jsonPathOpRow'; -import type {JsonPathOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeJsonPathOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -export class JsonPathOpTreeNode extends TreeNode { +export class JsonPathOpTreeNode extends TreeNode { printNode(): string { return `JSON PATH - ${this.id}`; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/notOpTreeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/notOpTreeNode.tsx index 4b3c288f06f46a..66ec71791e3131 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/notOpTreeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/notOpTreeNode.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {NotOpRow} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/rows/notOpRow'; -import type {NotOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeNotOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -export class NotOpTreeNode extends TreeNode { +export class NotOpTreeNode extends TreeNode { override get nextOps() { return [this.value.operand]; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/orOpTreeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/orOpTreeNode.tsx index 65cf12d5c270fc..4c2aa55de2ce85 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/orOpTreeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/orOpTreeNode.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {OrOpRow} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/rows/orOpRow'; -import type {OrOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeOrOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -export class OrOpTreeNode extends TreeNode { +export class OrOpTreeNode extends TreeNode { printNode(): string { return `OR - ${this.id}`; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/statusCodeOpTreeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/statusCodeOpTreeNode.tsx index 0c6c9a555d6b88..0dee2346c2d7a5 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/statusCodeOpTreeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/statusCodeOpTreeNode.tsx @@ -1,11 +1,11 @@ import type {ReactNode} from 'react'; import {StatusCodeOpRow} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/rows/statusCodeOpRow'; -import type {StatusCodeOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeStatusCodeOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -export class StatusCodeOpTreeNode extends TreeNode { +export class StatusCodeOpTreeNode extends TreeNode { printNode(): string { return `STATUS CODE - ${this.id}`; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.spec.tsx index aaa097b40d21ea..673502ca018265 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.spec.tsx @@ -6,13 +6,13 @@ import { makeOrOp, makeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/assertions/testUtils'; -import type {Assertion} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAssertion} from 'sentry/views/alerts/rules/uptime/types'; import {Tree} from './tree'; describe('Assertion Failure Tree model', () => { it('Builds the expected tree', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: makeAndOp({ id: 'op-1', children: [ @@ -38,7 +38,7 @@ describe('Assertion Failure Tree model', () => { }); it('Merges logical ops - root', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: makeAndOp({ id: 'op-1', children: [ @@ -58,7 +58,7 @@ describe('Assertion Failure Tree model', () => { }); it('Merges logical ops - non-root', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: makeAndOp({ id: 'op-1', children: [ diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.tsx index 2c11a0f5bd5ace..0a2a1f12d10dbf 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/tree.tsx @@ -7,7 +7,7 @@ import { isOrOp, isStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/assertions/typeGuards'; -import type {Assertion, Op} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAssertion, UptimeOp} from 'sentry/views/alerts/rules/uptime/types'; import {AndOpTreeNode} from './andOpTreeNode'; import {HeaderCheckOpTreeNode} from './headerCheckOpTreeNode'; @@ -25,7 +25,7 @@ export class Tree { this.root = root; } - static FromAssertion(assertion: Assertion): Tree { + static FromAssertion(assertion: UptimeAssertion): Tree { const root = Tree.buildNode(assertion.root, null); const tree = new Tree(root); @@ -34,7 +34,7 @@ export class Tree { return tree; } - private static nodeFromOp(op: Op, parent: TreeNode | null): TreeNode { + private static nodeFromOp(op: UptimeOp, parent: TreeNode | null): TreeNode { if (isAndOp(op)) { return new AndOpTreeNode(op, parent); } @@ -58,7 +58,7 @@ export class Tree { throw new Error('Unknown uptime assertion op'); } - private static buildNode(op: Op, parent: TreeNode | null): TreeNode { + private static buildNode(op: UptimeOp, parent: TreeNode | null): TreeNode { const node = Tree.nodeFromOp(op, parent); for (const nextOp of node.nextOps) { diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.spec.tsx index 6ed19f3ecc2dea..ae4244ecf4c2e8 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.spec.tsx @@ -5,11 +5,11 @@ import { makeJsonPathOp, makeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/assertions/testUtils'; -import type {Op} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeOp} from 'sentry/views/alerts/rules/uptime/types'; import {TreeNode} from './treeNode'; -class MockTreeNode extends TreeNode { +class MockTreeNode extends TreeNode { renderRow() { return ( diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.tsx index 2c5a6777dbad66..93e14daa2da73b 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/models/treeNode.tsx @@ -1,6 +1,6 @@ import type {ReactNode} from 'react'; -import type {Op} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeOp} from 'sentry/views/alerts/rules/uptime/types'; export type ConnectorType = 'vertical' | 'horizontal'; @@ -9,7 +9,7 @@ export type Connector = { type: ConnectorType; }; -export abstract class TreeNode { +export abstract class TreeNode { value: T; parent: TreeNode | null; children: TreeNode[]; @@ -29,7 +29,7 @@ export abstract class TreeNode { return this.value.id; } - get nextOps(): Op[] { + get nextOps(): UptimeOp[] { return 'children' in this.value ? this.value.children : []; } diff --git a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/rows/jsonPathOpRow.tsx b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/rows/jsonPathOpRow.tsx index 27400cd4074e03..a09bc72547b91a 100644 --- a/static/app/views/alerts/rules/uptime/assertions/assertionFailure/rows/jsonPathOpRow.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/assertionFailure/rows/jsonPathOpRow.tsx @@ -9,10 +9,10 @@ import { getJsonPathOperandValue, normalizeJsonPathOp, } from 'sentry/views/alerts/rules/uptime/assertions/utils'; -import type {JsonPathOp} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeJsonPathOp} from 'sentry/views/alerts/rules/uptime/types'; export function JsonPathOpRow({node}: {node: JsonPathOpTreeNode}) { - const normalizedOp: JsonPathOp = normalizeJsonPathOp(node.value); + const normalizedOp: UptimeJsonPathOp = normalizeJsonPathOp(node.value); const operandValue = getJsonPathOperandValue(normalizedOp.operand); const {combinedLabel, combinedTooltip} = diff --git a/static/app/views/alerts/rules/uptime/assertions/dragDrop.tsx b/static/app/views/alerts/rules/uptime/assertions/dragDrop.tsx index b420b4ea7f3713..fa68d41ed44663 100644 --- a/static/app/views/alerts/rules/uptime/assertions/dragDrop.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/dragDrop.tsx @@ -12,11 +12,15 @@ import {restrictToVerticalAxis} from '@dnd-kit/modifiers'; import {Container} from '@sentry/scraps/layout'; -import {OpType, type LogicalOp, type Op} from 'sentry/views/alerts/rules/uptime/types'; +import { + UptimeOpType, + type UptimeLogicalOp, + type UptimeOp, +} from 'sentry/views/alerts/rules/uptime/types'; import {isAfterOp, moveTo} from './utils'; -function isOp(data: unknown): data is Op { +function isOp(data: unknown): data is UptimeOp { return ( typeof data === 'object' && data !== null && @@ -54,7 +58,7 @@ interface DroppableProps extends React.HTMLAttributes { disabled: boolean; groupId: string; idIndex: number; - op: Op; + op: UptimeOp; position: 'before' | 'after' | 'inside'; } @@ -76,7 +80,8 @@ export function DroppableHitbox(props: DroppableProps) { disabled: dropzoneDisabled, }); - const isGroup = op.op === OpType.AND || op.op === OpType.OR || op.op === OpType.NOT; + const isGroup = + op.op === UptimeOpType.AND || op.op === UptimeOpType.OR || op.op === UptimeOpType.NOT; const offsetConfig = { before: {bottom: isGroup ? 'calc(100% - 10px)' : '50%'}, @@ -102,8 +107,8 @@ export function DroppableHitbox(props: DroppableProps) { } interface DropHandlerProps { - onChange: (op: LogicalOp) => void; - rootOp: LogicalOp; + onChange: (op: UptimeLogicalOp) => void; + rootOp: UptimeLogicalOp; } /** @@ -117,7 +122,7 @@ export function DropHandler({rootOp, onChange}: DropHandlerProps) { const {active, over} = event; // the root op should ALWAYS be `and`, but the typing doesn't strictly enforce that - if (rootOp.op !== OpType.AND) { + if (rootOp.op !== UptimeOpType.AND) { return; } diff --git a/static/app/views/alerts/rules/uptime/assertions/field.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/field.spec.tsx index 242f57361645e3..4e3804be030e52 100644 --- a/static/app/views/alerts/rules/uptime/assertions/field.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/field.spec.tsx @@ -3,11 +3,11 @@ import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrar import Form from 'sentry/components/forms/form'; import FormModel from 'sentry/components/forms/model'; import { - ComparisonType, - OpType, - type AndOp, - type Assertion, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeAndOp, + type UptimeAssertion, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {normalizeAssertion, UptimeAssertionsField} from './field'; @@ -50,36 +50,36 @@ describe('UptimeAssertionsField', () => { expect(await screen.findAllByRole('textbox')).toHaveLength(3); // Verify the model has been updated with the assertion structure - const fieldValue = model.fields.get('assertion') as unknown as Assertion; + const fieldValue = model.fields.get('assertion') as unknown as UptimeAssertion; expect(fieldValue).toMatchObject({ root: { - op: OpType.AND, + op: UptimeOpType.AND, children: [ - {op: OpType.STATUS_CODE_CHECK}, - {op: OpType.STATUS_CODE_CHECK}, - {op: OpType.STATUS_CODE_CHECK}, + {op: UptimeOpType.STATUS_CODE_CHECK}, + {op: UptimeOpType.STATUS_CODE_CHECK}, + {op: UptimeOpType.STATUS_CODE_CHECK}, ], }, }); }); it('renders existing assertion from initialData', async () => { - const existingAssertion: Assertion = { + const existingAssertion: UptimeAssertion = { root: { id: 'root-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'json-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.data.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }, ], @@ -130,18 +130,18 @@ describe('UptimeAssertionsField', () => { expect(transformedData.assertion).toMatchObject({ root: { id: expect.any(String), - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: expect.any(String), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.GREATER_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.GREATER_THAN}, value: 199, }, { id: expect.any(String), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 300, }, ], @@ -151,15 +151,15 @@ describe('UptimeAssertionsField', () => { it('normalizes NaN values to 200 via getValue on form submission', async () => { // Set up assertion with NaN value (simulating cleared input submitted without blur) - const assertionWithNaN: Assertion = { + const assertionWithNaN: UptimeAssertion = { root: { id: 'root-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: NaN, }, ], @@ -182,31 +182,31 @@ describe('UptimeAssertionsField', () => { }); // Raw field value should still have NaN - const rawValue = model.fields.get('assertion') as unknown as Assertion; + const rawValue = model.fields.get('assertion') as unknown as UptimeAssertion; expect(rawValue.root.children[0]).toMatchObject({ - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, value: NaN, }); // getTransformedData (used by saveForm) should normalize via getValue const transformedData = model.getTransformedData(); - const transformedAssertion = transformedData.assertion as Assertion; + const transformedAssertion = transformedData.assertion as UptimeAssertion; expect(transformedAssertion.root.children[0]).toMatchObject({ - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, value: 200, // NaN normalized to 200 }); }); it('clamps values below minimum to 100 via getValue on form submission', async () => { - const assertionWithInvalidValues: Assertion = { + const assertionWithInvalidValues: UptimeAssertion = { root: { id: 'root-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 50, // Below valid range (100-599) }, ], @@ -230,23 +230,23 @@ describe('UptimeAssertionsField', () => { // getTransformedData should clamp to valid range const transformedData = model.getTransformedData(); - const transformedAssertion = transformedData.assertion as Assertion; + const transformedAssertion = transformedData.assertion as UptimeAssertion; expect(transformedAssertion.root.children[0]).toMatchObject({ - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, value: 100, // Clamped to minimum }); }); it('clamps values above maximum to 599 via getValue on form submission', async () => { - const assertionWithInvalidValues: Assertion = { + const assertionWithInvalidValues: UptimeAssertion = { root: { id: 'root-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 700, // Above valid range (100-599) }, ], @@ -268,9 +268,9 @@ describe('UptimeAssertionsField', () => { }); const transformedData = model.getTransformedData(); - const transformedAssertion = transformedData.assertion as Assertion; + const transformedAssertion = transformedData.assertion as UptimeAssertion; expect(transformedAssertion.root.children[0]).toMatchObject({ - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, value: 599, // Clamped to maximum }); }); @@ -278,10 +278,10 @@ describe('UptimeAssertionsField', () => { it('shows empty UI when editing monitor with no assertions', () => { // When editing a monitor that has no assertions, pass empty assertion structure // (FormField converts null to '' so we can't use null directly) - const emptyAssertion: Assertion = { + const emptyAssertion: UptimeAssertion = { root: { id: 'empty', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }, }; @@ -305,10 +305,10 @@ describe('UptimeAssertionsField', () => { it('allows adding assertions when editing monitor with no assertions', async () => { // When editing a monitor that has no assertions, user should be able to add new ones - const emptyAssertion: Assertion = { + const emptyAssertion: UptimeAssertion = { root: { id: 'empty', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }, }; @@ -336,8 +336,8 @@ describe('UptimeAssertionsField', () => { const transformedData = model.getTransformedData(); expect(transformedData.assertion).toMatchObject({ root: { - op: OpType.AND, - children: [{op: OpType.STATUS_CODE_CHECK}], + op: UptimeOpType.AND, + children: [{op: UptimeOpType.STATUS_CODE_CHECK}], }, }); }); @@ -377,73 +377,73 @@ describe('UptimeAssertionsField', () => { describe('normalizeAssertion', () => { it('handles NaN status code value by defaulting to 200', () => { - const op: StatusCodeOp = { + const op: UptimeStatusCodeOp = { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: NaN, }; expect(normalizeAssertion(op)).toEqual({ id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); }); it('clamps status code values to valid HTTP range', () => { - const tooLow: StatusCodeOp = { + const tooLow: UptimeStatusCodeOp = { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 50, }; - const tooHigh: StatusCodeOp = { + const tooHigh: UptimeStatusCodeOp = { id: 'test-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 700, }; - expect((normalizeAssertion(tooLow) as StatusCodeOp).value).toBe(100); - expect((normalizeAssertion(tooHigh) as StatusCodeOp).value).toBe(599); + expect((normalizeAssertion(tooLow) as UptimeStatusCodeOp).value).toBe(100); + expect((normalizeAssertion(tooHigh) as UptimeStatusCodeOp).value).toBe(599); }); it('preserves valid status code values', () => { - const valid: StatusCodeOp = { + const valid: UptimeStatusCodeOp = { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 404, }; - expect((normalizeAssertion(valid) as StatusCodeOp).value).toBe(404); + expect((normalizeAssertion(valid) as UptimeStatusCodeOp).value).toBe(404); }); it('recursively normalizes nested assertions in and/or groups', () => { - const nested: AndOp = { + const nested: UptimeAndOp = { id: 'group-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: NaN, }, { id: 'test-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 800, }, ], }; - const result = normalizeAssertion(nested) as AndOp; - expect((result.children[0] as StatusCodeOp).value).toBe(200); - expect((result.children[1] as StatusCodeOp).value).toBe(599); + const result = normalizeAssertion(nested) as UptimeAndOp; + expect((result.children[0] as UptimeStatusCodeOp).value).toBe(200); + expect((result.children[1] as UptimeStatusCodeOp).value).toBe(599); }); }); diff --git a/static/app/views/alerts/rules/uptime/assertions/field.tsx b/static/app/views/alerts/rules/uptime/assertions/field.tsx index 9a0261accbf1bf..3bdd898cc15980 100644 --- a/static/app/views/alerts/rules/uptime/assertions/field.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/field.tsx @@ -2,11 +2,11 @@ import type {FormFieldProps} from 'sentry/components/forms/formField'; import FormField from 'sentry/components/forms/formField'; import {uniqueId} from 'sentry/utils/guid'; import { - ComparisonType, - OpType, - type AndOp, - type Assertion, - type Op, + UptimeComparisonType, + UptimeOpType, + type UptimeAndOp, + type UptimeAssertion, + type UptimeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {AssertionOpGroup} from './opGroup'; @@ -15,21 +15,21 @@ import {AssertionOpGroup} from './opGroup'; * Recursively normalizes assertion values to ensure they are valid before submission. * Handles NaN values (from cleared inputs) and clamps to valid HTTP status code range. */ -export function normalizeAssertion(op: Op): Op { +export function normalizeAssertion(op: UptimeOp): UptimeOp { switch (op.op) { - case OpType.STATUS_CODE_CHECK: + case UptimeOpType.STATUS_CODE_CHECK: return { ...op, // Default to 200 if NaN (e.g., user cleared input and submitted without blur) value: isNaN(op.value) ? 200 : Math.max(100, Math.min(599, op.value)), }; - case OpType.AND: - case OpType.OR: + case UptimeOpType.AND: + case UptimeOpType.OR: return { ...op, children: op.children.map(normalizeAssertion), }; - case OpType.NOT: + case UptimeOpType.NOT: return { ...op, operand: normalizeAssertion(op.operand), @@ -44,9 +44,9 @@ export function normalizeAssertion(op: Op): Op { * Used when editing monitors that have no assertions - empty children signals * "edit with no assertions" vs the default assertions for new monitors. */ -export function createEmptyAssertionRoot(): AndOp { +export function createEmptyAssertionRoot(): UptimeAndOp { return { - op: OpType.AND, + op: UptimeOpType.AND, id: uniqueId(), children: [], }; @@ -55,21 +55,21 @@ export function createEmptyAssertionRoot(): AndOp { /** * Creates a default assertion root that validates 2xx status codes (>199 AND <300) */ -function createDefaultAssertionRoot(): AndOp { +function createDefaultAssertionRoot(): UptimeAndOp { return { - op: OpType.AND, + op: UptimeOpType.AND, id: uniqueId(), children: [ { - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, id: uniqueId(), - operator: {cmp: ComparisonType.GREATER_THAN}, + operator: {cmp: UptimeComparisonType.GREATER_THAN}, value: 199, }, { - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, id: uniqueId(), - operator: {cmp: ComparisonType.LESS_THAN}, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 300, }, ], @@ -80,12 +80,12 @@ function createDefaultAssertionRoot(): AndOp { // abysmal, so we're leaving this untyped for now. function UptimeAssertionsControl({onChange, onBlur, value}: any) { - // value is an Assertion object from initialData or defaultValue. + // value is an UptimeAssertion object from initialData or defaultValue. // During initial render, value may briefly be undefined before FormField processes defaultValue. if (!value?.root) { return null; } - const rootOp: AndOp = value.root; + const rootOp: UptimeAndOp = value.root; return ( ) { // Use getValue (not getData) to transform field value at submission time. // getData only works for save-on-blur; getValue is used by getTransformedData() // which is called during full form submission via saveForm(). - getValue={(value: Assertion) => { + getValue={(value: UptimeAssertion) => { // Handle edge cases where FormField may pass undefined/null/empty string if (!value?.root) { return null; diff --git a/static/app/views/alerts/rules/uptime/assertions/index.stories.tsx b/static/app/views/alerts/rules/uptime/assertions/index.stories.tsx index 8a18d61f0dce28..16c5792919cf05 100644 --- a/static/app/views/alerts/rules/uptime/assertions/index.stories.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/index.stories.tsx @@ -5,12 +5,12 @@ import {Stack} from '@sentry/scraps/layout'; import * as Storybook from 'sentry/stories'; import { - ComparisonType, - OpType, - type HeaderCheckOp, - type JsonPathOp, - type LogicalOp, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeHeaderCheckOp, + type UptimeJsonPathOp, + type UptimeLogicalOp, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {AddOpButton} from './addOpButton'; @@ -21,10 +21,10 @@ import {AssertionOpStatusCode} from './opStatusCode'; export default Storybook.story('Uptime Assertions', story => { story('Status Code Op', () => { - const [statusCodeOp, setStatusCodeOp] = useState({ + const [statusCodeOp, setStatusCodeOp] = useState({ id: 'story-status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -47,11 +47,11 @@ export default Storybook.story('Uptime Assertions', story => { }); story('JSON Path Op', () => { - const [jsonPathOp, setJsonPathOp] = useState({ + const [jsonPathOp, setJsonPathOp] = useState({ id: 'story-json-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'ok'}, }); @@ -72,11 +72,11 @@ export default Storybook.story('Uptime Assertions', story => { }); story('JSON Path Op - Glob Pattern', () => { - const [jsonPathOp, setJsonPathOp] = useState({ + const [jsonPathOp, setJsonPathOp] = useState({ id: 'story-json-glob-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'glob', pattern: {value: 'ok*'}}, }); @@ -98,12 +98,12 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Header Op - With Values', () => { - const [headerOp, setHeaderOp] = useState({ + const [headerOp, setHeaderOp] = useState({ id: 'story-header-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -121,12 +121,12 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Header Op - Always/Never', () => { - const [headerOp, setHeaderOp] = useState({ + const [headerOp, setHeaderOp] = useState({ id: 'story-header-2', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.ALWAYS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.ALWAYS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.ALWAYS}, + value_op: {cmp: UptimeComparisonType.ALWAYS}, value_operand: {header_op: 'none'}, }); @@ -144,12 +144,12 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Header Op - Glob Pattern', () => { - const [headerOp, setHeaderOp] = useState({ + const [headerOp, setHeaderOp] = useState({ id: 'story-header-3', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'glob', pattern: {value: 'X-*'}}, - value_op: {cmp: ComparisonType.NOT_EQUAL}, + value_op: {cmp: UptimeComparisonType.NOT_EQUAL}, value_operand: {header_op: 'glob', pattern: {value: '*error*'}}, }); @@ -166,27 +166,27 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Multiple Ops', () => { - const [statusCodeOp, setStatusCodeOp] = useState({ + const [statusCodeOp, setStatusCodeOp] = useState({ id: 'story-status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }); - const [jsonPathOp, setJsonPathOp] = useState({ + const [jsonPathOp, setJsonPathOp] = useState({ id: 'story-json-2', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.error', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }); - const [headerOp, setHeaderOp] = useState({ + const [headerOp, setHeaderOp] = useState({ id: 'story-header-4', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -218,7 +218,7 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Add Op Button', () => { - const [ops, setOps] = useState>([]); + const [ops, setOps] = useState>([]); return ( @@ -229,7 +229,7 @@ export default Storybook.story('Uptime Assertions', story => { { - setOps(prev => [...prev, op as StatusCodeOp | JsonPathOp]); + setOps(prev => [...prev, op as UptimeStatusCodeOp | UptimeJsonPathOp]); }} /> {ops.length > 0 && ( @@ -238,7 +238,7 @@ export default Storybook.story('Uptime Assertions', story => { {ops.map((op, index) => (
- {op.op === OpType.STATUS_CODE_CHECK ? ( + {op.op === UptimeOpType.STATUS_CODE_CHECK ? ( { @@ -270,21 +270,21 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Group Op - Assert All (And)', () => { - const [groupOp, setGroupOp] = useState({ + const [groupOp, setGroupOp] = useState({ id: 'story-group-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'story-status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }, { id: 'story-json-3', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.success', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'true'}, }, ], @@ -304,20 +304,20 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Group Op - Assert Any (Or)', () => { - const [groupOp, setGroupOp] = useState({ + const [groupOp, setGroupOp] = useState({ id: 'story-group-2', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'story-status-4', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'story-status-5', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 204, }, ], @@ -336,23 +336,23 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Group Op - Assert Not All (Not And)', () => { - const [groupOp, setGroupOp] = useState({ + const [groupOp, setGroupOp] = useState({ id: 'story-not-1', - op: OpType.NOT, + op: UptimeOpType.NOT, operand: { id: 'story-group-3', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'story-status-6', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.GREATER_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.GREATER_THAN}, value: 499, }, { id: 'story-status-7', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 600, }, ], @@ -372,23 +372,23 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Group Op - Assert None (Not Or)', () => { - const [groupOp, setGroupOp] = useState({ + const [groupOp, setGroupOp] = useState({ id: 'story-not-2', - op: OpType.NOT, + op: UptimeOpType.NOT, operand: { id: 'story-group-4', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'story-status-8', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 404, }, { id: 'story-status-9', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 500, }, ], @@ -408,33 +408,33 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Group Op - Nested Groups', () => { - const [groupOp, setGroupOp] = useState({ + const [groupOp, setGroupOp] = useState({ id: 'story-group-5', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'story-status-10', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }, { id: 'story-group-6', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'story-json-4', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'ok'}, }, { id: 'story-header-5', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'X-Status'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'ok'}, }, ], @@ -456,9 +456,9 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Group Op - Empty Group', () => { - const [groupOp, setGroupOp] = useState({ + const [groupOp, setGroupOp] = useState({ id: 'story-group-7', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }); @@ -475,29 +475,29 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Root Group', () => { - const [rootGroup, setRootGroup] = useState({ + const [rootGroup, setRootGroup] = useState({ id: 'story-group-8', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'story-status-11', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }, { id: 'story-json-5', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.success', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'true'}, }, { id: 'story-header-6', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }, ], @@ -518,9 +518,9 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Root Group - Empty', () => { - const [rootGroup, setRootGroup] = useState({ + const [rootGroup, setRootGroup] = useState({ id: 'story-group-9', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }); @@ -537,29 +537,29 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Drag and Drop - Reordering', () => { - const [rootGroup, setRootGroup] = useState({ + const [rootGroup, setRootGroup] = useState({ id: 'story-dnd-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'story-status-dnd-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }, { id: 'story-json-dnd-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.success', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'true'}, }, { id: 'story-header-dnd-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }, ], @@ -579,42 +579,42 @@ export default Storybook.story('Uptime Assertions', story => { }); story('Drag and Drop - Between Groups', () => { - const [rootGroup, setRootGroup] = useState({ + const [rootGroup, setRootGroup] = useState({ id: 'story-dnd-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'story-status-dnd-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }, { id: 'story-or-dnd-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'story-json-dnd-2', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.data.id', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: '1234567890'}, }, { id: 'story-json-dnd-3', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.data.name', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'John Doe'}, }, ], }, { id: 'story-header-dnd-2', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'X-Request-Id'}, - value_op: {cmp: ComparisonType.ALWAYS}, + value_op: {cmp: UptimeComparisonType.ALWAYS}, value_operand: {header_op: 'none'}, }, ], diff --git a/static/app/views/alerts/rules/uptime/assertions/opCommon.tsx b/static/app/views/alerts/rules/uptime/assertions/opCommon.tsx index cf608fe932d3bc..4be75385c721d8 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opCommon.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opCommon.tsx @@ -10,13 +10,16 @@ import {Text} from '@sentry/scraps/text'; import QuestionTooltip from 'sentry/components/questionTooltip'; import {IconDelete, IconGrabbable} from 'sentry/icons'; import {t} from 'sentry/locale'; -import {ComparisonType, type Op} from 'sentry/views/alerts/rules/uptime/types'; +import { + UptimeComparisonType, + type UptimeOp, +} from 'sentry/views/alerts/rules/uptime/types'; interface AnimatedOpProps extends MotionProps, Omit, keyof MotionProps> { children: React.ReactNode; isDragging: boolean; - op: Op; + op: UptimeOp; ref: React.Ref; } @@ -53,7 +56,7 @@ interface OpContainerProps { children: React.ReactNode; label: React.ReactNode; onRemove: () => void; - op: Op; + op: UptimeOp; inputId?: string; tooltip?: React.ReactNode; } @@ -107,45 +110,46 @@ export function OpContainer({ ); } -export const COMPARISON_OPTIONS: Array & {symbol: string}> = - [ - { - value: ComparisonType.EQUALS, - label: t('equal'), - symbol: '=', - trailingItems: =, - }, - { - value: ComparisonType.NOT_EQUAL, - label: t('not equal'), - symbol: '\u2260', - trailingItems: {'\u2260'}, - }, - { - value: ComparisonType.LESS_THAN, - label: t('less than'), - symbol: '<', - trailingItems: {'<'}, - }, - { - value: ComparisonType.GREATER_THAN, - label: t('greater than'), - symbol: '>', - trailingItems: {'>'}, - }, - { - value: ComparisonType.ALWAYS, - label: t('present'), - symbol: '\u22A4', - trailingItems: {'\u22A4'}, - }, - { - value: ComparisonType.NEVER, - label: t('not present'), - symbol: '\u2205', - trailingItems: {'\u2205'}, - }, - ]; +export const COMPARISON_OPTIONS: Array< + SelectOption & {symbol: string} +> = [ + { + value: UptimeComparisonType.EQUALS, + label: t('equal'), + symbol: '=', + trailingItems: =, + }, + { + value: UptimeComparisonType.NOT_EQUAL, + label: t('not equal'), + symbol: '\u2260', + trailingItems: {'\u2260'}, + }, + { + value: UptimeComparisonType.LESS_THAN, + label: t('less than'), + symbol: '<', + trailingItems: {'<'}, + }, + { + value: UptimeComparisonType.GREATER_THAN, + label: t('greater than'), + symbol: '>', + trailingItems: {'>'}, + }, + { + value: UptimeComparisonType.ALWAYS, + label: t('present'), + symbol: '\u22A4', + trailingItems: {'\u22A4'}, + }, + { + value: UptimeComparisonType.NEVER, + label: t('not present'), + symbol: '\u2205', + trailingItems: {'\u2205'}, + }, +]; export const STRING_OPERAND_OPTIONS: Array< SelectOption<'literal' | 'glob'> & {symbol: string} diff --git a/static/app/views/alerts/rules/uptime/assertions/opGroup.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/opGroup.spec.tsx index c0ea6c03567d50..3c12c4d369ceda 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opGroup.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opGroup.spec.tsx @@ -3,15 +3,15 @@ import {useState} from 'react'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import { - ComparisonType, - OpType, - type AndOp, - type GroupOp, - type HeaderCheckOp, - type JsonPathOp, - type NotOp, - type OrOp, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeAndOp, + type UptimeGroupOp, + type UptimeHeaderCheckOp, + type UptimeJsonPathOp, + type UptimeNotOp, + type UptimeOrOp, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {AssertionOpGroup} from './opGroup'; @@ -21,7 +21,7 @@ function StatefulOpGroup({ initialValue, onRemove, }: { - initialValue: GroupOp | NotOp; + initialValue: UptimeGroupOp | UptimeNotOp; onRemove?: () => void; }) { const [value, setValue] = useState(initialValue); @@ -32,7 +32,7 @@ describe('AssertionOpGroup', () => { const mockOnChange = jest.fn(); const mockOnRemove = jest.fn(); - const renderRootGroup = async (value: GroupOp | NotOp) => { + const renderRootGroup = async (value: UptimeGroupOp | UptimeNotOp) => { const result = render( ); @@ -40,7 +40,10 @@ describe('AssertionOpGroup', () => { return result; }; - const renderGroup = async (value: GroupOp | NotOp, onRemove?: () => void) => { + const renderGroup = async ( + value: UptimeGroupOp | UptimeNotOp, + onRemove?: () => void + ) => { const result = render( { describe('root mode', () => { it('renders root group without border controls', async () => { - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; @@ -78,16 +81,16 @@ describe('AssertionOpGroup', () => { }); it('renders children in root mode', async () => { - const statusCodeOp: StatusCodeOp = { + const statusCodeOp: UptimeStatusCodeOp = { id: 'test-id-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [statusCodeOp], }; @@ -97,9 +100,9 @@ describe('AssertionOpGroup', () => { }); it('adds operation in root mode', async () => { - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-root', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; @@ -111,12 +114,12 @@ describe('AssertionOpGroup', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-root', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: expect.any(String), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -126,17 +129,17 @@ describe('AssertionOpGroup', () => { describe('non-root mode', () => { it('cycles through all group types by clicking', async () => { - const statusCodeOp: StatusCodeOp = { + const statusCodeOp: UptimeStatusCodeOp = { id: 'test-id-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; // Start with "and" group - const initialValue: AndOp = { + const initialValue: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [statusCodeOp], }; @@ -173,9 +176,9 @@ describe('AssertionOpGroup', () => { }); it('shows empty state message when no children', async () => { - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; @@ -184,9 +187,9 @@ describe('AssertionOpGroup', () => { }); it('calls onRemove when remove button is clicked', async () => { - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; @@ -196,9 +199,9 @@ describe('AssertionOpGroup', () => { }); it('does not show remove button when onRemove is not provided', async () => { - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; @@ -213,9 +216,9 @@ describe('AssertionOpGroup', () => { describe('adding and removing children', () => { it('cycles through adding and removing different operation types', async () => { - const initialValue: AndOp = { + const initialValue: UptimeAndOp = { id: 'test-id-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; @@ -273,16 +276,16 @@ describe('AssertionOpGroup', () => { describe('updating children', () => { it('renders status code child', async () => { - const statusCodeOp: StatusCodeOp = { + const statusCodeOp: UptimeStatusCodeOp = { id: 'test-id-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [statusCodeOp], }; @@ -294,17 +297,17 @@ describe('AssertionOpGroup', () => { }); it('updates json path child', async () => { - const jsonPathOp: JsonPathOp = { + const jsonPathOp: UptimeJsonPathOp = { id: 'test-id-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [jsonPathOp], }; @@ -317,13 +320,13 @@ describe('AssertionOpGroup', () => { // Verify onChange was called with the updated value expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-id-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: 'x', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }, ], @@ -331,18 +334,18 @@ describe('AssertionOpGroup', () => { }); it('updates header child', async () => { - const headerOp: HeaderCheckOp = { + const headerOp: UptimeHeaderCheckOp = { id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: ''}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [headerOp], }; @@ -355,14 +358,14 @@ describe('AssertionOpGroup', () => { // Verify onChange was called with the updated value expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'x'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }, ], @@ -372,39 +375,39 @@ describe('AssertionOpGroup', () => { describe('rendering different child types', () => { it('renders all different child types together', async () => { - const statusCodeOp: StatusCodeOp = { + const statusCodeOp: UptimeStatusCodeOp = { id: 'test-id-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; - const jsonPathOp: JsonPathOp = { + const jsonPathOp: UptimeJsonPathOp = { id: 'test-id-2', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: ''}, }; - const headerOp: HeaderCheckOp = { + const headerOp: UptimeHeaderCheckOp = { id: 'test-id-3', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'X-Custom'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'test'}, }; - const nestedGroup: OrOp = { + const nestedGroup: UptimeOrOp = { id: 'test-id-4', - op: OpType.OR, + op: UptimeOpType.OR, children: [], }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-5', - op: OpType.AND, + op: UptimeOpType.AND, children: [statusCodeOp, jsonPathOp, headerOp, nestedGroup], }; @@ -423,22 +426,22 @@ describe('AssertionOpGroup', () => { describe('complex interactions', () => { it('updates nested group child', async () => { - const statusCodeOp: StatusCodeOp = { + const statusCodeOp: UptimeStatusCodeOp = { id: 'test-id-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; - const nestedGroup: AndOp = { + const nestedGroup: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [statusCodeOp], }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-3', - op: OpType.AND, + op: UptimeOpType.AND, children: [nestedGroup], }; @@ -458,11 +461,11 @@ describe('AssertionOpGroup', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-3', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-id-2', - op: OpType.OR, + op: UptimeOpType.OR, children: [statusCodeOp], }, ], @@ -470,15 +473,15 @@ describe('AssertionOpGroup', () => { }); it('removes nested group', async () => { - const nestedGroup: AndOp = { + const nestedGroup: UptimeAndOp = { id: 'test-id-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }; - const value: AndOp = { + const value: UptimeAndOp = { id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [nestedGroup], }; @@ -498,7 +501,7 @@ describe('AssertionOpGroup', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [], }); }); diff --git a/static/app/views/alerts/rules/uptime/assertions/opGroup.tsx b/static/app/views/alerts/rules/uptime/assertions/opGroup.tsx index 8a2ba5d69d2607..1cdf3349aae989 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opGroup.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opGroup.tsx @@ -13,10 +13,10 @@ import {IconAdd, IconDelete, IconGrabbable} from 'sentry/icons'; import {t} from 'sentry/locale'; import {uniqueId} from 'sentry/utils/guid'; import { - OpType, - type GroupOp, - type LogicalOp, - type Op, + UptimeOpType, + type UptimeGroupOp, + type UptimeLogicalOp, + type UptimeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {AddOpButton} from './addOpButton'; @@ -28,16 +28,16 @@ import {AssertionOpStatusCode} from './opStatusCode'; import {getGroupOpLabel} from './utils'; interface AssertionOpGroupProps { - onChange: (op: LogicalOp) => void; - value: LogicalOp; + onChange: (op: UptimeLogicalOp) => void; + value: UptimeLogicalOp; disableDropping?: boolean; onRemove?: () => void; root?: boolean; } -const GROUP_TYPE_OPTIONS: Array> = [ - {value: OpType.AND, label: t('Assert All')}, - {value: OpType.OR, label: t('Assert Any')}, +const GROUP_TYPE_OPTIONS: Array> = [ + {value: UptimeOpType.AND, label: t('Assert All')}, + {value: UptimeOpType.OR, label: t('Assert Any')}, ]; export function AssertionOpGroup({ @@ -47,15 +47,15 @@ export function AssertionOpGroup({ root, disableDropping, }: AssertionOpGroupProps) { - const isNegated = value.op === OpType.NOT; + const isNegated = value.op === UptimeOpType.NOT; const groupOp = isNegated ? // Negated ops could technically contain something other than a logic group, // the UI right now only lets you structure the logic this way, but it is // possible that someone could send something to the API that we can't // render, in which case we'll just render this as an empty group - value.operand.op !== OpType.AND && value.operand.op !== OpType.OR - ? {id: value.id, op: OpType.AND as const, children: []} + value.operand.op !== UptimeOpType.AND && value.operand.op !== UptimeOpType.OR + ? {id: value.id, op: UptimeOpType.AND as const, children: []} : value.operand : value; @@ -65,37 +65,45 @@ export function AssertionOpGroup({ // Use existing value.id when already negated, or the generated ID for new negations const notId = isNegated ? value.id : newNotId; - const handleAddOp = (newOp: Op) => { - const newGroupOp: GroupOp = { + const handleAddOp = (newOp: UptimeOp) => { + const newGroupOp: UptimeGroupOp = { ...groupOp, children: [...groupOp.children, newOp], }; - onChange(isNegated ? {id: notId, op: OpType.NOT, operand: newGroupOp} : newGroupOp); + onChange( + isNegated ? {id: notId, op: UptimeOpType.NOT, operand: newGroupOp} : newGroupOp + ); }; - const handleUpdateChild = (index: number, updatedOp: Op) => { + const handleUpdateChild = (index: number, updatedOp: UptimeOp) => { const newChildren = [...groupOp.children]; newChildren[index] = updatedOp; - const newGroupOp: GroupOp = {...groupOp, children: newChildren}; - onChange(isNegated ? {id: notId, op: OpType.NOT, operand: newGroupOp} : newGroupOp); + const newGroupOp: UptimeGroupOp = {...groupOp, children: newChildren}; + onChange( + isNegated ? {id: notId, op: UptimeOpType.NOT, operand: newGroupOp} : newGroupOp + ); }; const handleRemoveChild = (index: number) => { const newChildren = groupOp.children.filter((_, i) => i !== index); - const newGroupOp: GroupOp = {...groupOp, children: newChildren}; - onChange(isNegated ? {id: notId, op: OpType.NOT, operand: newGroupOp} : newGroupOp); + const newGroupOp: UptimeGroupOp = {...groupOp, children: newChildren}; + onChange( + isNegated ? {id: notId, op: UptimeOpType.NOT, operand: newGroupOp} : newGroupOp + ); }; - const handleGroupTypeChange = (newType: OpType.AND | OpType.OR) => { - const newGroupOp: GroupOp = { + const handleGroupTypeChange = (newType: UptimeOpType.AND | UptimeOpType.OR) => { + const newGroupOp: UptimeGroupOp = { ...groupOp, op: newType, }; - onChange(isNegated ? {id: notId, op: OpType.NOT, operand: newGroupOp} : newGroupOp); + onChange( + isNegated ? {id: notId, op: UptimeOpType.NOT, operand: newGroupOp} : newGroupOp + ); }; const handleNegationToggle = (negated: boolean) => { - onChange(negated ? {id: newNotId, op: OpType.NOT, operand: groupOp} : groupOp); + onChange(negated ? {id: newNotId, op: UptimeOpType.NOT, operand: groupOp} : groupOp); }; const triggerLabel = getGroupOpLabel(groupOp, isNegated); @@ -108,9 +116,9 @@ export function AssertionOpGroup({ const innerDroppableDisabled = !root && (isDragging || !!disableDropping); - const renderOp = (op: Op, index: number) => { + const renderOp = (op: UptimeOp, index: number) => { switch (op.op) { - case OpType.STATUS_CODE_CHECK: + case UptimeOpType.STATUS_CODE_CHECK: return ( handleRemoveChild(index)} /> ); - case OpType.JSON_PATH: + case UptimeOpType.JSON_PATH: return ( handleRemoveChild(index)} /> ); - case OpType.HEADER_CHECK: + case UptimeOpType.HEADER_CHECK: return ( handleRemoveChild(index)} /> ); - case OpType.AND: - case OpType.OR: - case OpType.NOT: + case UptimeOpType.AND: + case UptimeOpType.OR: + case UptimeOpType.NOT: return ( { const mockOnChange = jest.fn(); const mockOnRemove = jest.fn(); - const op = OpType.HEADER_CHECK; + const op = UptimeOpType.HEADER_CHECK; - const renderOp = async (value: HeaderCheckOp) => { + const renderOp = async (value: UptimeHeaderCheckOp) => { const result = render( ); @@ -31,9 +31,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -45,9 +45,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: ''}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); @@ -59,9 +59,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: ''}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); @@ -72,9 +72,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: ''}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); @@ -83,10 +83,10 @@ describe('AssertionOpHeader', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'a'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); }); @@ -95,9 +95,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -110,9 +110,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -123,9 +123,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.ALWAYS}, + key_op: {cmp: UptimeComparisonType.ALWAYS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.ALWAYS}, + value_op: {cmp: UptimeComparisonType.ALWAYS}, value_operand: {header_op: 'none'}, }); @@ -139,9 +139,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'glob', pattern: {value: 'X-*'}}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); @@ -153,9 +153,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -168,10 +168,10 @@ describe('AssertionOpHeader', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.NOT_EQUAL}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.NOT_EQUAL}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); }); @@ -180,9 +180,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -195,10 +195,10 @@ describe('AssertionOpHeader', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'glob', pattern: {value: 'Content-Type'}}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); }); @@ -207,9 +207,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -222,10 +222,10 @@ describe('AssertionOpHeader', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.ALWAYS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.ALWAYS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.ALWAYS}, + value_op: {cmp: UptimeComparisonType.ALWAYS}, value_operand: {header_op: 'none'}, }); }); @@ -234,9 +234,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.ALWAYS}, + key_op: {cmp: UptimeComparisonType.ALWAYS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.ALWAYS}, + value_op: {cmp: UptimeComparisonType.ALWAYS}, value_operand: {header_op: 'none'}, }); @@ -249,10 +249,10 @@ describe('AssertionOpHeader', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: ''}, }); }); @@ -263,9 +263,9 @@ describe('AssertionOpHeader', () => { await renderOp({ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }); @@ -279,10 +279,10 @@ describe('AssertionOpHeader', () => { // key_operand should be preserved, value_operand should be set to 'none' expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.ALWAYS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.ALWAYS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.ALWAYS}, + value_op: {cmp: UptimeComparisonType.ALWAYS}, value_operand: {header_op: 'none'}, }); }); @@ -294,9 +294,9 @@ describe('AssertionOpHeader', () => { value={{ id: 'test-id-1', op, - key_op: {cmp: ComparisonType.EQUALS}, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'Content-Type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, }} onChange={mockOnChange} diff --git a/static/app/views/alerts/rules/uptime/assertions/opHeader.tsx b/static/app/views/alerts/rules/uptime/assertions/opHeader.tsx index a8b92c605762fc..d23e8075f61545 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opHeader.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opHeader.tsx @@ -8,9 +8,9 @@ import {Text} from '@sentry/scraps/text'; import {t} from 'sentry/locale'; import { - ComparisonType, - type HeaderCheckOp, - type HeaderOperand, + UptimeComparisonType, + type UptimeHeaderCheckOp, + type UptimeHeaderOperand, } from 'sentry/views/alerts/rules/uptime/types'; import {COMPARISON_OPTIONS, OpContainer, STRING_OPERAND_OPTIONS} from './opCommon'; @@ -22,19 +22,22 @@ import { } from './utils'; interface AssertionOpHeaderProps { - onChange: (op: HeaderCheckOp) => void; + onChange: (op: UptimeHeaderCheckOp) => void; onRemove: () => void; - value: HeaderCheckOp; + value: UptimeHeaderCheckOp; } export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeaderProps) { const inputId = useId(); const headerKeyComparisonOptions = COMPARISON_OPTIONS.filter( - opt => ![ComparisonType.LESS_THAN, ComparisonType.GREATER_THAN].includes(opt.value) + opt => + ![UptimeComparisonType.LESS_THAN, UptimeComparisonType.GREATER_THAN].includes( + opt.value + ) ); const headerValueComparisonOptions = COMPARISON_OPTIONS.filter(opt => - [ComparisonType.EQUALS, ComparisonType.NOT_EQUAL].includes(opt.value) + [UptimeComparisonType.EQUALS, UptimeComparisonType.NOT_EQUAL].includes(opt.value) ); const showValueInput = shouldShowHeaderValueInput(value); @@ -75,12 +78,12 @@ export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeader value={value.key_op.cmp} onChange={option => { const isAlwaysNever = [ - ComparisonType.ALWAYS, - ComparisonType.NEVER, + UptimeComparisonType.ALWAYS, + UptimeComparisonType.NEVER, ].includes(option.value); const wasAlwaysNever = [ - ComparisonType.ALWAYS, - ComparisonType.NEVER, + UptimeComparisonType.ALWAYS, + UptimeComparisonType.NEVER, ].includes(value.key_op.cmp); onChange({ @@ -89,7 +92,7 @@ export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeader value_op: isAlwaysNever ? {cmp: option.value} : wasAlwaysNever - ? {cmp: ComparisonType.EQUALS} + ? {cmp: UptimeComparisonType.EQUALS} : value.value_op, value_operand: isAlwaysNever ? {header_op: 'none'} @@ -104,7 +107,7 @@ export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeader label={t('String type')} value={keyOperandType === 'none' ? 'literal' : keyOperandType} onChange={option => { - const newOperand: HeaderOperand = + const newOperand: UptimeHeaderOperand = option.value === 'literal' ? {header_op: 'literal', value: keyOperandValue} : {header_op: 'glob', pattern: {value: keyOperandValue}}; @@ -118,7 +121,7 @@ export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeader id={inputId} value={keyOperandValue} onChange={e => { - const newOperand: HeaderOperand = + const newOperand: UptimeHeaderOperand = keyOperandType === 'glob' ? {header_op: 'glob', pattern: {value: e.target.value}} : {header_op: 'literal', value: e.target.value}; @@ -163,7 +166,7 @@ export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeader label={t('String type')} value={valueOperandType} onChange={option => { - const newOperand: HeaderOperand = + const newOperand: UptimeHeaderOperand = option.value === 'literal' ? {header_op: 'literal', value: valueOperandValue} : {header_op: 'glob', pattern: {value: valueOperandValue}}; @@ -177,7 +180,7 @@ export function AssertionOpHeader({value, onChange, onRemove}: AssertionOpHeader { - const newOperand: HeaderOperand = + const newOperand: UptimeHeaderOperand = valueOperandType === 'glob' ? {header_op: 'glob', pattern: {value: e.target.value}} : {header_op: 'literal', value: e.target.value}; diff --git a/static/app/views/alerts/rules/uptime/assertions/opJsonPath.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/opJsonPath.spec.tsx index 667945a3285904..4a3263fed4c5de 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opJsonPath.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opJsonPath.spec.tsx @@ -3,11 +3,11 @@ import {useState} from 'react'; import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import { - ComparisonType, - OpType, - type Comparison, - type JsonPathOp, - type JsonPathOperand, + UptimeComparisonType, + UptimeOpType, + type UptimeComparison, + type UptimeJsonPathOp, + type UptimeJsonPathOperand, } from 'sentry/views/alerts/rules/uptime/types'; import {AssertionsDndContext} from './dragDrop'; @@ -18,10 +18,10 @@ describe('AssertionOpJsonPath', () => { const mockOnChange = jest.fn(); const mockOnRemove = jest.fn(); - const defaultOperator: Comparison = {cmp: ComparisonType.EQUALS}; - const defaultOperand: JsonPathOperand = {jsonpath_op: 'literal', value: 'ok'}; + const defaultOperator: UptimeComparison = {cmp: UptimeComparisonType.EQUALS}; + const defaultOperand: UptimeJsonPathOperand = {jsonpath_op: 'literal', value: 'ok'}; - const renderOp = async (value: JsonPathOp) => { + const renderOp = async (value: UptimeJsonPathOp) => { const result = render( { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: 'a', operator: defaultOperator, operand: defaultOperand, @@ -87,7 +87,7 @@ describe('AssertionOpJsonPath', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', operator: defaultOperator, operand: {jsonpath_op: 'literal', value: 'a'}, @@ -146,7 +146,7 @@ describe('AssertionOpJsonPath', () => { it('allows < and > comparisons for numeric operand values and hides string type selector', async () => { function Stateful() { - const [state, setState] = useState({ + const [state, setState] = useState({ ...makeJsonPathOp({ operator: defaultOperator, operand: defaultOperand, @@ -186,7 +186,7 @@ describe('AssertionOpJsonPath', () => { makeJsonPathOp({ id: 'test-id-1', value: '$.status', - operator: {cmp: ComparisonType.LESS_THAN}, + operator: {cmp: UptimeComparisonType.LESS_THAN}, operand: defaultOperand, }) ); @@ -194,9 +194,9 @@ describe('AssertionOpJsonPath', () => { await waitFor(() => expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: defaultOperand, }) ); @@ -205,9 +205,9 @@ describe('AssertionOpJsonPath', () => { it('renders safely when given a legacy op without operator or operand', async () => { await renderOp({ id: 'test-id-1', - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - } as JsonPathOp); + } as UptimeJsonPathOp); // Should render with safe defaults (equals + literal) instead of crashing. expect(screen.getByTestId('json-path-value-input')).toHaveValue('$.status'); diff --git a/static/app/views/alerts/rules/uptime/assertions/opJsonPath.tsx b/static/app/views/alerts/rules/uptime/assertions/opJsonPath.tsx index 4531d529293cce..9a11090b574958 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opJsonPath.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opJsonPath.tsx @@ -10,9 +10,9 @@ import {Text} from '@sentry/scraps/text'; import {t, tct} from 'sentry/locale'; import {isNumericString} from 'sentry/utils'; import { - ComparisonType, - type JsonPathOp, - type JsonPathOperand, + UptimeComparisonType, + type UptimeJsonPathOp, + type UptimeJsonPathOperand, } from 'sentry/views/alerts/rules/uptime/types'; import {COMPARISON_OPTIONS, OpContainer, STRING_OPERAND_OPTIONS} from './opCommon'; @@ -23,9 +23,9 @@ import { } from './utils'; interface AssertionOpJsonPathProps { - onChange: (op: JsonPathOp) => void; + onChange: (op: UptimeJsonPathOp) => void; onRemove: () => void; - value: JsonPathOp; + value: UptimeJsonPathOp; } export function AssertionOpJsonPath({ @@ -35,7 +35,7 @@ export function AssertionOpJsonPath({ }: AssertionOpJsonPathProps) { const inputId = useId(); - const normalizedOp: JsonPathOp = normalizeJsonPathOp(value); + const normalizedOp: UptimeJsonPathOp = normalizeJsonPathOp(value); const operandValue = getJsonPathOperandValue(normalizedOp.operand); const {combinedLabel, combinedTooltip} = @@ -44,13 +44,16 @@ export function AssertionOpJsonPath({ const isNumeric = isNumericString(operandValue); const comparisonOptions = COMPARISON_OPTIONS.filter(opt => { - if (opt.value === ComparisonType.ALWAYS || opt.value === ComparisonType.NEVER) { + if ( + opt.value === UptimeComparisonType.ALWAYS || + opt.value === UptimeComparisonType.NEVER + ) { return false; } if ( !isNumeric && - (opt.value === ComparisonType.LESS_THAN || - opt.value === ComparisonType.GREATER_THAN) + (opt.value === UptimeComparisonType.LESS_THAN || + opt.value === UptimeComparisonType.GREATER_THAN) ) { return false; } @@ -62,14 +65,14 @@ export function AssertionOpJsonPath({ // // - Non-numeric: disallow < and > comparisons (force equals). // - Numeric: force a literal operand (hide/disable glob). - let nextValue: JsonPathOp | null = null; + let nextValue: UptimeJsonPathOp | null = null; if ( !isNumeric && - (normalizedOp.operator.cmp === ComparisonType.LESS_THAN || - normalizedOp.operator.cmp === ComparisonType.GREATER_THAN) + (normalizedOp.operator.cmp === UptimeComparisonType.LESS_THAN || + normalizedOp.operator.cmp === UptimeComparisonType.GREATER_THAN) ) { - nextValue = {...normalizedOp, operator: {cmp: ComparisonType.EQUALS}}; + nextValue = {...normalizedOp, operator: {cmp: UptimeComparisonType.EQUALS}}; } if (isNumeric && normalizedOp.operand.jsonpath_op === 'glob') { @@ -137,7 +140,7 @@ export function AssertionOpJsonPath({ label={t('String operand types')} value={normalizedOp.operand.jsonpath_op} onChange={option => { - const newOperand: JsonPathOperand = + const newOperand: UptimeJsonPathOperand = option.value === 'literal' ? {jsonpath_op: 'literal', value: operandValue} : {jsonpath_op: 'glob', pattern: {value: operandValue}}; @@ -154,7 +157,7 @@ export function AssertionOpJsonPath({ const nextValue = e.target.value; const nextIsNumeric = isNumericString(nextValue); - const newOperand: JsonPathOperand = nextIsNumeric + const newOperand: UptimeJsonPathOperand = nextIsNumeric ? {jsonpath_op: 'literal', value: nextValue} : normalizedOp.operand.jsonpath_op === 'glob' ? {jsonpath_op: 'glob', pattern: {value: nextValue}} diff --git a/static/app/views/alerts/rules/uptime/assertions/opStatusCode.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/opStatusCode.spec.tsx index d8b5adddfa2215..fcefb621a09259 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opStatusCode.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opStatusCode.spec.tsx @@ -3,9 +3,9 @@ import {useState} from 'react'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import { - ComparisonType, - OpType, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {AssertionsDndContext} from './dragDrop'; @@ -15,9 +15,9 @@ describe('AssertionOpStatusCode', () => { const mockOnChange = jest.fn(); const mockOnRemove = jest.fn(); - const op = OpType.STATUS_CODE_CHECK; + const op = UptimeOpType.STATUS_CODE_CHECK; - const renderOp = async (value: StatusCodeOp) => { + const renderOp = async (value: UptimeStatusCodeOp) => { const result = render( { onChangeSpy, onRemoveSpy, }: { - initialValue: StatusCodeOp; + initialValue: UptimeStatusCodeOp; onChangeSpy: jest.Mock; onRemoveSpy: jest.Mock; }) { @@ -52,7 +52,7 @@ describe('AssertionOpStatusCode', () => { ); } - const renderStatefulOp = async (initialValue: StatusCodeOp) => { + const renderStatefulOp = async (initialValue: UptimeStatusCodeOp) => { const result = render( { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -86,7 +86,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); expect(getComparison('=')).toBeInTheDocument(); @@ -96,7 +96,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.NOT_EQUAL}, + operator: {cmp: UptimeComparisonType.NOT_EQUAL}, value: 200, }); expect(getComparison('\u2260')).toBeInTheDocument(); @@ -106,7 +106,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.LESS_THAN}, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 200, }); expect(getComparison('<')).toBeInTheDocument(); @@ -116,7 +116,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.GREATER_THAN}, + operator: {cmp: UptimeComparisonType.GREATER_THAN}, value: 200, }); expect(getComparison('>')).toBeInTheDocument(); @@ -126,7 +126,7 @@ describe('AssertionOpStatusCode', () => { await renderStatefulOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -137,7 +137,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 404, }); }); @@ -146,7 +146,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -159,7 +159,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.LESS_THAN}, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 200, }); }); @@ -168,7 +168,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -181,7 +181,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -198,7 +198,7 @@ describe('AssertionOpStatusCode', () => { await renderStatefulOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -210,7 +210,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 5, }); @@ -219,7 +219,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 504, }); }); @@ -228,7 +228,7 @@ describe('AssertionOpStatusCode', () => { await renderStatefulOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -243,7 +243,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 100, }); }); @@ -252,7 +252,7 @@ describe('AssertionOpStatusCode', () => { await renderStatefulOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -264,7 +264,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 599, }); }); @@ -273,7 +273,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -288,7 +288,7 @@ describe('AssertionOpStatusCode', () => { await renderStatefulOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 50, }); @@ -299,7 +299,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 100, }); }); @@ -308,7 +308,7 @@ describe('AssertionOpStatusCode', () => { await renderStatefulOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -321,7 +321,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenLastCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); }); @@ -330,7 +330,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -363,7 +363,7 @@ describe('AssertionOpStatusCode', () => { await renderOp({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }); @@ -377,7 +377,7 @@ describe('AssertionOpStatusCode', () => { expect(mockOnChange).toHaveBeenCalledWith({ id: 'test-id-1', op, - operator: {cmp: ComparisonType.GREATER_THAN}, + operator: {cmp: UptimeComparisonType.GREATER_THAN}, value: 200, // Original value preserved }); }); @@ -389,7 +389,7 @@ describe('AssertionOpStatusCode', () => { value={{ id: 'test-id-1', op, - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }} onChange={mockOnChange} diff --git a/static/app/views/alerts/rules/uptime/assertions/opStatusCode.tsx b/static/app/views/alerts/rules/uptime/assertions/opStatusCode.tsx index 6be6fb76fc624e..efc91dc9e6c976 100644 --- a/static/app/views/alerts/rules/uptime/assertions/opStatusCode.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/opStatusCode.tsx @@ -6,14 +6,17 @@ import {OverlayTrigger} from '@sentry/scraps/overlayTrigger'; import {Text} from '@sentry/scraps/text'; import {t} from 'sentry/locale'; -import {ComparisonType, type StatusCodeOp} from 'sentry/views/alerts/rules/uptime/types'; +import { + UptimeComparisonType, + type UptimeStatusCodeOp, +} from 'sentry/views/alerts/rules/uptime/types'; import {COMPARISON_OPTIONS, OpContainer} from './opCommon'; interface AssertionOpStatusCodeProps { - onChange: (op: StatusCodeOp) => void; + onChange: (op: UptimeStatusCodeOp) => void; onRemove: () => void; - value: StatusCodeOp; + value: UptimeStatusCodeOp; } export function AssertionOpStatusCode({ @@ -25,7 +28,7 @@ export function AssertionOpStatusCode({ // Filter out 'always' and 'never' which are not valid for status code checks const statusCodeOptions = COMPARISON_OPTIONS.filter( - opt => ![ComparisonType.ALWAYS, ComparisonType.NEVER].includes(opt.value) + opt => ![UptimeComparisonType.ALWAYS, UptimeComparisonType.NEVER].includes(opt.value) ); const selectedOption = statusCodeOptions.find(opt => opt.value === value.operator.cmp); diff --git a/static/app/views/alerts/rules/uptime/assertions/testUtils.tsx b/static/app/views/alerts/rules/uptime/assertions/testUtils.tsx index fa5f93f79c4332..8e7808d5f4ae16 100644 --- a/static/app/views/alerts/rules/uptime/assertions/testUtils.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/testUtils.tsx @@ -1,79 +1,79 @@ import {uniqueId} from 'sentry/utils/guid'; import { - ComparisonType, - OpType, - type AndOp, - type HeaderCheckOp, - type JsonPathOp, - type NotOp, - type OrOp, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeAndOp, + type UptimeHeaderCheckOp, + type UptimeJsonPathOp, + type UptimeNotOp, + type UptimeOrOp, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; -export function makeAndOp(overrides: Omit, 'op'> = {}): AndOp { +export function makeAndOp(overrides: Omit, 'op'> = {}): UptimeAndOp { const {children, ...rest} = overrides; return { id: uniqueId(), - op: OpType.AND, + op: UptimeOpType.AND, children: children ?? [], ...rest, }; } -export function makeOrOp(overrides: Omit, 'op'> = {}): OrOp { +export function makeOrOp(overrides: Omit, 'op'> = {}): UptimeOrOp { const {children, ...rest} = overrides; return { id: uniqueId(), - op: OpType.OR, + op: UptimeOpType.OR, children: children ?? [], ...rest, }; } -export function makeNotOp(overrides: Omit, 'op'> = {}): NotOp { +export function makeNotOp(overrides: Omit, 'op'> = {}): UptimeNotOp { const {operand, ...rest} = overrides; return { id: uniqueId(), - op: OpType.NOT, + op: UptimeOpType.NOT, operand: operand ?? makeAndOp({children: [makeStatusCodeOp()]}), ...rest, }; } export function makeStatusCodeOp( - overrides: Omit, 'op'> = {} -): StatusCodeOp { + overrides: Omit, 'op'> = {} +): UptimeStatusCodeOp { return { id: uniqueId(), - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, ...overrides, }; } export function makeJsonPathOp( - overrides: Omit, 'op'> = {} -): JsonPathOp { + overrides: Omit, 'op'> = {} +): UptimeJsonPathOp { return { id: uniqueId(), - op: OpType.JSON_PATH, + op: UptimeOpType.JSON_PATH, value: '$.status', - operator: {cmp: ComparisonType.EQUALS}, + operator: {cmp: UptimeComparisonType.EQUALS}, operand: {jsonpath_op: 'literal', value: 'ok'}, ...overrides, }; } export function makeHeaderCheckOp( - overrides: Omit, 'op'> = {} -): HeaderCheckOp { + overrides: Omit, 'op'> = {} +): UptimeHeaderCheckOp { return { id: uniqueId(), - op: OpType.HEADER_CHECK, - key_op: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.HEADER_CHECK, + key_op: {cmp: UptimeComparisonType.EQUALS}, key_operand: {header_op: 'literal', value: 'content-type'}, - value_op: {cmp: ComparisonType.EQUALS}, + value_op: {cmp: UptimeComparisonType.EQUALS}, value_operand: {header_op: 'literal', value: 'application/json'}, ...overrides, }; diff --git a/static/app/views/alerts/rules/uptime/assertions/typeGuards.tsx b/static/app/views/alerts/rules/uptime/assertions/typeGuards.tsx index f96adc2d7b4ff6..a715f1d66b6d28 100644 --- a/static/app/views/alerts/rules/uptime/assertions/typeGuards.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/typeGuards.tsx @@ -1,39 +1,39 @@ import { - OpType, - type AndOp, - type GroupOp, - type HeaderCheckOp, - type JsonPathOp, - type NotOp, - type Op, - type OrOp, - type StatusCodeOp, + UptimeOpType, + type UptimeAndOp, + type UptimeGroupOp, + type UptimeHeaderCheckOp, + type UptimeJsonPathOp, + type UptimeNotOp, + type UptimeOp, + type UptimeOrOp, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; -export function isAndOp(value: Op): value is AndOp { - return value.op === OpType.AND; +export function isAndOp(value: UptimeOp): value is UptimeAndOp { + return value.op === UptimeOpType.AND; } -export function isOrOp(value: Op): value is OrOp { - return value.op === OpType.OR; +export function isOrOp(value: UptimeOp): value is UptimeOrOp { + return value.op === UptimeOpType.OR; } -export function isGroupOp(value: Op): value is GroupOp { +export function isGroupOp(value: UptimeOp): value is UptimeGroupOp { return isAndOp(value) || isOrOp(value); } -export function isNotOp(value: Op): value is NotOp { - return value.op === OpType.NOT; +export function isNotOp(value: UptimeOp): value is UptimeNotOp { + return value.op === UptimeOpType.NOT; } -export function isStatusCodeOp(value: Op): value is StatusCodeOp { - return value.op === OpType.STATUS_CODE_CHECK; +export function isStatusCodeOp(value: UptimeOp): value is UptimeStatusCodeOp { + return value.op === UptimeOpType.STATUS_CODE_CHECK; } -export function isJsonPathOp(value: Op): value is JsonPathOp { - return value.op === OpType.JSON_PATH; +export function isJsonPathOp(value: UptimeOp): value is UptimeJsonPathOp { + return value.op === UptimeOpType.JSON_PATH; } -export function isHeaderCheckOp(value: Op): value is HeaderCheckOp { - return value.op === OpType.HEADER_CHECK; +export function isHeaderCheckOp(value: UptimeOp): value is UptimeHeaderCheckOp { + return value.op === UptimeOpType.HEADER_CHECK; } diff --git a/static/app/views/alerts/rules/uptime/assertions/utils.spec.tsx b/static/app/views/alerts/rules/uptime/assertions/utils.spec.tsx index cd3c61485a14b9..05d3067a5e4796 100644 --- a/static/app/views/alerts/rules/uptime/assertions/utils.spec.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/utils.spec.tsx @@ -1,36 +1,36 @@ import { - ComparisonType, - OpType, - type AndOp, - type NotOp, - type OrOp, - type StatusCodeOp, + UptimeComparisonType, + UptimeOpType, + type UptimeAndOp, + type UptimeNotOp, + type UptimeOrOp, + type UptimeStatusCodeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {isAfterOp, moveTo} from './utils'; describe('moveTo', () => { it('moves op to after another op in the same parent', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -42,26 +42,26 @@ describe('moveTo', () => { }); it('moves op to before another op in the same parent', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -73,26 +73,26 @@ describe('moveTo', () => { }); it('moves op to before the first element', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -104,26 +104,26 @@ describe('moveTo', () => { }); it('moves op to after the last element', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -135,30 +135,30 @@ describe('moveTo', () => { }); it('moves op from nested group to root level', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -170,35 +170,35 @@ describe('moveTo', () => { expect(result.children.map(c => c.id)).toEqual(['status-1', 'status-2', 'or-1']); // Verify the nested group still has status-3 - const orOp = result.children.find(c => c.id === 'or-1') as OrOp; + const orOp = result.children.find(c => c.id === 'or-1') as UptimeOrOp; expect(orOp.children.map(c => c.id)).toEqual(['status-3']); }); it('moves op from root to nested group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -210,41 +210,41 @@ describe('moveTo', () => { expect(result.children.map(c => c.id)).toEqual(['or-1']); // Verify status-1 is now in the nested group - const orOp = result.children.find(c => c.id === 'or-1') as OrOp; + const orOp = result.children.find(c => c.id === 'or-1') as UptimeOrOp; expect(orOp.children.map(c => c.id)).toEqual(['status-1', 'status-2', 'status-3']); }); it('moves op within a nested group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, { id: 'status-4', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 203, }, ], @@ -254,19 +254,19 @@ describe('moveTo', () => { const result = moveTo(rootOp, 'status-4', 'status-2', 'before'); - const orOp = result.children.find(c => c.id === 'or-1') as OrOp; + const orOp = result.children.find(c => c.id === 'or-1') as UptimeOrOp; expect(orOp.children.map(c => c.id)).toEqual(['status-4', 'status-2', 'status-3']); }); it('returns unchanged tree if source not found', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -278,14 +278,14 @@ describe('moveTo', () => { }); it('returns unchanged tree if target not found', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -297,20 +297,20 @@ describe('moveTo', () => { }); it('preserves op data when moving', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.LESS_THAN}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 400, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -318,26 +318,26 @@ describe('moveTo', () => { const result = moveTo(rootOp, 'status-1', 'status-2', 'after'); - const movedOp = result.children.find(c => c.id === 'status-1') as StatusCodeOp; - expect(movedOp.operator).toEqual({cmp: ComparisonType.LESS_THAN}); + const movedOp = result.children.find(c => c.id === 'status-1') as UptimeStatusCodeOp; + expect(movedOp.operator).toEqual({cmp: UptimeComparisonType.LESS_THAN}); expect(movedOp.value).toBe(400); }); it('handles two ops: move last before first, then back after first', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -353,26 +353,26 @@ describe('moveTo', () => { }); it('handles multiple consecutive moves', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -392,20 +392,20 @@ describe('moveTo', () => { }); it('does not create duplicate ops when moving rapidly', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -422,20 +422,20 @@ describe('moveTo', () => { }); it('handles moving an op to where it already is (no-op move)', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -448,20 +448,20 @@ describe('moveTo', () => { }); it('verifies no duplicate IDs after complex moves', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -480,20 +480,20 @@ describe('moveTo', () => { }); it('handles moving to same position (status-1 after status-1 should do nothing)', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -506,19 +506,19 @@ describe('moveTo', () => { }); it('moves op inside an empty group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [], }, ], @@ -529,27 +529,27 @@ describe('moveTo', () => { // Root should only have the or group now expect(result.children.map(c => c.id)).toEqual(['or-1']); // The or group should now contain status-1 - const orOp = result.children.find(c => c.id === 'or-1') as OrOp; + const orOp = result.children.find(c => c.id === 'or-1') as UptimeOrOp; expect(orOp.children.map(c => c.id)).toEqual(['status-1']); }); it('moves op inside an empty not group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'not-1', - op: OpType.NOT, + op: UptimeOpType.NOT, operand: { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [], }, }, @@ -561,30 +561,30 @@ describe('moveTo', () => { // Root should only have the not group now expect(result.children.map(c => c.id)).toEqual(['not-1']); // The not's operand (or group) should now contain status-1 - const notOp = result.children.find(c => c.id === 'not-1') as NotOp; - const orOp = notOp.operand as OrOp; + const notOp = result.children.find(c => c.id === 'not-1') as UptimeNotOp; + const orOp = notOp.operand as UptimeOrOp; expect(orOp.children.map(c => c.id)).toEqual(['status-1']); }); it('moves op inside a non-empty group (appends to end)', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -597,25 +597,25 @@ describe('moveTo', () => { // Root should only have the or group now expect(result.children.map(c => c.id)).toEqual(['or-1']); // The or group should now contain both ops - const orOp = result.children.find(c => c.id === 'or-1') as OrOp; + const orOp = result.children.find(c => c.id === 'or-1') as UptimeOrOp; expect(orOp.children.map(c => c.id)).toEqual(['status-2', 'status-1']); }); it('returns unchanged tree if target for inside move is not a group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -628,28 +628,28 @@ describe('moveTo', () => { }); it('returns unchanged tree when moving parent into its own descendant', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'and-2', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -672,31 +672,31 @@ describe('moveTo', () => { }); it('moves op from nested group inside another group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], }, { id: 'or-2', - op: OpType.OR, + op: UptimeOpType.OR, children: [], }, ], @@ -705,31 +705,31 @@ describe('moveTo', () => { const result = moveTo(rootOp, 'status-1', 'or-2', 'inside'); // or-1 should now have only status-2 - const or1 = result.children.find(c => c.id === 'or-1') as OrOp; + const or1 = result.children.find(c => c.id === 'or-1') as UptimeOrOp; expect(or1.children.map(c => c.id)).toEqual(['status-2']); // or-2 should now contain status-1 - const or2 = result.children.find(c => c.id === 'or-2') as OrOp; + const or2 = result.children.find(c => c.id === 'or-2') as UptimeOrOp; expect(or2.children.map(c => c.id)).toEqual(['status-1']); }); }); describe('isAfterOp', () => { it('returns true when op is directly after another op', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -739,26 +739,26 @@ describe('isAfterOp', () => { }); it('returns false when op is not directly after another op', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, { id: 'status-3', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 202, }, ], @@ -769,20 +769,20 @@ describe('isAfterOp', () => { }); it('returns false when op is before another op', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -792,24 +792,24 @@ describe('isAfterOp', () => { }); it('returns true when op is after another op in nested group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -821,27 +821,27 @@ describe('isAfterOp', () => { }); it('returns true when op is after another op inside not group', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'not-1', - op: OpType.NOT, + op: UptimeOpType.NOT, operand: { id: 'or-1', - op: OpType.OR, + op: UptimeOpType.OR, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, { id: 'status-2', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 201, }, ], @@ -854,14 +854,14 @@ describe('isAfterOp', () => { }); it('returns false for non-existent ops', () => { - const rootOp: AndOp = { + const rootOp: UptimeAndOp = { id: 'and-1', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -872,10 +872,10 @@ describe('isAfterOp', () => { }); it('returns false for leaf ops (non-group containers)', () => { - const statusOp: StatusCodeOp = { + const statusOp: UptimeStatusCodeOp = { id: 'status-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }; diff --git a/static/app/views/alerts/rules/uptime/assertions/utils.tsx b/static/app/views/alerts/rules/uptime/assertions/utils.tsx index f2fd5ce71499ac..95ce973c040c00 100644 --- a/static/app/views/alerts/rules/uptime/assertions/utils.tsx +++ b/static/app/views/alerts/rules/uptime/assertions/utils.tsx @@ -1,13 +1,13 @@ import {t} from 'sentry/locale'; import { - ComparisonType, - OpType, - type GroupOp, - type HeaderCheckOp, - type HeaderOperand, - type JsonPathOp, - type JsonPathOperand, - type Op, + UptimeComparisonType, + UptimeOpType, + type UptimeGroupOp, + type UptimeHeaderCheckOp, + type UptimeHeaderOperand, + type UptimeJsonPathOp, + type UptimeJsonPathOperand, + type UptimeOp, } from 'sentry/views/alerts/rules/uptime/types'; import {COMPARISON_OPTIONS, STRING_OPERAND_OPTIONS} from './opCommon'; @@ -20,9 +20,13 @@ import {COMPARISON_OPTIONS, STRING_OPERAND_OPTIONS} from './opCommon'; * @param afterOpId - The ID of the op that should come before * @returns true if opId is directly after afterOpId in the container's children, false otherwise */ -export function isAfterOp(containerOp: Op, opId: string, afterOpId: string): boolean { +export function isAfterOp( + containerOp: UptimeOp, + opId: string, + afterOpId: string +): boolean { // Only group ops have children where one can be after another - if (containerOp.op === OpType.AND || containerOp.op === OpType.OR) { + if (containerOp.op === UptimeOpType.AND || containerOp.op === UptimeOpType.OR) { for (let i = 0; i < containerOp.children.length - 1; i++) { if (containerOp.children[i]?.id === afterOpId) { return containerOp.children[i + 1]?.id === opId; @@ -31,12 +35,12 @@ export function isAfterOp(containerOp: Op, opId: string, afterOpId: string): boo } // Also check recursively in nested groups - if (containerOp.op === OpType.AND || containerOp.op === OpType.OR) { + if (containerOp.op === UptimeOpType.AND || containerOp.op === UptimeOpType.OR) { return containerOp.children.some(child => isAfterOp(child, opId, afterOpId)); } // Check in not operand - if (containerOp.op === OpType.NOT) { + if (containerOp.op === UptimeOpType.NOT) { return isAfterOp(containerOp.operand, opId, afterOpId); } @@ -48,19 +52,22 @@ export function isAfterOp(containerOp: Op, opId: string, afterOpId: string): boo * * @param op - The op tree to search in * @param id - The ID of the op to find - * @returns An object with the found op and its parent GroupOp, or null if not found + * @returns An object with the found op and its parent UptimeGroupOp, or null if not found */ -function findOpById(op: Op, id: string): {op: Op; parent: GroupOp | null} | null { +function findOpById( + op: UptimeOp, + id: string +): {op: UptimeOp; parent: UptimeGroupOp | null} | null { // Helper function for recursive search const search = ( - currentOp: Op, - parentOp: GroupOp | null - ): {op: Op; parent: GroupOp | null} | null => { + currentOp: UptimeOp, + parentOp: UptimeGroupOp | null + ): {op: UptimeOp; parent: UptimeGroupOp | null} | null => { if (currentOp.id === id) { return {op: currentOp, parent: parentOp}; } - if (currentOp.op === OpType.AND || currentOp.op === OpType.OR) { + if (currentOp.op === UptimeOpType.AND || currentOp.op === UptimeOpType.OR) { for (const child of currentOp.children) { const found = search(child, currentOp); if (found) { @@ -69,9 +76,12 @@ function findOpById(op: Op, id: string): {op: Op; parent: GroupOp | null} | null } } - if (currentOp.op === OpType.NOT) { + if (currentOp.op === UptimeOpType.NOT) { // For 'not' ops, check if the operand is a group op to use as parent - if (currentOp.operand.op === OpType.AND || currentOp.operand.op === OpType.OR) { + if ( + currentOp.operand.op === UptimeOpType.AND || + currentOp.operand.op === UptimeOpType.OR + ) { return search(currentOp.operand, currentOp.operand); } return search(currentOp.operand, parentOp); @@ -91,18 +101,18 @@ function findOpById(op: Op, id: string): {op: Op; parent: GroupOp | null} | null * @param descendantId - The ID of the potential descendant * @returns true if ancestorId is an ancestor of descendantId */ -function isAncestorOf(op: Op, ancestorId: string, descendantId: string): boolean { +function isAncestorOf(op: UptimeOp, ancestorId: string, descendantId: string): boolean { // Find the potential ancestor node if (op.id === ancestorId) { // Check if descendantId exists within this subtree - const hasDescendant = (node: Op): boolean => { + const hasDescendant = (node: UptimeOp): boolean => { if (node.id === descendantId) { return true; } - if (node.op === OpType.AND || node.op === OpType.OR) { + if (node.op === UptimeOpType.AND || node.op === UptimeOpType.OR) { return node.children.some(child => hasDescendant(child)); } - if (node.op === OpType.NOT) { + if (node.op === UptimeOpType.NOT) { return hasDescendant(node.operand); } return false; @@ -111,10 +121,10 @@ function isAncestorOf(op: Op, ancestorId: string, descendantId: string): boolean } // Continue searching for the ancestor in the tree - if (op.op === OpType.AND || op.op === OpType.OR) { + if (op.op === UptimeOpType.AND || op.op === UptimeOpType.OR) { return op.children.some(child => isAncestorOf(child, ancestorId, descendantId)); } - if (op.op === OpType.NOT) { + if (op.op === UptimeOpType.NOT) { return isAncestorOf(op.operand, ancestorId, descendantId); } @@ -131,11 +141,11 @@ function isAncestorOf(op: Op, ancestorId: string, descendantId: string): boolean * @returns A new root logical op tree with the source moved to the new position */ export function moveTo( - rootOp: GroupOp, + rootOp: UptimeGroupOp, sourceId: string, targetId: string, position: 'before' | 'after' | 'inside' -): GroupOp { +): UptimeGroupOp { // First, find and extract the source op const sourceResult = findOpById(rootOp, sourceId); if (!sourceResult) { @@ -158,7 +168,7 @@ export function moveTo( // For 'inside' position, target must be a group op and doesn't need a parent if (position === 'inside') { const {op: targetOp} = targetResult; - if (targetOp.op !== OpType.AND && targetOp.op !== OpType.OR) { + if (targetOp.op !== UptimeOpType.AND && targetOp.op !== UptimeOpType.OR) { return rootOp; // Can only move inside group ops } } else { @@ -169,22 +179,22 @@ export function moveTo( } // Remove the source op from its current location - const removeOp = (op: Op): Op => { - if (op.op === OpType.AND || op.op === OpType.OR) { + const removeOp = (op: UptimeOp): UptimeOp => { + if (op.op === UptimeOpType.AND || op.op === UptimeOpType.OR) { const newChildren = op.children .filter(child => child.id !== sourceId) .map(child => removeOp(child)); return {...op, children: newChildren}; } - if (op.op === OpType.NOT) { + if (op.op === UptimeOpType.NOT) { return {...op, operand: removeOp(op.operand)}; } return op; }; // Insert the source op at the target location - const insertOp = (op: Op): Op => { - if (op.op === OpType.AND || op.op === OpType.OR) { + const insertOp = (op: UptimeOp): UptimeOp => { + if (op.op === UptimeOpType.AND || op.op === UptimeOpType.OR) { // Check if we should insert inside this group if (position === 'inside' && op.id === targetId) { // Append to the end of this group's children @@ -204,7 +214,7 @@ export function moveTo( // Target not in this container, recurse into children return {...op, children: op.children.map(child => insertOp(child))}; } - if (op.op === OpType.NOT) { + if (op.op === UptimeOpType.NOT) { return {...op, operand: insertOp(op.operand)}; } return op; @@ -212,10 +222,10 @@ export function moveTo( // First remove, then insert const withoutSource = removeOp(rootOp); - return insertOp(withoutSource) as GroupOp; + return insertOp(withoutSource) as UptimeGroupOp; } -export function getHeaderOperandValue(operand: HeaderOperand): string { +export function getHeaderOperandValue(operand: UptimeHeaderOperand): string { return operand.header_op === 'literal' ? operand.value : operand.header_op === 'glob' @@ -223,11 +233,13 @@ export function getHeaderOperandValue(operand: HeaderOperand): string { : ''; } -export function shouldShowHeaderValueInput(op: HeaderCheckOp): boolean { - return [ComparisonType.EQUALS, ComparisonType.NOT_EQUAL].includes(op.key_op.cmp); +export function shouldShowHeaderValueInput(op: UptimeHeaderCheckOp): boolean { + return [UptimeComparisonType.EQUALS, UptimeComparisonType.NOT_EQUAL].includes( + op.key_op.cmp + ); } -export function getHeaderKeyCombinedLabelAndTooltip(op: HeaderCheckOp): { +export function getHeaderKeyCombinedLabelAndTooltip(op: UptimeHeaderCheckOp): { combinedLabel: string; combinedTooltip: string; } { @@ -259,7 +271,7 @@ export function getHeaderKeyCombinedLabelAndTooltip(op: HeaderCheckOp): { return {combinedLabel, combinedTooltip}; } -export function getHeaderValueCombinedLabelAndTooltip(op: HeaderCheckOp): { +export function getHeaderValueCombinedLabelAndTooltip(op: UptimeHeaderCheckOp): { combinedLabel: string; combinedTooltip: string; } { @@ -292,7 +304,7 @@ export function getHeaderValueCombinedLabelAndTooltip(op: HeaderCheckOp): { return {combinedLabel, combinedTooltip}; } -export function getJsonPathOperandValue(operand: JsonPathOperand): string { +export function getJsonPathOperandValue(operand: UptimeJsonPathOperand): string { return operand.jsonpath_op === 'literal' ? operand.value : operand.jsonpath_op === 'glob' @@ -304,18 +316,18 @@ export function getJsonPathOperandValue(operand: JsonPathOperand): string { // It just ensures that we don't break the UI when we receive legacy ops from the backend. // TODO Abdullah Khan: This is added during LA, only our own montors are affected. Can // remove once we have EA'd assertions. -export function normalizeJsonPathOp(op: JsonPathOp): JsonPathOp { +export function normalizeJsonPathOp(op: UptimeJsonPathOp): UptimeJsonPathOp { const hasOperator = 'operator' in op && op.operator; const hasOperand = 'operand' in op && op.operand; return { ...op, - operator: hasOperator ? op.operator : {cmp: ComparisonType.EQUALS}, + operator: hasOperator ? op.operator : {cmp: UptimeComparisonType.EQUALS}, operand: hasOperand ? op.operand : {jsonpath_op: 'literal', value: ''}, }; } -export function getJsonPathCombinedLabelAndTooltip(op: JsonPathOp): { +export function getJsonPathCombinedLabelAndTooltip(op: UptimeJsonPathOp): { combinedLabel: string; combinedTooltip: string; } { @@ -335,8 +347,8 @@ export function getJsonPathCombinedLabelAndTooltip(op: JsonPathOp): { : (STRING_OPERAND_OPTIONS.find(opt => opt.value === operandType)?.symbol ?? ''); const isNumericComparison = - op.operator.cmp === ComparisonType.LESS_THAN || - op.operator.cmp === ComparisonType.GREATER_THAN; + op.operator.cmp === UptimeComparisonType.LESS_THAN || + op.operator.cmp === UptimeComparisonType.GREATER_THAN; const combinedLabel = isNumericComparison ? comparisonSymbol @@ -354,8 +366,8 @@ export function getJsonPathCombinedLabelAndTooltip(op: JsonPathOp): { return {combinedLabel, combinedTooltip}; } -export function getGroupOpLabel(op: GroupOp, isNegated: boolean): string { - if (op.op === OpType.AND) { +export function getGroupOpLabel(op: UptimeGroupOp, isNegated: boolean): string { + if (op.op === UptimeOpType.AND) { // By De Morgan's Laws, NOT (A AND B) is equivalent to (NOT A OR NOT B), // i.e. passing when at least one child fails. return isNegated ? t('Assert Not All') : t('Assert All'); diff --git a/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.spec.tsx b/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.spec.tsx index 0574e5825e1100..89e57f74956298 100644 --- a/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.spec.tsx +++ b/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.spec.tsx @@ -5,10 +5,10 @@ import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrar import * as indicators from 'sentry/actionCreators/indicator'; import {TestUptimeMonitorButton} from 'sentry/views/alerts/rules/uptime/testUptimeMonitorButton'; import { - ComparisonType, - OpType, PreviewCheckStatus, - type Assertion, + UptimeComparisonType, + UptimeOpType, + type UptimeAssertion, } from 'sentry/views/alerts/rules/uptime/types'; describe('TestUptimeMonitorButton', () => { @@ -378,15 +378,15 @@ describe('TestUptimeMonitorButton', () => { }); it('sends assertion data to the preview endpoint', async () => { - const mockAssertion: Assertion = { + const mockAssertion: UptimeAssertion = { root: { id: 'root', - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'status-check', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], diff --git a/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.tsx b/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.tsx index f8b9f63dccb298..ce0ea63fcfc27d 100644 --- a/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.tsx +++ b/static/app/views/alerts/rules/uptime/testUptimeMonitorButton.tsx @@ -9,9 +9,9 @@ import type RequestError from 'sentry/utils/requestError/requestError'; import useOrganization from 'sentry/utils/useOrganization'; import { PreviewCheckStatus, - type Assertion, type PreviewCheckPayload, type PreviewCheckResponse, + type UptimeAssertion, } from 'sentry/views/alerts/rules/uptime/types'; interface TestUptimeMonitorButtonProps { @@ -20,7 +20,7 @@ interface TestUptimeMonitorButtonProps { * The caller is responsible for providing fallback values appropriate to their context. */ getFormData: () => { - assertion: Assertion | null; + assertion: UptimeAssertion | null; body: string | null; headers: Array<[string, string]>; method: string; diff --git a/static/app/views/alerts/rules/uptime/types.tsx b/static/app/views/alerts/rules/uptime/types.tsx index 243e7059236669..6f91e0eeae1a83 100644 --- a/static/app/views/alerts/rules/uptime/types.tsx +++ b/static/app/views/alerts/rules/uptime/types.tsx @@ -12,7 +12,7 @@ export enum UptimeMonitorMode { } export interface UptimeRule { - assertion: Assertion | null; + assertion: UptimeAssertion | null; body: string | null; downtimeThreshold: number; environment: string | null; @@ -33,7 +33,7 @@ export interface UptimeRule { } export interface UptimeCheck { - assertionFailureData: Assertion | null; + assertionFailureData: UptimeAssertion | null; checkStatus: CheckStatus; checkStatusReason: CheckStatusReason | null; durationMs: number; @@ -88,13 +88,13 @@ export type CheckStatusBucket = [timestamp: number, stats: StatsBucket]; // Uptime Assertion Types (matching Rust types from uptime-checker) -export interface Assertion { +export interface UptimeAssertion { // XXX(epurkhiser): The uptime-checker would actually allow this to be any - // Op, but we're restricting it on the frontend to always be a AndOp. - root: AndOp; + // Op, but we're restricting it on the frontend to always be a UptimeAndOp. + root: UptimeAndOp; } -export enum OpType { +export enum UptimeOpType { AND = 'and', OR = 'or', NOT = 'not', @@ -103,7 +103,7 @@ export enum OpType { HEADER_CHECK = 'header_check', } -export enum ComparisonType { +export enum UptimeComparisonType { EQUALS = 'equals', NOT_EQUAL = 'not_equal', LESS_THAN = 'less_than', @@ -112,64 +112,68 @@ export enum ComparisonType { NEVER = 'never', } -export type Comparison = {cmp: ComparisonType}; +export type UptimeComparison = {cmp: UptimeComparisonType}; -export type HeaderOperand = +export type UptimeHeaderOperand = | {header_op: 'none'} | {header_op: 'literal'; value: string} | {header_op: 'glob'; pattern: {value: string}}; -export type JsonPathOperand = +export type UptimeJsonPathOperand = | {jsonpath_op: 'none'} | {jsonpath_op: 'literal'; value: string} | {jsonpath_op: 'glob'; pattern: {value: string}}; -export interface AndOp { - children: Op[]; +export interface UptimeAndOp { + children: UptimeOp[]; id: string; - op: OpType.AND; + op: UptimeOpType.AND; } -export interface OrOp { - children: Op[]; +export interface UptimeOrOp { + children: UptimeOp[]; id: string; - op: OpType.OR; + op: UptimeOpType.OR; } -export interface NotOp { +export interface UptimeNotOp { id: string; - op: OpType.NOT; - operand: Op; + op: UptimeOpType.NOT; + operand: UptimeOp; } -export interface StatusCodeOp { +export interface UptimeStatusCodeOp { id: string; - op: OpType.STATUS_CODE_CHECK; - operator: Comparison; + op: UptimeOpType.STATUS_CODE_CHECK; + operator: UptimeComparison; value: number; } -export interface JsonPathOp { +export interface UptimeJsonPathOp { id: string; - op: OpType.JSON_PATH; - operand: JsonPathOperand; - operator: Comparison; + op: UptimeOpType.JSON_PATH; + operand: UptimeJsonPathOperand; + operator: UptimeComparison; value: string; } -export interface HeaderCheckOp { +export interface UptimeHeaderCheckOp { id: string; - key_op: Comparison; - key_operand: HeaderOperand; - op: OpType.HEADER_CHECK; - value_op: Comparison; - value_operand: HeaderOperand; + key_op: UptimeComparison; + key_operand: UptimeHeaderOperand; + op: UptimeOpType.HEADER_CHECK; + value_op: UptimeComparison; + value_operand: UptimeHeaderOperand; } -export type GroupOp = AndOp | OrOp; -export type LogicalOp = GroupOp | NotOp; +export type UptimeGroupOp = UptimeAndOp | UptimeOrOp; +export type UptimeLogicalOp = UptimeGroupOp | UptimeNotOp; -export type Op = LogicalOp | StatusCodeOp | JsonPathOp | HeaderCheckOp; +export type UptimeOp = + | UptimeLogicalOp + | UptimeStatusCodeOp + | UptimeJsonPathOp + | UptimeHeaderCheckOp; // Preview Check Types (raw response from uptime-checker /execute_config endpoint) @@ -225,7 +229,7 @@ export interface PreviewCheckResponse { export interface PreviewCheckPayload { timeoutMs: number; url: string; - assertion?: Assertion | null; + assertion?: UptimeAssertion | null; body?: string | null; headers?: Array<[string, string]>; method?: string; @@ -233,16 +237,16 @@ export interface PreviewCheckPayload { // Assertion Suggestions Types (from Seer-powered endpoint) -export enum AssertionType { +export enum UptimeAssertionType { STATUS_CODE = 'status_code', JSON_PATH = 'json_path', HEADER = 'header', } -export interface AssertionSuggestion { - assertion_json: Op; - assertion_type: AssertionType; - comparison: Exclude; +export interface UptimeAssertionSuggestion { + assertion_json: UptimeOp; + assertion_type: UptimeAssertionType; + comparison: Exclude; confidence: number; expected_value: string; explanation: string; @@ -250,8 +254,8 @@ export interface AssertionSuggestion { json_path: string | null; } -export interface AssertionSuggestionsResponse { +export interface UptimeAssertionSuggestionsResponse { preview_result: PreviewCheckResponse; - suggested_assertion: Assertion | null; - suggestions: AssertionSuggestion[] | null; + suggested_assertion: UptimeAssertion | null; + suggestions: UptimeAssertionSuggestion[] | null; } diff --git a/static/app/views/alerts/rules/uptime/uptimeAlertForm.spec.tsx b/static/app/views/alerts/rules/uptime/uptimeAlertForm.spec.tsx index 41fa8930f1fcff..b056cd3bc55c4e 100644 --- a/static/app/views/alerts/rules/uptime/uptimeAlertForm.spec.tsx +++ b/static/app/views/alerts/rules/uptime/uptimeAlertForm.spec.tsx @@ -17,9 +17,9 @@ import selectEvent from 'sentry-test/selectEvent'; import OrganizationStore from 'sentry/stores/organizationStore'; import ProjectsStore from 'sentry/stores/projectsStore'; import { - ComparisonType, - OpType, - type Assertion, + UptimeComparisonType, + UptimeOpType, + type UptimeAssertion, } from 'sentry/views/alerts/rules/uptime/types'; import {UptimeAlertForm} from 'sentry/views/alerts/rules/uptime/uptimeAlertForm'; @@ -466,19 +466,19 @@ describe('Uptime Alert Form', () => { url: 'http://example.com', assertion: { root: { - op: OpType.AND, + op: UptimeOpType.AND, id: expect.any(String), children: [ { - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, id: expect.any(String), - operator: {cmp: ComparisonType.GREATER_THAN}, + operator: {cmp: UptimeComparisonType.GREATER_THAN}, value: 199, }, { - op: OpType.STATUS_CODE_CHECK, + op: UptimeOpType.STATUS_CODE_CHECK, id: expect.any(String), - operator: {cmp: ComparisonType.LESS_THAN}, + operator: {cmp: UptimeComparisonType.LESS_THAN}, value: 300, }, ], @@ -508,14 +508,14 @@ describe('Uptime Alert Form', () => { }); OrganizationStore.onUpdate(orgWithAssertions); - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: { - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -723,14 +723,14 @@ describe('Uptime Alert Form', () => { }); OrganizationStore.onUpdate(orgWithoutAssertions); - const existingAssertion: Assertion = { + const existingAssertion: UptimeAssertion = { root: { - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], diff --git a/static/app/views/alerts/rules/uptime/uptimeAlertForm.tsx b/static/app/views/alerts/rules/uptime/uptimeAlertForm.tsx index b3cb7e15148700..2ecb031077275e 100644 --- a/static/app/views/alerts/rules/uptime/uptimeAlertForm.tsx +++ b/static/app/views/alerts/rules/uptime/uptimeAlertForm.tsx @@ -37,7 +37,7 @@ import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import useProjects from 'sentry/utils/useProjects'; import {makeAlertsPathname} from 'sentry/views/alerts/pathnames'; -import type {Assertion, UptimeRule} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAssertion, UptimeRule} from 'sentry/views/alerts/rules/uptime/types'; import {createEmptyAssertionRoot, UptimeAssertionsField} from './assertions/field'; import {mapAssertionFormErrors} from './assertionFormErrors'; @@ -217,7 +217,7 @@ export function UptimeAlertForm({handleDelete, rule}: Props) { // When runtime assertions are disabled, the assertions field is not mounted, // so its `getValue` transform won't run. Normalize empty/sentinel assertions to null. if (!hasRuntimeAssertions) { - const assertion = formModel.getValue('assertion'); + const assertion = formModel.getValue('assertion'); if (!assertion?.root || assertion.root.children?.length === 0) { formModel.setValue('assertion', null); } @@ -252,7 +252,7 @@ export function UptimeAlertForm({handleDelete, rule}: Props) { }; }} getCurrentAssertion={() => - formModel.getValue('assertion') + formModel.getValue('assertion') } onApplySuggestion={newAssertion => { // Cast to any to satisfy FormModel's FieldValue type diff --git a/static/app/views/detectors/components/forms/uptime/connectedAssertionSuggestionsButton.tsx b/static/app/views/detectors/components/forms/uptime/connectedAssertionSuggestionsButton.tsx index d7d1e973432cd8..a9f9c3b669ef6f 100644 --- a/static/app/views/detectors/components/forms/uptime/connectedAssertionSuggestionsButton.tsx +++ b/static/app/views/detectors/components/forms/uptime/connectedAssertionSuggestionsButton.tsx @@ -5,7 +5,7 @@ import type {ButtonProps} from '@sentry/scraps/button'; import FormContext from 'sentry/components/forms/formContext'; import {defined} from 'sentry/utils'; import {AssertionSuggestionsButton} from 'sentry/views/alerts/rules/uptime/assertionSuggestionsButton'; -import type {Assertion} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAssertion} from 'sentry/views/alerts/rules/uptime/types'; import {DEFAULT_UPTIME_DETECTOR_FORM_DATA_MAP} from 'sentry/views/detectors/components/forms/uptime/fields'; const HTTP_METHODS_NO_BODY = ['GET', 'HEAD', 'OPTIONS']; @@ -36,10 +36,10 @@ export function ConnectedAssertionSuggestionsButton({ }; }; - const getCurrentAssertion = (): Assertion | null => - (formRef.current?.getTransformedData()?.assertion as Assertion) ?? null; + const getCurrentAssertion = (): UptimeAssertion | null => + (formRef.current?.getTransformedData()?.assertion as UptimeAssertion) ?? null; - const handleApplySuggestion = (newAssertion: Assertion) => { + const handleApplySuggestion = (newAssertion: UptimeAssertion) => { formRef.current?.setValue('assertion', newAssertion as any); }; diff --git a/static/app/views/detectors/components/forms/uptime/fields.spec.tsx b/static/app/views/detectors/components/forms/uptime/fields.spec.tsx index 581e9bda109abf..008ed6718a3b03 100644 --- a/static/app/views/detectors/components/forms/uptime/fields.spec.tsx +++ b/static/app/views/detectors/components/forms/uptime/fields.spec.tsx @@ -1,10 +1,10 @@ import {UptimeDetectorFixture} from 'sentry-fixture/detectors'; import { - ComparisonType, - OpType, + UptimeComparisonType, UptimeMonitorMode, - type Assertion, + UptimeOpType, + type UptimeAssertion, } from 'sentry/views/alerts/rules/uptime/types'; import { UPTIME_DEFAULT_DOWNTIME_THRESHOLD, @@ -65,14 +65,14 @@ describe('uptimeFormDataToEndpointPayload', () => { }); it('includes assertion in payload when provided', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: { - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], @@ -199,11 +199,11 @@ describe('uptimeFormDataToEndpointPayload', () => { body: '', assertion: { root: { - op: OpType.AND, + op: UptimeOpType.AND, id: 'empty-root', children: [], }, - } satisfies Assertion, + } satisfies UptimeAssertion, recoveryThreshold: 1, downtimeThreshold: 3, environment: 'production', @@ -242,14 +242,14 @@ describe('uptimeSavedDetectorToFormData', () => { }); it('extracts assertion from data source', () => { - const assertion: Assertion = { + const assertion: UptimeAssertion = { root: { - op: OpType.AND, + op: UptimeOpType.AND, children: [ { id: 'test-1', - op: OpType.STATUS_CODE_CHECK, - operator: {cmp: ComparisonType.EQUALS}, + op: UptimeOpType.STATUS_CODE_CHECK, + operator: {cmp: UptimeComparisonType.EQUALS}, value: 200, }, ], diff --git a/static/app/views/detectors/components/forms/uptime/fields.tsx b/static/app/views/detectors/components/forms/uptime/fields.tsx index 4190c194bb31a9..96fbe1cb4d0cd5 100644 --- a/static/app/views/detectors/components/forms/uptime/fields.tsx +++ b/static/app/views/detectors/components/forms/uptime/fields.tsx @@ -5,7 +5,7 @@ import type { } from 'sentry/types/workflowEngine/detectors'; import {defined} from 'sentry/utils'; import {createEmptyAssertionRoot} from 'sentry/views/alerts/rules/uptime/assertions/field'; -import type {Assertion} from 'sentry/views/alerts/rules/uptime/types'; +import type {UptimeAssertion} from 'sentry/views/alerts/rules/uptime/types'; import {UptimeMonitorMode} from 'sentry/views/alerts/rules/uptime/types'; import {getDetectorEnvironment} from 'sentry/views/detectors/utils/getDetectorEnvironment'; @@ -13,7 +13,7 @@ export const UPTIME_DEFAULT_RECOVERY_THRESHOLD = 1; export const UPTIME_DEFAULT_DOWNTIME_THRESHOLD = 3; interface UptimeDetectorFormData { - assertion: Assertion | null; + assertion: UptimeAssertion | null; body: string; description: string | null; downtimeThreshold: number;