Can not filter only jira-admins and jira-user with Search Users field
Original Source Code:
export async function searchAssignees({ q, limit = 50 } = {}) {
if (!q || q.length < 1) {
return { assignees: [] };
}
try {
const safeLimit = Number(limit) && Number(limit) > 0 ? Math.min(Number(limit), 100) : 50;
const url = route`/rest/api/3/users/search?startAt=0&maxResults=${safeLimit}`;
const res = await api.asUser().requestJira(url, {
method: 'GET',
headers: { Accept: 'application/json' }
});
if (res.status >= 400) {
const text = await res.text();
console.error('searchAssignees API error', res.status, text);
throw new Error(`Jira API error ${res.status}`);
}
const json = await res.json();
// Filter users by search term and limit results to max 50
const assignees = (Array.isArray(json) ? json : json.users || [])
.filter(u => {
const displayName = (u.displayName || '').toLowerCase();
const searchTerm = (q || '').toLowerCase();
return displayName.includes(searchTerm);
})
.slice(0, safeLimit)
.map(u => ({
accountId: u.accountId,
displayName: u.displayName || u.name || 'Unknown',
avatarUrl: u.avatarUrls?.["24x24"] || u.avatarUrls?.["16x16"] || ''
}));
return { assignees };
} catch (err) {
console.error('searchAssignees error:', err);
throw err;
}
}
Documentation: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-user-search/#api-rest-api-3-user-search-get
Fixed Source Code by Copilot not work:
export async function searchAssignees({ q, limit = 50 } = {}) {
if (!q || q.length < 1) {
return { assignees: [] };
}
try {
const safeLimit = Number(limit) && Number(limit) > 0 ? Math.min(Number(limit), 50) : 50;
// Construct JQL for user search
// We strictly filter by membership in "jira-users" or "jira-administrators"
let jql = 'groups in ("jira-users", "jira-administrators")';
if (q && q.trim().length > 0) {
// Escape content for JQL string "..."
const term = q.trim().replace(/"/g, '\\"');
// Append search condition
jql += ` AND (displayName ~ "${term}*" OR emailAddress ~ "${term}*")`;
}
const url = route`/rest/api/3/user/search?query=${jql}&startAt=0&maxResults=${safeLimit}`;
const res = await api.asUser().requestJira(url, {
method: 'GET',
headers: { Accept: 'application/json' }
});
if (res.status >= 400) {
const text = await res.text();
console.error('searchAssignees API error', res.status, text);
throw new Error(`Jira API error ${res.status}`);
}
const json = await res.json();
// Defensively handle the response: ensure json exists and is an object
if (!json || typeof json !== 'object') {
console.error('searchAssignees: invalid response structure', json);
return { assignees: [] };
}
// /rest/api/3/user/search/query returns { values: [...users], ... }
// Ensure json.values is an array before mapping
const userList = Array.isArray(json.values) ? json.values : (Array.isArray(json) ? json : []);
const assignees = userList.map(u => ({
accountId: u.accountId,
displayName: u.displayName || u.name || 'Unknown',
avatarUrl: u.avatarUrls?.["24x24"] || u.avatarUrls?.["16x16"] || ''
}));
return { assignees };
} catch (err) {
console.error('searchAssignees error:', err);
throw err;
}
}
Working code made by Copilot but not filter JQL with displayName:
export async function searchAssignees({ q, limit = 50 } = {}) {
if (!q || q.length < 1) {
return { assignees: [] };
}
try {
const safeLimit = Number(limit) && Number(limit) > 0 ? Math.min(Number(limit), 50) : 50;
// Use the /rest/api/3/users/search endpoint
// The query parameter is a simple string matched against displayName and emailAddress
const searchTerm = String(q).trim();
const encodedQuery = encodeURIComponent(searchTerm);
const url = route`/rest/api/3/users/search?query=${encodedQuery}&maxResults=${safeLimit}`;
console.info('searchAssignees: calling', url, 'with query', searchTerm);
const res = await api.asUser().requestJira(url, {
method: 'GET',
headers: { Accept: 'application/json' }
});
if (res.status >= 400) {
const text = await res.text();
console.error('searchAssignees API error', res.status, text);
throw new Error(`Jira API error ${res.status}`);
}
const json = await res.json();
console.info('searchAssignees response count:', Array.isArray(json) ? json.length : 0);
// Response is an array of users - defensively ensure we have an array before mapping
if (!Array.isArray(json)) {
console.error('searchAssignees: expected array response, got:', typeof json, json);
return { assignees: [] };
}
// Filter to only include active atlassian users (not app users or inactive users)
const assignees = json
.filter(u => u.accountType === 'atlassian' && u.active !== false)
.map(u => ({
accountId: u.accountId,
displayName: u.displayName || u.name || 'Unknown',
avatarUrl: u.avatarUrls?.["24x24"] || u.avatarUrls?.["16x16"] || ''
}));
console.info('searchAssignees: returning', assignees.length, 'active atlassian users');
return { assignees };
} catch (err) {
console.error('searchAssignees error:', err);
throw err;
}
}
An another not list by displayName:
export async function searchAssignees({ q, limit = 50 } = {}) {
if (!q || q.length < 1) {
return { assignees: [] };
}
try {
const safeLimit = Number(limit) && Number(limit) > 0 ? Math.min(Number(limit), 50) : 50;
// Use simple search string - the API matches against displayName and emailAddress
const searchTerm = String(q).trim();
console.info('searchAssignees: searching for:', searchTerm);
// Use route template tag as required by Forge
const url = route`/rest/api/3/users/search?query=${searchTerm}&maxResults=${safeLimit}`;
const res = await api.asUser().requestJira(url, {
method: 'GET',
headers: { Accept: 'application/json' }
});
if (res.status >= 400) {
const text = await res.text();
console.error('searchAssignees API error', res.status, text);
throw new Error(`Jira API error ${res.status}`);
}
const json = await res.json();
console.info('searchAssignees response count:', Array.isArray(json) ? json.length : 0);
// Response is an array of users - defensively ensure we have an array before mapping
if (!Array.isArray(json)) {
console.error('searchAssignees: expected array response, got:', typeof json, json);
return { assignees: [] };
}
// Filter to only include active atlassian users (not app users or inactive users)
const assignees = json
.filter(u => u.accountType === 'atlassian' && u.active !== false)
.map(u => ({
accountId: u.accountId,
displayName: u.displayName || u.name || 'Unknown',
avatarUrl: u.avatarUrls?.["24x24"] || u.avatarUrls?.["16x16"] || ''
}));
console.info('searchAssignees: returning', assignees.length, 'active atlassian users');
return { assignees };
} catch (err) {
console.error('searchAssignees error:', err);
throw err;
}
}
This not work either:
export async function searchAssignees({ q, limit = 50 } = {}) {
if (!q || q.length < 1) {
return { assignees: [] };
}
try {
const safeLimit = Number(limit) && Number(limit) > 0 ? Math.min(Number(limit), 50) : 50;
// Construct JQL equivalent: filter by group membership and display name
// Note: /rest/api/3/users/search doesn't accept JQL in query parameter,
// so we build it for reference and use simple search + client-side filtering
const searchTerm = String(q).trim();
let jql = 'groups in ("jira-users", "jira-administrators")';
if (searchTerm) {
const escapedTerm = searchTerm.replace(/"/g, '\\"');
jql += ` AND displayName ~ "${escapedTerm}*"`;
}
console.info('searchAssignees: JQL equivalent:', jql);
// The /users/search API only accepts simple text search, not JQL
// Search by displayName/emailAddress prefix
const url = route`/rest/api/3/users/search?query=${searchTerm}&maxResults=${safeLimit}`;
const res = await api.asUser().requestJira(url, {
method: 'GET',
headers: { Accept: 'application/json' }
});
if (res.status >= 400) {
const text = await res.text();
console.error('searchAssignees API error', res.status, text);
throw new Error(`Jira API error ${res.status}`);
}
const json = await res.json();
console.info('searchAssignees raw response count:', Array.isArray(json) ? json.length : 0);
// Response is an array of users - defensively ensure we have an array before mapping
if (!Array.isArray(json)) {
console.error('searchAssignees: expected array response, got:', typeof json, json);
return { assignees: [] };
}
// Client-side filtering: only active atlassian users in jira-users or jira-administrators
// (This simulates the JQL filter since the API doesn't support JQL in query parameter)
const assignees = json
.filter(u => {
// Must be atlassian user (not app/bot)
if (u.accountType !== 'atlassian') return false;
// Must be active
if (u.active === false) return false;
return true;
})
.map(u => ({
accountId: u.accountId,
displayName: u.displayName || u.name || 'Unknown',
avatarUrl: u.avatarUrls?.["24x24"] || u.avatarUrls?.["16x16"] || ''
}));
console.info('searchAssignees: returning', assignees.length, 'filtered users');
return { assignees };
} catch (err) {
console.error('searchAssignees error:', err);
throw err;
}
}
This code filter users but not with JQL, so can anyone tell how use JQL?
export async function searchAssignees({ q, limit = 50 } = {}) {
if (!q || q.length < 1) {
return { assignees: [] };
}
try {
const safeLimit = Number(limit) && Number(limit) > 0 ? Math.min(Number(limit), 50) : 50;
// Construct JQL equivalent: filter by group membership and display name
// Note: /rest/api/3/users/search doesn't accept JQL in query parameter,
// so we build it for reference and use simple search + client-side filtering
const searchTerm = String(q).trim();
let jql = 'groups in ("jira-users", "jira-administrators")';
if (searchTerm) {
const escapedTerm = searchTerm.replace(/"/g, '\\"');
jql += ` AND displayName ~ "${escapedTerm}*"`;
}
// The /users/search API only accepts simple text search, not JQL
// Search by displayName/emailAddress prefix
const url = route`/rest/api/3/users/search?query=${searchTerm}&maxResults=${safeLimit}`;
const res = await api.asUser().requestJira(url, {
method: 'GET',
headers: { Accept: 'application/json' }
});
if (res.status >= 400) {
const text = await res.text();
console.error('searchAssignees API error', res.status, text);
throw new Error(`Jira API error ${res.status}`);
}
const json = await res.json();
// Response is an array of users - defensively ensure we have an array before mapping
if (!Array.isArray(json)) {
console.error('searchAssignees: expected array response, got:', typeof json, json);
return { assignees: [] };
}
// Client-side filtering: only active atlassian users in jira-users or jira-administrators
// (This simulates the JQL filter since the API doesn't support JQL in query parameter)
const assignees = json
.filter(u => {
// Must be atlassian user (not app/bot)
if (u.accountType !== 'atlassian') return false;
// Must be active
if (u.active === false) return false;
// Must match searchTerm in displayName (case-insensitive prefix match)
if (searchTerm && !(u.displayName || '').toLowerCase().includes(searchTerm.toLowerCase())) {
return false;
}
return true;
})
.map(u => ({
accountId: u.accountId,
displayName: u.displayName || u.name || 'Unknown',
avatarUrl: u.avatarUrls?.["24x24"] || u.avatarUrls?.["16x16"] || ''
}));
return { assignees };
} catch (err) {
console.error('searchAssignees error:', err);
throw err;
}
}