diff --git a/apps/docs/content/docs/en/tools/jira.mdx b/apps/docs/content/docs/en/tools/jira.mdx index 5a89a6173b6..db433a3d4d9 100644 --- a/apps/docs/content/docs/en/tools/jira.mdx +++ b/apps/docs/content/docs/en/tools/jira.mdx @@ -1014,4 +1014,36 @@ Get Jira users. If an account ID is provided, returns a single user. Otherwise, | `startAt` | number | Pagination start index | | `maxResults` | number | Maximum results per page | +### `jira_search_users` + +Search for Jira users by email address or display name. Returns matching users with their accountId, displayName, and emailAddress. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) | +| `query` | string | Yes | A query string to search for users. Can be an email address, display name, or partial match. | +| `maxResults` | number | No | Maximum number of users to return \(default: 50, max: 1000\) | +| `startAt` | number | No | The index of the first user to return \(for pagination, default: 0\) | +| `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 | ISO 8601 timestamp of the operation | +| `users` | array | Array of matching Jira users | +| ↳ `accountId` | string | Atlassian account ID of the user | +| ↳ `displayName` | string | Display name of the user | +| ↳ `active` | boolean | Whether the user account is active | +| ↳ `emailAddress` | string | Email address of the user | +| ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | +| ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | +| ↳ `timeZone` | string | User timezone | +| ↳ `self` | string | REST API URL for this user | +| `total` | number | Number of users returned in this page \(may be less than total matches\) | +| `startAt` | number | Pagination start index | +| `maxResults` | number | Maximum results per page | + diff --git a/apps/sim/blocks/blocks/jira.ts b/apps/sim/blocks/blocks/jira.ts index 3d7f0d8ede9..a75b7c7b85c 100644 --- a/apps/sim/blocks/blocks/jira.ts +++ b/apps/sim/blocks/blocks/jira.ts @@ -47,6 +47,7 @@ export const JiraBlock: BlockConfig = { { label: 'Add Watcher', id: 'add_watcher' }, { label: 'Remove Watcher', id: 'remove_watcher' }, { label: 'Get Users', id: 'get_users' }, + { label: 'Search Users', id: 'search_users' }, ], value: () => 'read', }, @@ -673,6 +674,31 @@ Return ONLY the comment text - no explanations.`, placeholder: 'Maximum users to return (default: 50)', condition: { field: 'operation', value: 'get_users' }, }, + // Search Users fields + { + id: 'searchUsersQuery', + title: 'Search Query', + type: 'short-input', + required: true, + placeholder: 'Enter email address or display name to search', + condition: { field: 'operation', value: 'search_users' }, + }, + { + id: 'searchUsersMaxResults', + title: 'Max Results', + type: 'short-input', + placeholder: 'Maximum users to return (default: 50)', + condition: { field: 'operation', value: 'search_users' }, + mode: 'advanced', + }, + { + id: 'searchUsersStartAt', + title: 'Start At', + type: 'short-input', + placeholder: 'Pagination start index (default: 0)', + condition: { field: 'operation', value: 'search_users' }, + mode: 'advanced', + }, // Trigger SubBlocks ...getTrigger('jira_issue_created').subBlocks, ...getTrigger('jira_issue_updated').subBlocks, @@ -707,6 +733,7 @@ Return ONLY the comment text - no explanations.`, 'jira_add_watcher', 'jira_remove_watcher', 'jira_get_users', + 'jira_search_users', ], config: { tool: (params) => { @@ -767,6 +794,8 @@ Return ONLY the comment text - no explanations.`, return 'jira_remove_watcher' case 'get_users': return 'jira_get_users' + case 'search_users': + return 'jira_search_users' default: return 'jira_retrieve' } @@ -1023,6 +1052,18 @@ Return ONLY the comment text - no explanations.`, : undefined, } } + case 'search_users': { + return { + ...baseParams, + query: params.searchUsersQuery, + maxResults: params.searchUsersMaxResults + ? Number.parseInt(params.searchUsersMaxResults) + : undefined, + startAt: params.searchUsersStartAt + ? Number.parseInt(params.searchUsersStartAt) + : undefined, + } + } default: return baseParams } @@ -1102,6 +1143,13 @@ Return ONLY the comment text - no explanations.`, }, usersStartAt: { type: 'string', description: 'Pagination start index for users' }, usersMaxResults: { type: 'string', description: 'Maximum users to return' }, + // Search Users operation inputs + searchUsersQuery: { + type: 'string', + description: 'Search query (email address or display name)', + }, + searchUsersMaxResults: { type: 'string', description: 'Maximum users to return from search' }, + searchUsersStartAt: { type: 'string', description: 'Pagination start index for user search' }, }, outputs: { // Common outputs across all Jira operations diff --git a/apps/sim/tools/jira/index.ts b/apps/sim/tools/jira/index.ts index ced24d2d060..877fdb8b51d 100644 --- a/apps/sim/tools/jira/index.ts +++ b/apps/sim/tools/jira/index.ts @@ -17,6 +17,7 @@ import { jiraGetWorklogsTool } from '@/tools/jira/get_worklogs' import { jiraRemoveWatcherTool } from '@/tools/jira/remove_watcher' import { jiraRetrieveTool } from '@/tools/jira/retrieve' import { jiraSearchIssuesTool } from '@/tools/jira/search_issues' +import { jiraSearchUsersTool } from '@/tools/jira/search_users' import { jiraTransitionIssueTool } from '@/tools/jira/transition_issue' import { jiraUpdateTool } from '@/tools/jira/update' import { jiraUpdateCommentTool } from '@/tools/jira/update_comment' @@ -48,4 +49,5 @@ export { jiraAddWatcherTool, jiraRemoveWatcherTool, jiraGetUsersTool, + jiraSearchUsersTool, } diff --git a/apps/sim/tools/jira/search_users.ts b/apps/sim/tools/jira/search_users.ts new file mode 100644 index 00000000000..a0cc9dd49f0 --- /dev/null +++ b/apps/sim/tools/jira/search_users.ts @@ -0,0 +1,166 @@ +import type { JiraSearchUsersParams, JiraSearchUsersResponse } from '@/tools/jira/types' +import { TIMESTAMP_OUTPUT, USER_OUTPUT_PROPERTIES } from '@/tools/jira/types' +import { getJiraCloudId, transformUser } from '@/tools/jira/utils' +import type { ToolConfig } from '@/tools/types' + +export const jiraSearchUsersTool: ToolConfig = { + id: 'jira_search_users', + name: 'Jira Search Users', + description: + 'Search for Jira users by email address or display name. Returns matching users with their accountId, displayName, and emailAddress.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'jira', + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'OAuth access token for Jira', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Your Jira domain (e.g., yourcompany.atlassian.net)', + }, + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'A query string to search for users. Can be an email address, display name, or partial match.', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of users to return (default: 50, max: 1000)', + }, + startAt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'The index of the first user to return (for pagination, default: 0)', + }, + cloudId: { + type: 'string', + required: false, + visibility: 'hidden', + description: + 'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.', + }, + }, + + request: { + url: (params: JiraSearchUsersParams) => { + if (params.cloudId) { + const queryParams = new URLSearchParams() + queryParams.append('query', params.query) + if (params.maxResults !== undefined) + queryParams.append('maxResults', String(params.maxResults)) + if (params.startAt !== undefined) queryParams.append('startAt', String(params.startAt)) + return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/api/3/user/search?${queryParams.toString()}` + } + return 'https://api.atlassian.com/oauth/token/accessible-resources' + }, + method: 'GET', + headers: (params: JiraSearchUsersParams) => ({ + Accept: 'application/json', + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response, params?: JiraSearchUsersParams) => { + const fetchUsers = async (cloudId: string) => { + const queryParams = new URLSearchParams() + queryParams.append('query', params!.query) + if (params!.maxResults !== undefined) + queryParams.append('maxResults', String(params!.maxResults)) + if (params!.startAt !== undefined) queryParams.append('startAt', String(params!.startAt)) + + const usersUrl = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/user/search?${queryParams.toString()}` + + const usersResponse = await fetch(usersUrl, { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${params!.accessToken}`, + }, + }) + + if (!usersResponse.ok) { + let message = `Failed to search Jira users (${usersResponse.status})` + try { + const err = await usersResponse.json() + message = err?.errorMessages?.join(', ') || err?.message || message + } catch (_e) {} + throw new Error(message) + } + + return usersResponse.json() + } + + let data: any + + if (!params?.cloudId) { + const cloudId = await getJiraCloudId(params!.domain, params!.accessToken) + data = await fetchUsers(cloudId) + } else { + if (!response.ok) { + let message = `Failed to search Jira users (${response.status})` + try { + const err = await response.json() + message = err?.errorMessages?.join(', ') || err?.message || message + } catch (_e) {} + throw new Error(message) + } + data = await response.json() + } + + const users = Array.isArray(data) ? data.filter(Boolean) : [] + + return { + success: true, + output: { + ts: new Date().toISOString(), + users: users.map((user: any) => ({ + ...(transformUser(user) ?? { accountId: '', displayName: '' }), + self: user.self ?? null, + })), + total: users.length, + startAt: params?.startAt ?? 0, + maxResults: params?.maxResults ?? 50, + }, + } + }, + + outputs: { + ts: TIMESTAMP_OUTPUT, + users: { + type: 'array', + description: 'Array of matching Jira users', + items: { + type: 'object', + properties: { + ...USER_OUTPUT_PROPERTIES, + self: { + type: 'string', + description: 'REST API URL for this user', + optional: true, + }, + }, + }, + }, + total: { + type: 'number', + description: 'Number of users returned in this page (may be less than total matches)', + }, + startAt: { type: 'number', description: 'Pagination start index' }, + maxResults: { type: 'number', description: 'Maximum results per page' }, + }, +} diff --git a/apps/sim/tools/jira/types.ts b/apps/sim/tools/jira/types.ts index 527efa3a55f..74d98758196 100644 --- a/apps/sim/tools/jira/types.ts +++ b/apps/sim/tools/jira/types.ts @@ -1549,6 +1549,34 @@ export interface JiraGetUsersParams { cloudId?: string } +export interface JiraSearchUsersParams { + accessToken: string + domain: string + query: string + maxResults?: number + startAt?: number + cloudId?: string +} + +export interface JiraSearchUsersResponse extends ToolResponse { + output: { + ts: string + users: Array<{ + accountId: string + accountType?: string | null + active?: boolean | null + displayName: string + emailAddress?: string | null + avatarUrl?: string | null + timeZone?: string | null + self?: string | null + }> + total: number + startAt: number + maxResults: number + } +} + export interface JiraGetUsersResponse extends ToolResponse { output: { ts: string @@ -1594,3 +1622,4 @@ export type JiraResponse = | JiraAddWatcherResponse | JiraRemoveWatcherResponse | JiraGetUsersResponse + | JiraSearchUsersResponse diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 06abac79559..3539724f68d 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1085,6 +1085,7 @@ import { jiraRemoveWatcherTool, jiraRetrieveTool, jiraSearchIssuesTool, + jiraSearchUsersTool, jiraTransitionIssueTool, jiraUpdateCommentTool, jiraUpdateTool, @@ -2536,6 +2537,7 @@ export const tools: Record = { jira_add_watcher: jiraAddWatcherTool, jira_remove_watcher: jiraRemoveWatcherTool, jira_get_users: jiraGetUsersTool, + jira_search_users: jiraSearchUsersTool, jsm_get_service_desks: jsmGetServiceDesksTool, jsm_get_request_types: jsmGetRequestTypesTool, jsm_get_request_type_fields: jsmGetRequestTypeFieldsTool,