mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(jsm): add all Forms API endpoints for jira (#4142)
* feat(jsm): add all Forms API endpoints for two-step form workflow * removed tyoes * fix(jsm): handle 204 No Content on action endpoints and reject array answers * fix(jsm): validate formIds is an array in copy_forms route and block * fix(jsm): add formTemplateId validation and conditional required on formAnswers
This commit is contained in:
@@ -758,4 +758,235 @@ List forms (ProForma/JSM Forms) attached to a Jira issue with metadata (name, su
|
||||
| ↳ `formTemplateId` | string | Source form template ID \(UUID\) |
|
||||
| `total` | number | Total number of forms |
|
||||
|
||||
### `jsm_attach_form`
|
||||
|
||||
Attach a form template to an existing Jira issue or JSM request
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key to attach the form to \(e.g., "SD-123"\) |
|
||||
| `formTemplateId` | string | Yes | Form template UUID \(from Get Form Templates\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `id` | string | Attached form instance ID \(UUID\) |
|
||||
| `name` | string | Form name |
|
||||
| `updated` | string | Last updated timestamp |
|
||||
| `submitted` | boolean | Whether the form has been submitted |
|
||||
| `lock` | boolean | Whether the form is locked |
|
||||
| `internal` | boolean | Whether the form is internal only |
|
||||
| `formTemplateId` | string | Form template ID |
|
||||
|
||||
### `jsm_save_form_answers`
|
||||
|
||||
Save answers to a form attached to a Jira issue or JSM request
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID \(from Attach Form or Get Issue Forms\) |
|
||||
| `answers` | json | Yes | Form answers using numeric question IDs as keys \(e.g., \{"1": \{"text": "Title"\}, "4": \{"choices": \["5"\]\}\}\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `state` | json | Form state with status \(open, submitted, locked\) |
|
||||
| `updated` | string | Last updated timestamp |
|
||||
|
||||
### `jsm_submit_form`
|
||||
|
||||
Submit a form on a Jira issue or JSM request, locking it from further edits
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID \(from Attach Form or Get Issue Forms\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `status` | string | Form status after submission \(open, submitted, locked\) |
|
||||
|
||||
### `jsm_get_form`
|
||||
|
||||
Get a single form with full design, state, and answers from a Jira issue
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID \(from Attach Form or Get Issue Forms\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `design` | json | Full form design with questions, layout, conditions, sections, settings |
|
||||
| `state` | json | Form state with answers map, status \(o=open, s=submitted, l=locked\), visibility \(i=internal, e=external\) |
|
||||
| `updated` | string | Last updated timestamp |
|
||||
|
||||
### `jsm_get_form_answers`
|
||||
|
||||
Get simplified answers from a form attached to a Jira issue or JSM request
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID \(from Attach Form or Get Issue Forms\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `answers` | json | Simplified form answers as key-value pairs \(question label to answer text/choices\) |
|
||||
|
||||
### `jsm_reopen_form`
|
||||
|
||||
Reopen a submitted form on a Jira issue or JSM request, allowing further edits
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID \(from Get Issue Forms\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `status` | string | Form status after reopening \(open, submitted, locked\) |
|
||||
|
||||
### `jsm_delete_form`
|
||||
|
||||
Remove a form from a Jira issue or JSM request
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID to delete |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Deleted form instance UUID |
|
||||
| `deleted` | boolean | Whether the form was successfully deleted |
|
||||
|
||||
### `jsm_externalise_form`
|
||||
|
||||
Make a form visible to customers on a Jira issue or JSM request
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `visibility` | string | Form visibility after change \(internal or external\) |
|
||||
|
||||
### `jsm_internalise_form`
|
||||
|
||||
Make a form internal only (not visible to customers) on a Jira issue or JSM request
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `issueIdOrKey` | string | Yes | Issue ID or key \(e.g., "SD-123"\) |
|
||||
| `formId` | string | Yes | Form instance UUID |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `issueIdOrKey` | string | Issue ID or key |
|
||||
| `formId` | string | Form instance UUID |
|
||||
| `visibility` | string | Form visibility after change \(internal or external\) |
|
||||
|
||||
### `jsm_copy_forms`
|
||||
|
||||
Copy forms from one Jira issue to another
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
|
||||
| `cloudId` | string | No | Jira Cloud ID for the instance |
|
||||
| `sourceIssueIdOrKey` | string | Yes | Source issue ID or key to copy forms from \(e.g., "SD-123"\) |
|
||||
| `targetIssueIdOrKey` | string | Yes | Target issue ID or key to copy forms to \(e.g., "SD-456"\) |
|
||||
| `formIds` | json | No | Optional JSON array of form UUIDs to copy \(e.g., \["uuid1", "uuid2"\]\). If omitted, copies all forms. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `ts` | string | Timestamp of the operation |
|
||||
| `sourceIssueIdOrKey` | string | Source issue ID or key |
|
||||
| `targetIssueIdOrKey` | string | Target issue ID or key |
|
||||
| `copiedForms` | json | Array of successfully copied forms |
|
||||
| `errors` | json | Array of errors encountered during copy |
|
||||
|
||||
|
||||
|
||||
@@ -6868,9 +6868,49 @@
|
||||
{
|
||||
"name": "Get Issue Forms",
|
||||
"description": "List forms (ProForma/JSM Forms) attached to a Jira issue with metadata (name, submitted status, lock)"
|
||||
},
|
||||
{
|
||||
"name": "Attach Form",
|
||||
"description": "Attach a form template to an existing Jira issue or JSM request"
|
||||
},
|
||||
{
|
||||
"name": "Save Form Answers",
|
||||
"description": "Save answers to a form attached to a Jira issue or JSM request"
|
||||
},
|
||||
{
|
||||
"name": "Submit Form",
|
||||
"description": "Submit a form on a Jira issue or JSM request, locking it from further edits"
|
||||
},
|
||||
{
|
||||
"name": "Get Form",
|
||||
"description": "Get a single form with full design, state, and answers from a Jira issue"
|
||||
},
|
||||
{
|
||||
"name": "Get Form Answers",
|
||||
"description": "Get simplified answers from a form attached to a Jira issue or JSM request"
|
||||
},
|
||||
{
|
||||
"name": "Reopen Form",
|
||||
"description": "Reopen a submitted form on a Jira issue or JSM request, allowing further edits"
|
||||
},
|
||||
{
|
||||
"name": "Delete Form",
|
||||
"description": "Remove a form from a Jira issue or JSM request"
|
||||
},
|
||||
{
|
||||
"name": "Externalise Form",
|
||||
"description": "Make a form visible to customers on a Jira issue or JSM request"
|
||||
},
|
||||
{
|
||||
"name": "Internalise Form",
|
||||
"description": "Make a form internal only (not visible to customers) on a Jira issue or JSM request"
|
||||
},
|
||||
{
|
||||
"name": "Copy Forms",
|
||||
"description": "Copy forms from one Jira issue to another"
|
||||
}
|
||||
],
|
||||
"operationCount": 24,
|
||||
"operationCount": 34,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "oauth",
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmGetFormAnswersAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}/format/answers`
|
||||
|
||||
logger.info('Getting form answers:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
answers: data ?? null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error getting form answers:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmAttachFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formTemplateId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formTemplateId) {
|
||||
logger.error('Missing formTemplateId in request')
|
||||
return NextResponse.json({ error: 'Form template ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formTemplateIdValidation = validateJiraCloudId(formTemplateId, 'formTemplateId')
|
||||
if (!formTemplateIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formTemplateIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form`
|
||||
|
||||
logger.info('Attaching form to issue:', { url, issueIdOrKey, formTemplateId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
body: JSON.stringify({
|
||||
formTemplate: { id: formTemplateId },
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
id: data.id ?? null,
|
||||
name: data.name ?? null,
|
||||
updated: data.updated ?? null,
|
||||
submitted: data.submitted ?? false,
|
||||
lock: data.lock ?? false,
|
||||
internal: data.internal ?? null,
|
||||
formTemplateId: (data.formTemplate as Record<string, unknown>)?.id ?? null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error attaching form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmCopyFormsAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const {
|
||||
domain,
|
||||
accessToken,
|
||||
cloudId: cloudIdParam,
|
||||
sourceIssueIdOrKey,
|
||||
targetIssueIdOrKey,
|
||||
formIds,
|
||||
} = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!sourceIssueIdOrKey) {
|
||||
logger.error('Missing sourceIssueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Source issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!targetIssueIdOrKey) {
|
||||
logger.error('Missing targetIssueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Target issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const sourceValidation = validateJiraIssueKey(sourceIssueIdOrKey, 'sourceIssueIdOrKey')
|
||||
if (!sourceValidation.isValid) {
|
||||
return NextResponse.json({ error: sourceValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const targetValidation = validateJiraIssueKey(targetIssueIdOrKey, 'targetIssueIdOrKey')
|
||||
if (!targetValidation.isValid) {
|
||||
return NextResponse.json({ error: targetValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(sourceIssueIdOrKey)}/form/copy/${encodeURIComponent(targetIssueIdOrKey)}`
|
||||
|
||||
if (formIds !== undefined && !Array.isArray(formIds)) {
|
||||
return NextResponse.json({ error: 'formIds must be an array of form UUIDs' }, { status: 400 })
|
||||
}
|
||||
|
||||
const requestBody = Array.isArray(formIds) && formIds.length > 0 ? { ids: formIds } : {}
|
||||
|
||||
logger.info('Copying forms:', { url, sourceIssueIdOrKey, targetIssueIdOrKey, formIds })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
body: JSON.stringify(requestBody),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
sourceIssueIdOrKey,
|
||||
targetIssueIdOrKey,
|
||||
copiedForms: data.copiedForms ?? [],
|
||||
errors: data.errors ?? [],
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error copying forms:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmDeleteFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}`
|
||||
|
||||
logger.info('Deleting form:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
await response.text()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
deleted: true,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error deleting form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmExternaliseFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}/action/external`
|
||||
|
||||
logger.info('Externalising form:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const bodyText = await response.text()
|
||||
const data = bodyText ? JSON.parse(bodyText) : {}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
visibility: data.visibility ?? 'external',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error externalising form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmGetFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}`
|
||||
|
||||
logger.info('Getting form:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
design: data.design ?? null,
|
||||
state: data.state ?? null,
|
||||
updated: data.updated ?? null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error getting form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmInternaliseFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}/action/internal`
|
||||
|
||||
logger.info('Internalising form:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const bodyText = await response.text()
|
||||
const data = bodyText ? JSON.parse(bodyText) : {}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
visibility: data.visibility ?? 'internal',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error internalising form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmReopenFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}/action/reopen`
|
||||
|
||||
logger.info('Reopening form:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const bodyText = await response.text()
|
||||
const data = bodyText ? JSON.parse(bodyText) : {}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
status: data.status ?? 'open',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error reopening form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmSaveFormAnswersAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId, answers } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!answers || typeof answers !== 'object' || Array.isArray(answers)) {
|
||||
logger.error('Missing or invalid answers in request')
|
||||
return NextResponse.json({ error: 'Answers object is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}`
|
||||
|
||||
logger.info('Saving form answers:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
body: JSON.stringify({ answers }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
state: data.state ?? null,
|
||||
updated: data.updated ?? null,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error saving form answers:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { checkInternalAuth } from '@/lib/auth/hybrid'
|
||||
import { validateJiraCloudId, validateJiraIssueKey } from '@/lib/core/security/input-validation'
|
||||
import { getJiraCloudId, parseAtlassianErrorMessage } from '@/tools/jira/utils'
|
||||
import { getJsmFormsApiBaseUrl, getJsmHeaders } from '@/tools/jsm/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('JsmSubmitFormAPI')
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await checkInternalAuth(request)
|
||||
if (!auth.success || !auth.userId) {
|
||||
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { domain, accessToken, cloudId: cloudIdParam, issueIdOrKey, formId } = body
|
||||
|
||||
if (!domain) {
|
||||
logger.error('Missing domain in request')
|
||||
return NextResponse.json({ error: 'Domain is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!accessToken) {
|
||||
logger.error('Missing access token in request')
|
||||
return NextResponse.json({ error: 'Access token is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!issueIdOrKey) {
|
||||
logger.error('Missing issueIdOrKey in request')
|
||||
return NextResponse.json({ error: 'Issue ID or key is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!formId) {
|
||||
logger.error('Missing formId in request')
|
||||
return NextResponse.json({ error: 'Form ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const cloudId = cloudIdParam || (await getJiraCloudId(domain, accessToken))
|
||||
|
||||
const cloudIdValidation = validateJiraCloudId(cloudId, 'cloudId')
|
||||
if (!cloudIdValidation.isValid) {
|
||||
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const issueIdOrKeyValidation = validateJiraIssueKey(issueIdOrKey, 'issueIdOrKey')
|
||||
if (!issueIdOrKeyValidation.isValid) {
|
||||
return NextResponse.json({ error: issueIdOrKeyValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const formIdValidation = validateJiraCloudId(formId, 'formId')
|
||||
if (!formIdValidation.isValid) {
|
||||
return NextResponse.json({ error: formIdValidation.error }, { status: 400 })
|
||||
}
|
||||
|
||||
const baseUrl = getJsmFormsApiBaseUrl(cloudId)
|
||||
const url = `${baseUrl}/issue/${encodeURIComponent(issueIdOrKey)}/form/${encodeURIComponent(formId)}/action/submit`
|
||||
|
||||
logger.info('Submitting form:', { url, issueIdOrKey, formId })
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: getJsmHeaders(accessToken),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
logger.error('JSM Forms API error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: errorText,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: parseAtlassianErrorMessage(response.status, response.statusText, errorText),
|
||||
details: errorText,
|
||||
},
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
|
||||
const bodyText = await response.text()
|
||||
const data = bodyText ? JSON.parse(bodyText) : {}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey,
|
||||
formId,
|
||||
status: data.status ?? 'submitted',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error submitting form:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Internal server error',
|
||||
success: false,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,16 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
||||
{ label: 'Get Form Templates', id: 'get_form_templates' },
|
||||
{ label: 'Get Form Structure', id: 'get_form_structure' },
|
||||
{ label: 'Get Issue Forms', id: 'get_issue_forms' },
|
||||
{ label: 'Attach Form', id: 'attach_form' },
|
||||
{ label: 'Save Form Answers', id: 'save_form_answers' },
|
||||
{ label: 'Submit Form', id: 'submit_form' },
|
||||
{ label: 'Get Form', id: 'get_form' },
|
||||
{ label: 'Get Form Answers', id: 'get_form_answers' },
|
||||
{ label: 'Reopen Form', id: 'reopen_form' },
|
||||
{ label: 'Delete Form', id: 'delete_form' },
|
||||
{ label: 'Externalise Form', id: 'externalise_form' },
|
||||
{ label: 'Internalise Form', id: 'internalise_form' },
|
||||
{ label: 'Copy Forms', id: 'copy_forms' },
|
||||
],
|
||||
value: () => 'get_service_desks',
|
||||
},
|
||||
@@ -195,6 +205,15 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
||||
'get_approvals',
|
||||
'answer_approval',
|
||||
'get_issue_forms',
|
||||
'attach_form',
|
||||
'save_form_answers',
|
||||
'submit_form',
|
||||
'get_form',
|
||||
'get_form_answers',
|
||||
'reopen_form',
|
||||
'delete_form',
|
||||
'externalise_form',
|
||||
'internalise_form',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -211,8 +230,54 @@ export const JiraServiceManagementBlock: BlockConfig<JsmResponse> = {
|
||||
title: 'Form ID',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Enter form ID (UUID from Get Form Templates)',
|
||||
condition: { field: 'operation', value: 'get_form_structure' },
|
||||
placeholder: 'Enter form ID (UUID from Get Form Templates or Attach Form)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'get_form_structure',
|
||||
'save_form_answers',
|
||||
'submit_form',
|
||||
'get_form',
|
||||
'get_form_answers',
|
||||
'reopen_form',
|
||||
'delete_form',
|
||||
'externalise_form',
|
||||
'internalise_form',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'formTemplateId',
|
||||
title: 'Form Template ID',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Enter form template UUID (from Get Form Templates)',
|
||||
condition: { field: 'operation', value: 'attach_form' },
|
||||
},
|
||||
{
|
||||
id: 'sourceIssueIdOrKey',
|
||||
title: 'Source Issue ID or Key',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Issue to copy forms from (e.g., SD-123)',
|
||||
condition: { field: 'operation', value: 'copy_forms' },
|
||||
},
|
||||
{
|
||||
id: 'targetIssueIdOrKey',
|
||||
title: 'Target Issue ID or Key',
|
||||
type: 'short-input',
|
||||
required: true,
|
||||
placeholder: 'Issue to copy forms to (e.g., SD-456)',
|
||||
condition: { field: 'operation', value: 'copy_forms' },
|
||||
},
|
||||
{
|
||||
id: 'formIds',
|
||||
title: 'Form IDs to Copy',
|
||||
type: 'long-input',
|
||||
placeholder:
|
||||
'JSON array of form UUIDs (e.g., ["uuid1", "uuid2"]). Leave empty to copy all forms.',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'copy_forms' },
|
||||
},
|
||||
{
|
||||
id: 'summary',
|
||||
@@ -291,8 +356,8 @@ Return ONLY the description text - no explanations.`,
|
||||
type: 'long-input',
|
||||
placeholder:
|
||||
'JSON object using form question IDs as keys (e.g., {"1": {"text": "Title"}, "4": {"choices": ["5"]}, "14": {"text": "Details"}})',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_request' },
|
||||
required: { field: 'operation', value: 'save_form_answers' },
|
||||
condition: { field: 'operation', value: ['create_request', 'save_form_answers'] },
|
||||
},
|
||||
{
|
||||
id: 'searchQuery',
|
||||
@@ -526,6 +591,16 @@ Return ONLY the comment text - no explanations.`,
|
||||
'jsm_get_form_templates',
|
||||
'jsm_get_form_structure',
|
||||
'jsm_get_issue_forms',
|
||||
'jsm_attach_form',
|
||||
'jsm_save_form_answers',
|
||||
'jsm_submit_form',
|
||||
'jsm_get_form',
|
||||
'jsm_get_form_answers',
|
||||
'jsm_reopen_form',
|
||||
'jsm_delete_form',
|
||||
'jsm_externalise_form',
|
||||
'jsm_internalise_form',
|
||||
'jsm_copy_forms',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
@@ -578,6 +653,26 @@ Return ONLY the comment text - no explanations.`,
|
||||
return 'jsm_get_form_structure'
|
||||
case 'get_issue_forms':
|
||||
return 'jsm_get_issue_forms'
|
||||
case 'attach_form':
|
||||
return 'jsm_attach_form'
|
||||
case 'save_form_answers':
|
||||
return 'jsm_save_form_answers'
|
||||
case 'submit_form':
|
||||
return 'jsm_submit_form'
|
||||
case 'get_form':
|
||||
return 'jsm_get_form'
|
||||
case 'get_form_answers':
|
||||
return 'jsm_get_form_answers'
|
||||
case 'reopen_form':
|
||||
return 'jsm_reopen_form'
|
||||
case 'delete_form':
|
||||
return 'jsm_delete_form'
|
||||
case 'externalise_form':
|
||||
return 'jsm_externalise_form'
|
||||
case 'internalise_form':
|
||||
return 'jsm_internalise_form'
|
||||
case 'copy_forms':
|
||||
return 'jsm_copy_forms'
|
||||
default:
|
||||
return 'jsm_get_service_desks'
|
||||
}
|
||||
@@ -865,6 +960,151 @@ Return ONLY the comment text - no explanations.`,
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
}
|
||||
case 'attach_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formTemplateId) {
|
||||
throw new Error('Form template ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formTemplateId: params.formTemplateId,
|
||||
}
|
||||
case 'save_form_answers':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
if (!params.formAnswers) {
|
||||
throw new Error('Form answers are required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
answers: (() => {
|
||||
try {
|
||||
return JSON.parse(params.formAnswers)
|
||||
} catch {
|
||||
throw new Error('formAnswers must be valid JSON')
|
||||
}
|
||||
})(),
|
||||
}
|
||||
case 'submit_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'get_form_answers':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'reopen_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'get_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'delete_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'externalise_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'internalise_form':
|
||||
if (!params.issueIdOrKey) {
|
||||
throw new Error('Issue ID or key is required')
|
||||
}
|
||||
if (!params.formId) {
|
||||
throw new Error('Form ID is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}
|
||||
case 'copy_forms':
|
||||
if (!params.sourceIssueIdOrKey) {
|
||||
throw new Error('Source issue ID or key is required')
|
||||
}
|
||||
if (!params.targetIssueIdOrKey) {
|
||||
throw new Error('Target issue ID or key is required')
|
||||
}
|
||||
return {
|
||||
...baseParams,
|
||||
sourceIssueIdOrKey: params.sourceIssueIdOrKey,
|
||||
targetIssueIdOrKey: params.targetIssueIdOrKey,
|
||||
formIds: params.formIds
|
||||
? (() => {
|
||||
try {
|
||||
const parsed = JSON.parse(params.formIds)
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error('formIds must be a JSON array')
|
||||
}
|
||||
return parsed
|
||||
} catch (e) {
|
||||
throw e instanceof Error && e.message === 'formIds must be a JSON array'
|
||||
? e
|
||||
: new Error('formIds must be valid JSON array')
|
||||
}
|
||||
})()
|
||||
: undefined,
|
||||
}
|
||||
default:
|
||||
return baseParams
|
||||
}
|
||||
@@ -916,6 +1156,10 @@ Return ONLY the comment text - no explanations.`,
|
||||
},
|
||||
projectIdOrKey: { type: 'string', description: 'Jira project ID or key' },
|
||||
formId: { type: 'string', description: 'Form ID (UUID)' },
|
||||
formTemplateId: { type: 'string', description: 'Form template UUID' },
|
||||
sourceIssueIdOrKey: { type: 'string', description: 'Source issue ID or key for copy' },
|
||||
targetIssueIdOrKey: { type: 'string', description: 'Target issue ID or key for copy' },
|
||||
formIds: { type: 'string', description: 'JSON array of form UUIDs to copy' },
|
||||
searchQuery: { type: 'string', description: 'Filter request types by name' },
|
||||
groupId: { type: 'string', description: 'Filter by request type group ID' },
|
||||
expand: { type: 'string', description: 'Comma-separated fields to expand' },
|
||||
@@ -978,5 +1222,28 @@ Return ONLY the comment text - no explanations.`,
|
||||
description:
|
||||
'Array of forms attached to an issue (id, name, updated, submitted, lock, internal, formTemplateId)',
|
||||
},
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
formTemplateId: { type: 'string', description: 'Form template ID' },
|
||||
submitted: { type: 'boolean', description: 'Whether the form has been submitted' },
|
||||
lock: { type: 'boolean', description: 'Whether the form is locked' },
|
||||
internal: { type: 'boolean', description: 'Whether the form is internal only' },
|
||||
state: {
|
||||
type: 'json',
|
||||
description: 'Form state with status (open, submitted, locked)',
|
||||
},
|
||||
status: { type: 'string', description: 'Form status (open, submitted, locked)' },
|
||||
answers: {
|
||||
type: 'json',
|
||||
description: 'Form answers as key-value pairs (question ID to answer)',
|
||||
},
|
||||
deleted: { type: 'boolean', description: 'Whether the form was successfully deleted' },
|
||||
visibility: {
|
||||
type: 'string',
|
||||
description: 'Form visibility (internal or external)',
|
||||
},
|
||||
copiedForms: { type: 'json', description: 'Array of successfully copied forms' },
|
||||
errors: { type: 'json', description: 'Array of errors from copy forms operation' },
|
||||
sourceIssueIdOrKey: { type: 'string', description: 'Source issue ID or key' },
|
||||
targetIssueIdOrKey: { type: 'string', description: 'Target issue ID or key' },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -495,7 +495,6 @@ export const URL_OUTPUT: OutputProperty = {
|
||||
description: 'URL to view in Confluence',
|
||||
}
|
||||
|
||||
// Page operations
|
||||
export interface ConfluenceRetrieveParams {
|
||||
accessToken: string
|
||||
pageId: string
|
||||
@@ -573,7 +572,6 @@ export interface ConfluenceDeletePageResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Search operations
|
||||
export interface ConfluenceSearchParams {
|
||||
accessToken: string
|
||||
domain: string
|
||||
@@ -595,7 +593,6 @@ export interface ConfluenceSearchResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Comment operations
|
||||
export interface ConfluenceCommentParams {
|
||||
accessToken: string
|
||||
domain: string
|
||||
@@ -612,7 +609,6 @@ export interface ConfluenceCommentResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Attachment operations
|
||||
export interface ConfluenceAttachmentParams {
|
||||
accessToken: string
|
||||
domain: string
|
||||
@@ -659,7 +655,6 @@ export interface ConfluenceUploadAttachmentResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Label operations
|
||||
export interface ConfluenceLabelParams {
|
||||
accessToken: string
|
||||
domain: string
|
||||
@@ -683,7 +678,6 @@ export interface ConfluenceLabelResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// Space operations
|
||||
export interface ConfluenceSpaceParams {
|
||||
accessToken: string
|
||||
domain: string
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { JsmAttachFormParams, JsmAttachFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmAttachFormTool: ToolConfig<JsmAttachFormParams, JsmAttachFormResponse> = {
|
||||
id: 'jsm_attach_form',
|
||||
name: 'JSM Attach Form',
|
||||
description: 'Attach a form template to an existing Jira issue or JSM request',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key to attach the form to (e.g., "SD-123")',
|
||||
},
|
||||
formTemplateId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form template UUID (from Get Form Templates)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/attach',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formTemplateId: params.formTemplateId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
id: '',
|
||||
name: '',
|
||||
updated: null,
|
||||
submitted: false,
|
||||
lock: false,
|
||||
internal: null,
|
||||
formTemplateId: null,
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
id: '',
|
||||
name: '',
|
||||
updated: null,
|
||||
submitted: false,
|
||||
lock: false,
|
||||
internal: null,
|
||||
formTemplateId: null,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
id: { type: 'string', description: 'Attached form instance ID (UUID)' },
|
||||
name: { type: 'string', description: 'Form name' },
|
||||
updated: { type: 'string', description: 'Last updated timestamp', optional: true },
|
||||
submitted: { type: 'boolean', description: 'Whether the form has been submitted' },
|
||||
lock: { type: 'boolean', description: 'Whether the form is locked' },
|
||||
internal: { type: 'boolean', description: 'Whether the form is internal only', optional: true },
|
||||
formTemplateId: {
|
||||
type: 'string',
|
||||
description: 'Form template ID',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { JsmCopyFormsParams, JsmCopyFormsResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmCopyFormsTool: ToolConfig<JsmCopyFormsParams, JsmCopyFormsResponse> = {
|
||||
id: 'jsm_copy_forms',
|
||||
name: 'JSM Copy Forms',
|
||||
description: 'Copy forms from one Jira issue to another',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'jira' },
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
sourceIssueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Source issue ID or key to copy forms from (e.g., "SD-123")',
|
||||
},
|
||||
targetIssueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Target issue ID or key to copy forms to (e.g., "SD-456")',
|
||||
},
|
||||
formIds: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Optional JSON array of form UUIDs to copy (e.g., ["uuid1", "uuid2"]). If omitted, copies all forms.',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/copy',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
sourceIssueIdOrKey: params.sourceIssueIdOrKey,
|
||||
targetIssueIdOrKey: params.targetIssueIdOrKey,
|
||||
formIds: params.formIds,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
sourceIssueIdOrKey: '',
|
||||
targetIssueIdOrKey: '',
|
||||
copiedForms: [],
|
||||
errors: [],
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
const data = JSON.parse(responseText)
|
||||
if (data.success && data.output) return data
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
sourceIssueIdOrKey: '',
|
||||
targetIssueIdOrKey: '',
|
||||
copiedForms: [],
|
||||
errors: [],
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
sourceIssueIdOrKey: { type: 'string', description: 'Source issue ID or key' },
|
||||
targetIssueIdOrKey: { type: 'string', description: 'Target issue ID or key' },
|
||||
copiedForms: { type: 'json', description: 'Array of successfully copied forms' },
|
||||
errors: { type: 'json', description: 'Array of errors encountered during copy' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { JsmDeleteFormParams, JsmDeleteFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmDeleteFormTool: ToolConfig<JsmDeleteFormParams, JsmDeleteFormResponse> = {
|
||||
id: 'jsm_delete_form',
|
||||
name: 'JSM Delete Form',
|
||||
description: 'Remove a form from a Jira issue or JSM request',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'jira' },
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID to delete',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/delete',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: { ts: new Date().toISOString(), issueIdOrKey: '', formId: '', deleted: false },
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
const data = JSON.parse(responseText)
|
||||
if (data.success && data.output) return data
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
deleted: false,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Deleted form instance UUID' },
|
||||
deleted: { type: 'boolean', description: 'Whether the form was successfully deleted' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { JsmExternaliseFormParams, JsmExternaliseFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmExternaliseFormTool: ToolConfig<
|
||||
JsmExternaliseFormParams,
|
||||
JsmExternaliseFormResponse
|
||||
> = {
|
||||
id: 'jsm_externalise_form',
|
||||
name: 'JSM Externalise Form',
|
||||
description: 'Make a form visible to customers on a Jira issue or JSM request',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'jira' },
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/externalise',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: { ts: new Date().toISOString(), issueIdOrKey: '', formId: '', visibility: '' },
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
const data = JSON.parse(responseText)
|
||||
if (data.success && data.output) return data
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
visibility: '',
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
visibility: {
|
||||
type: 'string',
|
||||
description: 'Form visibility after change (internal or external)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { JsmGetFormParams, JsmGetFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmGetFormTool: ToolConfig<JsmGetFormParams, JsmGetFormResponse> = {
|
||||
id: 'jsm_get_form',
|
||||
name: 'JSM Get Form',
|
||||
description: 'Get a single form with full design, state, and answers from a Jira issue',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID (from Attach Form or Get Issue Forms)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/get',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
design: null,
|
||||
state: null,
|
||||
updated: null,
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
design: null,
|
||||
state: null,
|
||||
updated: null,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
design: {
|
||||
type: 'json',
|
||||
description: 'Full form design with questions, layout, conditions, sections, settings',
|
||||
optional: true,
|
||||
},
|
||||
state: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Form state with answers map, status (o=open, s=submitted, l=locked), visibility (i=internal, e=external)',
|
||||
optional: true,
|
||||
},
|
||||
updated: {
|
||||
type: 'string',
|
||||
description: 'Last updated timestamp',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import type { JsmGetFormAnswersParams, JsmGetFormAnswersResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmGetFormAnswersTool: ToolConfig<JsmGetFormAnswersParams, JsmGetFormAnswersResponse> =
|
||||
{
|
||||
id: 'jsm_get_form_answers',
|
||||
name: 'JSM Get Form Answers',
|
||||
description: 'Get simplified answers from a form attached to a Jira issue or JSM request',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID (from Attach Form or Get Issue Forms)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/answers',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
answers: null,
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
answers: null,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
answers: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Simplified form answers as key-value pairs (question label to answer text/choices)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -3,11 +3,17 @@ import { jsmAddCustomerTool } from '@/tools/jsm/add_customer'
|
||||
import { jsmAddOrganizationTool } from '@/tools/jsm/add_organization'
|
||||
import { jsmAddParticipantsTool } from '@/tools/jsm/add_participants'
|
||||
import { jsmAnswerApprovalTool } from '@/tools/jsm/answer_approval'
|
||||
import { jsmAttachFormTool } from '@/tools/jsm/attach_form'
|
||||
import { jsmCopyFormsTool } from '@/tools/jsm/copy_forms'
|
||||
import { jsmCreateOrganizationTool } from '@/tools/jsm/create_organization'
|
||||
import { jsmCreateRequestTool } from '@/tools/jsm/create_request'
|
||||
import { jsmDeleteFormTool } from '@/tools/jsm/delete_form'
|
||||
import { jsmExternaliseFormTool } from '@/tools/jsm/externalise_form'
|
||||
import { jsmGetApprovalsTool } from '@/tools/jsm/get_approvals'
|
||||
import { jsmGetCommentsTool } from '@/tools/jsm/get_comments'
|
||||
import { jsmGetCustomersTool } from '@/tools/jsm/get_customers'
|
||||
import { jsmGetFormTool } from '@/tools/jsm/get_form'
|
||||
import { jsmGetFormAnswersTool } from '@/tools/jsm/get_form_answers'
|
||||
import { jsmGetFormStructureTool } from '@/tools/jsm/get_form_structure'
|
||||
import { jsmGetFormTemplatesTool } from '@/tools/jsm/get_form_templates'
|
||||
import { jsmGetIssueFormsTool } from '@/tools/jsm/get_issue_forms'
|
||||
@@ -21,6 +27,10 @@ import { jsmGetRequestsTool } from '@/tools/jsm/get_requests'
|
||||
import { jsmGetServiceDesksTool } from '@/tools/jsm/get_service_desks'
|
||||
import { jsmGetSlaTool } from '@/tools/jsm/get_sla'
|
||||
import { jsmGetTransitionsTool } from '@/tools/jsm/get_transitions'
|
||||
import { jsmInternaliseFormTool } from '@/tools/jsm/internalise_form'
|
||||
import { jsmReopenFormTool } from '@/tools/jsm/reopen_form'
|
||||
import { jsmSaveFormAnswersTool } from '@/tools/jsm/save_form_answers'
|
||||
import { jsmSubmitFormTool } from '@/tools/jsm/submit_form'
|
||||
import { jsmTransitionRequestTool } from '@/tools/jsm/transition_request'
|
||||
|
||||
export {
|
||||
@@ -29,11 +39,17 @@ export {
|
||||
jsmAddOrganizationTool,
|
||||
jsmAddParticipantsTool,
|
||||
jsmAnswerApprovalTool,
|
||||
jsmAttachFormTool,
|
||||
jsmCopyFormsTool,
|
||||
jsmCreateOrganizationTool,
|
||||
jsmCreateRequestTool,
|
||||
jsmDeleteFormTool,
|
||||
jsmExternaliseFormTool,
|
||||
jsmGetApprovalsTool,
|
||||
jsmGetCommentsTool,
|
||||
jsmGetCustomersTool,
|
||||
jsmGetFormTool,
|
||||
jsmGetFormAnswersTool,
|
||||
jsmGetFormStructureTool,
|
||||
jsmGetFormTemplatesTool,
|
||||
jsmGetIssueFormsTool,
|
||||
@@ -47,5 +63,9 @@ export {
|
||||
jsmGetServiceDesksTool,
|
||||
jsmGetSlaTool,
|
||||
jsmGetTransitionsTool,
|
||||
jsmInternaliseFormTool,
|
||||
jsmReopenFormTool,
|
||||
jsmSaveFormAnswersTool,
|
||||
jsmSubmitFormTool,
|
||||
jsmTransitionRequestTool,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { JsmInternaliseFormParams, JsmInternaliseFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmInternaliseFormTool: ToolConfig<
|
||||
JsmInternaliseFormParams,
|
||||
JsmInternaliseFormResponse
|
||||
> = {
|
||||
id: 'jsm_internalise_form',
|
||||
name: 'JSM Internalise Form',
|
||||
description:
|
||||
'Make a form internal only (not visible to customers) on a Jira issue or JSM request',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'jira' },
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID',
|
||||
},
|
||||
},
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/internalise',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: { ts: new Date().toISOString(), issueIdOrKey: '', formId: '', visibility: '' },
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
const data = JSON.parse(responseText)
|
||||
if (data.success && data.output) return data
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
visibility: '',
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
visibility: {
|
||||
type: 'string',
|
||||
description: 'Form visibility after change (internal or external)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { JsmReopenFormParams, JsmReopenFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmReopenFormTool: ToolConfig<JsmReopenFormParams, JsmReopenFormResponse> = {
|
||||
id: 'jsm_reopen_form',
|
||||
name: 'JSM Reopen Form',
|
||||
description: 'Reopen a submitted form on a Jira issue or JSM request, allowing further edits',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID (from Get Issue Forms)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/reopen',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
status: '',
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
status: '',
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Form status after reopening (open, submitted, locked)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { JsmSaveFormAnswersParams, JsmSaveFormAnswersResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmSaveFormAnswersTool: ToolConfig<
|
||||
JsmSaveFormAnswersParams,
|
||||
JsmSaveFormAnswersResponse
|
||||
> = {
|
||||
id: 'jsm_save_form_answers',
|
||||
name: 'JSM Save Form Answers',
|
||||
description: 'Save answers to a form attached to a Jira issue or JSM request',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID (from Attach Form or Get Issue Forms)',
|
||||
},
|
||||
answers: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Form answers using numeric question IDs as keys (e.g., {"1": {"text": "Title"}, "4": {"choices": ["5"]}})',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/save',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
answers: params.answers,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
state: null,
|
||||
updated: null,
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
state: null,
|
||||
updated: null,
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
state: {
|
||||
type: 'json',
|
||||
description: 'Form state with status (open, submitted, locked)',
|
||||
optional: true,
|
||||
},
|
||||
updated: { type: 'string', description: 'Last updated timestamp', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { JsmSubmitFormParams, JsmSubmitFormResponse } from '@/tools/jsm/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const jsmSubmitFormTool: ToolConfig<JsmSubmitFormParams, JsmSubmitFormResponse> = {
|
||||
id: 'jsm_submit_form',
|
||||
name: 'JSM Submit Form',
|
||||
description: 'Submit a form on a Jira issue or JSM request, locking it from further edits',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'jira',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token for Jira Service Management',
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
|
||||
},
|
||||
cloudId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Jira Cloud ID for the instance',
|
||||
},
|
||||
issueIdOrKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue ID or key (e.g., "SD-123")',
|
||||
},
|
||||
formId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Form instance UUID (from Attach Form or Get Issue Forms)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/jsm/forms/submit',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
domain: params.domain,
|
||||
accessToken: params.accessToken,
|
||||
cloudId: params.cloudId,
|
||||
issueIdOrKey: params.issueIdOrKey,
|
||||
formId: params.formId,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const responseText = await response.text()
|
||||
|
||||
if (!responseText) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
status: '',
|
||||
},
|
||||
error: 'Empty response from API',
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.parse(responseText)
|
||||
|
||||
if (data.success && data.output) {
|
||||
return data
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success || false,
|
||||
output: data.output || {
|
||||
ts: new Date().toISOString(),
|
||||
issueIdOrKey: '',
|
||||
formId: '',
|
||||
status: '',
|
||||
},
|
||||
error: data.error,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ts: { type: 'string', description: 'Timestamp of the operation' },
|
||||
issueIdOrKey: { type: 'string', description: 'Issue ID or key' },
|
||||
formId: { type: 'string', description: 'Form instance UUID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Form status after submission (open, submitted, locked)',
|
||||
},
|
||||
},
|
||||
}
|
||||
+164
-19
@@ -1,9 +1,5 @@
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared output property constants for JSM tools (following Confluence pattern)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Reusable date output properties with ISO 8601, friendly, and epoch formats */
|
||||
export const DATE_OUTPUT_PROPERTIES = {
|
||||
iso8601: { type: 'string', description: 'ISO 8601 formatted date' },
|
||||
@@ -260,10 +256,6 @@ export const ISSUE_FORM_PROPERTIES = {
|
||||
},
|
||||
} as const
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Data model interfaces
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Common parameters for all JSM API calls */
|
||||
export interface JsmBaseParams {
|
||||
accessToken: string
|
||||
@@ -427,10 +419,6 @@ export interface JsmRequestTypeField {
|
||||
jiraSchema: { type: string; system?: string; custom?: string; customId?: number }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Params interfaces
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface JsmGetServiceDesksParams extends JsmBaseParams {
|
||||
expand?: string
|
||||
start?: number
|
||||
@@ -570,10 +558,6 @@ export interface JsmGetRequestTypeFieldsParams extends JsmBaseParams {
|
||||
requestTypeId: string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Response interfaces
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface JsmGetServiceDesksResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
@@ -899,9 +883,160 @@ export interface JsmGetIssueFormsResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Union type for all JSM responses
|
||||
// ---------------------------------------------------------------------------
|
||||
export interface JsmAttachFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formTemplateId: string
|
||||
}
|
||||
|
||||
export interface JsmAttachFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
id: string
|
||||
name: string
|
||||
updated: string | null
|
||||
submitted: boolean
|
||||
lock: boolean
|
||||
internal: boolean | null
|
||||
formTemplateId: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmSaveFormAnswersParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
answers: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface JsmSaveFormAnswersResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
state: { status: string } | null
|
||||
updated: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmSubmitFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmSubmitFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
status: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmGetFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmGetFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
design: Record<string, unknown> | null
|
||||
state: {
|
||||
answers: Record<string, unknown>
|
||||
status: string
|
||||
visibility: string
|
||||
} | null
|
||||
updated: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmDeleteFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmDeleteFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmExternaliseFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmExternaliseFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
visibility: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmInternaliseFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmInternaliseFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
visibility: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmCopyFormsParams extends JsmBaseParams {
|
||||
sourceIssueIdOrKey: string
|
||||
targetIssueIdOrKey: string
|
||||
formIds?: string[]
|
||||
}
|
||||
|
||||
export interface JsmCopyFormsResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
sourceIssueIdOrKey: string
|
||||
targetIssueIdOrKey: string
|
||||
copiedForms: Array<Record<string, unknown>>
|
||||
errors: Array<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmGetFormAnswersParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmGetFormAnswersResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
answers: Record<string, unknown> | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsmReopenFormParams extends JsmBaseParams {
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
}
|
||||
|
||||
export interface JsmReopenFormResponse extends ToolResponse {
|
||||
output: {
|
||||
ts: string
|
||||
issueIdOrKey: string
|
||||
formId: string
|
||||
status: string
|
||||
}
|
||||
}
|
||||
|
||||
/** Union type for all JSM responses */
|
||||
export type JsmResponse =
|
||||
@@ -929,3 +1064,13 @@ export type JsmResponse =
|
||||
| JsmGetFormTemplatesResponse
|
||||
| JsmGetFormStructureResponse
|
||||
| JsmGetIssueFormsResponse
|
||||
| JsmAttachFormResponse
|
||||
| JsmSaveFormAnswersResponse
|
||||
| JsmSubmitFormResponse
|
||||
| JsmGetFormResponse
|
||||
| JsmDeleteFormResponse
|
||||
| JsmExternaliseFormResponse
|
||||
| JsmInternaliseFormResponse
|
||||
| JsmCopyFormsResponse
|
||||
| JsmGetFormAnswersResponse
|
||||
| JsmReopenFormResponse
|
||||
|
||||
@@ -1326,13 +1326,19 @@ import {
|
||||
jsmAddOrganizationTool,
|
||||
jsmAddParticipantsTool,
|
||||
jsmAnswerApprovalTool,
|
||||
jsmAttachFormTool,
|
||||
jsmCopyFormsTool,
|
||||
jsmCreateOrganizationTool,
|
||||
jsmCreateRequestTool,
|
||||
jsmDeleteFormTool,
|
||||
jsmExternaliseFormTool,
|
||||
jsmGetApprovalsTool,
|
||||
jsmGetCommentsTool,
|
||||
jsmGetCustomersTool,
|
||||
jsmGetFormAnswersTool,
|
||||
jsmGetFormStructureTool,
|
||||
jsmGetFormTemplatesTool,
|
||||
jsmGetFormTool,
|
||||
jsmGetIssueFormsTool,
|
||||
jsmGetOrganizationsTool,
|
||||
jsmGetParticipantsTool,
|
||||
@@ -1344,6 +1350,10 @@ import {
|
||||
jsmGetServiceDesksTool,
|
||||
jsmGetSlaTool,
|
||||
jsmGetTransitionsTool,
|
||||
jsmInternaliseFormTool,
|
||||
jsmReopenFormTool,
|
||||
jsmSaveFormAnswersTool,
|
||||
jsmSubmitFormTool,
|
||||
jsmTransitionRequestTool,
|
||||
} from '@/tools/jsm'
|
||||
import {
|
||||
@@ -3153,9 +3163,19 @@ export const tools: Record<string, ToolConfig> = {
|
||||
jsm_add_participants: jsmAddParticipantsTool,
|
||||
jsm_get_approvals: jsmGetApprovalsTool,
|
||||
jsm_answer_approval: jsmAnswerApprovalTool,
|
||||
jsm_get_form: jsmGetFormTool,
|
||||
jsm_get_form_answers: jsmGetFormAnswersTool,
|
||||
jsm_get_form_templates: jsmGetFormTemplatesTool,
|
||||
jsm_get_form_structure: jsmGetFormStructureTool,
|
||||
jsm_get_issue_forms: jsmGetIssueFormsTool,
|
||||
jsm_attach_form: jsmAttachFormTool,
|
||||
jsm_save_form_answers: jsmSaveFormAnswersTool,
|
||||
jsm_submit_form: jsmSubmitFormTool,
|
||||
jsm_reopen_form: jsmReopenFormTool,
|
||||
jsm_delete_form: jsmDeleteFormTool,
|
||||
jsm_externalise_form: jsmExternaliseFormTool,
|
||||
jsm_internalise_form: jsmInternaliseFormTool,
|
||||
jsm_copy_forms: jsmCopyFormsTool,
|
||||
kalshi_get_markets: kalshiGetMarketsTool,
|
||||
kalshi_get_markets_v2: kalshiGetMarketsV2Tool,
|
||||
kalshi_get_market: kalshiGetMarketTool,
|
||||
|
||||
Reference in New Issue
Block a user