@commandable/integration-data 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/credentials-index.d.ts.map +1 -1
- package/dist/credentials-index.js +119 -0
- package/dist/credentials-index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +33 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +36 -4
- package/dist/loader.js.map +1 -1
- package/integrations/__tests__/liveHarness.ts +16 -3
- package/integrations/airtable/.env.test +9 -0
- package/integrations/airtable/.env.test.example +11 -0
- package/integrations/airtable/README.md +27 -0
- package/integrations/airtable/__tests__/get_handlers.test.ts +43 -5
- package/integrations/confluence/.env.test +25 -0
- package/integrations/confluence/.env.test.example +36 -0
- package/integrations/confluence/README.md +28 -0
- package/integrations/confluence/__tests__/get_handlers.test.ts +121 -0
- package/integrations/confluence/__tests__/usage_parity.test.ts +14 -0
- package/integrations/confluence/__tests__/write_handlers.test.ts +131 -0
- package/integrations/confluence/credentials.json +39 -0
- package/integrations/confluence/credentials_hint.md +4 -0
- package/integrations/confluence/credentials_hint_api_token.md +9 -0
- package/integrations/confluence/credentials_hint_oauth_token.md +8 -0
- package/integrations/confluence/handlers/add_comment.js +19 -0
- package/integrations/confluence/handlers/add_label.js +16 -0
- package/integrations/confluence/handlers/create_page.js +22 -0
- package/integrations/confluence/handlers/delete_page.js +17 -0
- package/integrations/confluence/handlers/get_comments.js +33 -0
- package/integrations/confluence/handlers/get_page_children.js +30 -0
- package/integrations/confluence/handlers/get_space.js +22 -0
- package/integrations/confluence/handlers/list_spaces.js +39 -0
- package/integrations/confluence/handlers/read_page.js +49 -0
- package/integrations/confluence/handlers/search_pages.js +42 -0
- package/integrations/confluence/handlers/update_page.js +42 -0
- package/integrations/confluence/manifest.json +85 -0
- package/integrations/confluence/prompt.md +55 -0
- package/integrations/confluence/schemas/add_comment.json +22 -0
- package/integrations/confluence/schemas/add_label.json +19 -0
- package/integrations/confluence/schemas/create_page.json +33 -0
- package/integrations/confluence/schemas/delete_page.json +23 -0
- package/integrations/confluence/schemas/empty.json +6 -0
- package/integrations/confluence/schemas/get_comments.json +24 -0
- package/integrations/confluence/schemas/get_page_children.json +28 -0
- package/integrations/confluence/schemas/get_space.json +18 -0
- package/integrations/confluence/schemas/list_spaces.json +36 -0
- package/integrations/confluence/schemas/read_page.json +28 -0
- package/integrations/confluence/schemas/search_pages.json +26 -0
- package/integrations/confluence/schemas/update_page.json +31 -0
- package/integrations/github/.env.test +16 -0
- package/integrations/github/.env.test.example +17 -0
- package/integrations/github/README.md +75 -0
- package/integrations/github/__tests__/get_handlers.test.ts +5 -5
- package/integrations/github/__tests__/write_handlers.test.ts +176 -58
- package/integrations/github/handlers/create_file.js +46 -0
- package/integrations/github/handlers/delete_file.js +14 -1
- package/integrations/github/handlers/edit_file.js +52 -0
- package/integrations/github/handlers/edit_files.js +107 -0
- package/integrations/github/manifest.json +74 -47
- package/integrations/github/prompt.md +36 -0
- package/integrations/github/schemas/create_file.json +13 -0
- package/integrations/github/schemas/delete_file.json +2 -2
- package/integrations/github/schemas/edit_file.json +26 -0
- package/integrations/github/schemas/edit_files.json +39 -0
- package/integrations/google-calendar/.env.test.example +11 -0
- package/integrations/google-calendar/README.md +41 -0
- package/integrations/google-calendar/__tests__/write_and_admin_handlers.test.ts +7 -7
- package/integrations/google-calendar/manifest.json +27 -17
- package/integrations/google-docs/README.md +30 -0
- package/integrations/google-drive/README.md +26 -0
- package/integrations/google-gmail/.env.test.example +11 -0
- package/integrations/google-gmail/README.md +49 -0
- package/integrations/google-gmail/handlers/create_draft_email.js +1 -1
- package/integrations/google-gmail/handlers/read_email.js +1 -1
- package/integrations/google-gmail/handlers/send_email.js +1 -1
- package/integrations/google-gmail/manifest.json +36 -25
- package/integrations/google-sheet/README.md +27 -0
- package/integrations/google-slides/README.md +28 -0
- package/integrations/hubspot/.env.test.example +20 -0
- package/integrations/hubspot/README.md +48 -0
- package/integrations/hubspot/__tests__/get_handlers.test.ts +151 -0
- package/integrations/hubspot/__tests__/usage_parity.test.ts +10 -0
- package/integrations/hubspot/__tests__/write_handlers.test.ts +244 -0
- package/integrations/hubspot/credentials.json +48 -0
- package/integrations/hubspot/credentials_hint.md +20 -0
- package/integrations/hubspot/credentials_hint_oauth_token.md +16 -0
- package/integrations/hubspot/handlers/archive_company.js +13 -0
- package/integrations/hubspot/handlers/archive_contact.js +13 -0
- package/integrations/hubspot/handlers/archive_deal.js +13 -0
- package/integrations/hubspot/handlers/archive_ticket.js +13 -0
- package/integrations/hubspot/handlers/create_association.js +18 -0
- package/integrations/hubspot/handlers/create_company.js +13 -0
- package/integrations/hubspot/handlers/create_contact.js +14 -0
- package/integrations/hubspot/handlers/create_deal.js +16 -0
- package/integrations/hubspot/handlers/create_note.js +44 -0
- package/integrations/hubspot/handlers/create_task.js +48 -0
- package/integrations/hubspot/handlers/create_ticket.js +15 -0
- package/integrations/hubspot/handlers/get_associations.js +14 -0
- package/integrations/hubspot/handlers/get_company.js +18 -0
- package/integrations/hubspot/handlers/get_contact.js +18 -0
- package/integrations/hubspot/handlers/get_deal.js +18 -0
- package/integrations/hubspot/handlers/get_ticket.js +20 -0
- package/integrations/hubspot/handlers/list_owners.js +12 -0
- package/integrations/hubspot/handlers/list_pipelines.js +5 -0
- package/integrations/hubspot/handlers/list_properties.js +11 -0
- package/integrations/hubspot/handlers/remove_association.js +22 -0
- package/integrations/hubspot/handlers/search_companies.js +43 -0
- package/integrations/hubspot/handlers/search_contacts.js +43 -0
- package/integrations/hubspot/handlers/search_deals.js +43 -0
- package/integrations/hubspot/handlers/search_notes.js +43 -0
- package/integrations/hubspot/handlers/search_tasks.js +43 -0
- package/integrations/hubspot/handlers/search_tickets.js +43 -0
- package/integrations/hubspot/handlers/update_company.js +13 -0
- package/integrations/hubspot/handlers/update_contact.js +14 -0
- package/integrations/hubspot/handlers/update_deal.js +16 -0
- package/integrations/hubspot/handlers/update_task.js +17 -0
- package/integrations/hubspot/handlers/update_ticket.js +15 -0
- package/integrations/hubspot/manifest.json +230 -0
- package/integrations/hubspot/prompt.md +69 -0
- package/integrations/hubspot/schemas/archive_company.json +13 -0
- package/integrations/hubspot/schemas/archive_contact.json +13 -0
- package/integrations/hubspot/schemas/archive_deal.json +9 -0
- package/integrations/hubspot/schemas/archive_ticket.json +9 -0
- package/integrations/hubspot/schemas/create_association.json +24 -0
- package/integrations/hubspot/schemas/create_company.json +14 -0
- package/integrations/hubspot/schemas/create_contact.json +15 -0
- package/integrations/hubspot/schemas/create_deal.json +20 -0
- package/integrations/hubspot/schemas/create_note.json +37 -0
- package/integrations/hubspot/schemas/create_task.json +51 -0
- package/integrations/hubspot/schemas/create_ticket.json +16 -0
- package/integrations/hubspot/schemas/empty.json +6 -0
- package/integrations/hubspot/schemas/get_associations.json +30 -0
- package/integrations/hubspot/schemas/get_company.json +27 -0
- package/integrations/hubspot/schemas/get_contact.json +27 -0
- package/integrations/hubspot/schemas/get_deal.json +20 -0
- package/integrations/hubspot/schemas/get_ticket.json +20 -0
- package/integrations/hubspot/schemas/list_owners.json +25 -0
- package/integrations/hubspot/schemas/list_pipelines.json +13 -0
- package/integrations/hubspot/schemas/list_properties.json +17 -0
- package/integrations/hubspot/schemas/remove_association.json +24 -0
- package/integrations/hubspot/schemas/search_companies.json +56 -0
- package/integrations/hubspot/schemas/search_contacts.json +56 -0
- package/integrations/hubspot/schemas/search_deals.json +43 -0
- package/integrations/hubspot/schemas/search_notes.json +43 -0
- package/integrations/hubspot/schemas/search_tasks.json +43 -0
- package/integrations/hubspot/schemas/search_tickets.json +43 -0
- package/integrations/hubspot/schemas/update_company.json +20 -0
- package/integrations/hubspot/schemas/update_contact.json +21 -0
- package/integrations/hubspot/schemas/update_deal.json +19 -0
- package/integrations/hubspot/schemas/update_task.json +31 -0
- package/integrations/hubspot/schemas/update_ticket.json +18 -0
- package/integrations/jira/.env.test +46 -0
- package/integrations/jira/.env.test.example +41 -0
- package/integrations/jira/README.md +46 -0
- package/integrations/jira/__tests__/get_handlers.test.ts +193 -0
- package/integrations/jira/__tests__/usage_parity.test.ts +14 -0
- package/integrations/jira/__tests__/write_handlers.test.ts +157 -0
- package/integrations/jira/credentials.json +39 -0
- package/integrations/jira/credentials_hint.md +4 -0
- package/integrations/jira/credentials_hint_api_token.md +6 -0
- package/integrations/jira/credentials_hint_oauth_token.md +6 -0
- package/integrations/jira/handlers/add_comment.js +9 -0
- package/integrations/jira/handlers/assign_issue.js +11 -0
- package/integrations/jira/handlers/create_issue.js +37 -0
- package/integrations/jira/handlers/create_sprint.js +19 -0
- package/integrations/jira/handlers/delete_issue.js +10 -0
- package/integrations/jira/handlers/get_backlog_issues.js +13 -0
- package/integrations/jira/handlers/get_board.js +6 -0
- package/integrations/jira/handlers/get_issue.js +63 -0
- package/integrations/jira/handlers/get_issue_comments.js +31 -0
- package/integrations/jira/handlers/get_myself.js +14 -0
- package/integrations/jira/handlers/get_project.js +28 -0
- package/integrations/jira/handlers/get_sprint.js +5 -0
- package/integrations/jira/handlers/get_sprint_issues.js +13 -0
- package/integrations/jira/handlers/get_transitions.js +23 -0
- package/integrations/jira/handlers/list_boards.js +34 -0
- package/integrations/jira/handlers/list_projects.js +29 -0
- package/integrations/jira/handlers/list_sprints.js +29 -0
- package/integrations/jira/handlers/move_issues_to_sprint.js +11 -0
- package/integrations/jira/handlers/search_issues.js +43 -0
- package/integrations/jira/handlers/search_users.js +21 -0
- package/integrations/jira/handlers/transition_issue.js +44 -0
- package/integrations/jira/handlers/update_issue.js +40 -0
- package/integrations/jira/handlers/update_sprint.js +20 -0
- package/integrations/jira/manifest.json +204 -0
- package/integrations/jira/prompt.md +80 -0
- package/integrations/jira/schemas/add_comment.json +16 -0
- package/integrations/jira/schemas/assign_issue.json +16 -0
- package/integrations/jira/schemas/create_issue.json +49 -0
- package/integrations/jira/schemas/create_sprint.json +29 -0
- package/integrations/jira/schemas/delete_issue.json +12 -0
- package/integrations/jira/schemas/empty.json +6 -0
- package/integrations/jira/schemas/get_backlog_issues.json +33 -0
- package/integrations/jira/schemas/get_board.json +13 -0
- package/integrations/jira/schemas/get_issue.json +23 -0
- package/integrations/jira/schemas/get_issue_comments.json +23 -0
- package/integrations/jira/schemas/get_project.json +17 -0
- package/integrations/jira/schemas/get_sprint.json +13 -0
- package/integrations/jira/schemas/get_sprint_issues.json +33 -0
- package/integrations/jira/schemas/get_transitions.json +12 -0
- package/integrations/jira/schemas/list_boards.json +27 -0
- package/integrations/jira/schemas/list_projects.json +22 -0
- package/integrations/jira/schemas/list_sprints.json +29 -0
- package/integrations/jira/schemas/move_issues_to_sprint.json +19 -0
- package/integrations/jira/schemas/search_issues.json +28 -0
- package/integrations/jira/schemas/search_users.json +18 -0
- package/integrations/jira/schemas/transition_issue.json +38 -0
- package/integrations/jira/schemas/update_issue.json +47 -0
- package/integrations/jira/schemas/update_sprint.json +33 -0
- package/integrations/new_integration_prompt.md +173 -2
- package/integrations/notion/.env.test +10 -0
- package/integrations/notion/.env.test.example +13 -0
- package/integrations/notion/README.md +42 -0
- package/integrations/notion/manifest.json +64 -35
- package/integrations/trello/.env.test +6 -0
- package/integrations/trello/.env.test.example +9 -0
- package/integrations/trello/README.md +50 -0
- package/package.json +7 -3
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
2
|
+
import { createCredentialStore, createIntegrationNode, createProxy, createToolbox, hasEnv, safeCleanup } from '../../__tests__/liveHarness.js'
|
|
3
|
+
|
|
4
|
+
// LIVE Jira write tests using credentials
|
|
5
|
+
//
|
|
6
|
+
// Required for write tests:
|
|
7
|
+
// - JIRA_TEST_PROJECT_KEY
|
|
8
|
+
//
|
|
9
|
+
// Variant: api_token
|
|
10
|
+
// - JIRA_DOMAIN
|
|
11
|
+
// - JIRA_EMAIL
|
|
12
|
+
// - JIRA_API_TOKEN
|
|
13
|
+
//
|
|
14
|
+
// Optional:
|
|
15
|
+
// - JIRA_TEST_TRANSITION_NAME (enables transition_issue)
|
|
16
|
+
// - JIRA_TEST_BOARD_ID (enables sprint roundtrip: create_sprint -> update_sprint -> move_issues_to_sprint -> update_sprint)
|
|
17
|
+
|
|
18
|
+
const env = process.env as Record<string, string | undefined>
|
|
19
|
+
|
|
20
|
+
const suiteOrSkip = (hasEnv('JIRA_DOMAIN', 'JIRA_EMAIL', 'JIRA_API_TOKEN') && hasEnv('JIRA_TEST_PROJECT_KEY'))
|
|
21
|
+
? describe
|
|
22
|
+
: describe.skip
|
|
23
|
+
|
|
24
|
+
suiteOrSkip('jira write handlers (live)', () => {
|
|
25
|
+
describe('variant: api_token', () => {
|
|
26
|
+
const ctx: {
|
|
27
|
+
createdIssueKey?: string
|
|
28
|
+
createdSprintId?: number
|
|
29
|
+
} = {}
|
|
30
|
+
|
|
31
|
+
let jira: ReturnType<typeof createToolbox>
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
const credentialStore = createCredentialStore(async () => ({
|
|
35
|
+
domain: env.JIRA_DOMAIN!,
|
|
36
|
+
email: env.JIRA_EMAIL!,
|
|
37
|
+
apiToken: env.JIRA_API_TOKEN!,
|
|
38
|
+
}))
|
|
39
|
+
const proxy = createProxy(credentialStore)
|
|
40
|
+
jira = createToolbox('jira', proxy, createIntegrationNode('jira', { label: 'Jira', credentialId: 'jira-creds', credentialVariant: 'api_token' }), 'api_token')
|
|
41
|
+
}, 60000)
|
|
42
|
+
|
|
43
|
+
afterAll(async () => {
|
|
44
|
+
await safeCleanup(async () => {
|
|
45
|
+
if (!ctx.createdSprintId)
|
|
46
|
+
return
|
|
47
|
+
const update_sprint = jira.write('update_sprint')
|
|
48
|
+
await update_sprint({ sprintId: ctx.createdSprintId, state: 'closed' })
|
|
49
|
+
})
|
|
50
|
+
await safeCleanup(async () => {
|
|
51
|
+
if (!ctx.createdIssueKey)
|
|
52
|
+
return
|
|
53
|
+
const delete_issue = jira.write('delete_issue')
|
|
54
|
+
await delete_issue({ issueIdOrKey: ctx.createdIssueKey })
|
|
55
|
+
})
|
|
56
|
+
}, 60000)
|
|
57
|
+
|
|
58
|
+
it('create_issue -> get_issue -> update_issue -> add_comment -> get_issue_comments (and optional assign/transition) roundtrip', async () => {
|
|
59
|
+
const get_myself = jira.read('get_myself')
|
|
60
|
+
const me = await get_myself({})
|
|
61
|
+
expect(me?.accountId).toBeTruthy()
|
|
62
|
+
|
|
63
|
+
const get_project = jira.read('get_project')
|
|
64
|
+
const project = await get_project({ projectIdOrKey: env.JIRA_TEST_PROJECT_KEY, expandIssueTypes: true })
|
|
65
|
+
const issueTypes = Array.isArray(project?.issueTypes) ? project.issueTypes : []
|
|
66
|
+
const picked = issueTypes.find((t: any) => t && t.subtask === false) || issueTypes[0]
|
|
67
|
+
if (!picked?.id && !picked?.name)
|
|
68
|
+
return expect(true).toBe(true)
|
|
69
|
+
|
|
70
|
+
const create_issue = jira.write('create_issue')
|
|
71
|
+
const created = await create_issue({
|
|
72
|
+
projectKey: env.JIRA_TEST_PROJECT_KEY,
|
|
73
|
+
issueTypeId: picked?.id || undefined,
|
|
74
|
+
issueTypeName: picked?.id ? undefined : (picked?.name || undefined),
|
|
75
|
+
summary: `CmdTest ${Date.now()}`,
|
|
76
|
+
descriptionText: 'Created by integration tests.',
|
|
77
|
+
assigneeAccountId: me.accountId,
|
|
78
|
+
labels: ['cmdtest'],
|
|
79
|
+
})
|
|
80
|
+
const issueKey = created?.key || created?.id
|
|
81
|
+
expect(issueKey).toBeTruthy()
|
|
82
|
+
ctx.createdIssueKey = created?.key || created?.id
|
|
83
|
+
|
|
84
|
+
const get_issue = jira.read('get_issue')
|
|
85
|
+
const got = await get_issue({ issueIdOrKey: issueKey })
|
|
86
|
+
expect(got?.key).toBeTruthy()
|
|
87
|
+
|
|
88
|
+
const update_issue = jira.write('update_issue')
|
|
89
|
+
const updated = await update_issue({
|
|
90
|
+
issueIdOrKey: issueKey,
|
|
91
|
+
summary: `CmdTest Updated ${Date.now()}`,
|
|
92
|
+
descriptionText: 'Updated by integration tests.',
|
|
93
|
+
})
|
|
94
|
+
expect(updated?.success === true || updated).toBeTruthy()
|
|
95
|
+
|
|
96
|
+
const add_comment = jira.write('add_comment')
|
|
97
|
+
const commentText = `Test comment ${Date.now()}`
|
|
98
|
+
const comment = await add_comment({ issueIdOrKey: issueKey, bodyText: commentText })
|
|
99
|
+
expect(comment?.id).toBeTruthy()
|
|
100
|
+
|
|
101
|
+
const get_issue_comments = jira.read('get_issue_comments')
|
|
102
|
+
const comments = await get_issue_comments({ issueIdOrKey: issueKey, maxResults: 20 })
|
|
103
|
+
const found = (comments?.comments || []).some((c: any) =>
|
|
104
|
+
(c?.bodyMarkdown && String(c.bodyMarkdown).includes('Test comment'))
|
|
105
|
+
|| (c?.bodyText && String(c.bodyText).includes('Test comment')),
|
|
106
|
+
)
|
|
107
|
+
expect(found).toBe(true)
|
|
108
|
+
|
|
109
|
+
const assign_issue = jira.write('assign_issue')
|
|
110
|
+
const assigned = await assign_issue({ issueIdOrKey: issueKey, accountId: me.accountId })
|
|
111
|
+
expect(assigned?.success === true || assigned).toBeTruthy()
|
|
112
|
+
const unassigned = await assign_issue({ issueIdOrKey: issueKey, accountId: null })
|
|
113
|
+
expect(unassigned?.success === true || unassigned).toBeTruthy()
|
|
114
|
+
|
|
115
|
+
if (env.JIRA_TEST_TRANSITION_NAME) {
|
|
116
|
+
const transition_issue = jira.write('transition_issue')
|
|
117
|
+
const transitioned = await transition_issue({
|
|
118
|
+
issueIdOrKey: issueKey,
|
|
119
|
+
transitionName: env.JIRA_TEST_TRANSITION_NAME,
|
|
120
|
+
commentText: 'Transitioned by integration tests.',
|
|
121
|
+
})
|
|
122
|
+
expect(transitioned?.success === true || transitioned).toBeTruthy()
|
|
123
|
+
}
|
|
124
|
+
}, 120000)
|
|
125
|
+
|
|
126
|
+
it('sprint roundtrip: create_sprint -> update_sprint (start) -> move_issues_to_sprint -> update_sprint (close) (optional)', async () => {
|
|
127
|
+
if (!env.JIRA_TEST_BOARD_ID || !ctx.createdIssueKey)
|
|
128
|
+
return expect(true).toBe(true)
|
|
129
|
+
const boardId = Number(env.JIRA_TEST_BOARD_ID)
|
|
130
|
+
if (!Number.isFinite(boardId))
|
|
131
|
+
return expect(true).toBe(true)
|
|
132
|
+
|
|
133
|
+
const create_sprint = jira.write('create_sprint')
|
|
134
|
+
const name = `CmdTest Sprint ${Date.now()}`
|
|
135
|
+
const sprint = await create_sprint({ boardId, name })
|
|
136
|
+
const sprintId = sprint?.id
|
|
137
|
+
expect(typeof sprintId).toBe('number')
|
|
138
|
+
ctx.createdSprintId = sprintId
|
|
139
|
+
|
|
140
|
+
const update_sprint = jira.write('update_sprint')
|
|
141
|
+
const today = new Date().toISOString().slice(0, 10)
|
|
142
|
+
const twoWeeks = new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)
|
|
143
|
+
const started = await update_sprint({ sprintId, state: 'active', startDate: today, endDate: twoWeeks })
|
|
144
|
+
expect(started?.state === 'active' || started?.id === sprintId || started).toBeTruthy()
|
|
145
|
+
|
|
146
|
+
const move_issues_to_sprint = jira.write('move_issues_to_sprint')
|
|
147
|
+
const moved = await move_issues_to_sprint({ sprintId, issueKeys: [ctx.createdIssueKey] })
|
|
148
|
+
expect(moved?.success === true || moved).toBeTruthy()
|
|
149
|
+
|
|
150
|
+
const closed = await update_sprint({ sprintId, state: 'closed' })
|
|
151
|
+
expect(closed?.state === 'closed' || closed?.id === sprintId || closed).toBeTruthy()
|
|
152
|
+
ctx.createdSprintId = undefined
|
|
153
|
+
}, 120000)
|
|
154
|
+
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"variants": {
|
|
3
|
+
"api_token": {
|
|
4
|
+
"label": "API Token (Email + Token)",
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"domain": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"title": "Jira site domain",
|
|
11
|
+
"description": "The subdomain of your Jira Cloud site. Example: for https://mycompany.atlassian.net, enter 'mycompany'."
|
|
12
|
+
},
|
|
13
|
+
"email": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"title": "Atlassian account email",
|
|
16
|
+
"description": "Email address of the Atlassian account that owns the API token."
|
|
17
|
+
},
|
|
18
|
+
"apiToken": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"title": "Atlassian API token",
|
|
21
|
+
"description": "Atlassian API token for Jira Cloud. The server will convert this into the required Basic auth header."
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"required": ["domain", "email", "apiToken"],
|
|
25
|
+
"additionalProperties": false
|
|
26
|
+
},
|
|
27
|
+
"baseUrlTemplate": "https://{{domain}}.atlassian.net",
|
|
28
|
+
"injection": {
|
|
29
|
+
"headers": {
|
|
30
|
+
"Authorization": "Basic {{basicAuth}}",
|
|
31
|
+
"Accept": "application/json"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"preprocess": "jira_api_token"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"default": "api_token"
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
1. Open your Jira Cloud site in the browser (it looks like `https://YOUR_DOMAIN.atlassian.net`).
|
|
2
|
+
2. Copy `YOUR_DOMAIN` (the subdomain) and use it as `domain`.
|
|
3
|
+
3. Create an Atlassian API token at `https://id.atlassian.com/manage-profile/security/api-tokens`.
|
|
4
|
+
4. Use the same Atlassian account email as `email` and paste the token as `apiToken`.
|
|
5
|
+
5. Ensure the Atlassian account has access to the Jira projects you want to work with.
|
|
6
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
1. Create an OAuth 2.0 (3LO) app in the Atlassian developer console.
|
|
2
|
+
2. Add Jira scopes (typical minimum): `read:jira-work`, `write:jira-work`, `read:jira-user` (and `offline_access` if you want refresh tokens).
|
|
3
|
+
3. Complete the OAuth flow to obtain an access token.
|
|
4
|
+
4. Discover your `cloudId` by calling `GET https://api.atlassian.com/oauth/token/accessible-resources` with `Authorization: Bearer <access_token>` and using the returned `id`.
|
|
5
|
+
5. Paste `cloudId` and the current OAuth `token` here.
|
|
6
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}/assignee`, {
|
|
3
|
+
method: 'PUT',
|
|
4
|
+
body: { accountId: input.accountId },
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
if (res.status === 204)
|
|
8
|
+
return { success: true }
|
|
9
|
+
return await res.json()
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const fields = {
|
|
3
|
+
project: { key: input.projectKey },
|
|
4
|
+
summary: input.summary,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (input.descriptionText)
|
|
8
|
+
fields.description = utils.adf?.fromMarkdown(input.descriptionText)
|
|
9
|
+
|
|
10
|
+
if (input.issueTypeId) {
|
|
11
|
+
fields.issuetype = { id: String(input.issueTypeId) }
|
|
12
|
+
}
|
|
13
|
+
else if (input.issueTypeName) {
|
|
14
|
+
fields.issuetype = { name: String(input.issueTypeName) }
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
throw new Error(`Missing issue type. Provide issueTypeId or issueTypeName (call get_project to discover available issue types).`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (input.priorityId)
|
|
21
|
+
fields.priority = { id: input.priorityId }
|
|
22
|
+
else if (input.priorityName)
|
|
23
|
+
fields.priority = { name: input.priorityName }
|
|
24
|
+
|
|
25
|
+
if (Array.isArray(input.labels))
|
|
26
|
+
fields.labels = input.labels
|
|
27
|
+
|
|
28
|
+
if (input.assigneeAccountId)
|
|
29
|
+
fields.assignee = { accountId: input.assigneeAccountId }
|
|
30
|
+
|
|
31
|
+
const res = await integration.fetch('/rest/api/3/issue', {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
body: { fields },
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return await res.json()
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {
|
|
3
|
+
originBoardId: input.boardId,
|
|
4
|
+
name: input.name,
|
|
5
|
+
}
|
|
6
|
+
if (input.startDate)
|
|
7
|
+
body.startDate = input.startDate
|
|
8
|
+
if (input.endDate)
|
|
9
|
+
body.endDate = input.endDate
|
|
10
|
+
if (input.goal)
|
|
11
|
+
body.goal = input.goal
|
|
12
|
+
|
|
13
|
+
const res = await integration.fetch('/rest/agile/1.0/sprint', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
body,
|
|
16
|
+
})
|
|
17
|
+
return await res.json()
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.jql)
|
|
4
|
+
params.set('jql', input.jql)
|
|
5
|
+
if (Array.isArray(input.fields) && input.fields.length)
|
|
6
|
+
params.set('fields', input.fields.join(','))
|
|
7
|
+
params.set('startAt', String(input.startAt ?? 0))
|
|
8
|
+
params.set('maxResults', String(input.maxResults ?? 50))
|
|
9
|
+
|
|
10
|
+
const res = await integration.fetch(`/rest/agile/1.0/board/${encodeURIComponent(String(input.boardId))}/backlog?${params.toString()}`)
|
|
11
|
+
return await res.json()
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const defaultFields = [
|
|
3
|
+
'summary',
|
|
4
|
+
'status',
|
|
5
|
+
'assignee',
|
|
6
|
+
'priority',
|
|
7
|
+
'issuetype',
|
|
8
|
+
'project',
|
|
9
|
+
'description',
|
|
10
|
+
'created',
|
|
11
|
+
'updated',
|
|
12
|
+
'labels',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const fields = Array.isArray(input.fields) && input.fields.length ? input.fields : defaultFields
|
|
16
|
+
const params = new URLSearchParams()
|
|
17
|
+
if (fields?.length)
|
|
18
|
+
params.set('fields', fields.join(','))
|
|
19
|
+
if (Array.isArray(input.expand) && input.expand.length)
|
|
20
|
+
params.set('expand', input.expand.join(','))
|
|
21
|
+
|
|
22
|
+
const path = `/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}${params.toString() ? `?${params.toString()}` : ''}`
|
|
23
|
+
const res = await integration.fetch(path)
|
|
24
|
+
const data = await res.json()
|
|
25
|
+
|
|
26
|
+
const descAdf = data?.fields?.description
|
|
27
|
+
const descMarkdown = utils.adf?.toMarkdown(descAdf) || ''
|
|
28
|
+
const descText = descMarkdown ? '' : (utils.adf?.toPlainText(descAdf) || '')
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
id: data.id ?? null,
|
|
32
|
+
key: data.key ?? null,
|
|
33
|
+
self: data.self ?? null,
|
|
34
|
+
summary: data.fields?.summary ?? null,
|
|
35
|
+
status: data.fields?.status
|
|
36
|
+
? {
|
|
37
|
+
id: data.fields.status.id ?? null,
|
|
38
|
+
name: data.fields.status.name ?? null,
|
|
39
|
+
category: data.fields.status.statusCategory
|
|
40
|
+
? {
|
|
41
|
+
key: data.fields.status.statusCategory.key ?? null,
|
|
42
|
+
name: data.fields.status.statusCategory.name ?? null,
|
|
43
|
+
}
|
|
44
|
+
: null,
|
|
45
|
+
}
|
|
46
|
+
: null,
|
|
47
|
+
assignee: data.fields?.assignee
|
|
48
|
+
? {
|
|
49
|
+
accountId: data.fields.assignee.accountId ?? null,
|
|
50
|
+
displayName: data.fields.assignee.displayName ?? null,
|
|
51
|
+
}
|
|
52
|
+
: null,
|
|
53
|
+
priority: data.fields?.priority ? { id: data.fields.priority.id ?? null, name: data.fields.priority.name ?? null } : null,
|
|
54
|
+
issueType: data.fields?.issuetype ? { id: data.fields.issuetype.id ?? null, name: data.fields.issuetype.name ?? null } : null,
|
|
55
|
+
project: data.fields?.project ? { id: data.fields.project.id ?? null, key: data.fields.project.key ?? null, name: data.fields.project.name ?? null } : null,
|
|
56
|
+
labels: Array.isArray(data.fields?.labels) ? data.fields.labels : [],
|
|
57
|
+
descriptionMarkdown: descMarkdown || null,
|
|
58
|
+
descriptionText: descMarkdown ? null : (descText || null),
|
|
59
|
+
created: data.fields?.created ?? null,
|
|
60
|
+
updated: data.fields?.updated ?? null,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
params.set('startAt', String(input.startAt ?? 0))
|
|
4
|
+
params.set('maxResults', String(input.maxResults ?? 50))
|
|
5
|
+
|
|
6
|
+
const path = `/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}/comment?${params.toString()}`
|
|
7
|
+
const res = await integration.fetch(path)
|
|
8
|
+
const data = await res.json()
|
|
9
|
+
const comments = Array.isArray(data.comments) ? data.comments : []
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
startAt: data.startAt ?? (input.startAt ?? 0),
|
|
13
|
+
maxResults: data.maxResults ?? (input.maxResults ?? 50),
|
|
14
|
+
total: data.total ?? comments.length,
|
|
15
|
+
comments: comments.map((c) => {
|
|
16
|
+
const md = utils.adf?.toMarkdown(c.body) || ''
|
|
17
|
+
const text = md ? '' : (utils.adf?.toPlainText(c.body) || '')
|
|
18
|
+
return {
|
|
19
|
+
id: c.id ?? null,
|
|
20
|
+
created: c.created ?? null,
|
|
21
|
+
updated: c.updated ?? null,
|
|
22
|
+
author: c.author
|
|
23
|
+
? { accountId: c.author.accountId ?? null, displayName: c.author.displayName ?? null }
|
|
24
|
+
: null,
|
|
25
|
+
bodyMarkdown: md || null,
|
|
26
|
+
bodyText: md ? null : (text || null),
|
|
27
|
+
}
|
|
28
|
+
}),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
async (_input) => {
|
|
2
|
+
const res = await integration.fetch('/rest/api/3/myself')
|
|
3
|
+
const data = await res.json()
|
|
4
|
+
|
|
5
|
+
return {
|
|
6
|
+
accountId: data.accountId ?? null,
|
|
7
|
+
displayName: data.displayName ?? null,
|
|
8
|
+
active: data.active ?? null,
|
|
9
|
+
timeZone: data.timeZone ?? null,
|
|
10
|
+
locale: data.locale ?? null,
|
|
11
|
+
emailAddress: data.emailAddress ?? null,
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.expandIssueTypes !== false)
|
|
4
|
+
params.set('expand', 'issueTypes')
|
|
5
|
+
|
|
6
|
+
const path = `/rest/api/3/project/${encodeURIComponent(input.projectIdOrKey)}${params.toString() ? `?${params.toString()}` : ''}`
|
|
7
|
+
const res = await integration.fetch(path)
|
|
8
|
+
const data = await res.json()
|
|
9
|
+
|
|
10
|
+
const issueTypes = Array.isArray(data.issueTypes) ? data.issueTypes : []
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
id: data.id ?? null,
|
|
14
|
+
key: data.key ?? null,
|
|
15
|
+
name: data.name ?? null,
|
|
16
|
+
projectTypeKey: data.projectTypeKey ?? null,
|
|
17
|
+
simplified: data.simplified ?? null,
|
|
18
|
+
style: data.style ?? null,
|
|
19
|
+
isPrivate: data.isPrivate ?? null,
|
|
20
|
+
issueTypes: issueTypes.map(t => ({
|
|
21
|
+
id: t.id ?? null,
|
|
22
|
+
name: t.name ?? null,
|
|
23
|
+
description: t.description ?? null,
|
|
24
|
+
subtask: t.subtask ?? null,
|
|
25
|
+
})),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input.jql)
|
|
4
|
+
params.set('jql', input.jql)
|
|
5
|
+
if (Array.isArray(input.fields) && input.fields.length)
|
|
6
|
+
params.set('fields', input.fields.join(','))
|
|
7
|
+
params.set('startAt', String(input.startAt ?? 0))
|
|
8
|
+
params.set('maxResults', String(input.maxResults ?? 50))
|
|
9
|
+
|
|
10
|
+
const res = await integration.fetch(`/rest/agile/1.0/sprint/${encodeURIComponent(String(input.sprintId))}/issue?${params.toString()}`)
|
|
11
|
+
return await res.json()
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const path = `/rest/api/3/issue/${encodeURIComponent(input.issueIdOrKey)}/transitions`
|
|
3
|
+
const res = await integration.fetch(path)
|
|
4
|
+
const data = await res.json()
|
|
5
|
+
const transitions = Array.isArray(data.transitions) ? data.transitions : []
|
|
6
|
+
|
|
7
|
+
return {
|
|
8
|
+
transitions: transitions.map(t => ({
|
|
9
|
+
id: t.id ?? null,
|
|
10
|
+
name: t.name ?? null,
|
|
11
|
+
to: t.to
|
|
12
|
+
? {
|
|
13
|
+
id: t.to.id ?? null,
|
|
14
|
+
name: t.to.name ?? null,
|
|
15
|
+
statusCategory: t.to.statusCategory
|
|
16
|
+
? { key: t.to.statusCategory.key ?? null, name: t.to.statusCategory.name ?? null }
|
|
17
|
+
: null,
|
|
18
|
+
}
|
|
19
|
+
: null,
|
|
20
|
+
})),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input?.projectKeyOrId)
|
|
4
|
+
params.set('projectKeyOrId', input.projectKeyOrId)
|
|
5
|
+
if (input?.type)
|
|
6
|
+
params.set('type', input.type)
|
|
7
|
+
params.set('startAt', String(input?.startAt ?? 0))
|
|
8
|
+
params.set('maxResults', String(input?.maxResults ?? 50))
|
|
9
|
+
|
|
10
|
+
const res = await integration.fetch(`/rest/agile/1.0/board?${params.toString()}`)
|
|
11
|
+
const data = await res.json()
|
|
12
|
+
const values = Array.isArray(data.values) ? data.values : []
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
startAt: data.startAt ?? (input?.startAt ?? 0),
|
|
16
|
+
maxResults: data.maxResults ?? (input?.maxResults ?? 50),
|
|
17
|
+
total: data.total ?? values.length,
|
|
18
|
+
isLast: Boolean(data.isLast),
|
|
19
|
+
boards: values.map(b => ({
|
|
20
|
+
id: b.id ?? null,
|
|
21
|
+
name: b.name ?? null,
|
|
22
|
+
type: b.type ?? null,
|
|
23
|
+
location: b.location
|
|
24
|
+
? {
|
|
25
|
+
projectId: b.location.projectId ?? null,
|
|
26
|
+
projectKey: b.location.projectKey ?? null,
|
|
27
|
+
projectName: b.location.projectName ?? null,
|
|
28
|
+
}
|
|
29
|
+
: null,
|
|
30
|
+
self: b.self ?? null,
|
|
31
|
+
})),
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (input?.query)
|
|
4
|
+
params.set('query', input.query)
|
|
5
|
+
params.set('startAt', String(input?.startAt ?? 0))
|
|
6
|
+
params.set('maxResults', String(input?.maxResults ?? 50))
|
|
7
|
+
|
|
8
|
+
const path = `/rest/api/3/project/search?${params.toString()}`
|
|
9
|
+
const res = await integration.fetch(path)
|
|
10
|
+
const data = await res.json()
|
|
11
|
+
const values = Array.isArray(data.values) ? data.values : []
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
startAt: data.startAt ?? (input?.startAt ?? 0),
|
|
15
|
+
maxResults: data.maxResults ?? (input?.maxResults ?? 50),
|
|
16
|
+
total: data.total ?? values.length,
|
|
17
|
+
isLast: Boolean(data.isLast),
|
|
18
|
+
projects: values.map(p => ({
|
|
19
|
+
id: p.id ?? null,
|
|
20
|
+
key: p.key ?? null,
|
|
21
|
+
name: p.name ?? null,
|
|
22
|
+
projectTypeKey: p.projectTypeKey ?? null,
|
|
23
|
+
simplified: p.simplified ?? null,
|
|
24
|
+
style: p.style ?? null,
|
|
25
|
+
isPrivate: p.isPrivate ?? null,
|
|
26
|
+
})),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const params = new URLSearchParams()
|
|
3
|
+
if (Array.isArray(input.state) && input.state.length)
|
|
4
|
+
params.set('state', input.state.join(','))
|
|
5
|
+
params.set('startAt', String(input.startAt ?? 0))
|
|
6
|
+
params.set('maxResults', String(input.maxResults ?? 50))
|
|
7
|
+
|
|
8
|
+
const res = await integration.fetch(`/rest/agile/1.0/board/${encodeURIComponent(String(input.boardId))}/sprint?${params.toString()}`)
|
|
9
|
+
const data = await res.json()
|
|
10
|
+
const values = Array.isArray(data.values) ? data.values : []
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
startAt: data.startAt ?? (input.startAt ?? 0),
|
|
14
|
+
maxResults: data.maxResults ?? (input.maxResults ?? 50),
|
|
15
|
+
total: data.total ?? values.length,
|
|
16
|
+
isLast: Boolean(data.isLast),
|
|
17
|
+
sprints: values.map(s => ({
|
|
18
|
+
id: s.id ?? null,
|
|
19
|
+
name: s.name ?? null,
|
|
20
|
+
state: s.state ?? null,
|
|
21
|
+
goal: s.goal ?? null,
|
|
22
|
+
startDate: s.startDate ?? null,
|
|
23
|
+
endDate: s.endDate ?? null,
|
|
24
|
+
completeDate: s.completeDate ?? null,
|
|
25
|
+
self: s.self ?? null,
|
|
26
|
+
})),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const res = await integration.fetch(`/rest/agile/1.0/sprint/${encodeURIComponent(String(input.sprintId))}/issue`, {
|
|
3
|
+
method: 'POST',
|
|
4
|
+
body: { issues: input.issueKeys },
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
if (res.status === 204)
|
|
8
|
+
return { success: true }
|
|
9
|
+
return await res.json()
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
async (input) => {
|
|
2
|
+
const body = {
|
|
3
|
+
jql: input.jql,
|
|
4
|
+
maxResults: input.maxResults ?? 50,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
if (Array.isArray(input.fields) && input.fields.length)
|
|
8
|
+
body.fields = input.fields
|
|
9
|
+
if (input.nextPageToken)
|
|
10
|
+
body.nextPageToken = input.nextPageToken
|
|
11
|
+
|
|
12
|
+
const res = await integration.fetch('/rest/api/3/search/jql', {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
body,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const data = await res.json()
|
|
18
|
+
const issues = Array.isArray(data.issues) ? data.issues : []
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
isLast: Boolean(data.isLast),
|
|
22
|
+
nextPageToken: data.nextPageToken ?? null,
|
|
23
|
+
issues: issues.map((i) => ({
|
|
24
|
+
id: i.id,
|
|
25
|
+
key: i.key,
|
|
26
|
+
summary: i.fields?.summary ?? null,
|
|
27
|
+
status: i.fields?.status?.name ?? null,
|
|
28
|
+
assignee: i.fields?.assignee
|
|
29
|
+
? {
|
|
30
|
+
accountId: i.fields.assignee.accountId ?? null,
|
|
31
|
+
displayName: i.fields.assignee.displayName ?? null,
|
|
32
|
+
}
|
|
33
|
+
: null,
|
|
34
|
+
priority: i.fields?.priority?.name ?? null,
|
|
35
|
+
issueType: i.fields?.issuetype?.name ?? null,
|
|
36
|
+
project: i.fields?.project
|
|
37
|
+
? { key: i.fields.project.key ?? null, name: i.fields.project.name ?? null }
|
|
38
|
+
: null,
|
|
39
|
+
updated: i.fields?.updated ?? null,
|
|
40
|
+
})),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|