From 82bfc9eed5c3a5ff5c3fce5188d287fa51a76715 Mon Sep 17 00:00:00 2001 From: Samuel Bodin <1637651+bodinsamuel@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:34:59 +0200 Subject: [PATCH] fix(connect): add display name, auto generate form from connection_config (#2775) ## Describe your changes Contributes to https://linear.app/nango/issue/NAN-1703/create-ui - API: Add display name to `GET /integrations` - Connect: use display name - Connect: auto generate form based on `connection_config` inside providers.yaml Screenshot 2024-09-26 at 17 47 27 --- docs-v2/reference/api/integration/get.mdx | 1 + docs-v2/reference/api/integration/list.mdx | 2 + docs-v2/spec.yaml | 4 + .../connect-ui/src/components/ui/input.tsx | 2 +- packages/connect-ui/src/lib/utils.ts | 13 ++ packages/connect-ui/src/views/Go.tsx | 113 ++++++++++++++---- .../connect-ui/src/views/IntegrationsList.tsx | 2 +- packages/frontend/lib/types.ts | 3 +- .../getListIntegrations.integration.test.ts | 1 + .../controllers/config/getListIntegrations.ts | 11 +- .../getListIntegrations.integration.test.ts | 1 + .../integrations/getListIntegrations.ts | 10 +- .../getIntegration.integration.test.ts | 1 + .../integrations/uniqueKey/getIntegration.ts | 2 +- packages/server/lib/formatters/integration.ts | 23 ++-- packages/shared/flows.yaml | 111 +++++++++-------- packages/types/lib/integration/api.ts | 1 + 17 files changed, 205 insertions(+), 96 deletions(-) diff --git a/docs-v2/reference/api/integration/get.mdx b/docs-v2/reference/api/integration/get.mdx index c0272b4753f..6229aef997e 100644 --- a/docs-v2/reference/api/integration/get.mdx +++ b/docs-v2/reference/api/integration/get.mdx @@ -8,6 +8,7 @@ openapi: 'GET /integrations/{uniqueKey}' { "data": { "unique_key": "slack-nango-community", + "display_name": "Slack", "provider": "slack", "logo": "http://localhost:3003/images/template-logos/github.svg", "created_at": "2023-10-16T08:45:26.241Z", diff --git a/docs-v2/reference/api/integration/list.mdx b/docs-v2/reference/api/integration/list.mdx index 2b69c5103ce..c1384640f63 100644 --- a/docs-v2/reference/api/integration/list.mdx +++ b/docs-v2/reference/api/integration/list.mdx @@ -19,6 +19,7 @@ const response = await nango.listIntegrations(); "data": [ { "unique_key": "slack-nango-community", + "display_name": "Slack", "provider": "slack", "logo": "http://localhost:3003/images/template-logos/slack.svg", "created_at": "2023-10-16T08:45:26.241Z", @@ -26,6 +27,7 @@ const response = await nango.listIntegrations(); }, { "unique_key": "github-prod", + "display_name": "GitHub", "provider": "github", "logo": "http://localhost:3003/images/template-logos/github.svg", "created_at": "2023-10-16T08:45:26.241Z", diff --git a/docs-v2/spec.yaml b/docs-v2/spec.yaml index 756e458b0ae..c98693edb95 100644 --- a/docs-v2/spec.yaml +++ b/docs-v2/spec.yaml @@ -1948,6 +1948,7 @@ components: additionalProperties: false required: - unique_key + - display_name - provider - created_at - updated_at @@ -1955,6 +1956,9 @@ components: unique_key: type: string description: The integration ID that you created in Nango. + display_name: + type: string + description: The provider display name. provider: type: string description: The Nango API Configuration. diff --git a/packages/connect-ui/src/components/ui/input.tsx b/packages/connect-ui/src/components/ui/input.tsx index 0bbbdc73fcb..ff4bed5001c 100644 --- a/packages/connect-ui/src/components/ui/input.tsx +++ b/packages/connect-ui/src/components/ui/input.tsx @@ -9,7 +9,7 @@ const Input = React.forwardRef(({ className, type, = { API_KEY: z.object({ - credentials: z.object({ - apiKey: z.string().min(1) - }) + apiKey: z.string().min(1) }), BASIC: z.object({ - credentials: z.object({ - username: z.string().min(1), - password: z.string().min(1) - }) + username: z.string().min(1), + password: z.string().min(1) }), APP: z.object({}), APP_STORE: z.object({}), @@ -58,14 +57,6 @@ export const Go: React.FC = () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unsafe-assignment const authMode = provider?.auth_mode; - const fields = authMode ? formSchema[authMode] : null; - const hasField = fields ? Object.keys(fields.shape).length > 0 : true; - const form = useForm>({ - resolver: zodResolver(formSchema[authMode || 'NONE']), - defaultValues: { - username: '' - } - }); useEffect(() => { // on unmount always clear popup and state @@ -74,6 +65,43 @@ export const Go: React.FC = () => { }; }, []); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { resolver, hasField } = useMemo<{ resolver: Resolver; hasField: boolean }>(() => { + if (!provider) { + return { hasField: true, resolver: () => ({ values: {}, errors: {} }) }; + } + + const authMode = provider.auth_mode; + const baseForm = formSchema[authMode]; + + // Modify base form with credentials specific + for (const [name, schema] of Object.entries(provider.credentials || [])) { + baseForm.shape[name] = jsonSchemaToZod(schema); + } + + // Append connectionConfig object + const additionalFields: z.ZodRawShape = {}; + for (const [name, schema] of Object.entries(provider.connection_config || [])) { + additionalFields[name] = jsonSchemaToZod(schema); + } + + // Only add objects if they have something otherwise it breaks react-form + const fields = z.object({ + ...(Object.keys(baseForm.shape).length > 0 ? { credentials: baseForm } : {}), + ...(Object.keys(additionalFields).length > 0 ? { params: z.object(additionalFields) } : {}) + }); + + const hasField = Object.keys(fields.shape).length > 0; + const resolver = zodResolver(fields); + return { hasField, resolver }; + }, [provider]); + + const form = useForm>({ + resolver: resolver, + mode: 'onChange', + reValidateMode: 'onChange' + }); + const onSubmit = useCallback( async (values: z.infer<(typeof formSchema)[AuthModeType]>) => { if (!integration || loading || !provider) { @@ -95,14 +123,15 @@ export const Go: React.FC = () => { if (err.type === 'blocked_by_browser') { setError('Auth pop-up blocked by your browser, please allow pop-ups to open'); return; - } - if (err.type === 'windowClosed') { + } else if (err.type === 'windowClosed') { setError('The auth pop-up was closed before the end of the process'); return; + } else if (err.type === 'connection_test_failed') { + setError(`${provider.display_name} refused your credentials. Please check the values and try again.`); + return; } } - console.error(err); setError('An error occurred, please try again'); } finally { setLoading(false); @@ -178,15 +207,16 @@ export const Go: React.FC = () => { control={form.control} name="credentials.apiKey" render={({ field }) => { + const def = provider.credentials?.apiKey; return (
- API Key - Find your API Key in your own {provider.name} account + {def?.title || 'API Key'} + {def?.description}
- +
@@ -378,15 +408,48 @@ export const Go: React.FC = () => { /> )} + + {provider.connection_config && + Object.entries(provider.connection_config).map(([key, schema]) => { + return ( + { + return ( + +
+ {schema.title} + {schema.description && {schema.description}} +
+
+ + + + +
+
+ ); + }} + /> + ); + })} -
+
{error && (
- {error} + {error}
)} {hasField && ( - )} diff --git a/packages/connect-ui/src/views/IntegrationsList.tsx b/packages/connect-ui/src/views/IntegrationsList.tsx index b01334dfaa6..4f1304a8c3d 100644 --- a/packages/connect-ui/src/views/IntegrationsList.tsx +++ b/packages/connect-ui/src/views/IntegrationsList.tsx @@ -101,7 +101,7 @@ const Integration: React.FC<{ integration: ApiPublicIntegration }> = ({ integrat
-
{integration.provider}
+
{integration.display_name}
{error && (
{error} diff --git a/packages/frontend/lib/types.ts b/packages/frontend/lib/types.ts index 2870e3bc9d1..ef351cbb721 100644 --- a/packages/frontend/lib/types.ts +++ b/packages/frontend/lib/types.ts @@ -6,7 +6,8 @@ export type AuthErrorType = | 'missingCredentials' | 'windowClosed' | 'request_error' - | 'missing_ws_client_id'; + | 'missing_ws_client_id' + | 'connection_test_failed'; export interface AuthResult { providerConfigKey: string; diff --git a/packages/server/lib/controllers/config/getListIntegrations.integration.test.ts b/packages/server/lib/controllers/config/getListIntegrations.integration.test.ts index 56386f8bfb4..6544762acf7 100644 --- a/packages/server/lib/controllers/config/getListIntegrations.integration.test.ts +++ b/packages/server/lib/controllers/config/getListIntegrations.integration.test.ts @@ -67,6 +67,7 @@ describe(`GET ${endpoint}`, () => { { provider: 'github', unique_key: 'github', + display_name: 'GitHub', logo: 'http://localhost:3003/images/template-logos/github.svg', created_at: expect.toBeIsoDate(), updated_at: expect.toBeIsoDate() diff --git a/packages/server/lib/controllers/config/getListIntegrations.ts b/packages/server/lib/controllers/config/getListIntegrations.ts index a9f121d0014..baf739c9e0e 100644 --- a/packages/server/lib/controllers/config/getListIntegrations.ts +++ b/packages/server/lib/controllers/config/getListIntegrations.ts @@ -1,7 +1,7 @@ import { asyncWrapper } from '../../utils/asyncWrapper.js'; import { requireEmptyQuery, zodErrorToHTTP } from '@nangohq/utils'; import type { GetPublicListIntegrationsLegacy } from '@nangohq/types'; -import { configService } from '@nangohq/shared'; +import { configService, getProviders } from '@nangohq/shared'; import { integrationToPublicApi } from '../../formatters/integration.js'; export const getPublicListIntegrationsLegacy = asyncWrapper(async (req, res) => { @@ -13,8 +13,15 @@ export const getPublicListIntegrationsLegacy = asyncWrapper { - return integrationToPublicApi(config); + return integrationToPublicApi({ integration: config, provider: providers[config.provider]! }); }); res.status(200).send({ configs: results }); diff --git a/packages/server/lib/controllers/integrations/getListIntegrations.integration.test.ts b/packages/server/lib/controllers/integrations/getListIntegrations.integration.test.ts index 92fc882e292..a248fb086f7 100644 --- a/packages/server/lib/controllers/integrations/getListIntegrations.integration.test.ts +++ b/packages/server/lib/controllers/integrations/getListIntegrations.integration.test.ts @@ -67,6 +67,7 @@ describe(`GET ${endpoint}`, () => { { provider: 'github', unique_key: 'github', + display_name: 'GitHub', logo: 'http://localhost:3003/images/template-logos/github.svg', created_at: expect.toBeIsoDate(), updated_at: expect.toBeIsoDate() diff --git a/packages/server/lib/controllers/integrations/getListIntegrations.ts b/packages/server/lib/controllers/integrations/getListIntegrations.ts index 7c1077a2374..f13fa7fe5a6 100644 --- a/packages/server/lib/controllers/integrations/getListIntegrations.ts +++ b/packages/server/lib/controllers/integrations/getListIntegrations.ts @@ -1,7 +1,7 @@ import { asyncWrapper } from '../../utils/asyncWrapper.js'; import { requireEmptyQuery, zodErrorToHTTP } from '@nangohq/utils'; import type { GetPublicListIntegrations } from '@nangohq/types'; -import { configService } from '@nangohq/shared'; +import { configService, getProviders } from '@nangohq/shared'; import { integrationToPublicApi } from '../../formatters/integration.js'; export const getPublicListIntegrations = asyncWrapper(async (req, res) => { @@ -14,9 +14,15 @@ export const getPublicListIntegrations = asyncWrapper const { environment } = res.locals; const configs = await configService.listProviderConfigs(environment.id); + const providers = getProviders(); + if (!providers) { + res.status(500).send({ error: { code: 'server_error', message: `failed to load providers` } }); + return; + } + res.status(200).send({ data: configs.map((config) => { - return integrationToPublicApi(config); + return integrationToPublicApi({ integration: config, provider: providers[config.provider]! }); }) }); }); diff --git a/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.integration.test.ts b/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.integration.test.ts index 9e6f4aec190..da84ec29f1a 100644 --- a/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.integration.test.ts +++ b/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.integration.test.ts @@ -66,6 +66,7 @@ describe(`GET ${endpoint}`, () => { data: { provider: 'github', unique_key: 'github', + display_name: 'GitHub', logo: 'http://localhost:3003/images/template-logos/github.svg', created_at: expect.toBeIsoDate(), updated_at: expect.toBeIsoDate() diff --git a/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.ts b/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.ts index bbf1e39acc6..30047ad6369 100644 --- a/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.ts +++ b/packages/server/lib/controllers/integrations/uniqueKey/getIntegration.ts @@ -59,6 +59,6 @@ export const getPublicIntegration = asyncWrapper(async (re } res.status(200).send({ - data: integrationToPublicApi(integration, include) + data: integrationToPublicApi({ integration, include, provider }) }); }); diff --git a/packages/server/lib/formatters/integration.ts b/packages/server/lib/formatters/integration.ts index 8eb685173ed..e06487b7286 100644 --- a/packages/server/lib/formatters/integration.ts +++ b/packages/server/lib/formatters/integration.ts @@ -1,4 +1,4 @@ -import type { ApiIntegration, ApiPublicIntegration, ApiPublicIntegrationInclude, IntegrationConfig } from '@nangohq/types'; +import type { ApiIntegration, ApiPublicIntegration, ApiPublicIntegrationInclude, IntegrationConfig, Provider } from '@nangohq/types'; import { basePublicUrl } from '@nangohq/utils'; export function integrationToApi(data: IntegrationConfig): ApiIntegration { @@ -9,13 +9,22 @@ export function integrationToApi(data: IntegrationConfig): ApiIntegration { }; } -export function integrationToPublicApi(data: IntegrationConfig, include?: ApiPublicIntegrationInclude): ApiPublicIntegration { +export function integrationToPublicApi({ + integration, + include, + provider +}: { + integration: IntegrationConfig; + provider: Provider; + include?: ApiPublicIntegrationInclude; +}): ApiPublicIntegration { return { - unique_key: data.unique_key, - provider: data.provider, - logo: `${basePublicUrl}/images/template-logos/${data.provider}.svg`, + unique_key: integration.unique_key, + provider: integration.provider, + display_name: provider.display_name, + logo: `${basePublicUrl}/images/template-logos/${integration.provider}.svg`, ...include, - created_at: data.created_at.toISOString(), - updated_at: data.updated_at.toISOString() + created_at: integration.created_at.toISOString(), + updated_at: integration.updated_at.toISOString() }; } diff --git a/packages/shared/flows.yaml b/packages/shared/flows.yaml index 5a1524d9ef9..bbff3af00c9 100644 --- a/packages/shared/flows.yaml +++ b/packages/shared/flows.yaml @@ -23,25 +23,25 @@ integrations: Creates an ephemeral transaction in Anrok. input: Transaction output: TransactionActionResponse - endpoint: POST /ephmeral-transactions + endpoint: POST /ephmeral-transaction create-or-update-transaction: description: | Creates or updates a transaction in Anrok. input: Transaction[] output: TransactionActionResponse - endpoint: POST /transactions + endpoint: POST /transaction void-transaction: description: | Voids a transaction in Anrok. input: TransactionToDelete[] output: TransactionDeletionActionResponse - endpoint: POST /transactions/void + endpoint: POST /transaction/voide negate-transaction: description: | Creates a negation in Anrok. input: TransactionToNegate[] output: TransactionNegationActionResponse - endpoint: POST /transactions/negate + endpoint: POST /transaction/negate models: AnrokAddress: line1: string @@ -142,42 +142,42 @@ integrations: runs: every hour description: Retrieve all tasks that exist in the workspace output: Task - endpoint: GET /tasks + endpoint: GET /ticketing/tasks sync_type: incremental users: runs: every hour description: Retrieve all users that exist in the workspace output: User - endpoint: GET /users + endpoint: GET /ticketing/users sync_type: incremental workspaces: runs: every hour description: Retrieve all workspaces for a user output: AsanaWorkspace - endpoint: GET /workspaces + endpoint: GET /ticketing/workspaces projects: runs: every hour description: Retrieves all projects for a user output: AsanaProject - endpoint: GET /projects + endpoint: GET /ticketing/projects actions: fetch-workspaces: description: >- Fetch the workspaces with a limit (default 10) of a user to allow them to selection of projects to sync - endpoint: GET /workspaces/limit + endpoint: GET /ticketing/workspaces/limit output: BaseAsanaModel[] input: Limit fetch-projects: description: >- Fetch the projects with a limit (default 10) given a workspace of a user to allow selection when choosing the tasks to sync. - endpoint: GET /projects/limit + endpoint: GET /ticketing/projects/limit output: BaseAsanaModel[] input: AsanaProjectInput create-task: input: CreateAsanaTask - endpoint: POST /tasks + endpoint: POST /ticketing/task output: Task description: > Create a task using Asana specific fields and return a unified model @@ -186,11 +186,11 @@ integrations: update-task: description: Update a task and be able to assign the task to a specific user input: AsanaUpdateTask - endpoint: PATCH /tasks + endpoint: PATCH /ticketing/task output: Task delete-task: input: Id - endpoint: DELETE /tasks + endpoint: DELETE /ticketing/task output: boolean models: Id: @@ -311,7 +311,7 @@ integrations: Fetches a list of all candidates from your ashby account scopes: candidatelastsyncToken sync_type: incremental - endpoint: GET /candidates + endpoint: GET /ashby/candidates jobs: runs: every hour output: AshbyJob @@ -319,20 +319,20 @@ integrations: Fetches a list of all jobs from your ashby account scopes: jobslastsyncToken sync_type: incremental - endpoint: GET /jobs + endpoint: GET /ashby/jobs actions: create-application: output: AshbyCreateApplicationResponse input: AshbyCreateCandidateInput description: | Action to consider a candidate for a job - endpoint: POST /applications + endpoint: POST /ashby/create-application create-note: output: AshbyCreateNoteResponse input: AshbyCreateNoteInput description: | Action to create a note on a candidate. - endpoint: POST /notes + endpoint: POST /ashby/create-note models: AshbyCandidate: id: string @@ -485,22 +485,22 @@ integrations: Fetches a list of current employees from bamboohr output: BamboohrEmployee sync_type: incremental - endpoint: GET /employees + endpoint: GET /bamboohr/employees actions: create-employee: description: | Action to create a new employee output: BamboohrCreateEmployeeResponse input: BamboohrCreateEmployee - endpoint: POST /employees + endpoint: POST /bamboohr/create-employee version: 1.0.0 update-employee: - endpoint: PUT /employees + endpoint: PUT /bamboohr/employee input: BamboohrUpdateEmployee output: BamboohrResponseStatus description: Update an existing employee in the system fetch-fields: - endpoint: GET /fields + endpoint: GET /bamboohr/fields output: BamboohrField[] description: Introspection to retrieve available fields models: @@ -1118,7 +1118,7 @@ integrations: Fetches a list of calls from your account. For the first sync, it will go back to the past one year sync_type: incremental - endpoint: GET /calls + endpoint: GET /clari-copilot/call models: ClariCopilotCall: id: string @@ -1177,7 +1177,7 @@ integrations: Fetches a list of spaces from confluence sync_type: full scopes: read:space:confluence - endpoint: GET /spaces + endpoint: GET /confluence/spaces pages: runs: every 4 hours output: ConfluencePage @@ -1185,7 +1185,7 @@ integrations: Fetches a list of pages from confluence sync_type: full scopes: read:page:confluence - endpoint: GET /pages + endpoint: GET /confluence/pages models: ConfluenceSpace: id: string @@ -1222,7 +1222,7 @@ integrations: active-users: description: | Fetches a list of active users from Discourse. - endpoint: /users + endpoint: /discourse/active-users sync_type: full runs: every 1 hour output: User @@ -3433,7 +3433,7 @@ integrations: output: Customer sync_type: incremental scopes: com.intuit.quickbooks.accounting - endpoint: GET /quickbooks/customers + endpoint: GET /customers accounts: description: > Fetches all accounts in QuickBooks. Handles both active and archived @@ -3442,7 +3442,7 @@ integrations: output: Account sync_type: incremental scopes: com.intuit.quickbooks.accounting - endpoint: GET /quickbooks/accounts + endpoint: GET /accounts payments: description: > Fetches all payments in QuickBooks. Handles both active and voided @@ -3451,7 +3451,7 @@ integrations: output: Payment sync_type: incremental scopes: com.intuit.quickbooks.accounting - endpoint: GET /quickbooks/payments + endpoint: GET /payments items: description: > Fetches all items in QuickBooks. Handles both active and archived @@ -3460,7 +3460,7 @@ integrations: output: Item sync_type: incremental scopes: com.intuit.quickbooks.accounting - endpoint: GET /quickbooks/items + endpoint: GET /items invoices: description: > Fetches all invoices in QuickBooks. Handles both active and voided @@ -3469,7 +3469,7 @@ integrations: output: Invoice sync_type: incremental scopes: com.intuit.quickbooks.accounting - endpoint: GET /quickbooks/invoices + endpoint: GET /invoices actions: create-customer: description: | @@ -3477,77 +3477,77 @@ integrations: input: CreateCustomer scopes: com.intuit.quickbooks.accounting output: Customer - endpoint: POST /quickbooks/customer + endpoint: POST /customers update-customer: description: | Update a single customer in QuickBooks. input: UpdateCustomer scopes: com.intuit.quickbooks.accounting output: Customer - endpoint: PUT /quickbooks/customer + endpoint: PUT /customers create-item: description: | Creates a single item in QuickBooks. input: CreateItem scopes: com.intuit.quickbooks.accounting output: Item - endpoint: POST /quickbooks/item + endpoint: POST /items update-item: description: | Update a single item in QuickBooks. input: UpdateItem scopes: com.intuit.quickbooks.accounting output: Item - endpoint: PUT /quickbooks/item + endpoint: PUT /items create-account: description: | Creates a single account in QuickBooks. input: CreateAccount scopes: com.intuit.quickbooks.accounting output: Account - endpoint: POST /quickbooks/account + endpoint: POST /accounts update-account: description: | Updates a single account in QuickBooks. input: UpdateAccount scopes: com.intuit.quickbooks.accounting output: Account - endpoint: PUT /quickbooks/account + endpoint: PUT /accounts create-invoice: description: | Creates a single invoice in QuickBooks. input: CreateInvoice scopes: com.intuit.quickbooks.accounting output: Invoice - endpoint: POST /quickbooks/invoice + endpoint: POST /invoices update-invoice: description: | Updates a single invoice in QuickBooks. input: UpdateInvoice scopes: com.intuit.quickbooks.accounting output: Invoice - endpoint: PUT /quickbooks/invoice + endpoint: PUT /invoices create-credit-memo: description: | Creates a single credit memo in QuickBooks. input: CreateCreditMemo scopes: com.intuit.quickbooks.accounting output: CreditMemo - endpoint: POST /quickbooks/credit-memo + endpoint: POST /credit-memos update-credit-memo: description: | Updates a single credit memo in QuickBooks. input: UpdateCreditMemo scopes: com.intuit.quickbooks.accounting output: CreditMemo - endpoint: PUT /quickbooks/credit-memo + endpoint: PUT /credit-memos create-payment: description: | Creates a single payment in QuickBooks. input: CreatePayment scopes: com.intuit.quickbooks.accounting output: Payment - endpoint: POST /quickbooks/payment + endpoint: POST /payments models: Updates: id: string @@ -3800,7 +3800,6 @@ integrations: description: | Fetches a list of articles from salesforce output: SalesforceArticle - auto_start: false sync_type: incremental endpoint: GET /salesforce/articles tickets: @@ -4715,7 +4714,7 @@ integrations: runs: every hour output: Contact sync_type: incremental - endpoint: GET /xero/contacts + endpoint: GET /contacts accounts: description: > Fetches all accounts in Xero (chart of accounts). Incremental sync, @@ -4724,7 +4723,7 @@ integrations: scopes: accounting.settings output: Account sync_type: incremental - endpoint: GET /xero/accounts + endpoint: GET /accounts items: description: > Fetches all items in Xero. Incremental sync, does not detect deletes, @@ -4735,7 +4734,7 @@ integrations: scopes: accounting.settings output: Item sync_type: incremental - endpoint: GET /xero/items + endpoint: GET /items invoices: description: | Fetches all invoices in Xero. Incremental sync. @@ -4743,7 +4742,7 @@ integrations: scopes: accounting.transactions output: Invoice sync_type: incremental - endpoint: GET /xero/invoices + endpoint: GET /invoices payments: description: | Fetches all payments in Xero. Incremental sync. @@ -4751,7 +4750,7 @@ integrations: scopes: accounting.transactions output: Payment sync_type: incremental - endpoint: GET /xero/payments + endpoint: GET /payments actions: create-contact: description: | @@ -4760,7 +4759,7 @@ integrations: input: CreateContact[] scopes: accounting.contacts output: ContactActionResponse - endpoint: POST /xero/contacts + endpoint: POST /contacts update-contact: description: > Updates one or multiple contacts in Xero. Only fields that are passed @@ -4769,7 +4768,7 @@ integrations: input: Contact[] scopes: accounting.contacts output: ContactActionResponse - endpoint: PUT /xero/contacts + endpoint: PUT /contacts create-invoice: description: | Creates one or more invoices in Xero. @@ -4777,7 +4776,7 @@ integrations: input: CreateInvoice[] scopes: accounting.transactions output: InvoiceActionResponse - endpoint: POST /xero/invoices + endpoint: POST /invoices version: 1.0.0 update-invoice: description: | @@ -4788,7 +4787,7 @@ integrations: input: UpdateInvoice[] scopes: accounting.transactions output: InvoiceActionResponse - endpoint: PUT /xero/invoices + endpoint: PUT /invoices version: 1.0.0 create-credit-note: description: | @@ -4797,14 +4796,14 @@ integrations: input: CreditNote[] scopes: accounting.transactions output: CreditNoteActionResponse - endpoint: POST /xero/creditnotes + endpoint: POST /creditnotes update-credit-note: description: | Updates one or more credit notes in Xero. input: CreditNote[] scopes: accounting.transactions output: CreditNoteActionResponse - endpoint: PUT /xero/creditnotes + endpoint: PUT /creditnotes create-payment: description: | Creates one or more payments in Xero. @@ -4812,7 +4811,7 @@ integrations: scopes: accounting.transactions input: CreatePayment[] output: PaymentActionResponse - endpoint: POST /xero/payments + endpoint: POST /payments create-item: description: | Creates one or more items in Xero. @@ -4820,14 +4819,14 @@ integrations: scopes: accounting.settings input: Item[] output: ItemActionResponse - endpoint: POST /xero/items + endpoint: POST /items update-item: description: | Updates one or more items in Xero. scopes: accounting.settings input: Item[] output: ItemActionResponse - endpoint: PUT /xero/items + endpoint: PUT /items models: ActionErrorResponse: message: string diff --git a/packages/types/lib/integration/api.ts b/packages/types/lib/integration/api.ts index 8ad257e1cf8..86134f5ab4e 100644 --- a/packages/types/lib/integration/api.ts +++ b/packages/types/lib/integration/api.ts @@ -10,6 +10,7 @@ import type { SyncType } from '../scripts/syncs/api'; export type ApiPublicIntegration = Merge, ApiTimestamps> & { logo: string; + display_name: string; } & ApiPublicIntegrationInclude; export interface ApiPublicIntegrationInclude { webhook_url?: string | null;