chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const airtableSelectors = {
|
||||
'airtable.bases': {
|
||||
key: 'airtable.bases',
|
||||
contracts: [selectorContracts.airtableBasesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'airtable.bases',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'airtable.bases')
|
||||
const data = await requestJson(selectorContracts.airtableBasesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.bases || []).map((base) => ({
|
||||
id: base.id,
|
||||
label: base.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'airtable.bases')
|
||||
const data = await requestJson(selectorContracts.airtableBasesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
baseId: detailId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const base = (data.bases || []).find((b) => b.id === detailId) ?? null
|
||||
if (!base) return null
|
||||
return { id: base.id, label: base.name }
|
||||
},
|
||||
},
|
||||
'airtable.tables': {
|
||||
key: 'airtable.tables',
|
||||
contracts: [selectorContracts.airtableTablesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'airtable.tables',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.baseId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.baseId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'airtable.tables')
|
||||
if (!context.baseId) {
|
||||
throw new Error('Missing base ID for airtable.tables selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.airtableTablesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
baseId: context.baseId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.tables || []).map((table) => ({
|
||||
id: table.id,
|
||||
label: table.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'airtable.tables')
|
||||
if (!context.baseId) return null
|
||||
const data = await requestJson(selectorContracts.airtableTablesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
baseId: context.baseId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const table = (data.tables || []).find((t) => t.id === detailId) ?? null
|
||||
if (!table) return null
|
||||
return { id: table.id, label: table.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'airtable.bases' | 'airtable.tables'>, SelectorDefinition>
|
||||
@@ -0,0 +1,37 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const asanaSelectors = {
|
||||
'asana.workspaces': {
|
||||
key: 'asana.workspaces',
|
||||
contracts: [selectorContracts.asanaWorkspacesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'asana.workspaces',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'asana.workspaces')
|
||||
const data = await requestJson(selectorContracts.asanaWorkspacesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.workspaces || []).map((ws) => ({ id: ws.id, label: ws.name }))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'asana.workspaces')
|
||||
const data = await requestJson(selectorContracts.asanaWorkspacesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const ws = (data.workspaces || []).find((w) => w.id === detailId) ?? null
|
||||
if (!ws) return null
|
||||
return { id: ws.id, label: ws.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'asana.workspaces'>, SelectorDefinition>
|
||||
@@ -0,0 +1,73 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const attioSelectors = {
|
||||
'attio.objects': {
|
||||
key: 'attio.objects',
|
||||
contracts: [selectorContracts.attioObjectsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'attio.objects',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'attio.objects')
|
||||
const data = await requestJson(selectorContracts.attioObjectsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.objects || []).map((obj) => ({
|
||||
id: obj.id,
|
||||
label: obj.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'attio.objects')
|
||||
const data = await requestJson(selectorContracts.attioObjectsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const obj = (data.objects || []).find((o) => o.id === detailId) ?? null
|
||||
if (!obj) return null
|
||||
return { id: obj.id, label: obj.name }
|
||||
},
|
||||
},
|
||||
'attio.lists': {
|
||||
key: 'attio.lists',
|
||||
contracts: [selectorContracts.attioListsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'attio.lists',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'attio.lists')
|
||||
const data = await requestJson(selectorContracts.attioListsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.lists || []).map((list) => ({
|
||||
id: list.id,
|
||||
label: list.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'attio.lists')
|
||||
const data = await requestJson(selectorContracts.attioListsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const list = (data.lists || []).find((l) => l.id === detailId) ?? null
|
||||
if (!list) return null
|
||||
return { id: list.id, label: list.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'attio.objects' | 'attio.lists'>, SelectorDefinition>
|
||||
@@ -0,0 +1,111 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const bigquerySelectors = {
|
||||
'bigquery.datasets': {
|
||||
key: 'bigquery.datasets',
|
||||
contracts: [selectorContracts.bigQueryDatasetsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'bigquery.datasets',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.projectId ?? 'none',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.projectId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'bigquery.datasets')
|
||||
if (!context.projectId) throw new Error('Missing project ID for bigquery.datasets selector')
|
||||
const data = await requestJson(selectorContracts.bigQueryDatasetsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
projectId: context.projectId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.datasets || []).map((ds) => ({
|
||||
id: ds.datasetReference.datasetId,
|
||||
label: ds.friendlyName || ds.datasetReference.datasetId,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId || !context.projectId) return null
|
||||
const credentialId = ensureCredential(context, 'bigquery.datasets')
|
||||
const data = await requestJson(selectorContracts.bigQueryDatasetsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
projectId: context.projectId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const ds =
|
||||
(data.datasets || []).find((d) => d.datasetReference.datasetId === detailId) ?? null
|
||||
if (!ds) return null
|
||||
return {
|
||||
id: ds.datasetReference.datasetId,
|
||||
label: ds.friendlyName || ds.datasetReference.datasetId,
|
||||
}
|
||||
},
|
||||
},
|
||||
'bigquery.tables': {
|
||||
key: 'bigquery.tables',
|
||||
contracts: [selectorContracts.bigQueryTablesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'bigquery.tables',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.projectId ?? 'none',
|
||||
context.datasetId ?? 'none',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) =>
|
||||
Boolean(context.oauthCredential && context.projectId && context.datasetId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'bigquery.tables')
|
||||
if (!context.projectId) throw new Error('Missing project ID for bigquery.tables selector')
|
||||
if (!context.datasetId) throw new Error('Missing dataset ID for bigquery.tables selector')
|
||||
const data = await requestJson(selectorContracts.bigQueryTablesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
projectId: context.projectId,
|
||||
datasetId: context.datasetId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.tables || []).map((t) => ({
|
||||
id: t.tableReference.tableId,
|
||||
label: t.friendlyName || t.tableReference.tableId,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId || !context.projectId || !context.datasetId) return null
|
||||
const credentialId = ensureCredential(context, 'bigquery.tables')
|
||||
const data = await requestJson(selectorContracts.bigQueryTablesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
projectId: context.projectId,
|
||||
datasetId: context.datasetId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const t = (data.tables || []).find((tbl) => tbl.tableReference.tableId === detailId) ?? null
|
||||
if (!t) return null
|
||||
return { id: t.tableReference.tableId, label: t.friendlyName || t.tableReference.tableId }
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'bigquery.datasets' | 'bigquery.tables'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,76 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const calcomSelectors = {
|
||||
'calcom.eventTypes': {
|
||||
key: 'calcom.eventTypes',
|
||||
contracts: [selectorContracts.calcomEventTypesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'calcom.eventTypes',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'calcom.eventTypes')
|
||||
const data = await requestJson(selectorContracts.calcomEventTypesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.eventTypes || []).map((et) => ({
|
||||
id: et.id,
|
||||
label: et.title || et.slug,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'calcom.eventTypes')
|
||||
const data = await requestJson(selectorContracts.calcomEventTypesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const et = (data.eventTypes || []).find((e) => e.id === detailId) ?? null
|
||||
if (!et) return null
|
||||
return { id: et.id, label: et.title || et.slug }
|
||||
},
|
||||
},
|
||||
'calcom.schedules': {
|
||||
key: 'calcom.schedules',
|
||||
contracts: [selectorContracts.calcomSchedulesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'calcom.schedules',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'calcom.schedules')
|
||||
const data = await requestJson(selectorContracts.calcomSchedulesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.schedules || []).map((s) => ({
|
||||
id: s.id,
|
||||
label: s.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'calcom.schedules')
|
||||
const data = await requestJson(selectorContracts.calcomSchedulesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const s = (data.schedules || []).find((sc) => sc.id === detailId) ?? null
|
||||
if (!s) return null
|
||||
return { id: s.id, label: s.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'calcom.eventTypes' | 'calcom.schedules'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,94 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
function ensureAwsSelectorCredentials(context: SelectorQueryArgs['context'], key: SelectorKey) {
|
||||
if (!context.awsAccessKeyId || !context.awsSecretAccessKey || !context.awsRegion) {
|
||||
throw new Error(`Missing AWS credentials for selector ${key}`)
|
||||
}
|
||||
|
||||
return {
|
||||
accessKeyId: context.awsAccessKeyId,
|
||||
secretAccessKey: context.awsSecretAccessKey,
|
||||
region: context.awsRegion,
|
||||
}
|
||||
}
|
||||
|
||||
export const cloudwatchSelectors = {
|
||||
'cloudwatch.logGroups': {
|
||||
key: 'cloudwatch.logGroups',
|
||||
contracts: [selectorContracts.cloudwatchLogGroupsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'cloudwatch.logGroups',
|
||||
context.awsAccessKeyId ?? 'none',
|
||||
context.awsRegion ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) =>
|
||||
Boolean(context.awsAccessKeyId && context.awsSecretAccessKey && context.awsRegion),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const awsCredentials = ensureAwsSelectorCredentials(context, 'cloudwatch.logGroups')
|
||||
const data = await requestJson(selectorContracts.cloudwatchLogGroupsSelectorContract, {
|
||||
body: {
|
||||
...awsCredentials,
|
||||
prefix: search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.output?.logGroups || []).map((lg) => ({
|
||||
id: lg.logGroupName,
|
||||
label: lg.logGroupName,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
return { id: detailId, label: detailId }
|
||||
},
|
||||
},
|
||||
'cloudwatch.logStreams': {
|
||||
key: 'cloudwatch.logStreams',
|
||||
contracts: [selectorContracts.cloudwatchLogStreamsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'cloudwatch.logStreams',
|
||||
context.awsAccessKeyId ?? 'none',
|
||||
context.awsRegion ?? 'none',
|
||||
context.logGroupName ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) =>
|
||||
Boolean(
|
||||
context.awsAccessKeyId &&
|
||||
context.awsSecretAccessKey &&
|
||||
context.awsRegion &&
|
||||
context.logGroupName
|
||||
),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const awsCredentials = ensureAwsSelectorCredentials(context, 'cloudwatch.logStreams')
|
||||
if (!context.logGroupName) {
|
||||
throw new Error('Missing log group name for cloudwatch.logStreams selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.cloudwatchLogStreamsSelectorContract, {
|
||||
body: {
|
||||
...awsCredentials,
|
||||
logGroupName: context.logGroupName,
|
||||
prefix: search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.output?.logStreams || []).map((ls) => ({
|
||||
id: ls.logStreamName,
|
||||
label: ls.logStreamName,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
return { id: detailId, label: detailId }
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'cloudwatch.logGroups' | 'cloudwatch.logStreams'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,134 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { fetchOAuthToken } from '@/hooks/selectors/helpers'
|
||||
import { ensureCredential, ensureDomain, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
function formatConfluenceSpaceLabel(space: { name: string; key: string; status?: string }): string {
|
||||
const base = `${space.name} (${space.key})`
|
||||
return space.status === 'archived' ? `${base} — archived` : base
|
||||
}
|
||||
|
||||
function toSpaceOption(space: { name: string; key: string; status?: string }): {
|
||||
id: string
|
||||
label: string
|
||||
} {
|
||||
return { id: space.key, label: formatConfluenceSpaceLabel(space) }
|
||||
}
|
||||
|
||||
export const confluenceSelectors = {
|
||||
'confluence.spaces': {
|
||||
key: 'confluence.spaces',
|
||||
contracts: [selectorContracts.confluenceSpacesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'confluence.spaces',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
/**
|
||||
* Drives pagination through {@link useSelectorOptions}, which drains every
|
||||
* page via this callback. No `fetchList` — the paged path supersedes it.
|
||||
*/
|
||||
fetchPage: async ({ context, cursor, signal }) => {
|
||||
const credentialId = ensureCredential(context, 'confluence.spaces')
|
||||
const domain = ensureDomain(context, 'confluence.spaces')
|
||||
const data = await requestJson(selectorContracts.confluenceSpacesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
domain,
|
||||
cursor,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return {
|
||||
items: (data.spaces || []).map(toSpaceOption),
|
||||
nextCursor: data.nextCursor,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Resolves a single space label. Hits only the first page — the dropdown's
|
||||
* `fetchPage` stream populates the options cache for spaces beyond page 1,
|
||||
* and `useSelectorOptionMap` merges them in. Walking all pages here would
|
||||
* double API load since the stream is already running in parallel.
|
||||
*/
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'confluence.spaces')
|
||||
const domain = ensureDomain(context, 'confluence.spaces')
|
||||
const data = await requestJson(selectorContracts.confluenceSpacesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
domain,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const space = (data.spaces || []).find((s) => s.key === detailId) ?? null
|
||||
if (!space) return null
|
||||
return toSpaceOption(space)
|
||||
},
|
||||
},
|
||||
'confluence.pages': {
|
||||
key: 'confluence.pages',
|
||||
contracts: [
|
||||
selectorContracts.confluencePagesSelectorContract,
|
||||
selectorContracts.confluencePageSelectorContract,
|
||||
],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'confluence.pages',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'confluence.pages')
|
||||
const domain = ensureDomain(context, 'confluence.pages')
|
||||
const bundle = await fetchOAuthToken(credentialId, context.workflowId)
|
||||
if (!bundle) {
|
||||
throw new Error('Missing Confluence access token')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.confluencePagesSelectorContract, {
|
||||
body: {
|
||||
domain,
|
||||
accessToken: bundle.accessToken,
|
||||
cloudId: bundle.cloudId,
|
||||
title: search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'confluence.pages')
|
||||
const domain = ensureDomain(context, 'confluence.pages')
|
||||
const bundle = await fetchOAuthToken(credentialId, context.workflowId)
|
||||
if (!bundle) {
|
||||
throw new Error('Missing Confluence access token')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.confluencePageSelectorContract, {
|
||||
body: {
|
||||
domain,
|
||||
accessToken: bundle.accessToken,
|
||||
cloudId: bundle.cloudId,
|
||||
pageId: detailId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return { id: data.id, label: data.title }
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'confluence.spaces' | 'confluence.pages'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,188 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const googleSelectors = {
|
||||
'google.tasks.lists': {
|
||||
key: 'google.tasks.lists',
|
||||
contracts: [selectorContracts.googleTasksTaskListsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.tasks.lists',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.tasks.lists')
|
||||
const data = await requestJson(selectorContracts.googleTasksTaskListsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.taskLists || []).map((tl) => ({ id: tl.id, label: tl.title }))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'google.tasks.lists')
|
||||
const data = await requestJson(selectorContracts.googleTasksTaskListsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const tl = (data.taskLists || []).find((t) => t.id === detailId) ?? null
|
||||
if (!tl) return null
|
||||
return { id: tl.id, label: tl.title }
|
||||
},
|
||||
},
|
||||
'gmail.labels': {
|
||||
key: 'gmail.labels',
|
||||
contracts: [selectorContracts.gmailLabelsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'gmail.labels',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'gmail.labels')
|
||||
const data = await requestJson(selectorContracts.gmailLabelsSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.labels || []).map((label) => ({
|
||||
id: label.id,
|
||||
label: label.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'google.calendar': {
|
||||
key: 'google.calendar',
|
||||
contracts: [selectorContracts.googleCalendarSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.calendar',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.calendar')
|
||||
const data = await requestJson(selectorContracts.googleCalendarSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.calendars || []).map((calendar) => ({
|
||||
id: calendar.id,
|
||||
label: calendar.summary,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'google.drive': {
|
||||
key: 'google.drive',
|
||||
contracts: [
|
||||
selectorContracts.googleDriveFilesSelectorContract,
|
||||
selectorContracts.googleDriveFileSelectorContract,
|
||||
],
|
||||
staleTime: 15 * 1000,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.drive',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.mimeType ?? 'any',
|
||||
context.fileId ?? 'root',
|
||||
search ?? '',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.drive')
|
||||
const data = await requestJson(selectorContracts.googleDriveFilesSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
mimeType: context.mimeType,
|
||||
parentId: context.fileId,
|
||||
query: search,
|
||||
workflowId: context.workflowId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'google.drive')
|
||||
const data = await requestJson(selectorContracts.googleDriveFileSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
fileId: detailId,
|
||||
workflowId: context.workflowId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const file = data.file
|
||||
if (!file) return null
|
||||
return { id: file.id, label: file.name }
|
||||
},
|
||||
},
|
||||
'google.sheets': {
|
||||
key: 'google.sheets',
|
||||
contracts: [selectorContracts.googleSheetsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'google.sheets',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.spreadsheetId ?? 'none',
|
||||
context.impersonateUserEmail ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.spreadsheetId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'google.sheets')
|
||||
if (!context.spreadsheetId) {
|
||||
throw new Error('Missing spreadsheet ID for google.sheets selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.googleSheetsSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
spreadsheetId: context.spreadsheetId,
|
||||
workflowId: context.workflowId,
|
||||
impersonateEmail: context.impersonateUserEmail,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.sheets || []).map((sheet) => ({
|
||||
id: sheet.id,
|
||||
label: sheet.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<
|
||||
SelectorKey,
|
||||
'google.tasks.lists' | 'gmail.labels' | 'google.calendar' | 'google.drive' | 'google.sheets'
|
||||
>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,134 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { fetchOAuthToken } from '@/hooks/selectors/helpers'
|
||||
import { ensureCredential, ensureDomain, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const jiraSelectors = {
|
||||
'jira.projects': {
|
||||
key: 'jira.projects',
|
||||
contracts: [
|
||||
selectorContracts.jiraProjectsSelectorContract,
|
||||
selectorContracts.jiraProjectSelectorContract,
|
||||
],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jira.projects',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jira.projects')
|
||||
const domain = ensureDomain(context, 'jira.projects')
|
||||
const bundle = await fetchOAuthToken(credentialId, context.workflowId)
|
||||
if (!bundle) {
|
||||
throw new Error('Missing Jira access token')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.jiraProjectsSelectorContract, {
|
||||
query: {
|
||||
domain,
|
||||
accessToken: bundle.accessToken,
|
||||
cloudId: bundle.cloudId,
|
||||
query: search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.projects || []).map((project) => ({
|
||||
id: project.id,
|
||||
label: project.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'jira.projects')
|
||||
const domain = ensureDomain(context, 'jira.projects')
|
||||
const bundle = await fetchOAuthToken(credentialId, context.workflowId)
|
||||
if (!bundle) {
|
||||
throw new Error('Missing Jira access token')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.jiraProjectSelectorContract, {
|
||||
body: {
|
||||
domain,
|
||||
accessToken: bundle.accessToken,
|
||||
cloudId: bundle.cloudId,
|
||||
projectId: detailId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
if (!data.project) return null
|
||||
return {
|
||||
id: data.project.id,
|
||||
label: data.project.name,
|
||||
}
|
||||
},
|
||||
},
|
||||
'jira.issues': {
|
||||
key: 'jira.issues',
|
||||
contracts: [
|
||||
selectorContracts.jiraIssuesSelectorContract,
|
||||
selectorContracts.jiraIssueSelectorContract,
|
||||
],
|
||||
staleTime: 15 * 1000,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jira.issues',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
context.projectId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jira.issues')
|
||||
const domain = ensureDomain(context, 'jira.issues')
|
||||
const bundle = await fetchOAuthToken(credentialId, context.workflowId)
|
||||
if (!bundle) {
|
||||
throw new Error('Missing Jira access token')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.jiraIssuesSelectorContract, {
|
||||
query: {
|
||||
domain,
|
||||
accessToken: bundle.accessToken,
|
||||
cloudId: bundle.cloudId,
|
||||
projectId: context.projectId,
|
||||
query: search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const issues =
|
||||
data.sections?.flatMap((section) =>
|
||||
(section.issues || []).map((issue) => ({
|
||||
id: issue.id || issue.key || '',
|
||||
name: issue.summary || issue.key || '',
|
||||
}))
|
||||
) || []
|
||||
return issues
|
||||
.filter((issue) => issue.id)
|
||||
.map((issue) => ({ id: issue.id, label: issue.name || issue.id }))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'jira.issues')
|
||||
const domain = ensureDomain(context, 'jira.issues')
|
||||
const bundle = await fetchOAuthToken(credentialId, context.workflowId)
|
||||
if (!bundle) {
|
||||
throw new Error('Missing Jira access token')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.jiraIssueSelectorContract, {
|
||||
body: {
|
||||
domain,
|
||||
accessToken: bundle.accessToken,
|
||||
cloudId: bundle.cloudId,
|
||||
issueKeys: [detailId],
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const issue = data.issues?.[0]
|
||||
if (!issue) return null
|
||||
return { id: issue.id, label: issue.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'jira.projects' | 'jira.issues'>, SelectorDefinition>
|
||||
@@ -0,0 +1,104 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, ensureDomain, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const jsmSelectors = {
|
||||
'jsm.serviceDesks': {
|
||||
key: 'jsm.serviceDesks',
|
||||
contracts: [selectorContracts.jsmServiceDesksSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jsm.serviceDesks',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.domain),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jsm.serviceDesks')
|
||||
const domain = ensureDomain(context, 'jsm.serviceDesks')
|
||||
const data = await requestJson(selectorContracts.jsmServiceDesksSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
domain,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.serviceDesks || []).map((sd) => ({
|
||||
id: sd.id,
|
||||
label: sd.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'jsm.serviceDesks')
|
||||
const domain = ensureDomain(context, 'jsm.serviceDesks')
|
||||
const data = await requestJson(selectorContracts.jsmServiceDesksSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
domain,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const sd = (data.serviceDesks || []).find((s) => s.id === detailId) ?? null
|
||||
if (!sd) return null
|
||||
return { id: sd.id, label: sd.name }
|
||||
},
|
||||
},
|
||||
'jsm.requestTypes': {
|
||||
key: 'jsm.requestTypes',
|
||||
contracts: [selectorContracts.jsmRequestTypesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'jsm.requestTypes',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.domain ?? 'none',
|
||||
context.serviceDeskId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) =>
|
||||
Boolean(context.oauthCredential && context.domain && context.serviceDeskId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'jsm.requestTypes')
|
||||
const domain = ensureDomain(context, 'jsm.requestTypes')
|
||||
if (!context.serviceDeskId) throw new Error('Missing serviceDeskId for jsm.requestTypes')
|
||||
const data = await requestJson(selectorContracts.jsmRequestTypesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
domain,
|
||||
serviceDeskId: context.serviceDeskId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.requestTypes || []).map((rt) => ({
|
||||
id: rt.id,
|
||||
label: rt.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'jsm.requestTypes')
|
||||
const domain = ensureDomain(context, 'jsm.requestTypes')
|
||||
if (!context.serviceDeskId) return null
|
||||
const data = await requestJson(selectorContracts.jsmRequestTypesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
domain,
|
||||
serviceDeskId: context.serviceDeskId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const rt = (data.requestTypes || []).find((r) => r.id === detailId) ?? null
|
||||
if (!rt) return null
|
||||
return { id: rt.id, label: rt.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'jsm.serviceDesks' | 'jsm.requestTypes'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,61 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureKnowledgeBase, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
const KNOWLEDGE_DOCUMENTS_PAGE_LIMIT = 100
|
||||
|
||||
export const knowledgeSelectors = {
|
||||
'knowledge.documents': {
|
||||
key: 'knowledge.documents',
|
||||
contracts: [
|
||||
selectorContracts.listKnowledgeSelectorDocumentsContract,
|
||||
selectorContracts.getKnowledgeSelectorDocumentContract,
|
||||
],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'knowledge.documents',
|
||||
context.knowledgeBaseId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.knowledgeBaseId),
|
||||
/**
|
||||
* Drives pagination through {@link useSelectorOptions}, which drains every
|
||||
* page via this callback. The `pagination.hasMore` flag from the route
|
||||
* decides when to stop; `nextCursor` encodes the next `offset`.
|
||||
*/
|
||||
fetchPage: async ({ context, search, cursor, signal }) => {
|
||||
const knowledgeBaseId = ensureKnowledgeBase(context)
|
||||
const offset = cursor ? Number(cursor) : 0
|
||||
const result = await requestJson(selectorContracts.listKnowledgeSelectorDocumentsContract, {
|
||||
params: { id: knowledgeBaseId },
|
||||
query: {
|
||||
limit: KNOWLEDGE_DOCUMENTS_PAGE_LIMIT,
|
||||
offset,
|
||||
search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const { pagination } = result.data
|
||||
const nextOffset = pagination.offset + pagination.limit
|
||||
return {
|
||||
items: result.data.documents.map((doc) => ({
|
||||
id: doc.id,
|
||||
label: doc.filename,
|
||||
})),
|
||||
nextCursor: pagination.hasMore ? String(nextOffset) : undefined,
|
||||
}
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const knowledgeBaseId = ensureKnowledgeBase(context)
|
||||
const result = await requestJson(selectorContracts.getKnowledgeSelectorDocumentContract, {
|
||||
params: { id: knowledgeBaseId, documentId: detailId },
|
||||
query: { includeDisabled: 'true' },
|
||||
signal,
|
||||
})
|
||||
return { id: result.data.id, label: result.data.filename }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'knowledge.documents'>, SelectorDefinition>
|
||||
@@ -0,0 +1,59 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const linearSelectors = {
|
||||
'linear.teams': {
|
||||
key: 'linear.teams',
|
||||
contracts: [selectorContracts.linearTeamsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'linear.teams',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'linear.teams')
|
||||
const data = await requestJson(selectorContracts.linearTeamsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.teams || []).map((team) => ({
|
||||
id: team.id,
|
||||
label: team.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'linear.projects': {
|
||||
key: 'linear.projects',
|
||||
contracts: [selectorContracts.linearProjectsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'linear.projects',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.teamId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.teamId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'linear.projects')
|
||||
if (!context.teamId) {
|
||||
throw new Error('Missing team ID for linear.projects selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.linearProjectsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
teamId: context.teamId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.projects || []).map((project) => ({
|
||||
id: project.id,
|
||||
label: project.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'linear.teams' | 'linear.projects'>, SelectorDefinition>
|
||||
@@ -0,0 +1,378 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const microsoftSelectors = {
|
||||
'microsoft.planner.plans': {
|
||||
key: 'microsoft.planner.plans',
|
||||
contracts: [selectorContracts.microsoftPlannerPlansSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.planner.plans',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.planner.plans')
|
||||
const data = await requestJson(selectorContracts.microsoftPlannerPlansSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.plans || []).map((plan) => ({ id: plan.id, label: plan.title }))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'microsoft.planner.plans')
|
||||
const data = await requestJson(selectorContracts.microsoftPlannerPlansSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const plan = (data.plans || []).find((p) => p.id === detailId) ?? null
|
||||
if (!plan) return null
|
||||
return { id: plan.id, label: plan.title }
|
||||
},
|
||||
},
|
||||
'outlook.folders': {
|
||||
key: 'outlook.folders',
|
||||
contracts: [selectorContracts.outlookFoldersSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'outlook.folders',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'outlook.folders')
|
||||
const data = await requestJson(selectorContracts.outlookFoldersSelectorContract, {
|
||||
query: { credentialId },
|
||||
signal,
|
||||
})
|
||||
return (data.folders || []).map((folder) => ({
|
||||
id: folder.id,
|
||||
label: folder.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.teams': {
|
||||
key: 'microsoft.teams',
|
||||
contracts: [selectorContracts.microsoftTeamsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.teams',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.teams')
|
||||
const data = await requestJson(selectorContracts.microsoftTeamsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.teams || []).map((team) => ({
|
||||
id: team.id,
|
||||
label: team.displayName,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.chats': {
|
||||
key: 'microsoft.chats',
|
||||
contracts: [selectorContracts.microsoftChatsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.chats',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.chats')
|
||||
const data = await requestJson(selectorContracts.microsoftChatsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.chats || []).map((chat) => ({
|
||||
id: chat.id,
|
||||
label: chat.displayName,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.channels': {
|
||||
key: 'microsoft.channels',
|
||||
contracts: [selectorContracts.microsoftChannelsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.channels',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.teamId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.teamId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.channels')
|
||||
if (!context.teamId) {
|
||||
throw new Error('Missing team ID for microsoft.channels selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.microsoftChannelsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
teamId: context.teamId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.channels || []).map((channel) => ({
|
||||
id: channel.id,
|
||||
label: channel.displayName,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.planner': {
|
||||
key: 'microsoft.planner',
|
||||
contracts: [selectorContracts.microsoftPlannerTasksSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.planner',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.planId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.planId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.planner')
|
||||
if (!context.planId) {
|
||||
throw new Error('Missing plan ID for microsoft.planner selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.microsoftPlannerTasksSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
planId: context.planId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.tasks || []).map((task) => ({
|
||||
id: task.id,
|
||||
label: task.title,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId || !context.planId) return null
|
||||
const credentialId = ensureCredential(context, 'microsoft.planner')
|
||||
const data = await requestJson(selectorContracts.microsoftPlannerTasksSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
planId: context.planId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const task = (data.tasks || []).find((t) => t.id === detailId) ?? null
|
||||
if (!task) return null
|
||||
return { id: task.id, label: task.title }
|
||||
},
|
||||
},
|
||||
'onedrive.files': {
|
||||
key: 'onedrive.files',
|
||||
contracts: [selectorContracts.onedriveFilesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'onedrive.files',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'onedrive.files')
|
||||
const data = await requestJson(selectorContracts.onedriveFilesSelectorContract, {
|
||||
query: { credentialId },
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'onedrive.folders': {
|
||||
key: 'onedrive.folders',
|
||||
contracts: [selectorContracts.onedriveFoldersSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'onedrive.folders',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'onedrive.folders')
|
||||
const data = await requestJson(selectorContracts.onedriveFoldersSelectorContract, {
|
||||
query: { credentialId },
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.excel.sheets': {
|
||||
key: 'microsoft.excel.sheets',
|
||||
contracts: [selectorContracts.microsoftExcelSheetsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.excel.sheets',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.spreadsheetId ?? 'none',
|
||||
context.driveId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.spreadsheetId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.excel.sheets')
|
||||
if (!context.spreadsheetId) {
|
||||
throw new Error('Missing spreadsheet ID for microsoft.excel.sheets selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.microsoftExcelSheetsSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
spreadsheetId: context.spreadsheetId,
|
||||
driveId: context.driveId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.sheets || []).map((sheet) => ({
|
||||
id: sheet.id,
|
||||
label: sheet.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.excel.drives': {
|
||||
key: 'microsoft.excel.drives',
|
||||
contracts: [
|
||||
selectorContracts.microsoftExcelDrivesSelectorContract,
|
||||
selectorContracts.microsoftExcelDriveSelectorContract,
|
||||
],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.excel.drives',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.siteId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.excel.drives')
|
||||
if (!context.siteId) {
|
||||
throw new Error('Missing site ID for microsoft.excel.drives selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.microsoftExcelDrivesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
siteId: context.siteId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return data.drives.map((drive) => ({
|
||||
id: drive.id,
|
||||
label: drive.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId || !context.siteId) return null
|
||||
const credentialId = ensureCredential(context, 'microsoft.excel.drives')
|
||||
const data = await requestJson(selectorContracts.microsoftExcelDriveSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
siteId: context.siteId,
|
||||
driveId: detailId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const { drive } = data
|
||||
if (!drive) return null
|
||||
return { id: drive.id, label: drive.name }
|
||||
},
|
||||
},
|
||||
'microsoft.excel': {
|
||||
key: 'microsoft.excel',
|
||||
contracts: [selectorContracts.microsoftFilesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.excel',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.driveId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.excel')
|
||||
const data = await requestJson(selectorContracts.microsoftFilesSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
query: search,
|
||||
driveId: context.driveId,
|
||||
workflowId: context.workflowId,
|
||||
fileType: 'excel',
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'microsoft.word': {
|
||||
key: 'microsoft.word',
|
||||
contracts: [selectorContracts.microsoftFilesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'microsoft.word',
|
||||
context.oauthCredential ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'microsoft.word')
|
||||
const data = await requestJson(selectorContracts.microsoftFilesSelectorContract, {
|
||||
query: {
|
||||
credentialId,
|
||||
query: search,
|
||||
workflowId: context.workflowId,
|
||||
fileType: 'word',
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<
|
||||
SelectorKey,
|
||||
| 'microsoft.planner.plans'
|
||||
| 'outlook.folders'
|
||||
| 'microsoft.teams'
|
||||
| 'microsoft.chats'
|
||||
| 'microsoft.channels'
|
||||
| 'microsoft.planner'
|
||||
| 'onedrive.files'
|
||||
| 'onedrive.folders'
|
||||
| 'microsoft.excel.sheets'
|
||||
| 'microsoft.excel.drives'
|
||||
| 'microsoft.excel'
|
||||
| 'microsoft.word'
|
||||
>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,86 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const mondaySelectors = {
|
||||
'monday.boards': {
|
||||
key: 'monday.boards',
|
||||
contracts: [selectorContracts.mondayBoardsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'monday.boards',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'monday.boards')
|
||||
const data = await requestJson(selectorContracts.mondayBoardsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.boards || []).map((board) => ({
|
||||
id: board.id,
|
||||
label: board.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'monday.boards')
|
||||
const data = await requestJson(selectorContracts.mondayBoardsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const board = (data.boards || []).find((b) => b.id === detailId) ?? null
|
||||
if (!board) return null
|
||||
return { id: board.id, label: board.name }
|
||||
},
|
||||
},
|
||||
'monday.groups': {
|
||||
key: 'monday.groups',
|
||||
contracts: [selectorContracts.mondayGroupsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'monday.groups',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.boardId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.boardId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'monday.groups')
|
||||
if (!context.boardId) {
|
||||
throw new Error('Missing board ID for monday.groups selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.mondayGroupsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
boardId: context.boardId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.groups || []).map((group) => ({
|
||||
id: group.id,
|
||||
label: group.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'monday.groups')
|
||||
if (!context.boardId) return null
|
||||
const data = await requestJson(selectorContracts.mondayGroupsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
boardId: context.boardId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const group = (data.groups || []).find((g) => g.id === detailId) ?? null
|
||||
if (!group) return null
|
||||
return { id: group.id, label: group.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'monday.boards' | 'monday.groups'>, SelectorDefinition>
|
||||
@@ -0,0 +1,73 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const notionSelectors = {
|
||||
'notion.databases': {
|
||||
key: 'notion.databases',
|
||||
contracts: [selectorContracts.notionDatabasesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'notion.databases',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'notion.databases')
|
||||
const data = await requestJson(selectorContracts.notionDatabasesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.databases || []).map((db) => ({
|
||||
id: db.id,
|
||||
label: db.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'notion.databases')
|
||||
const data = await requestJson(selectorContracts.notionDatabasesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const db = (data.databases || []).find((d) => d.id === detailId) ?? null
|
||||
if (!db) return null
|
||||
return { id: db.id, label: db.name }
|
||||
},
|
||||
},
|
||||
'notion.pages': {
|
||||
key: 'notion.pages',
|
||||
contracts: [selectorContracts.notionPagesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'notion.pages',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'notion.pages')
|
||||
const data = await requestJson(selectorContracts.notionPagesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.pages || []).map((page) => ({
|
||||
id: page.id,
|
||||
label: page.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'notion.pages')
|
||||
const data = await requestJson(selectorContracts.notionPagesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const page = (data.pages || []).find((p) => p.id === detailId) ?? null
|
||||
if (!page) return null
|
||||
return { id: page.id, label: page.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'notion.databases' | 'notion.pages'>, SelectorDefinition>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const pipedriveSelectors = {
|
||||
'pipedrive.pipelines': {
|
||||
key: 'pipedrive.pipelines',
|
||||
contracts: [selectorContracts.pipedrivePipelinesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'pipedrive.pipelines',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'pipedrive.pipelines')
|
||||
const data = await requestJson(selectorContracts.pipedrivePipelinesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.pipelines || []).map((p) => ({
|
||||
id: p.id,
|
||||
label: p.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'pipedrive.pipelines')
|
||||
const data = await requestJson(selectorContracts.pipedrivePipelinesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const p = (data.pipelines || []).find((pl) => pl.id === detailId) ?? null
|
||||
if (!p) return null
|
||||
return { id: p.id, label: p.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'pipedrive.pipelines'>, SelectorDefinition>
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
|
||||
|
||||
export const SELECTOR_STALE = 60 * 1000
|
||||
|
||||
export const ensureCredential = (context: SelectorContext, key: SelectorKey): string => {
|
||||
if (!context.oauthCredential) {
|
||||
throw new Error(`Missing credential for selector ${key}`)
|
||||
}
|
||||
return context.oauthCredential
|
||||
}
|
||||
|
||||
export const ensureDomain = (context: SelectorContext, key: SelectorKey): string => {
|
||||
if (!context.domain) {
|
||||
throw new Error(`Missing domain for selector ${key}`)
|
||||
}
|
||||
return context.domain
|
||||
}
|
||||
|
||||
export const ensureKnowledgeBase = (context: SelectorContext): string => {
|
||||
if (!context.knowledgeBaseId) {
|
||||
throw new Error('Missing knowledge base id')
|
||||
}
|
||||
return context.knowledgeBaseId
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const sharepointSelectors = {
|
||||
'sharepoint.lists': {
|
||||
key: 'sharepoint.lists',
|
||||
contracts: [selectorContracts.sharepointListsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'sharepoint.lists',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.siteId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'sharepoint.lists')
|
||||
if (!context.siteId) throw new Error('Missing site ID for sharepoint.lists selector')
|
||||
const data = await requestJson(selectorContracts.sharepointListsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
siteId: context.siteId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.lists || []).map((list) => ({ id: list.id, label: list.displayName }))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId || !context.siteId) return null
|
||||
const credentialId = ensureCredential(context, 'sharepoint.lists')
|
||||
const data = await requestJson(selectorContracts.sharepointListsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
siteId: context.siteId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const list = (data.lists || []).find((l) => l.id === detailId) ?? null
|
||||
if (!list) return null
|
||||
return { id: list.id, label: list.displayName }
|
||||
},
|
||||
},
|
||||
'sharepoint.sites': {
|
||||
key: 'sharepoint.sites',
|
||||
contracts: [selectorContracts.sharepointSitesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'sharepoint.sites',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'sharepoint.sites')
|
||||
const data = await requestJson(selectorContracts.sharepointSitesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.files || []).map((file) => ({
|
||||
id: file.id,
|
||||
label: file.name,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'sharepoint.sites')
|
||||
const data = await requestJson(selectorContracts.sharepointSitesSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
const site = (data.files || []).find((f) => f.id === detailId) ?? null
|
||||
if (!site) return null
|
||||
return { id: site.id, label: site.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'sharepoint.lists' | 'sharepoint.sites'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,119 @@
|
||||
import { getColumnId } from '@/lib/table/column-keys'
|
||||
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
|
||||
import { getTableDetailQueryOptions } from '@/hooks/queries/tables'
|
||||
import { getFolderMap } from '@/hooks/queries/utils/folder-cache'
|
||||
import { getFolderPath } from '@/hooks/queries/utils/folder-tree'
|
||||
import { getWorkflowById, getWorkflows } from '@/hooks/queries/utils/workflow-cache'
|
||||
import { getWorkflowListQueryOptions } from '@/hooks/queries/utils/workflow-list-query'
|
||||
import { SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import { selectorKeys } from '@/hooks/selectors/query-keys'
|
||||
import type {
|
||||
SelectorDefinition,
|
||||
SelectorKey,
|
||||
SelectorOption,
|
||||
SelectorQueryArgs,
|
||||
} from '@/hooks/selectors/types'
|
||||
import type { WorkflowFolder } from '@/stores/folders/types'
|
||||
import type { WorkflowMetadata } from '@/stores/workflows/registry/types'
|
||||
|
||||
/**
|
||||
* Builds a label for a workflow option, appending the folder path when another
|
||||
* workflow in the workspace shares the same display name. Avoids suffixing
|
||||
* every workflow so the dropdown stays readable for the common case.
|
||||
*/
|
||||
function buildDisambiguatedLabel(
|
||||
workflow: WorkflowMetadata,
|
||||
duplicateNames: Set<string>,
|
||||
folders: Record<string, WorkflowFolder>
|
||||
): string {
|
||||
const baseLabel = workflow.name || `Workflow ${workflow.id.slice(0, 8)}`
|
||||
if (!duplicateNames.has(baseLabel)) return baseLabel
|
||||
|
||||
const folderPath = getFolderPath(workflow.folderId, folders)
|
||||
return folderPath ? `${baseLabel} (${folderPath})` : `${baseLabel} (Root)`
|
||||
}
|
||||
|
||||
function collectDuplicateNames(workflows: WorkflowMetadata[]): Set<string> {
|
||||
const counts = new Map<string, number>()
|
||||
for (const workflow of workflows) {
|
||||
const label = workflow.name || `Workflow ${workflow.id.slice(0, 8)}`
|
||||
counts.set(label, (counts.get(label) ?? 0) + 1)
|
||||
}
|
||||
const duplicates = new Set<string>()
|
||||
for (const [label, count] of counts) {
|
||||
if (count > 1) duplicates.add(label)
|
||||
}
|
||||
return duplicates
|
||||
}
|
||||
|
||||
export const simSelectors = {
|
||||
'sim.workflows': {
|
||||
key: 'sim.workflows',
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) =>
|
||||
context.workspaceId
|
||||
? selectorKeys.simWorkflows(context.workspaceId, context.excludeWorkflowId)
|
||||
: [...selectorKeys.all, 'sim.workflows', 'none', context.excludeWorkflowId ?? 'none'],
|
||||
enabled: ({ context }) => Boolean(context.workspaceId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs): Promise<SelectorOption[]> => {
|
||||
if (!context.workspaceId) return []
|
||||
await getQueryClient().ensureQueryData(getWorkflowListQueryOptions(context.workspaceId))
|
||||
const workflows = getWorkflows(context.workspaceId)
|
||||
const folders = getFolderMap(context.workspaceId)
|
||||
const duplicateNames = collectDuplicateNames(workflows)
|
||||
return workflows
|
||||
.filter((w) => w.id !== context.excludeWorkflowId)
|
||||
.map((w) => ({
|
||||
id: w.id,
|
||||
label: buildDisambiguatedLabel(w, duplicateNames, folders),
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
},
|
||||
fetchById: async ({
|
||||
context,
|
||||
detailId,
|
||||
signal,
|
||||
}: SelectorQueryArgs): Promise<SelectorOption | null> => {
|
||||
if (!detailId || !context.workspaceId) return null
|
||||
await getQueryClient().ensureQueryData(getWorkflowListQueryOptions(context.workspaceId))
|
||||
const workflow = getWorkflowById(context.workspaceId, detailId)
|
||||
if (!workflow) return null
|
||||
const workflows = getWorkflows(context.workspaceId)
|
||||
const folders = getFolderMap(context.workspaceId)
|
||||
const duplicateNames = collectDuplicateNames(workflows)
|
||||
return {
|
||||
id: detailId,
|
||||
label: buildDisambiguatedLabel(workflow, duplicateNames, folders),
|
||||
}
|
||||
},
|
||||
},
|
||||
'table.columns': {
|
||||
key: 'table.columns',
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
...selectorKeys.all,
|
||||
'table.columns',
|
||||
context.workspaceId ?? 'none',
|
||||
context.tableId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.workspaceId && context.tableId),
|
||||
fetchList: async ({ context }: SelectorQueryArgs): Promise<SelectorOption[]> => {
|
||||
if (!context.workspaceId || !context.tableId) return []
|
||||
const table = await getQueryClient().ensureQueryData(
|
||||
getTableDetailQueryOptions(context.workspaceId, context.tableId)
|
||||
)
|
||||
return (table.schema?.columns ?? [])
|
||||
.filter((col) => col.unique)
|
||||
.map((col) => ({ id: getColumnId(col), label: col.name }))
|
||||
},
|
||||
fetchById: async ({ context, detailId }: SelectorQueryArgs): Promise<SelectorOption | null> => {
|
||||
if (!detailId || !context.workspaceId || !context.tableId) return null
|
||||
const table = await getQueryClient().ensureQueryData(
|
||||
getTableDetailQueryOptions(context.workspaceId, context.tableId)
|
||||
)
|
||||
const col = (table.schema?.columns ?? []).find((c) => getColumnId(c) === detailId)
|
||||
return col ? { id: getColumnId(col), label: col.name } : null
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'sim.workflows' | 'table.columns'>, SelectorDefinition>
|
||||
@@ -0,0 +1,57 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const slackSelectors = {
|
||||
'slack.channels': {
|
||||
key: 'slack.channels',
|
||||
contracts: [selectorContracts.slackChannelsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'slack.channels',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'slack.channels')
|
||||
const data = await requestJson(selectorContracts.slackChannelsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.channels || []).map((channel) => ({
|
||||
id: channel.id,
|
||||
label: `#${channel.name}`,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'slack.users': {
|
||||
key: 'slack.users',
|
||||
contracts: [selectorContracts.slackUsersSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'slack.users',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'slack.users')
|
||||
const data = await requestJson(selectorContracts.slackUsersSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return data.users.map((user) => ({
|
||||
id: user.id,
|
||||
label: user.real_name || user.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'slack.channels' | 'slack.users'>, SelectorDefinition>
|
||||
@@ -0,0 +1,39 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const trelloSelectors = {
|
||||
'trello.boards': {
|
||||
key: 'trello.boards',
|
||||
contracts: [selectorContracts.trelloBoardsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'trello.boards',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'trello.boards')
|
||||
const data = await requestJson(selectorContracts.trelloBoardsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.boards || [])
|
||||
.filter((board) => !board.closed)
|
||||
.map((board) => ({ id: board.id, label: board.name }))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'trello.boards')
|
||||
const data = await requestJson(selectorContracts.trelloBoardsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const board = (data.boards || []).find((b) => b.id === detailId) ?? null
|
||||
if (!board) return null
|
||||
return { id: board.id, label: board.name }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'trello.boards'>, SelectorDefinition>
|
||||
@@ -0,0 +1,30 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const wealthboxSelectors = {
|
||||
'wealthbox.contacts': {
|
||||
key: 'wealthbox.contacts',
|
||||
contracts: [selectorContracts.wealthboxItemsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'wealthbox.contacts',
|
||||
context.oauthCredential ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'wealthbox.contacts')
|
||||
const data = await requestJson(selectorContracts.wealthboxItemsSelectorContract, {
|
||||
query: { credentialId, type: 'contact', query: search ?? '' },
|
||||
signal,
|
||||
})
|
||||
return (data.items || []).map((item) => ({
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'wealthbox.contacts'>, SelectorDefinition>
|
||||
@@ -0,0 +1,94 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const webflowSelectors = {
|
||||
'webflow.sites': {
|
||||
key: 'webflow.sites',
|
||||
contracts: [selectorContracts.webflowSitesSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'webflow.sites',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'webflow.sites')
|
||||
const data = await requestJson(selectorContracts.webflowSitesSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.sites || []).map((site) => ({
|
||||
id: site.id,
|
||||
label: site.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'webflow.collections': {
|
||||
key: 'webflow.collections',
|
||||
contracts: [selectorContracts.webflowCollectionsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'webflow.collections',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.siteId ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.siteId),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'webflow.collections')
|
||||
if (!context.siteId) {
|
||||
throw new Error('Missing site ID for webflow.collections selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.webflowCollectionsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
siteId: context.siteId,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.collections || []).map((collection) => ({
|
||||
id: collection.id,
|
||||
label: collection.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
'webflow.items': {
|
||||
key: 'webflow.items',
|
||||
contracts: [selectorContracts.webflowItemsSelectorContract],
|
||||
staleTime: 15 * 1000,
|
||||
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'webflow.items',
|
||||
context.oauthCredential ?? 'none',
|
||||
context.collectionId ?? 'none',
|
||||
search ?? '',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential && context.collectionId),
|
||||
fetchList: async ({ context, search, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'webflow.items')
|
||||
if (!context.collectionId) {
|
||||
throw new Error('Missing collection ID for webflow.items selector')
|
||||
}
|
||||
const data = await requestJson(selectorContracts.webflowItemsSelectorContract, {
|
||||
body: {
|
||||
credential: credentialId,
|
||||
workflowId: context.workflowId,
|
||||
collectionId: context.collectionId,
|
||||
search,
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return (data.items || []).map((item) => ({
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
}))
|
||||
},
|
||||
},
|
||||
} satisfies Record<
|
||||
Extract<SelectorKey, 'webflow.sites' | 'webflow.collections' | 'webflow.items'>,
|
||||
SelectorDefinition
|
||||
>
|
||||
@@ -0,0 +1,40 @@
|
||||
import { requestJson } from '@/lib/api/client/request'
|
||||
import * as selectorContracts from '@/lib/api/contracts/selectors'
|
||||
import { ensureCredential, SELECTOR_STALE } from '@/hooks/selectors/providers/shared'
|
||||
import type { SelectorDefinition, SelectorKey, SelectorQueryArgs } from '@/hooks/selectors/types'
|
||||
|
||||
export const zoomSelectors = {
|
||||
'zoom.meetings': {
|
||||
key: 'zoom.meetings',
|
||||
contracts: [selectorContracts.zoomMeetingsSelectorContract],
|
||||
staleTime: SELECTOR_STALE,
|
||||
getQueryKey: ({ context }: SelectorQueryArgs) => [
|
||||
'selectors',
|
||||
'zoom.meetings',
|
||||
context.oauthCredential ?? 'none',
|
||||
],
|
||||
enabled: ({ context }) => Boolean(context.oauthCredential),
|
||||
fetchList: async ({ context, signal }: SelectorQueryArgs) => {
|
||||
const credentialId = ensureCredential(context, 'zoom.meetings')
|
||||
const data = await requestJson(selectorContracts.zoomMeetingsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
return (data.meetings || []).map((m) => ({
|
||||
id: m.id,
|
||||
label: m.name || `Meeting ${m.id}`,
|
||||
}))
|
||||
},
|
||||
fetchById: async ({ context, detailId, signal }: SelectorQueryArgs) => {
|
||||
if (!detailId) return null
|
||||
const credentialId = ensureCredential(context, 'zoom.meetings')
|
||||
const data = await requestJson(selectorContracts.zoomMeetingsSelectorContract, {
|
||||
body: { credential: credentialId, workflowId: context.workflowId },
|
||||
signal,
|
||||
})
|
||||
const meeting = (data.meetings || []).find((m) => m.id === detailId) ?? null
|
||||
if (!meeting) return null
|
||||
return { id: meeting.id, label: meeting.name || `Meeting ${meeting.id}` }
|
||||
},
|
||||
},
|
||||
} satisfies Record<Extract<SelectorKey, 'zoom.meetings'>, SelectorDefinition>
|
||||
Reference in New Issue
Block a user