Skip to content

Commit 41bd963

Browse files
committed
Merge remote-tracking branch 'origin/staging' into fix/tools-gh2
# Conflicts: # apps/sim/tools/generated/tool-metadata.ts
2 parents 32b606c + f3fb445 commit 41bd963

47 files changed

Lines changed: 1809 additions & 285 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/integrations/incidentio.mdx

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ List incidents from incident.io. Returns a list of incidents with their details
6262
|`id` | string | Incident ID |
6363
|`name` | string | Incident name/title |
6464
|`summary` | string | Incident summary |
65-
|`description` | string | Incident description |
6665
|`mode` | string | Incident mode \(standard, retrospective, test\) |
6766
|`call_url` | string | Video call URL |
6867
|`severity` | object | Incident severity |
@@ -82,7 +81,7 @@ List incidents from incident.io. Returns a list of incidents with their details
8281
|`is_default` | boolean | Whether this is the default incident type |
8382
|`created_at` | string | When the incident was created \(ISO 8601\) |
8483
|`updated_at` | string | When the incident was last updated \(ISO 8601\) |
85-
|`incident_url` | string | URL to the incident page |
84+
|`permalink` | string | Permalink to the incident in incident.io |
8685
|`slack_channel_id` | string | Slack channel ID |
8786
|`slack_channel_name` | string | Slack channel name |
8887
|`visibility` | string | Incident visibility \(public, private\) |
@@ -116,7 +115,6 @@ Create a new incident in incident.io. Requires idempotency_key, severity_id, and
116115
|`id` | string | Incident ID |
117116
|`name` | string | Incident name |
118117
|`summary` | string | Brief summary of the incident |
119-
|`description` | string | Detailed description of the incident |
120118
|`mode` | string | Incident mode \(e.g., standard, retrospective\) |
121119
|`call_url` | string | URL for the incident call/bridge |
122120
|`severity` | object | Severity of the incident |
@@ -132,7 +130,7 @@ Create a new incident in incident.io. Requires idempotency_key, severity_id, and
132130
|`name` | string | Type name |
133131
|`created_at` | string | Creation timestamp |
134132
|`updated_at` | string | Last update timestamp |
135-
|`incident_url` | string | URL to the incident |
133+
|`permalink` | string | Permalink to the incident in incident.io |
136134
|`slack_channel_id` | string | Associated Slack channel ID |
137135
|`slack_channel_name` | string | Associated Slack channel name |
138136
|`visibility` | string | Incident visibility |
@@ -156,10 +154,9 @@ Retrieve detailed information about a specific incident from incident.io by its
156154
|`id` | string | Incident ID |
157155
|`name` | string | Incident name |
158156
|`summary` | string | Brief summary of the incident |
159-
|`description` | string | Detailed description of the incident |
160157
|`mode` | string | Incident mode \(e.g., standard, retrospective\) |
161158
|`call_url` | string | URL for the incident call/bridge |
162-
|`permalink` | string | Permanent link to the incident |
159+
|`permalink` | string | Permalink to the incident in incident.io |
163160
|`severity` | object | Severity of the incident |
164161
|`id` | string | Severity ID |
165162
|`name` | string | Severity name |
@@ -173,7 +170,6 @@ Retrieve detailed information about a specific incident from incident.io by its
173170
|`name` | string | Type name |
174171
|`created_at` | string | Creation timestamp |
175172
|`updated_at` | string | Last update timestamp |
176-
|`incident_url` | string | URL to the incident |
177173
|`slack_channel_id` | string | Associated Slack channel ID |
178174
|`slack_channel_name` | string | Associated Slack channel name |
179175
|`visibility` | string | Incident visibility |
@@ -205,7 +201,6 @@ Update an existing incident in incident.io. Can update name, summary, severity,
205201
|`id` | string | Incident ID |
206202
|`name` | string | Incident name |
207203
|`summary` | string | Brief summary of the incident |
208-
|`description` | string | Detailed description of the incident |
209204
|`mode` | string | Incident mode \(e.g., standard, retrospective\) |
210205
|`call_url` | string | URL for the incident call/bridge |
211206
|`severity` | object | Severity of the incident |
@@ -221,7 +216,7 @@ Update an existing incident in incident.io. Can update name, summary, severity,
221216
|`name` | string | Type name |
222217
|`created_at` | string | Creation timestamp |
223218
|`updated_at` | string | Last update timestamp |
224-
|`incident_url` | string | URL to the incident |
219+
|`permalink` | string | Permalink to the incident in incident.io |
225220
|`slack_channel_id` | string | Associated Slack channel ID |
226221
|`slack_channel_name` | string | Associated Slack channel name |
227222
|`visibility` | string | Incident visibility |
@@ -486,7 +481,7 @@ Create a new workflow in incident.io.
486481
| `trigger` | string | No | Trigger type for the workflow \(e.g., "incident.updated", "incident.created"\) |
487482
| `steps` | string | No | Array of workflow steps as JSON string. Example: \[\{"label": "Notify team", "name": "slack.post_message"\}\] |
488483
| `condition_groups` | string | No | Array of condition groups as JSON string to control when the workflow runs. Example: \[\{"conditions": \[\{"operation": "one_of", "param_bindings": \[\], "subject": "incident.severity"\}\]\}\] |
489-
| `runs_on_incidents` | string | No | When to run the workflow: "newly_created" \(only new incidents\), "newly_created_and_active" \(new and active incidents\), "active" \(only active incidents\), or "all" \(all incidents\) |
484+
| `runs_on_incidents` | string | No | When to run the workflow: "newly_created" \(only newly created incidents\) or "newly_created_and_active" \(newly created and already active incidents\) |
490485
| `runs_on_incident_modes` | string | No | Array of incident modes to run on as JSON string. Example: \["standard", "retrospective"\] |
491486
| `include_private_incidents` | boolean | No | Whether to include private incidents |
492487
| `continue_on_step_error` | boolean | No | Whether to continue executing subsequent steps if a step fails |
@@ -569,7 +564,7 @@ Update an existing workflow in incident.io.
569564
| `name` | string | Yes | New name for the workflow \(e.g., "Notify on Critical Incidents"\) |
570565
| `steps` | string | Yes | Complete array of workflow steps as a JSON string |
571566
| `condition_groups` | string | Yes | Complete array of workflow condition groups as a JSON string |
572-
| `runs_on_incidents` | string | Yes | When to run the workflow: newly_created, newly_created_and_active, active, or all |
567+
| `runs_on_incidents` | string | Yes | When to run the workflow: newly_created or newly_created_and_active |
573568
| `runs_on_incident_modes` | string | Yes | Complete array of incident modes to run on as a JSON string |
574569
| `include_private_incidents` | boolean | Yes | Whether to include private incidents |
575570
| `continue_on_step_error` | boolean | Yes | Whether to continue executing subsequent steps if a step fails |
@@ -783,11 +778,15 @@ List all escalation policies in incident.io
783778

784779
| Parameter | Type | Description |
785780
| --------- | ---- | ----------- |
786-
| `escalations` | array | List of escalation policies |
787-
|`id` | string | The escalation policy ID |
788-
|`name` | string | The escalation policy name |
789-
|`created_at` | string | When the escalation policy was created |
790-
|`updated_at` | string | When the escalation policy was last updated |
781+
| `escalations` | array | List of escalations |
782+
|`id` | string | The escalation ID |
783+
|`title` | string | The escalation title |
784+
|`status` | string | The current escalation status |
785+
|`description` | string | Additional detail provided with this escalation |
786+
|`priority` | object | The escalation priority |
787+
|`name` | string | Priority name |
788+
|`created_at` | string | When the escalation was created |
789+
|`updated_at` | string | When the escalation was last updated |
791790
| `pagination_meta` | object | Pagination metadata |
792791
|`after` | string | Cursor for next page |
793792
|`page_size` | number | Number of results per page |
@@ -810,11 +809,15 @@ Create a new escalation policy in incident.io
810809

811810
| Parameter | Type | Description |
812811
| --------- | ---- | ----------- |
813-
| `escalation` | object | The created escalation policy |
814-
|`id` | string | The escalation policy ID |
815-
|`name` | string | The escalation policy name |
816-
|`created_at` | string | When the escalation policy was created |
817-
|`updated_at` | string | When the escalation policy was last updated |
812+
| `escalation` | object | The created escalation |
813+
|`id` | string | The escalation ID |
814+
|`title` | string | The escalation title |
815+
|`status` | string | The current escalation status |
816+
|`description` | string | Additional detail provided with this escalation |
817+
|`priority` | object | The escalation priority |
818+
|`name` | string | Priority name |
819+
|`created_at` | string | When the escalation was created |
820+
|`updated_at` | string | When the escalation was last updated |
818821

819822
### Show Escalation
820823

@@ -831,11 +834,15 @@ Get details of a specific escalation policy in incident.io
831834

832835
| Parameter | Type | Description |
833836
| --------- | ---- | ----------- |
834-
| `escalation` | object | The escalation policy details |
835-
|`id` | string | The escalation policy ID |
836-
|`name` | string | The escalation policy name |
837-
|`created_at` | string | When the escalation policy was created |
838-
|`updated_at` | string | When the escalation policy was last updated |
837+
| `escalation` | object | The escalation details |
838+
|`id` | string | The escalation ID |
839+
|`title` | string | The escalation title |
840+
|`status` | string | The current escalation status |
841+
|`description` | string | Additional detail provided with this escalation |
842+
|`priority` | object | The escalation priority |
843+
|`name` | string | Priority name |
844+
|`created_at` | string | When the escalation was created |
845+
|`updated_at` | string | When the escalation was last updated |
839846

840847
### incident.io Custom Fields List
841848

@@ -870,7 +877,7 @@ Create a new custom field in incident.io.
870877
| `apiKey` | string | Yes | incident.io API Key |
871878
| `name` | string | Yes | Name of the custom field \(e.g., "Affected Service"\) |
872879
| `description` | string | Yes | Description of the custom field \(required\) |
873-
| `field_type` | string | Yes | Type of the custom field \(e.g., text, single_select, multi_select, numeric, datetime, link, user, team\) |
880+
| `field_type` | string | Yes | Type of the custom field: text, link, numeric, single_select, or multi_select |
874881

875882
#### Output
876883

@@ -1200,20 +1207,30 @@ List all updates for a specific incident in incident.io
12001207
|`id` | string | The update ID |
12011208
|`incident_id` | string | The incident ID |
12021209
|`message` | string | The update message |
1210+
|`merged_into_incident_id` | string | ID of the incident this incident was merged into |
12031211
|`new_severity` | object | New severity if changed |
12041212
|`id` | string | Severity ID |
12051213
|`name` | string | Severity name |
12061214
|`rank` | number | Severity rank |
1207-
|`new_status` | object | New status if changed |
1215+
|`new_incident_status` | object | The incident status after this update |
12081216
|`id` | string | Status ID |
12091217
|`name` | string | Status name |
12101218
|`category` | string | Status category |
1211-
|`updater` | object | User who created the update |
1212-
|`id` | string | User ID |
1213-
|`name` | string | User name |
1214-
|`email` | string | User email |
1219+
|`updater` | object | Actor who created the update |
1220+
|`user` | object | Set when a user made the update |
1221+
|`id` | string | User ID |
1222+
|`name` | string | User name |
1223+
|`email` | string | User email |
1224+
|`api_key` | object | Set when an API key made the update |
1225+
|`id` | string | API key ID |
1226+
|`name` | string | API key name |
1227+
|`workflow` | object | Set when a workflow made the update |
1228+
|`id` | string | Workflow ID |
1229+
|`name` | string | Workflow name |
1230+
|`alert` | object | Set when an alert made the update |
1231+
|`id` | string | Alert ID |
1232+
|`title` | string | Alert title |
12151233
|`created_at` | string | When the update was created |
1216-
|`updated_at` | string | When the update was last modified |
12171234
| `pagination_meta` | object | Pagination information |
12181235
|`after` | string | Cursor for next page |
12191236
|`page_size` | number | Number of results per page |
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
5+
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
import { OrchestrationError } from '@/lib/core/orchestration/types'
7+
8+
const mocks = vi.hoisted(() => ({
9+
authorize: vi.fn(),
10+
getSession: vi.fn(),
11+
resumePage: vi.fn(() => null),
12+
unavailablePage: vi.fn(() => null),
13+
redirect: vi.fn((url: string) => {
14+
throw new Error(`NEXT_REDIRECT:${url}`)
15+
}),
16+
}))
17+
18+
vi.mock('@/lib/auth', () => ({
19+
auth: { api: { getSession: vi.fn() } },
20+
getSession: mocks.getSession,
21+
}))
22+
23+
vi.mock('next/navigation', () => ({
24+
redirect: mocks.redirect,
25+
}))
26+
27+
vi.mock('@/lib/workflows/application/read-paused-workflow-execution', () => ({
28+
readPausedWorkflowExecution: { authorize: mocks.authorize },
29+
}))
30+
31+
vi.mock('@/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client', () => ({
32+
default: mocks.resumePage,
33+
}))
34+
35+
vi.mock(
36+
'@/app/(interfaces)/resume/[workflowId]/[executionId]/resume-execution-unavailable',
37+
() => ({
38+
ResumeExecutionUnavailable: mocks.unavailablePage,
39+
})
40+
)
41+
42+
import ResumeExecutionPageWrapper from '@/app/(interfaces)/resume/[workflowId]/[executionId]/page'
43+
44+
const PAGE_PARAMS = { workflowId: 'workflow-1', executionId: 'execution-1' }
45+
46+
function pageProps(contextId?: string) {
47+
return {
48+
params: Promise.resolve(PAGE_PARAMS),
49+
searchParams: Promise.resolve(contextId ? { contextId } : {}),
50+
}
51+
}
52+
53+
describe('ResumeExecutionPageWrapper', () => {
54+
beforeEach(() => {
55+
vi.clearAllMocks()
56+
mocks.getSession.mockResolvedValue({
57+
user: { id: 'user-1' },
58+
session: { id: 'session-1' },
59+
})
60+
mocks.authorize.mockResolvedValue(undefined)
61+
})
62+
63+
it('redirects an unauthenticated visitor before any protected lookup', async () => {
64+
mocks.getSession.mockResolvedValueOnce(null)
65+
const callbackPath = '/resume/workflow-1/execution-1?contextId=context-1'
66+
67+
await expect(ResumeExecutionPageWrapper(pageProps('context-1'))).rejects.toThrow(
68+
`NEXT_REDIRECT:/login?callbackUrl=${encodeURIComponent(callbackPath)}`
69+
)
70+
expect(mocks.authorize).not.toHaveBeenCalled()
71+
})
72+
73+
it('authorizes the session without serializing paused execution detail into the page', async () => {
74+
const result = await ResumeExecutionPageWrapper(pageProps('context-1'))
75+
76+
expect(mocks.authorize).toHaveBeenCalledWith({
77+
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
78+
input: PAGE_PARAMS,
79+
})
80+
expect(result.props).toMatchObject({
81+
params: PAGE_PARAMS,
82+
initialContextId: 'context-1',
83+
})
84+
expect(result.type).toBe(mocks.resumePage)
85+
expect(result.key).toBe('workflow-1:execution-1:context-1')
86+
expect(result.props).not.toHaveProperty('initialExecutionDetail')
87+
expect(result.props).not.toHaveProperty('canLoadExecution')
88+
})
89+
90+
it.each([
91+
new OrchestrationError('forbidden', 'Insufficient workspace permissions'),
92+
new OrchestrationError('not_found', 'Workflow not found'),
93+
])('renders a data-free concealed state after authorization refusal: %s', async (error) => {
94+
mocks.authorize.mockRejectedValueOnce(error)
95+
96+
const result = await ResumeExecutionPageWrapper(pageProps())
97+
98+
expect(result.type).toBe(mocks.unavailablePage)
99+
expect(result.type).not.toBe(mocks.resumePage)
100+
expect(result.props).toEqual({})
101+
})
102+
103+
it('propagates authorization infrastructure failures', async () => {
104+
const infrastructureError = new Error('database unavailable')
105+
mocks.authorize.mockRejectedValueOnce(infrastructureError)
106+
107+
await expect(ResumeExecutionPageWrapper(pageProps())).rejects.toBe(infrastructureError)
108+
})
109+
})

apps/sim/app/(interfaces)/resume/[workflowId]/[executionId]/page.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import type { Metadata } from 'next'
2-
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
2+
import { redirect } from 'next/navigation'
3+
import { getSession } from '@/lib/auth'
4+
import { asOrchestrationError } from '@/lib/core/orchestration/types'
5+
import { readPausedWorkflowExecution } from '@/lib/workflows/application/read-paused-workflow-execution'
6+
import { ResumeExecutionUnavailable } from '@/app/(interfaces)/resume/[workflowId]/[executionId]/resume-execution-unavailable'
37
import ResumeExecutionPage from '@/app/(interfaces)/resume/[workflowId]/[executionId]/resume-page-client'
48

59
export const metadata: Metadata = {
@@ -30,16 +34,37 @@ export default async function ResumeExecutionPageWrapper({
3034
const initialContextId = Array.isArray(initialContextIdParam)
3135
? initialContextIdParam[0]
3236
: initialContextIdParam
37+
const resumePath = `/resume/${encodeURIComponent(workflowId)}/${encodeURIComponent(executionId)}${
38+
initialContextId ? `?${new URLSearchParams({ contextId: initialContextId })}` : ''
39+
}`
40+
const session = await getSession()
41+
if (!session?.user?.id) {
42+
redirect(`/login?callbackUrl=${encodeURIComponent(resumePath)}`)
43+
}
44+
if (!session.session?.id) throw new Error('Authenticated session is missing its session ID')
3345

34-
const detail = await PauseResumeManager.getPausedExecutionDetail({
35-
workflowId,
36-
executionId,
37-
})
46+
try {
47+
if (!readPausedWorkflowExecution.authorize) {
48+
throw new Error('Paused execution read use case does not expose authorization')
49+
}
50+
await readPausedWorkflowExecution.authorize({
51+
principal: {
52+
kind: 'session',
53+
userId: session.user.id,
54+
sessionId: session.session.id,
55+
},
56+
input: { workflowId, executionId },
57+
})
58+
} catch (error) {
59+
const classified = asOrchestrationError(error)
60+
if (classified?.code !== 'forbidden' && classified?.code !== 'not_found') throw error
61+
return <ResumeExecutionUnavailable />
62+
}
3863

3964
return (
4065
<ResumeExecutionPage
66+
key={`${workflowId}:${executionId}:${initialContextId ?? ''}`}
4167
params={resolvedParams}
42-
initialExecutionDetail={detail ? structuredClone(detail) : null}
4368
initialContextId={initialContextId}
4469
/>
4570
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ChipLink } from '@sim/emcn'
2+
3+
export function ResumeExecutionUnavailable() {
4+
return (
5+
<div className='flex flex-1 items-center justify-center p-6'>
6+
<div className='max-w-[400px] text-center'>
7+
<h1 className='mb-2 text-[var(--text-primary)] text-xl'>Execution Not Found</h1>
8+
<p className='mb-6 text-[var(--text-secondary)] text-sm'>
9+
This execution could not be located or has already completed.
10+
</p>
11+
<ChipLink variant='border' href='/'>
12+
Return Home
13+
</ChipLink>
14+
</div>
15+
</div>
16+
)
17+
}

0 commit comments

Comments
 (0)