Skip to content

Commit 6c89d4f

Browse files
committed
fix(incidentio): stop the alert filter sentinel reaching the API
The has_notes and include_maintenance_window dropdowns default to the string "any", meaning "do not filter". The params transform skipped the key in that case, but the executor merges its output over the raw inputs (`{ ...inputs, ...transformedParams }`), so the sentinel survived and the tool sent has_notes[is]=any, which incident.io rejects. The transform now always assigns the key, mapping "any" to undefined so it overwrites the sentinel instead of leaving it in place. The tool also only serializes these filters when it actually has a boolean. Adds tests covering the sentinel, both real boolean values, and the documented bracket-operator filter syntax.
1 parent 29ff30b commit 6c89d4f

3 files changed

Lines changed: 98 additions & 8 deletions

File tree

apps/sim/blocks/blocks/incidentio.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ const OVERRIDE_USER_FIELD = ['user_id', 'user_email', 'user_slack_id'] as const
1313
/** An escalation pages either a path or an explicit user list, never both. */
1414
const ESCALATION_TARGET_FIELD = ['escalation_path_id', 'user_ids'] as const
1515

16+
/**
17+
* Maps an optional-filter dropdown onto a real boolean, or `undefined` for the "Any" sentinel.
18+
*
19+
* The executor merges the transform output over the raw inputs, so a key this returns
20+
* `undefined` for still overwrites the sentinel string rather than letting it reach the tool.
21+
* Skipping the assignment instead would leak `has_notes[is]=any` to the API.
22+
*/
23+
function toTriState(value: unknown): boolean | undefined {
24+
if (value === true || value === 'true') return true
25+
if (value === false || value === 'false') return false
26+
return undefined
27+
}
28+
1629
export const IncidentioBlock: BlockConfig<IncidentioResponse> = {
1730
type: 'incidentio',
1831
name: 'incident.io',
@@ -1791,12 +1804,8 @@ Return ONLY the description - no explanations.`,
17911804
}
17921805
if (params.operation === 'incidentio_alerts_list') {
17931806
if (params.alert_status) result.status = params.alert_status
1794-
if (params.has_notes && params.has_notes !== 'any') {
1795-
result.has_notes = params.has_notes === 'true'
1796-
}
1797-
if (params.include_maintenance_window && params.include_maintenance_window !== 'any') {
1798-
result.include_maintenance_window = params.include_maintenance_window === 'true'
1799-
}
1807+
result.has_notes = toTriState(params.has_notes)
1808+
result.include_maintenance_window = toTriState(params.include_maintenance_window)
18001809
}
18011810
if (params.operation === 'incidentio_alert_events_create') {
18021811
if (params.alert_status) result.status = params.alert_status
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { IncidentioBlock } from '@/blocks/blocks/incidentio'
6+
import { alertsListTool } from '@/tools/incidentio/alerts_list'
7+
8+
/**
9+
* The executor merges the block's param transform over the raw inputs
10+
* (`{ ...inputs, ...transformedParams }`), so any subblock the transform leaves alone keeps its
11+
* raw editor value. The tri-state filter dropdowns default to the string 'any', which the
12+
* incident.io API rejects — these cover the whole path from editor value to query string.
13+
*/
14+
function resolveInputs(inputs: Record<string, unknown>): Record<string, unknown> {
15+
const transform = IncidentioBlock.tools.config!.params!
16+
return { ...inputs, ...transform(inputs as never) }
17+
}
18+
19+
function buildUrl(params: Record<string, unknown>): URL {
20+
const url = alertsListTool.request.url
21+
return new URL(typeof url === 'function' ? url(params as never) : url)
22+
}
23+
24+
describe('incidentio_alerts_list filters', () => {
25+
it('omits the tri-state filters when left on "Any"', () => {
26+
const resolved = resolveInputs({
27+
operation: 'incidentio_alerts_list',
28+
apiKey: 'k',
29+
has_notes: 'any',
30+
include_maintenance_window: 'any',
31+
})
32+
33+
expect(resolved.has_notes).toBeUndefined()
34+
expect(resolved.include_maintenance_window).toBeUndefined()
35+
36+
const url = buildUrl(resolved)
37+
expect(url.searchParams.has('has_notes[is]')).toBe(false)
38+
expect(url.searchParams.has('include_maintenance_window[is]')).toBe(false)
39+
})
40+
41+
it('sends the tri-state filters as booleans when set', () => {
42+
const url = buildUrl(
43+
resolveInputs({
44+
operation: 'incidentio_alerts_list',
45+
apiKey: 'k',
46+
has_notes: 'true',
47+
include_maintenance_window: 'false',
48+
})
49+
)
50+
51+
expect(url.searchParams.get('has_notes[is]')).toBe('true')
52+
expect(url.searchParams.get('include_maintenance_window[is]')).toBe('false')
53+
})
54+
55+
it('never forwards a non-boolean filter value to the query string', () => {
56+
const url = buildUrl({ apiKey: 'k', has_notes: 'any', include_maintenance_window: 'any' })
57+
58+
expect(url.searchParams.has('has_notes[is]')).toBe(false)
59+
expect(url.searchParams.has('include_maintenance_window[is]')).toBe(false)
60+
})
61+
62+
it('applies the documented bracket-operator filter syntax', () => {
63+
const url = buildUrl(
64+
resolveInputs({
65+
operation: 'incidentio_alerts_list',
66+
apiKey: 'k',
67+
alert_status: 'firing',
68+
status_operator: 'not_in',
69+
alert_source_id: '01GBSQF3FHF7FWZQNWGHAVQ804',
70+
deduplication_key: 'ABC',
71+
created_at_gte: '2025-01-01',
72+
})
73+
)
74+
75+
expect(url.searchParams.get('status[not_in]')).toBe('firing')
76+
expect(url.searchParams.get('alert_source[one_of]')).toBe('01GBSQF3FHF7FWZQNWGHAVQ804')
77+
expect(url.searchParams.get('deduplication_key[is]')).toBe('ABC')
78+
expect(url.searchParams.get('created_at[gte]')).toBe('2025-01-01')
79+
expect(url.searchParams.get('page_size')).toBe('25')
80+
})
81+
})

apps/sim/tools/incidentio/alerts_list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ export const alertsListTool: ToolConfig<IncidentioAlertsListParams, IncidentioAl
127127
if (params.created_at_lte) {
128128
url.searchParams.set('created_at[lte]', params.created_at_lte.trim())
129129
}
130-
if (params.has_notes !== undefined) {
130+
if (typeof params.has_notes === 'boolean') {
131131
url.searchParams.set('has_notes[is]', String(params.has_notes))
132132
}
133-
if (params.include_maintenance_window !== undefined) {
133+
if (typeof params.include_maintenance_window === 'boolean') {
134134
url.searchParams.set(
135135
'include_maintenance_window[is]',
136136
String(params.include_maintenance_window)

0 commit comments

Comments
 (0)