mirror of
https://github.com/simstudioai/sim.git
synced 2026-03-15 03:00:33 -04:00
Compare commits
21 Commits
improvemen
...
v0.5.108
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c0a2e04b1 | ||
|
|
0a52b09deb | ||
|
|
1d36b80172 | ||
|
|
e6a5e7f4e4 | ||
|
|
6586c5ce40 | ||
|
|
3ce947566d | ||
|
|
70c36cb7aa | ||
|
|
f1ec5fe824 | ||
|
|
e07e3c34cc | ||
|
|
0d2e6ff31d | ||
|
|
4fd0989264 | ||
|
|
67f8a687f6 | ||
|
|
af592349d3 | ||
|
|
0d86ea01f0 | ||
|
|
115f04e989 | ||
|
|
34d92fae89 | ||
|
|
67aa4bb332 | ||
|
|
15ace5e63f | ||
|
|
fdca73679d | ||
|
|
da46a387c9 | ||
|
|
b7e377ec4b |
@@ -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 |
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { SELECTOR_CONTEXT_FIELDS } from '@/lib/workflows/subblocks/context'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { extractEnvVarName, isEnvVarReference, isReference } from '@/executor/constants'
|
||||
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
|
||||
@@ -14,8 +15,7 @@ import { useDependsOnGate } from './use-depends-on-gate'
|
||||
*
|
||||
* Builds a `SelectorContext` by mapping each `dependsOn` entry through the
|
||||
* canonical index to its `canonicalParamId`, which maps directly to
|
||||
* `SelectorContext` field names (e.g. `siteId`, `teamId`, `collectionId`).
|
||||
* The one special case is `oauthCredential` which maps to `credentialId`.
|
||||
* `SelectorContext` field names (e.g. `siteId`, `teamId`, `oauthCredential`).
|
||||
*
|
||||
* @param blockId - The block containing the selector sub-block
|
||||
* @param subBlock - The sub-block config (must have `selectorKey` set)
|
||||
@@ -70,11 +70,8 @@ export function useSelectorSetup(
|
||||
if (isReference(strValue)) continue
|
||||
|
||||
const canonicalParamId = canonicalIndex.canonicalIdBySubBlockId[depKey] ?? depKey
|
||||
|
||||
if (canonicalParamId === 'oauthCredential') {
|
||||
context.credentialId = strValue
|
||||
} else if (canonicalParamId in CONTEXT_FIELD_SET) {
|
||||
;(context as Record<string, unknown>)[canonicalParamId] = strValue
|
||||
if (SELECTOR_CONTEXT_FIELDS.has(canonicalParamId as keyof SelectorContext)) {
|
||||
context[canonicalParamId as keyof SelectorContext] = strValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,19 +86,3 @@ export function useSelectorSetup(
|
||||
dependencyValues: resolvedDependencyValues,
|
||||
}
|
||||
}
|
||||
|
||||
const CONTEXT_FIELD_SET: Record<string, true> = {
|
||||
credentialId: true,
|
||||
domain: true,
|
||||
teamId: true,
|
||||
projectId: true,
|
||||
knowledgeBaseId: true,
|
||||
planId: true,
|
||||
siteId: true,
|
||||
collectionId: true,
|
||||
spreadsheetId: true,
|
||||
fileId: true,
|
||||
baseId: true,
|
||||
datasetId: true,
|
||||
serviceDeskId: true,
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ import { useWebhookManagement } from '@/hooks/use-webhook-management'
|
||||
const SLACK_OVERRIDES: SelectorOverrides = {
|
||||
transformContext: (context, deps) => {
|
||||
const authMethod = deps.authMethod as string
|
||||
const credentialId =
|
||||
const oauthCredential =
|
||||
authMethod === 'bot_token' ? String(deps.botToken ?? '') : String(deps.credential ?? '')
|
||||
return { ...context, credentialId }
|
||||
return { ...context, oauthCredential }
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -578,7 +578,7 @@ const SubBlockRow = memo(function SubBlockRow({
|
||||
subBlock,
|
||||
value: rawValue,
|
||||
workflowId,
|
||||
credentialId: typeof credentialId === 'string' ? credentialId : undefined,
|
||||
oauthCredential: typeof credentialId === 'string' ? credentialId : undefined,
|
||||
knowledgeBaseId: typeof knowledgeBaseId === 'string' ? knowledgeBaseId : undefined,
|
||||
domain: domainValue,
|
||||
teamId: teamIdValue,
|
||||
|
||||
@@ -47,6 +47,7 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
|
||||
{ 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
|
||||
|
||||
@@ -39,10 +39,10 @@ type FolderResponse = { id: string; name: string }
|
||||
type PlannerTask = { id: string; title: string }
|
||||
|
||||
const ensureCredential = (context: SelectorContext, key: SelectorKey): string => {
|
||||
if (!context.credentialId) {
|
||||
if (!context.oauthCredential) {
|
||||
throw new Error(`Missing credential for selector ${key}`)
|
||||
}
|
||||
return context.credentialId
|
||||
return context.oauthCredential
|
||||
}
|
||||
|
||||
const ensureDomain = (context: SelectorContext, key: SelectorKey): string => {
|
||||
@@ -66,9 +66,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'airtable.bases',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'airtable.bases')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -104,10 +104,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'airtable.tables',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.baseId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.baseId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.baseId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'airtable.tables')
|
||||
if (!context.baseId) {
|
||||
@@ -151,9 +151,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'asana.workspaces',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'asana.workspaces')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -182,9 +182,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'attio.objects',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'attio.objects')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -216,9 +216,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'attio.lists',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'attio.lists')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -250,10 +250,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'bigquery.datasets',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.projectId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.projectId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.projectId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'bigquery.datasets')
|
||||
if (!context.projectId) throw new Error('Missing project ID for bigquery.datasets selector')
|
||||
@@ -298,12 +298,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'bigquery.tables',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.projectId ?? 'none',
|
||||
context.datasetId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) =>
|
||||
Boolean(context.credentialId && context.projectId && context.datasetId),
|
||||
Boolean(context.oauthCredential && context.projectId && context.datasetId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'bigquery.tables')
|
||||
if (!context.projectId) throw new Error('Missing project ID for bigquery.tables selector')
|
||||
@@ -347,9 +347,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'calcom.eventTypes',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'calcom.eventTypes')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -381,9 +381,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'calcom.schedules',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'calcom.schedules')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -415,10 +415,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'confluence.spaces',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'confluence.spaces')
|
||||
const domain = ensureDomain(context, 'confluence.spaces')
|
||||
@@ -460,10 +460,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jsm.serviceDesks',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jsm.serviceDesks')
|
||||
const domain = ensureDomain(context, 'jsm.serviceDesks')
|
||||
@@ -505,12 +505,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jsm.requestTypes',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
context.serviceDeskId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) =>
|
||||
Boolean(context.credentialId && context.domain && context.serviceDeskId),
|
||||
Boolean(context.oauthCredential && context.domain && context.serviceDeskId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jsm.requestTypes')
|
||||
const domain = ensureDomain(context, 'jsm.requestTypes')
|
||||
@@ -556,9 +556,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.tasks.lists',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.tasks.lists')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -587,9 +587,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.planner.plans',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.planner.plans')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -618,9 +618,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'notion.databases',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'notion.databases')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -652,9 +652,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'notion.pages',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'notion.pages')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -686,9 +686,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'pipedrive.pipelines',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'pipedrive.pipelines')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -720,10 +720,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'sharepoint.lists',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.siteId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.siteId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'sharepoint.lists')
|
||||
if (!context.siteId) throw new Error('Missing site ID for sharepoint.lists selector')
|
||||
@@ -761,9 +761,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'trello.boards',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'trello.boards')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -794,9 +794,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'zoom.meetings',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'zoom.meetings')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -828,12 +828,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'slack.channels',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const body = JSON.stringify({
|
||||
credential: context.credentialId,
|
||||
credential: context.oauthCredential,
|
||||
workflowId: context.workflowId,
|
||||
})
|
||||
const data = await fetchJson<{ channels: SlackChannel[] }>('/api/tools/slack/channels', {
|
||||
@@ -852,12 +852,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'slack.users',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const body = JSON.stringify({
|
||||
credential: context.credentialId,
|
||||
credential: context.oauthCredential,
|
||||
workflowId: context.workflowId,
|
||||
})
|
||||
const data = await fetchJson<{ users: SlackUser[] }>('/api/tools/slack/users', {
|
||||
@@ -876,12 +876,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'gmail.labels',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const data = await fetchJson<{ labels: FolderResponse[] }>('/api/tools/gmail/labels', {
|
||||
searchParams: { credentialId: context.credentialId },
|
||||
searchParams: { credentialId: context.oauthCredential },
|
||||
})
|
||||
return (data.labels || []).map((label) => ({
|
||||
id: label.id,
|
||||
@@ -895,12 +895,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'outlook.folders',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const data = await fetchJson<{ folders: FolderResponse[] }>('/api/tools/outlook/folders', {
|
||||
searchParams: { credentialId: context.credentialId },
|
||||
searchParams: { credentialId: context.oauthCredential },
|
||||
})
|
||||
return (data.folders || []).map((folder) => ({
|
||||
id: folder.id,
|
||||
@@ -914,13 +914,13 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.calendar',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const data = await fetchJson<{ calendars: { id: string; summary: string }[] }>(
|
||||
'/api/tools/google_calendar/calendars',
|
||||
{ searchParams: { credentialId: context.credentialId } }
|
||||
{ searchParams: { credentialId: context.oauthCredential } }
|
||||
)
|
||||
return (data.calendars || []).map((calendar) => ({
|
||||
id: calendar.id,
|
||||
@@ -934,11 +934,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.teams',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const body = JSON.stringify({ credential: context.credentialId })
|
||||
const body = JSON.stringify({ credential: context.oauthCredential })
|
||||
const data = await fetchJson<{ teams: { id: string; displayName: string }[] }>(
|
||||
'/api/tools/microsoft-teams/teams',
|
||||
{ method: 'POST', body }
|
||||
@@ -955,11 +955,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.chats',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const body = JSON.stringify({ credential: context.credentialId })
|
||||
const body = JSON.stringify({ credential: context.oauthCredential })
|
||||
const data = await fetchJson<{ chats: { id: string; displayName: string }[] }>(
|
||||
'/api/tools/microsoft-teams/chats',
|
||||
{ method: 'POST', body }
|
||||
@@ -976,13 +976,13 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.channels',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.teamId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.teamId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.teamId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const body = JSON.stringify({
|
||||
credential: context.credentialId,
|
||||
credential: context.oauthCredential,
|
||||
teamId: context.teamId,
|
||||
})
|
||||
const data = await fetchJson<{ channels: { id: string; displayName: string }[] }>(
|
||||
@@ -1001,14 +1001,14 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'wealthbox.contacts',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const data = await fetchJson<{ items: { id: string; name: string }[] }>(
|
||||
'/api/tools/wealthbox/items',
|
||||
{
|
||||
searchParams: { credentialId: context.credentialId, type: 'contact' },
|
||||
searchParams: { credentialId: context.oauthCredential, type: 'contact' },
|
||||
}
|
||||
)
|
||||
return (data.items || []).map((item) => ({
|
||||
@@ -1023,9 +1023,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'sharepoint.sites',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'sharepoint.sites')
|
||||
const body = JSON.stringify({
|
||||
@@ -1069,10 +1069,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.planner',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.planId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.planId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.planId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.planner')
|
||||
const body = JSON.stringify({
|
||||
@@ -1112,11 +1112,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jira.projects',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jira.projects')
|
||||
const domain = ensureDomain(context, 'jira.projects')
|
||||
@@ -1171,12 +1171,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jira.issues',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
context.projectId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jira.issues')
|
||||
const domain = ensureDomain(context, 'jira.issues')
|
||||
@@ -1235,9 +1235,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'linear.teams',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'linear.teams')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -1260,10 +1260,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'linear.projects',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.teamId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.teamId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.teamId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'linear.projects')
|
||||
const body = JSON.stringify({
|
||||
@@ -1290,11 +1290,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'confluence.pages',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.domain),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'confluence.pages')
|
||||
const domain = ensureDomain(context, 'confluence.pages')
|
||||
@@ -1343,9 +1343,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'onedrive.files',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'onedrive.files')
|
||||
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
|
||||
@@ -1366,9 +1366,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'onedrive.folders',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'onedrive.folders')
|
||||
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
|
||||
@@ -1389,12 +1389,12 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.drive',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.mimeType ?? 'any',
|
||||
context.fileId ?? 'root',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.drive')
|
||||
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
|
||||
@@ -1438,10 +1438,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.sheets',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.spreadsheetId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.spreadsheetId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.spreadsheetId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.sheets')
|
||||
if (!context.spreadsheetId) {
|
||||
@@ -1469,10 +1469,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.excel.sheets',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.spreadsheetId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.spreadsheetId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.spreadsheetId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.excel.sheets')
|
||||
if (!context.spreadsheetId) {
|
||||
@@ -1500,10 +1500,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.excel',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.excel')
|
||||
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
|
||||
@@ -1528,10 +1528,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.word',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.word')
|
||||
const data = await fetchJson<{ files: { id: string; name: string }[] }>(
|
||||
@@ -1596,9 +1596,9 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'webflow.sites',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'webflow.sites')
|
||||
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
|
||||
@@ -1621,10 +1621,10 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'webflow.collections',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.siteId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.siteId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'webflow.collections')
|
||||
if (!context.siteId) {
|
||||
@@ -1654,11 +1654,11 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'webflow.items',
|
||||
context.credentialId ?? 'none',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.collectionId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.credentialId && context.collectionId),
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.collectionId),
|
||||
fetchList: async ({ context, search }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'webflow.items')
|
||||
if (!context.collectionId) {
|
||||
|
||||
@@ -7,46 +7,16 @@ export interface SelectorResolution {
|
||||
allowSearch: boolean
|
||||
}
|
||||
|
||||
export interface SelectorResolutionArgs {
|
||||
workflowId?: string
|
||||
credentialId?: string
|
||||
domain?: string
|
||||
projectId?: string
|
||||
planId?: string
|
||||
teamId?: string
|
||||
knowledgeBaseId?: string
|
||||
siteId?: string
|
||||
collectionId?: string
|
||||
spreadsheetId?: string
|
||||
fileId?: string
|
||||
baseId?: string
|
||||
datasetId?: string
|
||||
serviceDeskId?: string
|
||||
}
|
||||
|
||||
export function resolveSelectorForSubBlock(
|
||||
subBlock: SubBlockConfig,
|
||||
args: SelectorResolutionArgs
|
||||
context: SelectorContext
|
||||
): SelectorResolution | null {
|
||||
if (!subBlock.selectorKey) return null
|
||||
return {
|
||||
key: subBlock.selectorKey,
|
||||
context: {
|
||||
workflowId: args.workflowId,
|
||||
credentialId: args.credentialId,
|
||||
domain: args.domain,
|
||||
projectId: args.projectId,
|
||||
planId: args.planId,
|
||||
teamId: args.teamId,
|
||||
knowledgeBaseId: args.knowledgeBaseId,
|
||||
siteId: args.siteId,
|
||||
collectionId: args.collectionId,
|
||||
spreadsheetId: args.spreadsheetId,
|
||||
fileId: args.fileId,
|
||||
baseId: args.baseId,
|
||||
datasetId: args.datasetId,
|
||||
serviceDeskId: args.serviceDeskId,
|
||||
mimeType: subBlock.mimeType,
|
||||
...context,
|
||||
mimeType: subBlock.mimeType ?? context.mimeType,
|
||||
},
|
||||
allowSearch: subBlock.selectorAllowSearch ?? true,
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export interface SelectorOption {
|
||||
export interface SelectorContext {
|
||||
workspaceId?: string
|
||||
workflowId?: string
|
||||
credentialId?: string
|
||||
oauthCredential?: string
|
||||
serviceId?: string
|
||||
domain?: string
|
||||
teamId?: string
|
||||
|
||||
@@ -12,7 +12,7 @@ interface SelectorDisplayNameArgs {
|
||||
subBlock?: SubBlockConfig
|
||||
value: unknown
|
||||
workflowId?: string
|
||||
credentialId?: string
|
||||
oauthCredential?: string
|
||||
domain?: string
|
||||
projectId?: string
|
||||
planId?: string
|
||||
@@ -31,7 +31,7 @@ export function useSelectorDisplayName({
|
||||
subBlock,
|
||||
value,
|
||||
workflowId,
|
||||
credentialId,
|
||||
oauthCredential,
|
||||
domain,
|
||||
projectId,
|
||||
planId,
|
||||
@@ -51,7 +51,7 @@ export function useSelectorDisplayName({
|
||||
if (!subBlock || !detailId) return null
|
||||
return resolveSelectorForSubBlock(subBlock, {
|
||||
workflowId,
|
||||
credentialId,
|
||||
oauthCredential,
|
||||
domain,
|
||||
projectId,
|
||||
planId,
|
||||
@@ -69,7 +69,7 @@ export function useSelectorDisplayName({
|
||||
subBlock,
|
||||
detailId,
|
||||
workflowId,
|
||||
credentialId,
|
||||
oauthCredential,
|
||||
domain,
|
||||
projectId,
|
||||
planId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { buildSelectorContextFromBlock } from '@/lib/workflows/subblocks/context'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { SELECTOR_TYPES_HYDRATION_REQUIRED, type SubBlockConfig } from '@/blocks/types'
|
||||
import { CREDENTIAL_SET, isUuid } from '@/executor/constants'
|
||||
@@ -6,7 +7,7 @@ import { fetchCredentialSetById } from '@/hooks/queries/credential-sets'
|
||||
import { fetchOAuthCredentialDetail } from '@/hooks/queries/oauth-credentials'
|
||||
import { getSelectorDefinition } from '@/hooks/selectors/registry'
|
||||
import { resolveSelectorForSubBlock } from '@/hooks/selectors/resolution'
|
||||
import type { SelectorKey } from '@/hooks/selectors/types'
|
||||
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
|
||||
const logger = createLogger('ResolveValues')
|
||||
@@ -39,74 +40,8 @@ interface ResolutionContext {
|
||||
blockId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended context extracted from block subBlocks for selector resolution
|
||||
*/
|
||||
interface ExtendedSelectorContext {
|
||||
credentialId?: string
|
||||
domain?: string
|
||||
projectId?: string
|
||||
planId?: string
|
||||
teamId?: string
|
||||
knowledgeBaseId?: string
|
||||
siteId?: string
|
||||
collectionId?: string
|
||||
spreadsheetId?: string
|
||||
baseId?: string
|
||||
datasetId?: string
|
||||
serviceDeskId?: string
|
||||
}
|
||||
|
||||
function getSemanticFallback(subBlockId: string, subBlockConfig?: SubBlockConfig): string {
|
||||
if (subBlockConfig?.title) {
|
||||
return subBlockConfig.title.toLowerCase()
|
||||
}
|
||||
|
||||
const patterns: Record<string, string> = {
|
||||
credential: 'credential',
|
||||
channel: 'channel',
|
||||
channelId: 'channel',
|
||||
user: 'user',
|
||||
userId: 'user',
|
||||
workflow: 'workflow',
|
||||
workflowId: 'workflow',
|
||||
file: 'file',
|
||||
fileId: 'file',
|
||||
folder: 'folder',
|
||||
folderId: 'folder',
|
||||
project: 'project',
|
||||
projectId: 'project',
|
||||
team: 'team',
|
||||
teamId: 'team',
|
||||
sheet: 'sheet',
|
||||
sheetId: 'sheet',
|
||||
document: 'document',
|
||||
documentId: 'document',
|
||||
knowledgeBase: 'knowledge base',
|
||||
knowledgeBaseId: 'knowledge base',
|
||||
server: 'server',
|
||||
serverId: 'server',
|
||||
tool: 'tool',
|
||||
toolId: 'tool',
|
||||
calendar: 'calendar',
|
||||
calendarId: 'calendar',
|
||||
label: 'label',
|
||||
labelId: 'label',
|
||||
site: 'site',
|
||||
siteId: 'site',
|
||||
collection: 'collection',
|
||||
collectionId: 'collection',
|
||||
item: 'item',
|
||||
itemId: 'item',
|
||||
contact: 'contact',
|
||||
contactId: 'contact',
|
||||
task: 'task',
|
||||
taskId: 'task',
|
||||
chat: 'chat',
|
||||
chatId: 'chat',
|
||||
}
|
||||
|
||||
return patterns[subBlockId] || 'value'
|
||||
function getSemanticFallback(subBlockConfig: SubBlockConfig): string {
|
||||
return (subBlockConfig.title ?? subBlockConfig.id).toLowerCase()
|
||||
}
|
||||
|
||||
async function resolveCredential(credentialId: string, workflowId: string): Promise<string | null> {
|
||||
@@ -150,26 +85,10 @@ async function resolveWorkflow(workflowId: string): Promise<string | null> {
|
||||
async function resolveSelectorValue(
|
||||
value: string,
|
||||
selectorKey: SelectorKey,
|
||||
extendedContext: ExtendedSelectorContext,
|
||||
workflowId: string
|
||||
selectorContext: SelectorContext
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const definition = getSelectorDefinition(selectorKey)
|
||||
const selectorContext = {
|
||||
workflowId,
|
||||
credentialId: extendedContext.credentialId,
|
||||
domain: extendedContext.domain,
|
||||
projectId: extendedContext.projectId,
|
||||
planId: extendedContext.planId,
|
||||
teamId: extendedContext.teamId,
|
||||
knowledgeBaseId: extendedContext.knowledgeBaseId,
|
||||
siteId: extendedContext.siteId,
|
||||
collectionId: extendedContext.collectionId,
|
||||
spreadsheetId: extendedContext.spreadsheetId,
|
||||
baseId: extendedContext.baseId,
|
||||
datasetId: extendedContext.datasetId,
|
||||
serviceDeskId: extendedContext.serviceDeskId,
|
||||
}
|
||||
|
||||
if (definition.fetchById) {
|
||||
const result = await definition.fetchById({
|
||||
@@ -219,37 +138,14 @@ export function formatValueForDisplay(value: unknown): string {
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts extended context from a block's subBlocks for selector resolution.
|
||||
* This mirrors the context extraction done in the UI components.
|
||||
*/
|
||||
function extractExtendedContext(
|
||||
function extractSelectorContext(
|
||||
blockId: string,
|
||||
currentState: WorkflowState
|
||||
): ExtendedSelectorContext {
|
||||
currentState: WorkflowState,
|
||||
workflowId: string
|
||||
): SelectorContext {
|
||||
const block = currentState.blocks?.[blockId]
|
||||
if (!block?.subBlocks) return {}
|
||||
|
||||
const getStringValue = (id: string): string | undefined => {
|
||||
const subBlock = block.subBlocks[id] as { value?: unknown } | undefined
|
||||
const val = subBlock?.value
|
||||
return typeof val === 'string' ? val : undefined
|
||||
}
|
||||
|
||||
return {
|
||||
credentialId: getStringValue('credential'),
|
||||
domain: getStringValue('domain'),
|
||||
projectId: getStringValue('projectId'),
|
||||
planId: getStringValue('planId'),
|
||||
teamId: getStringValue('teamId'),
|
||||
knowledgeBaseId: getStringValue('knowledgeBaseId'),
|
||||
siteId: getStringValue('siteId'),
|
||||
collectionId: getStringValue('collectionId'),
|
||||
spreadsheetId: getStringValue('spreadsheetId') || getStringValue('fileId'),
|
||||
baseId: getStringValue('baseId') || getStringValue('baseSelector'),
|
||||
datasetId: getStringValue('datasetId') || getStringValue('datasetSelector'),
|
||||
serviceDeskId: getStringValue('serviceDeskId') || getStringValue('serviceDeskSelector'),
|
||||
}
|
||||
if (!block?.subBlocks) return { workflowId }
|
||||
return buildSelectorContextFromBlock(block.type, block.subBlocks, { workflowId })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,11 +171,14 @@ export async function resolveValueForDisplay(
|
||||
|
||||
const blockConfig = getBlock(context.blockType)
|
||||
const subBlockConfig = blockConfig?.subBlocks.find((sb) => sb.id === context.subBlockId)
|
||||
const semanticFallback = getSemanticFallback(context.subBlockId, subBlockConfig)
|
||||
if (!subBlockConfig) {
|
||||
return { original: value, displayLabel: formatValueForDisplay(value), resolved: false }
|
||||
}
|
||||
const semanticFallback = getSemanticFallback(subBlockConfig)
|
||||
|
||||
const extendedContext = context.blockId
|
||||
? extractExtendedContext(context.blockId, context.currentState)
|
||||
: {}
|
||||
const selectorCtx = context.blockId
|
||||
? extractSelectorContext(context.blockId, context.currentState, context.workflowId)
|
||||
: { workflowId: context.workflowId }
|
||||
|
||||
// Credential fields (oauth-input or credential subBlockId)
|
||||
const isCredentialField =
|
||||
@@ -311,29 +210,10 @@ export async function resolveValueForDisplay(
|
||||
// Selector types that require hydration (file-selector, sheet-selector, etc.)
|
||||
// These support external service IDs like Google Drive file IDs
|
||||
if (subBlockConfig && SELECTOR_TYPES_HYDRATION_REQUIRED.includes(subBlockConfig.type)) {
|
||||
const resolution = resolveSelectorForSubBlock(subBlockConfig, {
|
||||
workflowId: context.workflowId,
|
||||
credentialId: extendedContext.credentialId,
|
||||
domain: extendedContext.domain,
|
||||
projectId: extendedContext.projectId,
|
||||
planId: extendedContext.planId,
|
||||
teamId: extendedContext.teamId,
|
||||
knowledgeBaseId: extendedContext.knowledgeBaseId,
|
||||
siteId: extendedContext.siteId,
|
||||
collectionId: extendedContext.collectionId,
|
||||
spreadsheetId: extendedContext.spreadsheetId,
|
||||
baseId: extendedContext.baseId,
|
||||
datasetId: extendedContext.datasetId,
|
||||
serviceDeskId: extendedContext.serviceDeskId,
|
||||
})
|
||||
const resolution = resolveSelectorForSubBlock(subBlockConfig, selectorCtx)
|
||||
|
||||
if (resolution?.key) {
|
||||
const label = await resolveSelectorValue(
|
||||
value,
|
||||
resolution.key,
|
||||
extendedContext,
|
||||
context.workflowId
|
||||
)
|
||||
const label = await resolveSelectorValue(value, resolution.key, selectorCtx)
|
||||
if (label) {
|
||||
return { original: value, displayLabel: label, resolved: true }
|
||||
}
|
||||
|
||||
@@ -117,6 +117,10 @@ export async function loadDeployedWorkflowState(
|
||||
resolvedWorkspaceId = wfRow?.workspaceId ?? undefined
|
||||
}
|
||||
|
||||
if (!resolvedWorkspaceId) {
|
||||
throw new Error(`Workflow ${workflowId} has no workspace`)
|
||||
}
|
||||
|
||||
const { blocks: migratedBlocks } = await applyBlockMigrations(
|
||||
state.blocks || {},
|
||||
resolvedWorkspaceId
|
||||
@@ -139,7 +143,7 @@ export async function loadDeployedWorkflowState(
|
||||
|
||||
interface MigrationContext {
|
||||
blocks: Record<string, BlockState>
|
||||
workspaceId?: string
|
||||
workspaceId: string
|
||||
migrated: boolean
|
||||
}
|
||||
|
||||
@@ -148,7 +152,7 @@ type BlockMigration = (ctx: MigrationContext) => MigrationContext | Promise<Migr
|
||||
function createMigrationPipeline(migrations: BlockMigration[]) {
|
||||
return async (
|
||||
blocks: Record<string, BlockState>,
|
||||
workspaceId?: string
|
||||
workspaceId: string
|
||||
): Promise<{ blocks: Record<string, BlockState>; migrated: boolean }> => {
|
||||
let ctx: MigrationContext = { blocks, workspaceId, migrated: false }
|
||||
for (const migration of migrations) {
|
||||
@@ -170,7 +174,6 @@ const applyBlockMigrations = createMigrationPipeline([
|
||||
}),
|
||||
|
||||
async (ctx) => {
|
||||
if (!ctx.workspaceId) return ctx
|
||||
const { blocks, migrated } = await migrateCredentialIds(ctx.blocks, ctx.workspaceId)
|
||||
return { ...ctx, blocks, migrated: ctx.migrated || migrated }
|
||||
},
|
||||
@@ -409,9 +412,13 @@ export async function loadWorkflowFromNormalizedTables(
|
||||
blocksMap[block.id] = assembled
|
||||
})
|
||||
|
||||
if (!workflowRow?.workspaceId) {
|
||||
throw new Error(`Workflow ${workflowId} has no workspace`)
|
||||
}
|
||||
|
||||
const { blocks: finalBlocks, migrated } = await applyBlockMigrations(
|
||||
blocksMap,
|
||||
workflowRow?.workspaceId ?? undefined
|
||||
workflowRow.workspaceId
|
||||
)
|
||||
|
||||
if (migrated) {
|
||||
|
||||
125
apps/sim/lib/workflows/subblocks/context.test.ts
Normal file
125
apps/sim/lib/workflows/subblocks/context.test.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.unmock('@/blocks/registry')
|
||||
|
||||
import { getAllBlocks } from '@/blocks/registry'
|
||||
import { buildSelectorContextFromBlock, SELECTOR_CONTEXT_FIELDS } from './context'
|
||||
import { buildCanonicalIndex, isCanonicalPair } from './visibility'
|
||||
|
||||
describe('buildSelectorContextFromBlock', () => {
|
||||
it('should extract knowledgeBaseId from knowledgeBaseSelector via canonical mapping', () => {
|
||||
const ctx = buildSelectorContextFromBlock('knowledge', {
|
||||
operation: { id: 'operation', type: 'dropdown', value: 'search' },
|
||||
knowledgeBaseSelector: {
|
||||
id: 'knowledgeBaseSelector',
|
||||
type: 'knowledge-base-selector',
|
||||
value: 'kb-uuid-123',
|
||||
},
|
||||
})
|
||||
|
||||
expect(ctx.knowledgeBaseId).toBe('kb-uuid-123')
|
||||
})
|
||||
|
||||
it('should extract knowledgeBaseId from manualKnowledgeBaseId via canonical mapping', () => {
|
||||
const ctx = buildSelectorContextFromBlock('knowledge', {
|
||||
operation: { id: 'operation', type: 'dropdown', value: 'search' },
|
||||
manualKnowledgeBaseId: {
|
||||
id: 'manualKnowledgeBaseId',
|
||||
type: 'short-input',
|
||||
value: 'manual-kb-id',
|
||||
},
|
||||
})
|
||||
|
||||
expect(ctx.knowledgeBaseId).toBe('manual-kb-id')
|
||||
})
|
||||
|
||||
it('should skip null/empty values', () => {
|
||||
const ctx = buildSelectorContextFromBlock('knowledge', {
|
||||
knowledgeBaseSelector: {
|
||||
id: 'knowledgeBaseSelector',
|
||||
type: 'knowledge-base-selector',
|
||||
value: '',
|
||||
},
|
||||
})
|
||||
|
||||
expect(ctx.knowledgeBaseId).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return empty context for unknown block types', () => {
|
||||
const ctx = buildSelectorContextFromBlock('nonexistent_block', {
|
||||
foo: { id: 'foo', type: 'short-input', value: 'bar' },
|
||||
})
|
||||
|
||||
expect(ctx).toEqual({})
|
||||
})
|
||||
|
||||
it('should pass through workflowId from opts', () => {
|
||||
const ctx = buildSelectorContextFromBlock(
|
||||
'knowledge',
|
||||
{ operation: { id: 'operation', type: 'dropdown', value: 'search' } },
|
||||
{ workflowId: 'wf-123' }
|
||||
)
|
||||
|
||||
expect(ctx.workflowId).toBe('wf-123')
|
||||
})
|
||||
|
||||
it('should ignore subblock keys not in SELECTOR_CONTEXT_FIELDS', () => {
|
||||
const ctx = buildSelectorContextFromBlock('knowledge', {
|
||||
operation: { id: 'operation', type: 'dropdown', value: 'search' },
|
||||
query: { id: 'query', type: 'short-input', value: 'some search query' },
|
||||
})
|
||||
|
||||
expect((ctx as Record<string, unknown>).query).toBeUndefined()
|
||||
expect((ctx as Record<string, unknown>).operation).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('SELECTOR_CONTEXT_FIELDS validation', () => {
|
||||
it('every entry must be a canonicalParamId (if a canonical pair exists) or a direct subblock ID', () => {
|
||||
const allCanonicalParamIds = new Set<string>()
|
||||
const allSubBlockIds = new Set<string>()
|
||||
const idsInCanonicalPairs = new Set<string>()
|
||||
|
||||
for (const block of getAllBlocks()) {
|
||||
const index = buildCanonicalIndex(block.subBlocks)
|
||||
|
||||
for (const sb of block.subBlocks) {
|
||||
allSubBlockIds.add(sb.id)
|
||||
if (sb.canonicalParamId) {
|
||||
allCanonicalParamIds.add(sb.canonicalParamId)
|
||||
}
|
||||
}
|
||||
|
||||
for (const group of Object.values(index.groupsById)) {
|
||||
if (!isCanonicalPair(group)) continue
|
||||
if (group.basicId) idsInCanonicalPairs.add(group.basicId)
|
||||
for (const advId of group.advancedIds) idsInCanonicalPairs.add(advId)
|
||||
}
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
|
||||
for (const field of SELECTOR_CONTEXT_FIELDS) {
|
||||
const f = field as string
|
||||
if (allCanonicalParamIds.has(f)) continue
|
||||
|
||||
if (idsInCanonicalPairs.has(f)) {
|
||||
errors.push(
|
||||
`"${f}" is a member subblock ID inside a canonical pair — use the canonicalParamId instead`
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!allSubBlockIds.has(f)) {
|
||||
errors.push(`"${f}" is not a canonicalParamId or subblock ID in any block definition`)
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`SELECTOR_CONTEXT_FIELDS validation failed:\n${errors.join('\n')}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
60
apps/sim/lib/workflows/subblocks/context.ts
Normal file
60
apps/sim/lib/workflows/subblocks/context.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { getBlock } from '@/blocks'
|
||||
import type { SelectorContext } from '@/hooks/selectors/types'
|
||||
import type { SubBlockState } from '@/stores/workflows/workflow/types'
|
||||
import { buildCanonicalIndex } from './visibility'
|
||||
|
||||
/**
|
||||
* Canonical param IDs (or raw subblock IDs) that correspond to SelectorContext fields.
|
||||
* A subblock's resolved canonical key is set on the context only if it appears here.
|
||||
*/
|
||||
export const SELECTOR_CONTEXT_FIELDS = new Set<keyof SelectorContext>([
|
||||
'oauthCredential',
|
||||
'domain',
|
||||
'teamId',
|
||||
'projectId',
|
||||
'knowledgeBaseId',
|
||||
'planId',
|
||||
'siteId',
|
||||
'collectionId',
|
||||
'spreadsheetId',
|
||||
'fileId',
|
||||
'baseId',
|
||||
'datasetId',
|
||||
'serviceDeskId',
|
||||
])
|
||||
|
||||
/**
|
||||
* Builds a SelectorContext from a block's subBlocks using the canonical index.
|
||||
*
|
||||
* Iterates all subblocks, resolves each through canonicalIdBySubBlockId to get
|
||||
* the canonical key, then checks it against SELECTOR_CONTEXT_FIELDS.
|
||||
* This avoids hardcoding subblock IDs and automatically handles basic/advanced
|
||||
* renames.
|
||||
*/
|
||||
export function buildSelectorContextFromBlock(
|
||||
blockType: string,
|
||||
subBlocks: Record<string, SubBlockState | { value?: unknown }>,
|
||||
opts?: { workflowId?: string }
|
||||
): SelectorContext {
|
||||
const context: SelectorContext = {}
|
||||
if (opts?.workflowId) context.workflowId = opts.workflowId
|
||||
|
||||
const blockConfig = getBlock(blockType)
|
||||
if (!blockConfig) return context
|
||||
|
||||
const canonicalIndex = buildCanonicalIndex(blockConfig.subBlocks)
|
||||
|
||||
for (const [subBlockId, subBlock] of Object.entries(subBlocks)) {
|
||||
const val = subBlock?.value
|
||||
if (val === null || val === undefined) continue
|
||||
const strValue = typeof val === 'string' ? val : String(val)
|
||||
if (!strValue) continue
|
||||
|
||||
const canonicalKey = canonicalIndex.canonicalIdBySubBlockId[subBlockId] ?? subBlockId
|
||||
if (SELECTOR_CONTEXT_FIELDS.has(canonicalKey as keyof SelectorContext)) {
|
||||
context[canonicalKey as keyof SelectorContext] = strValue
|
||||
}
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
166
apps/sim/tools/jira/search_users.ts
Normal file
166
apps/sim/tools/jira/search_users.ts
Normal file
@@ -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<JiraSearchUsersParams, JiraSearchUsersResponse> = {
|
||||
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' },
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1085,6 +1085,7 @@ import {
|
||||
jiraRemoveWatcherTool,
|
||||
jiraRetrieveTool,
|
||||
jiraSearchIssuesTool,
|
||||
jiraSearchUsersTool,
|
||||
jiraTransitionIssueTool,
|
||||
jiraUpdateCommentTool,
|
||||
jiraUpdateTool,
|
||||
@@ -2536,6 +2537,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user