Skip to content

Commit a78027a

Browse files
committed
feat(jira): add search_users tool for user lookup by email
1 parent a713042 commit a78027a

File tree

6 files changed

+281
-0
lines changed

6 files changed

+281
-0
lines changed

apps/docs/content/docs/en/tools/jira.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,4 +1014,34 @@ Get Jira users. If an account ID is provided, returns a single user. Otherwise,
10141014
| `startAt` | number | Pagination start index |
10151015
| `maxResults` | number | Maximum results per page |
10161016

1017+
### `jira_search_users`
1018+
1019+
Search for Jira users by email address or display name. Returns matching users with their accountId, displayName, and emailAddress.
1020+
1021+
#### Input
1022+
1023+
| Parameter | Type | Required | Description |
1024+
| --------- | ---- | -------- | ----------- |
1025+
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
1026+
| `query` | string | Yes | A query string to search for users. Can be an email address, display name, or partial match. |
1027+
| `maxResults` | number | No | Maximum number of users to return \(default: 50, max: 1000\) |
1028+
| `startAt` | number | No | The index of the first user to return \(for pagination, default: 0\) |
1029+
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
1030+
1031+
#### Output
1032+
1033+
| Parameter | Type | Description |
1034+
| --------- | ---- | ----------- |
1035+
| `ts` | string | ISO 8601 timestamp of the operation |
1036+
| `users` | array | Array of matching Jira users |
1037+
|`accountId` | string | Atlassian account ID of the user |
1038+
|`displayName` | string | Display name of the user |
1039+
|`active` | boolean | Whether the user account is active |
1040+
|`emailAddress` | string | Email address of the user |
1041+
|`accountType` | string | Type of account \(e.g., atlassian, app, customer\) |
1042+
|`avatarUrl` | string | URL to the user avatar \(48x48\) |
1043+
|`timeZone` | string | User timezone |
1044+
|`self` | string | REST API URL for this user |
1045+
| `total` | number | Total number of users returned |
1046+
10171047

apps/sim/blocks/blocks/jira.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
4747
{ label: 'Add Watcher', id: 'add_watcher' },
4848
{ label: 'Remove Watcher', id: 'remove_watcher' },
4949
{ label: 'Get Users', id: 'get_users' },
50+
{ label: 'Search Users', id: 'search_users' },
5051
],
5152
value: () => 'read',
5253
},
@@ -673,6 +674,31 @@ Return ONLY the comment text - no explanations.`,
673674
placeholder: 'Maximum users to return (default: 50)',
674675
condition: { field: 'operation', value: 'get_users' },
675676
},
677+
// Search Users fields
678+
{
679+
id: 'searchUsersQuery',
680+
title: 'Search Query',
681+
type: 'short-input',
682+
required: true,
683+
placeholder: 'Enter email address or display name to search',
684+
condition: { field: 'operation', value: 'search_users' },
685+
},
686+
{
687+
id: 'searchUsersMaxResults',
688+
title: 'Max Results',
689+
type: 'short-input',
690+
placeholder: 'Maximum users to return (default: 50)',
691+
condition: { field: 'operation', value: 'search_users' },
692+
mode: 'advanced',
693+
},
694+
{
695+
id: 'searchUsersStartAt',
696+
title: 'Start At',
697+
type: 'short-input',
698+
placeholder: 'Pagination start index (default: 0)',
699+
condition: { field: 'operation', value: 'search_users' },
700+
mode: 'advanced',
701+
},
676702
// Trigger SubBlocks
677703
...getTrigger('jira_issue_created').subBlocks,
678704
...getTrigger('jira_issue_updated').subBlocks,
@@ -707,6 +733,7 @@ Return ONLY the comment text - no explanations.`,
707733
'jira_add_watcher',
708734
'jira_remove_watcher',
709735
'jira_get_users',
736+
'jira_search_users',
710737
],
711738
config: {
712739
tool: (params) => {
@@ -767,6 +794,8 @@ Return ONLY the comment text - no explanations.`,
767794
return 'jira_remove_watcher'
768795
case 'get_users':
769796
return 'jira_get_users'
797+
case 'search_users':
798+
return 'jira_search_users'
770799
default:
771800
return 'jira_retrieve'
772801
}
@@ -1023,6 +1052,18 @@ Return ONLY the comment text - no explanations.`,
10231052
: undefined,
10241053
}
10251054
}
1055+
case 'search_users': {
1056+
return {
1057+
...baseParams,
1058+
query: params.searchUsersQuery,
1059+
maxResults: params.searchUsersMaxResults
1060+
? Number.parseInt(params.searchUsersMaxResults)
1061+
: undefined,
1062+
startAt: params.searchUsersStartAt
1063+
? Number.parseInt(params.searchUsersStartAt)
1064+
: undefined,
1065+
}
1066+
}
10261067
default:
10271068
return baseParams
10281069
}
@@ -1102,6 +1143,13 @@ Return ONLY the comment text - no explanations.`,
11021143
},
11031144
usersStartAt: { type: 'string', description: 'Pagination start index for users' },
11041145
usersMaxResults: { type: 'string', description: 'Maximum users to return' },
1146+
// Search Users operation inputs
1147+
searchUsersQuery: {
1148+
type: 'string',
1149+
description: 'Search query (email address or display name)',
1150+
},
1151+
searchUsersMaxResults: { type: 'string', description: 'Maximum users to return from search' },
1152+
searchUsersStartAt: { type: 'string', description: 'Pagination start index for user search' },
11051153
},
11061154
outputs: {
11071155
// Common outputs across all Jira operations

apps/sim/tools/jira/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { jiraGetWorklogsTool } from '@/tools/jira/get_worklogs'
1717
import { jiraRemoveWatcherTool } from '@/tools/jira/remove_watcher'
1818
import { jiraRetrieveTool } from '@/tools/jira/retrieve'
1919
import { jiraSearchIssuesTool } from '@/tools/jira/search_issues'
20+
import { jiraSearchUsersTool } from '@/tools/jira/search_users'
2021
import { jiraTransitionIssueTool } from '@/tools/jira/transition_issue'
2122
import { jiraUpdateTool } from '@/tools/jira/update'
2223
import { jiraUpdateCommentTool } from '@/tools/jira/update_comment'
@@ -48,4 +49,5 @@ export {
4849
jiraAddWatcherTool,
4950
jiraRemoveWatcherTool,
5051
jiraGetUsersTool,
52+
jiraSearchUsersTool,
5153
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import type { JiraSearchUsersParams, JiraSearchUsersResponse } from '@/tools/jira/types'
2+
import { TIMESTAMP_OUTPUT, USER_OUTPUT_PROPERTIES } from '@/tools/jira/types'
3+
import { getJiraCloudId } from '@/tools/jira/utils'
4+
import type { ToolConfig } from '@/tools/types'
5+
6+
/**
7+
* Transforms a raw Jira user API object into typed output.
8+
*/
9+
function transformUserOutput(user: any) {
10+
return {
11+
accountId: user.accountId ?? '',
12+
accountType: user.accountType ?? null,
13+
active: user.active ?? false,
14+
displayName: user.displayName ?? '',
15+
emailAddress: user.emailAddress ?? null,
16+
avatarUrl: user.avatarUrls?.['48x48'] ?? null,
17+
timeZone: user.timeZone ?? null,
18+
self: user.self ?? null,
19+
}
20+
}
21+
22+
export const jiraSearchUsersTool: ToolConfig<JiraSearchUsersParams, JiraSearchUsersResponse> = {
23+
id: 'jira_search_users',
24+
name: 'Jira Search Users',
25+
description:
26+
'Search for Jira users by email address or display name. Returns matching users with their accountId, displayName, and emailAddress.',
27+
version: '1.0.0',
28+
29+
oauth: {
30+
required: true,
31+
provider: 'jira',
32+
},
33+
34+
params: {
35+
accessToken: {
36+
type: 'string',
37+
required: true,
38+
visibility: 'hidden',
39+
description: 'OAuth access token for Jira',
40+
},
41+
domain: {
42+
type: 'string',
43+
required: true,
44+
visibility: 'user-only',
45+
description: 'Your Jira domain (e.g., yourcompany.atlassian.net)',
46+
},
47+
query: {
48+
type: 'string',
49+
required: true,
50+
visibility: 'user-or-llm',
51+
description:
52+
'A query string to search for users. Can be an email address, display name, or partial match.',
53+
},
54+
maxResults: {
55+
type: 'number',
56+
required: false,
57+
visibility: 'user-or-llm',
58+
description: 'Maximum number of users to return (default: 50, max: 1000)',
59+
},
60+
startAt: {
61+
type: 'number',
62+
required: false,
63+
visibility: 'user-or-llm',
64+
description: 'The index of the first user to return (for pagination, default: 0)',
65+
},
66+
cloudId: {
67+
type: 'string',
68+
required: false,
69+
visibility: 'hidden',
70+
description:
71+
'Jira Cloud ID for the instance. If not provided, it will be fetched using the domain.',
72+
},
73+
},
74+
75+
request: {
76+
url: (params: JiraSearchUsersParams) => {
77+
if (params.cloudId) {
78+
const queryParams = new URLSearchParams()
79+
queryParams.append('query', params.query)
80+
if (params.maxResults !== undefined)
81+
queryParams.append('maxResults', String(params.maxResults))
82+
if (params.startAt !== undefined) queryParams.append('startAt', String(params.startAt))
83+
return `https://api.atlassian.com/ex/jira/${params.cloudId}/rest/api/3/user/search?${queryParams.toString()}`
84+
}
85+
return 'https://api.atlassian.com/oauth/token/accessible-resources'
86+
},
87+
method: 'GET',
88+
headers: (params: JiraSearchUsersParams) => ({
89+
Accept: 'application/json',
90+
Authorization: `Bearer ${params.accessToken}`,
91+
}),
92+
},
93+
94+
transformResponse: async (response: Response, params?: JiraSearchUsersParams) => {
95+
const fetchUsers = async (cloudId: string) => {
96+
const queryParams = new URLSearchParams()
97+
queryParams.append('query', params!.query)
98+
if (params!.maxResults !== undefined)
99+
queryParams.append('maxResults', String(params!.maxResults))
100+
if (params!.startAt !== undefined) queryParams.append('startAt', String(params!.startAt))
101+
102+
const usersUrl = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/user/search?${queryParams.toString()}`
103+
104+
const usersResponse = await fetch(usersUrl, {
105+
method: 'GET',
106+
headers: {
107+
Accept: 'application/json',
108+
Authorization: `Bearer ${params!.accessToken}`,
109+
},
110+
})
111+
112+
if (!usersResponse.ok) {
113+
let message = `Failed to search Jira users (${usersResponse.status})`
114+
try {
115+
const err = await usersResponse.json()
116+
message = err?.errorMessages?.join(', ') || err?.message || message
117+
} catch (_e) {}
118+
throw new Error(message)
119+
}
120+
121+
return usersResponse.json()
122+
}
123+
124+
let data: any
125+
126+
if (!params?.cloudId) {
127+
const cloudId = await getJiraCloudId(params!.domain, params!.accessToken)
128+
data = await fetchUsers(cloudId)
129+
} else {
130+
if (!response.ok) {
131+
let message = `Failed to search Jira users (${response.status})`
132+
try {
133+
const err = await response.json()
134+
message = err?.errorMessages?.join(', ') || err?.message || message
135+
} catch (_e) {}
136+
throw new Error(message)
137+
}
138+
data = await response.json()
139+
}
140+
141+
const users = Array.isArray(data) ? data : []
142+
143+
return {
144+
success: true,
145+
output: {
146+
ts: new Date().toISOString(),
147+
users: users.map(transformUserOutput),
148+
total: users.length,
149+
},
150+
}
151+
},
152+
153+
outputs: {
154+
ts: TIMESTAMP_OUTPUT,
155+
users: {
156+
type: 'array',
157+
description: 'Array of matching Jira users',
158+
items: {
159+
type: 'object',
160+
properties: {
161+
...USER_OUTPUT_PROPERTIES,
162+
self: {
163+
type: 'string',
164+
description: 'REST API URL for this user',
165+
optional: true,
166+
},
167+
},
168+
},
169+
},
170+
total: { type: 'number', description: 'Total number of users returned' },
171+
},
172+
}

apps/sim/tools/jira/types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,6 +1549,32 @@ export interface JiraGetUsersParams {
15491549
cloudId?: string
15501550
}
15511551

1552+
export interface JiraSearchUsersParams {
1553+
accessToken: string
1554+
domain: string
1555+
query: string
1556+
maxResults?: number
1557+
startAt?: number
1558+
cloudId?: string
1559+
}
1560+
1561+
export interface JiraSearchUsersResponse extends ToolResponse {
1562+
output: {
1563+
ts: string
1564+
users: Array<{
1565+
accountId: string
1566+
accountType?: string | null
1567+
active: boolean
1568+
displayName: string
1569+
emailAddress?: string | null
1570+
avatarUrl?: string | null
1571+
timeZone?: string | null
1572+
self?: string | null
1573+
}>
1574+
total: number
1575+
}
1576+
}
1577+
15521578
export interface JiraGetUsersResponse extends ToolResponse {
15531579
output: {
15541580
ts: string
@@ -1594,3 +1620,4 @@ export type JiraResponse =
15941620
| JiraAddWatcherResponse
15951621
| JiraRemoveWatcherResponse
15961622
| JiraGetUsersResponse
1623+
| JiraSearchUsersResponse

apps/sim/tools/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,7 @@ import {
10851085
jiraRemoveWatcherTool,
10861086
jiraRetrieveTool,
10871087
jiraSearchIssuesTool,
1088+
jiraSearchUsersTool,
10881089
jiraTransitionIssueTool,
10891090
jiraUpdateCommentTool,
10901091
jiraUpdateTool,
@@ -2536,6 +2537,7 @@ export const tools: Record<string, ToolConfig> = {
25362537
jira_add_watcher: jiraAddWatcherTool,
25372538
jira_remove_watcher: jiraRemoveWatcherTool,
25382539
jira_get_users: jiraGetUsersTool,
2540+
jira_search_users: jiraSearchUsersTool,
25392541
jsm_get_service_desks: jsmGetServiceDesksTool,
25402542
jsm_get_request_types: jsmGetRequestTypesTool,
25412543
jsm_get_request_type_fields: jsmGetRequestTypeFieldsTool,

0 commit comments

Comments
 (0)