diff --git a/.github/workflows/deploy-integrations-production.yml b/.github/workflows/deploy-integrations-production.yml index 178ce6f41a6..54a79372592 100644 --- a/.github/workflows/deploy-integrations-production.yml +++ b/.github/workflows/deploy-integrations-production.yml @@ -31,7 +31,7 @@ jobs: uses: ./.github/actions/deploy-integrations with: environment: 'production' - extra_filter: "-F '!docusign' -F '!zendesk-messaging-hitl' -F '!zendesk'" + extra_filter: "-F '!docusign' -F '!zendesk-messaging-hitl'" force: ${{ github.event.inputs.force == 'true' }} sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} token_cloud_ops_account: ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }} diff --git a/bots/bugbuster/src/services/recently-linted-manager.ts b/bots/bugbuster/src/services/recently-linted-manager.ts index bf882c32f16..351268751e8 100644 --- a/bots/bugbuster/src/services/recently-linted-manager.ts +++ b/bots/bugbuster/src/services/recently-linted-manager.ts @@ -8,7 +8,7 @@ export class RecentlyLintedManager { public async isRecentlyLinted(issue: lin.Issue): Promise { const me = await this._linear.getMe() const timestamps = issue.comments.nodes - .filter((comment) => comment.user.id === me.id) + .filter((comment) => comment.user?.id === me.id) .map((comment) => new Date(comment.createdAt).getTime()) const now = new Date().getTime() for (const timestamp of timestamps) { diff --git a/bots/bugbuster/src/utils/linear-utils/client.ts b/bots/bugbuster/src/utils/linear-utils/client.ts index 4c17e1ec21f..269e32b18eb 100644 --- a/bots/bugbuster/src/utils/linear-utils/client.ts +++ b/bots/bugbuster/src/utils/linear-utils/client.ts @@ -126,7 +126,7 @@ export class LinearApi { const promises: Promise[] = [] for (const comment of comments) { - if (comment.user.id === me.id && !comment.parentId && !comment.resolvedAt) { + if (comment.user?.id === me.id && !comment.parentId && !comment.resolvedAt) { promises.push(this._client.commentResolve(comment.id)) } } diff --git a/bots/bugbuster/src/utils/linear-utils/graphql-queries.ts b/bots/bugbuster/src/utils/linear-utils/graphql-queries.ts index ec4967352e3..88a4441140e 100644 --- a/bots/bugbuster/src/utils/linear-utils/graphql-queries.ts +++ b/bots/bugbuster/src/utils/linear-utils/graphql-queries.ts @@ -36,7 +36,7 @@ export type Issue = { } project: { id: string - name: string | null + name: string completedAt: string | null } | null comments: { @@ -46,7 +46,7 @@ export type Issue = { createdAt: string user: { id: string - } + } | null parentId: string | null }[] } diff --git a/integrations/zendesk/integration.definition.ts b/integrations/zendesk/integration.definition.ts index d12655163de..da8de455bfd 100644 --- a/integrations/zendesk/integration.definition.ts +++ b/integrations/zendesk/integration.definition.ts @@ -6,7 +6,7 @@ import { actions, events, configuration, channels, states, user } from './src/de export default new sdk.IntegrationDefinition({ name: 'zendesk', title: 'Zendesk', - version: '3.0.1', + version: '3.0.2', icon: 'icon.svg', description: 'Optimize your support workflow. Trigger workflows from ticket updates as well as manage tickets, access conversations, and engage with customers.', diff --git a/integrations/zendesk/src/oauth/strip-subdomain.test.ts b/integrations/zendesk/src/oauth/strip-subdomain.test.ts new file mode 100644 index 00000000000..af94c9315a9 --- /dev/null +++ b/integrations/zendesk/src/oauth/strip-subdomain.test.ts @@ -0,0 +1,204 @@ +import { test, expect, describe } from 'vitest' +import { stripSubdomain } from './utils' + +describe('stripSubdomain', () => { + test('full url https with -', () => { + expect(stripSubdomain('https://botpress-test-test.zendesk.com')).toBe('botpress-test-test') + }) + test('full url http with -', () => { + expect(stripSubdomain('http://botpress-test-test.zendesk.com')).toBe('botpress-test-test') + }) + test('subdomain with - with .zendesk.com', () => { + expect(stripSubdomain('botpress-test-test.zendesk.com')).toBe('botpress-test-test') + }) + test('subdomain with -', () => { + expect(stripSubdomain('botpress-test-test')).toBe('botpress-test-test') + }) + + test('full url https with - (leading spaces)', () => { + expect(stripSubdomain(' https://botpress-test-test.zendesk.com')).toBe('botpress-test-test') + }) + test('full url https with - (trailing spaces)', () => { + expect(stripSubdomain('https://botpress-test-test.zendesk.com ')).toBe('botpress-test-test') + }) + test('full url https with - (leading and trailing spaces)', () => { + expect(stripSubdomain(' https://botpress-test-test.zendesk.com ')).toBe('botpress-test-test') + }) + test('full url http with - (leading spaces)', () => { + expect(stripSubdomain(' http://botpress-test-test.zendesk.com')).toBe('botpress-test-test') + }) + test('full url http with - (trailing spaces)', () => { + expect(stripSubdomain('http://botpress-test-test.zendesk.com ')).toBe('botpress-test-test') + }) + test('full url http with - (leading and trailing spaces)', () => { + expect(stripSubdomain(' http://botpress-test-test.zendesk.com ')).toBe('botpress-test-test') + }) + test('subdomain with - with .zendesk.com (leading spaces)', () => { + expect(stripSubdomain(' botpress-test-test.zendesk.com')).toBe('botpress-test-test') + }) + test('subdomain with - with .zendesk.com (trailing spaces)', () => { + expect(stripSubdomain('botpress-test-test.zendesk.com ')).toBe('botpress-test-test') + }) + test('subdomain with - with .zendesk.com (leading and trailing spaces)', () => { + expect(stripSubdomain(' botpress-test-test.zendesk.com ')).toBe('botpress-test-test') + }) + test('subdomain with - (leading spaces)', () => { + expect(stripSubdomain(' botpress-test-test')).toBe('botpress-test-test') + }) + test('subdomain with - (trailing spaces)', () => { + expect(stripSubdomain('botpress-test-test ')).toBe('botpress-test-test') + }) + test('subdomain with - (leading and trailing spaces)', () => { + expect(stripSubdomain(' botpress-test-test ')).toBe('botpress-test-test') + }) + + test('full url https with .', () => { + expect(stripSubdomain('https://botpress........zendesk.com')).toBe('botpress.......') + }) + test('full url http with .', () => { + expect(stripSubdomain('http://botpress........zendesk.com')).toBe('botpress.......') + }) + test('subdomain with . with .zendesk.com', () => { + expect(stripSubdomain('botpress........zendesk.com')).toBe('botpress.......') + }) + test('subdomain with .', () => { + expect(stripSubdomain('botpress.......')).toBe('botpress.......') + }) + + test('full url https with . (leading spaces)', () => { + expect(stripSubdomain(' https://botpress........zendesk.com')).toBe('botpress.......') + }) + test('full url https with . (trailing spaces)', () => { + expect(stripSubdomain('https://botpress........zendesk.com ')).toBe('botpress.......') + }) + test('full url https with . (leading and trailing spaces)', () => { + expect(stripSubdomain(' https://botpress........zendesk.com ')).toBe('botpress.......') + }) + test('full url http with . (leading spaces)', () => { + expect(stripSubdomain(' http://botpress........zendesk.com')).toBe('botpress.......') + }) + test('full url http with . (trailing spaces)', () => { + expect(stripSubdomain('http://botpress........zendesk.com ')).toBe('botpress.......') + }) + test('full url http with . (leading and trailing spaces)', () => { + expect(stripSubdomain(' http://botpress........zendesk.com ')).toBe('botpress.......') + }) + test('subdomain with . with .zendesk.com (leading spaces)', () => { + expect(stripSubdomain(' botpress........zendesk.com')).toBe('botpress.......') + }) + test('subdomain with . with .zendesk.com (trailing spaces)', () => { + expect(stripSubdomain('botpress........zendesk.com ')).toBe('botpress.......') + }) + test('subdomain with . with .zendesk.com (leading and trailing spaces)', () => { + expect(stripSubdomain(' botpress........zendesk.com ')).toBe('botpress.......') + }) + test('subdomain with . (leading spaces)', () => { + expect(stripSubdomain(' botpress.......')).toBe('botpress.......') + }) + test('subdomain with . (trailing spaces)', () => { + expect(stripSubdomain('botpress....... ')).toBe('botpress.......') + }) + test('subdomain with . (leading and trailing spaces)', () => { + expect(stripSubdomain(' botpress....... ')).toBe('botpress.......') + }) + + test('full url https with symbols', () => { + expect(stripSubdomain('https://!@#$%^&*()_+~.zendesk.com')).toBe('!@#$%^&*()_+~') + }) + test('full url http with symbols', () => { + expect(stripSubdomain('http://!@#$%^&*()_+~.zendesk.com')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols with .zendesk.com', () => { + expect(stripSubdomain('!@#$%^&*()_+~.zendesk.com')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols', () => { + expect(stripSubdomain('!@#$%^&*()_+~')).toBe('!@#$%^&*()_+~') + }) + + test('full url https with symbols (leading spaces)', () => { + expect(stripSubdomain(' https://!@#$%^&*()_+~.zendesk.com')).toBe('!@#$%^&*()_+~') + }) + test('full url https with symbols (trailing spaces)', () => { + expect(stripSubdomain('https://!@#$%^&*()_+~.zendesk.com ')).toBe('!@#$%^&*()_+~') + }) + test('full url https with symbols (leading and trailing spaces)', () => { + expect(stripSubdomain(' https://!@#$%^&*()_+~.zendesk.com ')).toBe('!@#$%^&*()_+~') + }) + test('full url http with symbols (leading spaces)', () => { + expect(stripSubdomain(' http://!@#$%^&*()_+~.zendesk.com')).toBe('!@#$%^&*()_+~') + }) + test('full url http with symbols (trailing spaces)', () => { + expect(stripSubdomain('http://!@#$%^&*()_+~.zendesk.com ')).toBe('!@#$%^&*()_+~') + }) + test('full url http with symbols (leading and trailing spaces)', () => { + expect(stripSubdomain(' http://!@#$%^&*()_+~.zendesk.com ')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols with .zendesk.com (leading spaces)', () => { + expect(stripSubdomain(' !@#$%^&*()_+~.zendesk.com')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols with .zendesk.com (trailing spaces)', () => { + expect(stripSubdomain('!@#$%^&*()_+~.zendesk.com ')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols with .zendesk.com (leading and trailing spaces)', () => { + expect(stripSubdomain(' !@#$%^&*()_+~.zendesk.com ')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols (leading spaces)', () => { + expect(stripSubdomain(' !@#$%^&*()_+~')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols (trailing spaces)', () => { + expect(stripSubdomain('!@#$%^&*()_+~ ')).toBe('!@#$%^&*()_+~') + }) + test('subdomain with symbols (leading and trailing spaces)', () => { + expect(stripSubdomain(' !@#$%^&*()_+~ ')).toBe('!@#$%^&*()_+~') + }) + + test('full url https with all', () => { + expect(stripSubdomain('https://abc.123-!@#.zendesk.com')).toBe('abc.123-!@#') + }) + test('full url http with all', () => { + expect(stripSubdomain('http://abc.123-!@#.zendesk.com')).toBe('abc.123-!@#') + }) + test('subdomain with all with .zendesk.com', () => { + expect(stripSubdomain('abc.123-!@#.zendesk.com')).toBe('abc.123-!@#') + }) + test('subdomain with all', () => { + expect(stripSubdomain('abc.123-!@#')).toBe('abc.123-!@#') + }) + + test('full url https with all (leading spaces)', () => { + expect(stripSubdomain(' https://abc.123-!@#.zendesk.com')).toBe('abc.123-!@#') + }) + test('full url https with all (trailing spaces)', () => { + expect(stripSubdomain('https://abc.123-!@#.zendesk.com ')).toBe('abc.123-!@#') + }) + test('full url https with all (leading and trailing spaces)', () => { + expect(stripSubdomain(' https://abc.123-!@#.zendesk.com ')).toBe('abc.123-!@#') + }) + test('full url http with all (leading spaces)', () => { + expect(stripSubdomain(' http://abc.123-!@#.zendesk.com')).toBe('abc.123-!@#') + }) + test('full url http with all (trailing spaces)', () => { + expect(stripSubdomain('http://abc.123-!@#.zendesk.com ')).toBe('abc.123-!@#') + }) + test('full url http with all (leading and trailing spaces)', () => { + expect(stripSubdomain(' http://abc.123-!@#.zendesk.com ')).toBe('abc.123-!@#') + }) + test('subdomain with all with .zendesk.com (leading spaces)', () => { + expect(stripSubdomain(' abc.123-!@#.zendesk.com')).toBe('abc.123-!@#') + }) + test('subdomain with all with .zendesk.com (trailing spaces)', () => { + expect(stripSubdomain('abc.123-!@#.zendesk.com ')).toBe('abc.123-!@#') + }) + test('subdomain with all with .zendesk.com (leading and trailing spaces)', () => { + expect(stripSubdomain(' abc.123-!@#.zendesk.com ')).toBe('abc.123-!@#') + }) + test('subdomain with all (leading spaces)', () => { + expect(stripSubdomain(' abc.123-!@#')).toBe('abc.123-!@#') + }) + test('subdomain with all (trailing spaces)', () => { + expect(stripSubdomain('abc.123-!@# ')).toBe('abc.123-!@#') + }) + test('subdomain with all (leading and trailing spaces)', () => { + expect(stripSubdomain(' abc.123-!@# ')).toBe('abc.123-!@#') + }) +}) diff --git a/integrations/zendesk/src/oauth/utils.ts b/integrations/zendesk/src/oauth/utils.ts new file mode 100644 index 00000000000..7629392ede6 --- /dev/null +++ b/integrations/zendesk/src/oauth/utils.ts @@ -0,0 +1,14 @@ +export const stripSubdomain = (input: string): string => { + const trimedInput = input.trim() + + if (trimedInput.startsWith('https://') && trimedInput.endsWith('.zendesk.com')) { + return trimedInput.substring('https://'.length, trimedInput.length - '.zendesk.com'.length) + } + if (trimedInput.startsWith('http://') && trimedInput.endsWith('.zendesk.com')) { + return trimedInput.substring('http://'.length, trimedInput.length - '.zendesk.com'.length) + } + if (trimedInput.endsWith('.zendesk.com')) { + return trimedInput.substring(0, trimedInput.length - '.zendesk.com'.length) + } + return trimedInput +} diff --git a/integrations/zendesk/src/oauth/wizard.ts b/integrations/zendesk/src/oauth/wizard.ts index 5b9e9844c22..2578bb9af5c 100644 --- a/integrations/zendesk/src/oauth/wizard.ts +++ b/integrations/zendesk/src/oauth/wizard.ts @@ -2,6 +2,7 @@ import * as oauthWizard from '@botpress/common/src/oauth-wizard' import * as sdk from '@botpress/sdk' import axios from 'axios' import { webcrypto } from 'crypto' +import { stripSubdomain } from './utils' import * as bp from '.botpress' type WizardHandler = oauthWizard.WizardStepHandler @@ -75,13 +76,11 @@ const _validateSubdomain: WizardHandler = async (props) => { if (inputValue === undefined) { throw new sdk.RuntimeError('The subdomain given was empty') } - const subdomain = inputValue.endsWith('.zendesk.com') - ? inputValue.slice('https://'.length).slice(0, '.zendesk.com'.length) - : inputValue + const subdomain = stripSubdomain(inputValue) await _patchCredentialsState(client, ctx, { accessToken: undefined, subdomain }) return responses.displayButtons({ pageTitle: 'Validate Zendesk Subdomain', - htmlOrMarkdownPageContents: `Is ${subdomain} your Zendesk's subdomain?`, + htmlOrMarkdownPageContents: `Is ${subdomain} your Zendesk's subdomain?`, buttons: [ { action: 'navigate',