From 5ae9ccb3b240ac63079f7fe15920cb487c96606b Mon Sep 17 00:00:00 2001 From: Waldek Mastykarz Date: Sat, 28 Oct 2023 15:27:09 +0200 Subject: [PATCH] Adds the 'external connection urltoitemresolver add' command. Closes #5527 --- .eslintrc.cjs | 5 + .../connection-urltoitemresolver-add.mdx | 47 +++++++ docs/src/config/sidebars.ts | 5 + src/m365/external/commands.ts | 1 + .../connection-urltoitemresolver-add.spec.ts | 125 ++++++++++++++++++ .../connection-urltoitemresolver-add.ts | 92 +++++++++++++ 6 files changed, 275 insertions(+) create mode 100644 docs/docs/cmd/external/connection/connection-urltoitemresolver-add.mdx create mode 100644 src/m365/external/commands/connection/connection-urltoitemresolver-add.spec.ts create mode 100644 src/m365/external/commands/connection/connection-urltoitemresolver-add.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 5416cd98fef..682a30864f8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -51,6 +51,7 @@ const dictionary = [ 'installed', 'is', 'issue', + 'item', 'label', 'list', 'link', @@ -75,6 +76,7 @@ const dictionary = [ 'property', 'records', 'recycle', + 'resolver', 'retention', 'role', 'room', @@ -93,9 +95,12 @@ const dictionary = [ 'table', 'teams', 'threat', + 'to', + 'todo', 'token', 'type', 'unit', + 'url', 'user', 'web', 'webhook' diff --git a/docs/docs/cmd/external/connection/connection-urltoitemresolver-add.mdx b/docs/docs/cmd/external/connection/connection-urltoitemresolver-add.mdx new file mode 100644 index 00000000000..e63d4160c04 --- /dev/null +++ b/docs/docs/cmd/external/connection/connection-urltoitemresolver-add.mdx @@ -0,0 +1,47 @@ +import Global from '/docs/cmd/_global.mdx'; + +# external connection urltoitemresolver add + +Adds a URL to item resolver to an external connection + +## Usage + +```sh +m365 external connection urltoitemresolver add [options] +``` + +## Options + +```md definition-list +`-c, --externalConnectionId ` +: The ID of the external connection to which to add the resolver + +`--baseUrls ` +: Comma-separated list of base URLs + +`--urlPattern ` +: Regex pattern to match URLs + +`-i, --itemId ` +: Pattern that matches captured matches from the URL with the external item ID + +`-p, --priority ` +: Integer that defines the resolution order +``` + + +## Examples + +Adds a URL to item resolver to an existing connection + +```sh +m365 external connection urltoitemresolver add --externalConnectionId "samplesolutiongallery" --baseUrls "https://adoption.microsoft.com" --urlPattern "/sample-solution-gallery/sample/(?[^/]+)" --itemId "{sampleId}" --priority 1 +``` + +## Response + +The command won't return a response on success. + +## More information + +- Microsoft Graph external connection activitySettings: [https://learn.microsoft.com/graph/api/resources/externalconnectors-activitysettings?view=graph-rest-1.0](https://learn.microsoft.com/graph/api/resources/externalconnectors-activitysettings?view=graph-rest-1.0) diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index b30f19b6bf9..ece28ae1fc6 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -659,6 +659,11 @@ const sidebars: SidebarsConfig = { type: 'doc', label: 'connection schema add', id: 'cmd/external/connection/connection-schema-add' + }, + { + type: 'doc', + label: 'connection urltoitemresolver add', + id: 'cmd/external/connection/connection-urltoitemresolver-add' } ] }, diff --git a/src/m365/external/commands.ts b/src/m365/external/commands.ts index 6f1cd6dcb84..89770e0531a 100644 --- a/src/m365/external/commands.ts +++ b/src/m365/external/commands.ts @@ -8,6 +8,7 @@ export default { CONNECTION_LIST: `${prefix} connection list`, CONNECTION_REMOVE: `${prefix} connection remove`, CONNECTION_SCHEMA_ADD: `${prefix} connection schema add`, + CONNECTION_URLTOITEMRESOLVER_ADD: `${prefix} connection urltoitemresolver add`, EXTERNALCONNECTION_ADD: `${searchPrefix} externalconnection add`, EXTERNALCONNECTION_GET: `${searchPrefix} externalconnection get`, EXTERNALCONNECTION_LIST: `${searchPrefix} externalconnection list`, diff --git a/src/m365/external/commands/connection/connection-urltoitemresolver-add.spec.ts b/src/m365/external/commands/connection/connection-urltoitemresolver-add.spec.ts new file mode 100644 index 00000000000..855ec12f707 --- /dev/null +++ b/src/m365/external/commands/connection/connection-urltoitemresolver-add.spec.ts @@ -0,0 +1,125 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import { CommandError } from '../../../../Command.js'; +import { Logger } from '../../../../cli/Logger.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command from './connection-urltoitemresolver-add.js'; + +describe(commands.CONNECTION_URLTOITEMRESOLVER_ADD, () => { + let logger: Logger; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').returns(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + auth.service.connected = true; + }); + + beforeEach(() => { + logger = { + log: async () => { }, + logRaw: async () => { }, + logToStderr: async () => { } + }; + (command as any).items = []; + }); + + afterEach(() => { + sinonUtil.restore([ + request.patch + ]); + }); + + after(() => { + sinon.restore(); + auth.service.connected = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.CONNECTION_URLTOITEMRESOLVER_ADD); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('adds item to URL resolver to an existing external connection', async () => { + sinon.stub(request, 'patch').callsFake(async (opts: any) => { + if (opts.url === `https://graph.microsoft.com/v1.0/external/connections/conn`) { + return {}; + } + throw 'Invalid request'; + }); + const options: any = { + externalConnectionId: 'conn', + baseUrls: 'https://contoso.com', + urlPattern: '/(?.*)', + itemId: '{id}', + priority: 1 + }; + await command.action(logger, { options } as any); + }); + + it('correctly handles error', async () => { + sinon.stub(request, 'patch').callsFake(() => { + throw { + "error": { + "code": "Error", + "message": "An error has occurred", + "innerError": { + "request-id": "9b0df954-93b5-4de9-8b99-43c204a8aaf8", + "date": "2018-04-24T18:56:48" + } + } + }; + }); + + const options: any = { + externalConnectionId: 'conn', + baseUrls: 'https://contoso.com', + urlPattern: '/(?.*)', + itemId: '{id}', + priority: 1 + }; + + await assert.rejects(command.action(logger, { options } as any), + new CommandError(`An error has occurred`)); + }); + + it('supports specifying connection ID', () => { + const containsOption = !!command.options + .find(o => o.option.indexOf('--externalConnectionId') > -1); + assert(containsOption); + }); + + it('supports specifying base URLs', () => { + const containsOption = !!command.options + .find(o => o.option.indexOf('--baseUrls') > -1); + assert(containsOption); + }); + + it('supports specifying URL patterns', () => { + const containsOption = !!command.options + .find(o => o.option.indexOf('--urlPattern') > -1); + assert(containsOption); + }); + + it('supports specifying item ID', () => { + const containsOption = !!command.options + .find(o => o.option.indexOf('--itemId') > -1); + assert(containsOption); + }); + + it('supports specifying priority', () => { + const containsOption = !!command.options + .find(o => o.option.indexOf('--priority') > -1); + assert(containsOption); + }); +}); diff --git a/src/m365/external/commands/connection/connection-urltoitemresolver-add.ts b/src/m365/external/commands/connection/connection-urltoitemresolver-add.ts new file mode 100644 index 00000000000..2427c8f99e8 --- /dev/null +++ b/src/m365/external/commands/connection/connection-urltoitemresolver-add.ts @@ -0,0 +1,92 @@ +import { ExternalConnectors } from '@microsoft/microsoft-graph-types/microsoft-graph'; +import GlobalOptions from '../../../../GlobalOptions.js'; +import { Logger } from '../../../../cli/Logger.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import GraphCommand from '../../../base/GraphCommand.js'; +import commands from '../../commands.js'; + +interface CommandArgs { + options: Options; +} + +interface Options extends GlobalOptions { + externalConnectionId: string; + baseUrls: string; + urlPattern: string; + itemId: string; + priority: number; +} + +class ExternalConnectionUrlToItemResolverAddCommand extends GraphCommand { + public get name(): string { + return commands.CONNECTION_URLTOITEMRESOLVER_ADD; + } + + public get description(): string { + return 'Adds a URL to item resolver to an external connection'; + } + + constructor() { + super(); + + this.#initOptions(); + } + + #initOptions(): void { + this.options.unshift( + { + option: '-c, --externalConnectionId ' + }, + { + option: '--baseUrls ' + }, + { + option: '--urlPattern ' + }, + { + option: '-i, --itemId ' + }, + { + option: '-p, --priority ' + } + ); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + const baseUrls: string[] = args.options.baseUrls.split(',').map(b => b.trim()); + + const itemIdResolver: ExternalConnectors.ItemIdResolver = { + itemId: args.options.itemId, + priority: args.options.priority, + urlMatchInfo: { + baseUrls: baseUrls, + urlPattern: args.options.urlPattern + } + }; + // not a part of the type definition, but required by the API + (itemIdResolver as any)['@odata.type'] = '#microsoft.graph.externalConnectors.itemIdResolver'; + + const requestOptions: CliRequestOptions = { + url: `${this.resource}/v1.0/external/connections/${args.options.externalConnectionId}`, + headers: { + accept: 'application/json;odata.metadata=none', + 'content-type': 'application/json' + }, + responseType: 'json', + data: { + activitySettings: { + urlToItemResolvers: [itemIdResolver] + } + } as ExternalConnectors.ExternalConnection + }; + + try { + await request.patch(requestOptions); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new ExternalConnectionUrlToItemResolverAddCommand(); \ No newline at end of file