Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions apps/docs/content/docs/en/tools/jira.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,16 @@ Write a Jira issue
| `projectId` | string | Yes | Project ID for the issue |
| `summary` | string | Yes | Summary for the issue |
| `description` | string | No | Description for the issue |
| `priority` | string | No | Priority for the issue |
| `assignee` | string | No | Assignee for the issue |
| `priority` | string | No | Priority ID or name for the issue \(e.g., "10000" or "High"\) |
| `assignee` | string | No | Assignee account ID for the issue |
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
| `issueType` | string | Yes | Type of issue to create \(e.g., Task, Story\) |
| `labels` | array | No | Labels for the issue \(array of label names\) |
| `duedate` | string | No | Due date for the issue \(format: YYYY-MM-DD\) |
| `reporter` | string | No | Reporter account ID for the issue |
| `environment` | string | No | Environment information for the issue |
| `customFieldId` | string | No | Custom field ID \(e.g., customfield_10001\) |
| `customFieldValue` | string | No | Value for the custom field |

#### Output

Expand All @@ -110,6 +116,7 @@ Write a Jira issue
| `issueKey` | string | Created issue key \(e.g., PROJ-123\) |
| `summary` | string | Issue summary |
| `url` | string | URL to the created issue |
| `assigneeId` | string | Account ID of the assigned user \(if assigned\) |

### `jira_bulk_read`

Expand Down Expand Up @@ -523,6 +530,30 @@ Remove a watcher from a Jira issue
| `issueKey` | string | Issue key |
| `watcherAccountId` | string | Removed watcher account ID |

### `jira_get_users`

Get Jira users. If an account ID is provided, returns a single user. Otherwise, returns a list of all users.

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
| `accountId` | string | No | Optional account ID to get a specific user. If not provided, returns all users. |
| `startAt` | number | No | The index of the first user to return \(for pagination, default: 0\) |
| `maxResults` | number | No | Maximum number of users to return \(default: 50\) |
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |

#### Output

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `users` | json | Array of users with accountId, displayName, emailAddress, active status, and avatarUrls |
| `total` | number | Total number of users returned |
| `startAt` | number | Pagination start index |
| `maxResults` | number | Maximum results per page |



## Notes
Expand Down
93 changes: 85 additions & 8 deletions apps/sim/app/api/tools/jira/write/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export async function POST(request: Request) {
cloudId: providedCloudId,
issueType,
parent,
labels,
duedate,
reporter,
environment,
customFieldId,
customFieldValue,
} = await request.json()

if (!domain) {
Expand Down Expand Up @@ -94,17 +100,57 @@ export async function POST(request: Request) {
}

if (priority !== undefined && priority !== null && priority !== '') {
fields.priority = {
name: priority,
const isNumericId = /^\d+$/.test(priority)
fields.priority = isNumericId ? { id: priority } : { name: priority }
}

if (labels !== undefined && labels !== null && Array.isArray(labels) && labels.length > 0) {
fields.labels = labels
}

if (duedate !== undefined && duedate !== null && duedate !== '') {
fields.duedate = duedate
}

if (reporter !== undefined && reporter !== null && reporter !== '') {
fields.reporter = {
id: reporter,
}
}

if (assignee !== undefined && assignee !== null && assignee !== '') {
fields.assignee = {
id: assignee,
if (environment !== undefined && environment !== null && environment !== '') {
fields.environment = {
type: 'doc',
version: 1,
content: [
{
type: 'paragraph',
content: [
{
type: 'text',
text: environment,
},
],
},
],
}
}

if (
customFieldId !== undefined &&
customFieldId !== null &&
customFieldId !== '' &&
customFieldValue !== undefined &&
customFieldValue !== null &&
customFieldValue !== ''
) {
const fieldId = customFieldId.startsWith('customfield_')
? customFieldId
: `customfield_${customFieldId}`

fields[fieldId] = customFieldValue
}

const body = { fields }

const response = await fetch(url, {
Expand Down Expand Up @@ -132,16 +178,47 @@ export async function POST(request: Request) {
}

const responseData = await response.json()
logger.info('Successfully created Jira issue:', responseData.key)
const issueKey = responseData.key || 'unknown'
logger.info('Successfully created Jira issue:', issueKey)

let assigneeId: string | undefined
if (assignee !== undefined && assignee !== null && assignee !== '') {
const assignUrl = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/issue/${issueKey}/assignee`
logger.info('Assigning issue to:', assignee)

const assignResponse = await fetch(assignUrl, {
method: 'PUT',
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
accountId: assignee,
}),
})

if (!assignResponse.ok) {
const assignErrorText = await assignResponse.text()
logger.warn('Failed to assign issue (issue was created successfully):', {
status: assignResponse.status,
error: assignErrorText,
})
} else {
assigneeId = assignee
logger.info('Successfully assigned issue to:', assignee)
}
}

return NextResponse.json({
success: true,
output: {
ts: new Date().toISOString(),
issueKey: responseData.key || 'unknown',
issueKey: issueKey,
summary: responseData.fields?.summary || 'Issue created',
success: true,
url: `https://${domain}/browse/${responseData.key}`,
url: `https://${domain}/browse/${issueKey}`,
...(assigneeId && { assigneeId }),
},
})
} catch (error: any) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1214,31 +1214,25 @@ export const TagDropdown: React.FC<TagDropdownProps> = ({

let processedTag = tag

// Check if this is a file property and add [0] automatically
// Only include user-accessible fields (matches UserFile interface)
const fileProperties = ['id', 'name', 'url', 'size', 'type']
const parts = tag.split('.')
if (parts.length >= 2 && fileProperties.includes(parts[parts.length - 1])) {
const fieldName = parts[parts.length - 2]

if (blockGroup) {
const block = useWorkflowStore.getState().blocks[blockGroup.blockId]
const blockConfig = block ? (getBlock(block.type) ?? null) : null
const mergedSubBlocks = getMergedSubBlocks(blockGroup.blockId)

const fieldType = getOutputTypeForPath(
block,
blockConfig,
blockGroup.blockId,
fieldName,
mergedSubBlocks
)
if (parts.length >= 3 && blockGroup) {
const arrayFieldName = parts[1] // e.g., "channels", "files", "users"
const block = useWorkflowStore.getState().blocks[blockGroup.blockId]
const blockConfig = block ? (getBlock(block.type) ?? null) : null
const mergedSubBlocks = getMergedSubBlocks(blockGroup.blockId)

const fieldType = getOutputTypeForPath(
block,
blockConfig,
blockGroup.blockId,
arrayFieldName,
mergedSubBlocks
)

if (fieldType === 'files') {
const blockAndField = parts.slice(0, -1).join('.')
const property = parts[parts.length - 1]
processedTag = `${blockAndField}[0].${property}`
}
if (fieldType === 'files' || fieldType === 'array') {
const blockName = parts[0]
const remainingPath = parts.slice(2).join('.')
processedTag = `${blockName}.${arrayFieldName}[0].${remainingPath}`
}
}

Expand Down
Loading
Loading