From c3c8ea0d7d5553bc2b58ddfe3835c60553e386a5 Mon Sep 17 00:00:00 2001 From: Shantha Kumar T Date: Fri, 18 Oct 2024 08:11:43 +0530 Subject: [PATCH 1/3] Initial commit --- src/m365/pp/commands.ts | 3 +- src/m365/pp/commands/Website.ts | 8 + .../website/website-weblink-list.spec.ts | 0 .../commands/website/website-weblink-list.ts | 147 ++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/m365/pp/commands/Website.ts create mode 100644 src/m365/pp/commands/website/website-weblink-list.spec.ts create mode 100644 src/m365/pp/commands/website/website-weblink-list.ts diff --git a/src/m365/pp/commands.ts b/src/m365/pp/commands.ts index dae86ad2616..d81c4e51741 100644 --- a/src/m365/pp/commands.ts +++ b/src/m365/pp/commands.ts @@ -31,5 +31,6 @@ export default { SOLUTION_PUBLISHER_LIST: `${prefix} solution publisher list`, SOLUTION_PUBLISHER_REMOVE: `${prefix} solution publisher remove`, TENANT_SETTINGS_LIST: `${prefix} tenant settings list`, - TENANT_SETTINGS_SET: `${prefix} tenant settings set` + TENANT_SETTINGS_SET: `${prefix} tenant settings set`, + WEBSITE_WEBLINK_LIST: `${prefix} website weblink list` }; \ No newline at end of file diff --git a/src/m365/pp/commands/Website.ts b/src/m365/pp/commands/Website.ts new file mode 100644 index 00000000000..bf6aeb82401 --- /dev/null +++ b/src/m365/pp/commands/Website.ts @@ -0,0 +1,8 @@ +import GlobalOptions from "../../../GlobalOptions"; +export interface PpWebSiteOptions extends GlobalOptions { + environmentName: string; + id?: string; + name?: string; + url?: string; + asAdmin?: boolean; +} \ No newline at end of file diff --git a/src/m365/pp/commands/website/website-weblink-list.spec.ts b/src/m365/pp/commands/website/website-weblink-list.spec.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/m365/pp/commands/website/website-weblink-list.ts b/src/m365/pp/commands/website/website-weblink-list.ts new file mode 100644 index 00000000000..245aa863fbf --- /dev/null +++ b/src/m365/pp/commands/website/website-weblink-list.ts @@ -0,0 +1,147 @@ +import { Logger } from '../../../../cli/Logger.js'; +import GlobalOptions from '../../../../GlobalOptions.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { odata } from '../../../../utils/odata.js'; +import { powerPlatform } from '../../../../utils/powerPlatform.js'; +import { validation } from '../../../../utils/validation.js'; +import PowerPlatformCommand from '../../../base/PowerPlatformCommand.js'; +import commands from '../../commands.js'; +import { PpWebSiteOptions } from '../Website.js'; + +interface CommandArgs { + options: Options; +} + +interface Options extends GlobalOptions { + environmentName: string; + websiteId?: string; + websiteName?: string; + asAdmin?: boolean; +} + +class PpWebSiteWebLinkListCommand extends PowerPlatformCommand { + public get name(): string { + return commands.WEBSITE_WEBLINK_LIST; + } + + public get description(): string { + return 'List all weblinks for the specified Power Pages website'; + } + + public defaultProperties(): string[] | undefined { + return ['mspp_name', 'mspp_webfileid', 'mspp_summary', '_mspp_publishingstateid_value@OData.Community.Display.V1.FormattedValue']; + } + + constructor() { + super(); + + this.#initTelemetry(); + this.#initOptions(); + this.#initValidators(); + this.#initOptionsSets(); + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + websiteId: typeof args.options.websiteId !== 'undefined', + websiteName: typeof args.options.name !== 'undefined', + asAdmin: !!args.options.asAdmin + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { + option: '-e, --environmentName ' + }, + { + option: '--websiteId [websiteId]' + }, + { + option: '--websiteName [websiteName]' + }, + { + option: '--asAdmin' + } + ); + } + + #initOptionsSets(): void { + this.optionSets.push( + { options: ['websiteId', 'websiteName'] } + ); + } + + #initValidators(): void { + this.validators.push( + async (args: CommandArgs) => { + if (args.options.websiteId && !validation.isValidGuid(args.options.websiteId)) { + return `${args.options.websiteId} is not a valid GUID`; + } + return true; + } + ); + } + + private async getWebSiteId(dynamicsApiUrl: string, args: CommandArgs): Promise { + if (args.options.websiteId) { + return args.options.websiteId; + } + const options: PpWebSiteOptions = { + environmentName: args.options.environmentName, + id: args.options.websiteId, + name: args.options.websiteName, + output: 'json' + }; + + const requestOptions: CliRequestOptions = { + headers: { + accept: 'applciation/json;odata.metadata=none' + }, + responseType: 'json' + }; + + if (options.name) { + requestOptions.url = `${dynamicsApiUrl}/api/data/v9.2/weblinks?$filter=name eq '${options.name}'&$select=powerpagesiteid`; + const result = await request.get<{ value: any[] }>(requestOptions); + + if (result.value.length === 0) { + throw `The specified website '${args.options.websiteName}' does not exist.`; + } + return result.value[0].powerpagesiteid; + } + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (this.verbose) { + await logger.logToStderr(`Retrieving list of webfiles`); + } + + try { + const dynamicsApiUrl = await powerPlatform.getDynamicsInstanceApiUrl(args.options.environmentName, args.options.asAdmin); + const websiteId = await this.getWebSiteId(dynamicsApiUrl, args); + console.log(websiteId); + + const requestOptions: CliRequestOptions = { + //url: `${dynamicsApiUrl}/api/data/v9.2/mspp_webfiles?$filter=_mspp_websiteid_value eq '${websiteId}'`, + url: `${dynamicsApiUrl}/api/data/v9.2/mspp_weblinks`, + headers: { + accept: `application/json;`, + 'odata-version': '4.0', + prefer: `odata.include-annotations="*"` + }, + responseType: 'json' + }; + + const items = await odata.getAllItems(requestOptions); + await logger.log(items); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new PpWebSiteWebLinkListCommand(); \ No newline at end of file From 03459228f411fe94b5b75f25c9f67dacf9bfb652 Mon Sep 17 00:00:00 2001 From: Shantha Kumar T Date: Fri, 25 Oct 2024 16:00:39 +0530 Subject: [PATCH 2/3] new command: website-weblink-list --- .../cmd/pp/website/website-weblink-list.mdx | 133 +++++++ docs/src/config/sidebars.ts | 9 + .../website/website-weblink-list.spec.ts | 368 ++++++++++++++++++ .../commands/website/website-weblink-list.ts | 120 +++--- 4 files changed, 560 insertions(+), 70 deletions(-) create mode 100644 docs/docs/cmd/pp/website/website-weblink-list.mdx diff --git a/docs/docs/cmd/pp/website/website-weblink-list.mdx b/docs/docs/cmd/pp/website/website-weblink-list.mdx new file mode 100644 index 00000000000..b3384285858 --- /dev/null +++ b/docs/docs/cmd/pp/website/website-weblink-list.mdx @@ -0,0 +1,133 @@ +import Global from "/docs/cmd/_global.mdx"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +# pp webiste weblink list + +List all weblinks for the specified Power Pages website + +## Usage + +```sh +m365 pp webiste weblink list [options] +``` + +## Options + +```md definition-list +`-e, --environmentName ` +: The name of the environment where the Power Pages websites are located. + +`--websiteId [websiteId]` +:ID of the Power Pages website. Specify either `websiteId` or `websiteName` but not both. + +`--websiteName [websiteName]` +:The unique name (not the display name) of the Power Pages website. Specify either `websiteId` or `websiteName` but not both. + +`--asAdmin` +: Run the command as admin and retrieve Power Pages websites for environments you do not have explicitly assigned permissions to. +``` + + + +## Examples + +List all weblinks for the site Contoso + +```sh +m365 pp webiste weblink list --environmentName "Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c" --websiteName "Contoso" +``` + +List all weblinks for the site by id as admin + +```sh +m365 pp webiste weblink list --environmentName "Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c" --websiteId "3bbc8102-8ee7-4dac-afbb-807cc5b6f9c2" --asAdmin +``` + +## Response + + + + +```json +[ + { + "mspp_weblinkid": "18a5589e-6472-4ed6-90ba-fd51a15325d8", + "mspp_name": "Subpage 1", + "mspp_disablepagevalidation": false, + "mspp_displayimageonly": false, + "mspp_displayorder": 1, + "mspp_displaypagechildlinks": false, + "mspp_openinnewwindow": false, + "_mspp_pageid_value": "1a4cbb29-223b-4be2-ae9e-bc281de64b12", + "_mspp_publishingstateid_value": "ffefd269-446e-46aa-9379-92d1b2d323b8", + "mspp_robotsfollowlink": true, + "_mspp_weblinksetid_value": "28ae3dfa-5939-4f79-9373-1665a839a9b2", + "_mspp_createdby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "_mspp_modifiedby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "mspp_modifiedon": "2024-04-12T05:46:47Z", + "mspp_createdon": "2024-04-12T05:46:47Z", + "statecode": 0, + "statuscode": 1, + "mspp_description": null, + "mspp_imagealttext": null, + "mspp_imageurl": null, + "mspp_imageheight": null, + "mspp_modifiedbyusername": null, + "mspp_createdbyipaddress": null, + "mspp_createdbyusername": null, + "mspp_modifiedbyipaddress": null, + "_mspp_parentweblinkid_value": "4ebe880e-33fe-48a9-80f6-37f585207382", + "mspp_imagewidth": null, + "mspp_externalurl": null + } +] +``` + + + + +```text +mspp_name mspp_weblinkid mspp_description statecode +----------------------- ------------------------------------ ---------------- --------- +Add Attendee Profile 3ec6cc71-464a-ef11-a317-000d3a3083fa null 1 +``` + + + + +```csv +mspp_name,mspp_weblinkid,mspp_description,statecode +Add Attendee Profile,3ec6cc71-464a-ef11-a317-000d3a3083fa,null,1 +``` + + + + +```md + + +| Property | Value | +| ----------------------------------|------------------------------------- | +| mspp\_weblinkid | 21d71883-3f13-ef11-9f89-000d3a593739 | +| mspp\_name | [Admin] Topics | +| mspp\_disablepagevalidation | false | +| mspp\_displayimageonly | false | +| mspp\_displayorder | 10 | +| mspp\_displaypagechildlinks | false | +| mspp\_openinnewwindow | false | +| \_mspp\_pageid\_value | 6269458f-3f13-ef11-9f89-000d3a593739 | +| \_mspp\_publishingstateid\_value | 11d71883-3f13-ef11-9f89-000d3a593739 | +| mspp\_robotsfollowlink | true | +| \_mspp\_weblinksetid\_value | 3c881389-3f13-ef11-9f89-000d3a593739 | +| \_mspp\_createdby\_value | 5364ffa6-d185-ee11-8179-6045bd0027e0 | +| \_mspp\_modifiedby\_value | 5364ffa6-d185-ee11-8179-6045bd0027e0 | +| mspp\_modifiedon | 2024-05-16T04:49:11Z | +| mspp\_createdon | 2024-05-16T04:48:20Z | +| statecode | 0 | +| statuscode | 1 | + +``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 0370e28df09..1133df09e14 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -1404,6 +1404,15 @@ const sidebars: SidebarsConfig = { id: 'cmd/planner/tenant/tenant-settings-set' } ] + }, + { + website: [ + { + type: 'doc', + label: 'website weblink list', + id: 'cmd/pp/website/website-weblink-list' + } + ] } ] }, diff --git a/src/m365/pp/commands/website/website-weblink-list.spec.ts b/src/m365/pp/commands/website/website-weblink-list.spec.ts index e69de29bb2d..462e84c8bda 100644 --- a/src/m365/pp/commands/website/website-weblink-list.spec.ts +++ b/src/m365/pp/commands/website/website-weblink-list.spec.ts @@ -0,0 +1,368 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import { z } from 'zod'; +import auth from '../../../../Auth.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.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 './website-weblink-list.js'; +import { accessToken } from '../../../../utils/accessToken.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { cli } from '../../../../cli/cli.js'; + +describe(commands.WEBSITE_WEBLINK_LIST, () => { + //#region Mocked Responses + let commandInfo: CommandInfo; + const validEnvironment = 'Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c'; + const validWebsiteId = '3bbc8102-8ee7-4dac-afbb-807cc5b6f9c2'; + const validWebsiteName = 'CLI 365 PowerPageSite'; + + const envResponse: any = { "properties": { "linkedEnvironmentMetadata": { "instanceApiUrl": "https://contoso-dev.api.crm4.dynamics.com" } } }; + const weblinksetsResponse: any = { + "value": [ + { + "mspp_weblinksetid": "c94de7c8-5474-45b6-9172-c15e8e4ba1e1", + "mspp_name": "Default", + "_mspp_websitelanguageid_value": "403f1195-3f13-ef11-9f89-000d3a3755aa", + "mspp_display_name": "Default", + "_mspp_publishingstateid_value": "11d71883-3f13-ef11-9f89-000d3a593739", + "_mspp_websiteid_value": "3bbc8102-8ee7-4dac-afbb-807cc5b6f9c2", + "_mspp_createdby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "_mspp_modifiedby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "mspp_modifiedon": "2024-05-16T04:49:10Z", + "mspp_createdon": "2024-05-16T04:48:37Z", + "statecode": 0, + "statuscode": 1, + "mspp_title": null, + "mspp_copy": null + }, + { + "mspp_weblinksetid": "28ae3dfa-5939-4f79-9373-1665a839a9b2", + "mspp_name": "Default", + "_mspp_websitelanguageid_value": "403f1195-3f13-ef11-9f89-000d3a3755aa", + "mspp_display_name": "Default", + "_mspp_publishingstateid_value": "11d71883-3f13-ef11-9f89-000d3a593739", + "_mspp_websiteid_value": "3bbc8102-8ee7-4dac-afbb-807cc5b6f9c2", + "_mspp_createdby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "_mspp_modifiedby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "mspp_modifiedon": "2024-05-16T04:49:10Z", + "mspp_createdon": "2024-05-16T04:48:37Z", + "statecode": 0, + "statuscode": 1, + "mspp_title": null, + "mspp_copy": null + } + ] + }; + const weblinksResponse: any = { + "value": [ + { + "mspp_weblinkid": "fccea7a1-a1dc-418e-839e-f39c0ec400a2", + "mspp_name": "Contact us", + "mspp_disablepagevalidation": false, + "mspp_displayimageonly": false, + "mspp_displayorder": 3, + "mspp_displaypagechildlinks": false, + "mspp_openinnewwindow": false, + "_mspp_pageid_value": "4cdcd042-bb91-4673-ae89-b44bfdb3a751", + "_mspp_publishingstateid_value": "ffefd269-446e-46aa-9379-92d1b2d323b8", + "mspp_robotsfollowlink": true, + "_mspp_weblinksetid_value": "c94de7c8-5474-45b6-9172-c15e8e4ba1e1", + "_mspp_createdby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "_mspp_modifiedby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "mspp_modifiedon": "2024-04-12T05:46:36Z", + "mspp_createdon": "2024-04-12T05:46:36Z", + "statecode": 0, + "statuscode": 1, + "mspp_description": null, + "mspp_imagealttext": null, + "mspp_imageurl": null, + "mspp_imageheight": null, + "mspp_modifiedbyusername": null, + "mspp_createdbyipaddress": null, + "mspp_createdbyusername": null, + "mspp_modifiedbyipaddress": null, + "_mspp_parentweblinkid_value": null, + "mspp_imagewidth": null, + "mspp_externalurl": null + }, + { + "mspp_weblinkid": "18a5589e-6472-4ed6-90ba-fd51a15325d8", + "mspp_name": "Subpage 1", + "mspp_disablepagevalidation": false, + "mspp_displayimageonly": false, + "mspp_displayorder": 1, + "mspp_displaypagechildlinks": false, + "mspp_openinnewwindow": false, + "_mspp_pageid_value": "1a4cbb29-223b-4be2-ae9e-bc281de64b12", + "_mspp_publishingstateid_value": "ffefd269-446e-46aa-9379-92d1b2d323b8", + "mspp_robotsfollowlink": true, + "_mspp_weblinksetid_value": "28ae3dfa-5939-4f79-9373-1665a839a9b2", + "_mspp_createdby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "_mspp_modifiedby_value": "5364ffa6-d185-ee11-8179-6045bd0027e0", + "mspp_modifiedon": "2024-04-12T05:46:47Z", + "mspp_createdon": "2024-04-12T05:46:47Z", + "statecode": 0, + "statuscode": 1, + "mspp_description": null, + "mspp_imagealttext": null, + "mspp_imageurl": null, + "mspp_imageheight": null, + "mspp_modifiedbyusername": null, + "mspp_createdbyipaddress": null, + "mspp_createdbyusername": null, + "mspp_modifiedbyipaddress": null, + "_mspp_parentweblinkid_value": "4ebe880e-33fe-48a9-80f6-37f585207382", + "mspp_imagewidth": null, + "mspp_externalurl": null + } + ] + }; + //#endregion + + let log: string[]; + let logger: Logger; + let loggerLogSpy: sinon.SinonSpy; + let commandOptionsSchema: z.ZodTypeAny; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').returns(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + sinon.stub(accessToken, 'assertDelegatedAccessToken').returns(); + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + auth.connection.active = true; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + loggerLogSpy = sinon.spy(logger, 'log'); + }); + + afterEach(() => { + sinonUtil.restore([ + request.get + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.WEBSITE_WEBLINK_LIST); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('fails validation if websiteId is not a valid guid.', async () => { + const actual = commandOptionsSchema.safeParse({ environmentName: validEnvironment, websiteId: 'Invalid GUID' }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if both websiteId or websiteName is provided', async () => { + const actual = commandOptionsSchema.safeParse({ environmentName: validEnvironment, websiteId: validWebsiteId, websiteName: validWebsiteName }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if either websiteId or websiteName is not provided', async () => { + const actual = commandOptionsSchema.safeParse({ environmentName: validEnvironment }); + assert.strictEqual(actual.success, false); + }); + + it('passes validation if required option websiteId specified', async () => { + const actual = commandOptionsSchema.safeParse({ environmentName: validEnvironment, websiteId: validWebsiteId }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation if required option websiteName specified', async () => { + const actual = await command.validate({ options: { environmentName: validEnvironment, websiteName: validWebsiteName } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('defines correct properties for the default output', () => { + assert.deepStrictEqual(command.defaultProperties(), ['mspp_name', 'mspp_weblinkid', 'mspp_description', 'statecode']); + }); + + it('fails validation on unable to find website based on websiteName', async () => { + const EmptyWebsiteResponse = { + value: [ + ] + }; + + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/${validEnvironment}?api-version=2020-10-01&$select=properties.linkedEnvironmentMetadata.instanceApiUrl`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return envResponse; + } + } + + if ((opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/powerpagesites?$filter=name eq 'Invalid website'&$select=powerpagesiteid`)) { + return EmptyWebsiteResponse; + } + throw `Invalid request`; + }); + + await assert.rejects(command.action(logger, { + options: { + debug: true, + environmentName: `${validEnvironment}`, + websiteName: 'Invalid website' + } + }), new CommandError(`The specified website 'Invalid website' does not exist.`)); + }); + + it('retrieves weblinks', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if ((opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/${validEnvironment}?api-version=2020-10-01&$select=properties.linkedEnvironmentMetadata.instanceApiUrl`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return envResponse; + } + } + + if ((opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/mspp_weblinksets?$filter=_mspp_websiteid_value eq '${validWebsiteId}'`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return weblinksetsResponse; + } + } + + if ((opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/mspp_weblinks?$filter=Microsoft.Dynamics.CRM.ContainValues(PropertyName=@p1,PropertyValues=@p2)&@p1='mspp_weblinksetid'&@p2=['c94de7c8-5474-45b6-9172-c15e8e4ba1e1','28ae3dfa-5939-4f79-9373-1665a839a9b2']`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return weblinksResponse; + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, environmentName: `${validEnvironment}`, websiteId: `${validWebsiteId}` } }); + assert(loggerLogSpy.calledWith(weblinksResponse.value)); + + }); + + it('failed to fetch weblinksets based on websiteid', async () => { + const EmptyLinksetsResponse = { + value: [ + ] + }; + + sinon.stub(request, 'get').callsFake(async opts => { + if ((opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/${validEnvironment}?api-version=2020-10-01&$select=properties.linkedEnvironmentMetadata.instanceApiUrl`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return envResponse; + } + } + + if (opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/powerpagesites?$filter=name eq '${validWebsiteName}'&$select=powerpagesiteid`) { + return { + "value": [ + { + "powerpagesiteid": validWebsiteId + } + ] + }; + } + + if ((opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/mspp_weblinksets?$filter=_mspp_websiteid_value eq '${validWebsiteId}'`)) { + return EmptyLinksetsResponse; + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { + options: { + debug: true, + environmentName: `${validEnvironment}`, + websiteName: `${validWebsiteName}` + } + }), new CommandError(`The specified website '${validWebsiteId}' does not have links.`)); + }); + + it('retrieves weblinks based on website name as admin', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if ((opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments/${validEnvironment}?api-version=2020-10-01&$select=properties.linkedEnvironmentMetadata.instanceApiUrl`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return envResponse; + } + } + + if (opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/powerpagesites?$filter=name eq '${validWebsiteName}'&$select=powerpagesiteid`) { + return { + "value": [ + { + "powerpagesiteid": validWebsiteId + } + ] + }; + } + + if ((opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/mspp_weblinksets?$filter=_mspp_websiteid_value eq '${validWebsiteId}'`)) { + return weblinksetsResponse; + } + + + if ((opts.url === `https://contoso-dev.api.crm4.dynamics.com/api/data/v9.2/mspp_weblinks?$filter=Microsoft.Dynamics.CRM.ContainValues(PropertyName=@p1,PropertyValues=@p2)&@p1='mspp_weblinksetid'&@p2=['c94de7c8-5474-45b6-9172-c15e8e4ba1e1','28ae3dfa-5939-4f79-9373-1665a839a9b2']`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return weblinksResponse; + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, environmentName: validEnvironment, websiteName: validWebsiteName, asAdmin: true } }); + assert(loggerLogSpy.calledWith(weblinksResponse.value)); + }); + + it('correctly handles API OData error', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if ((opts.url === `https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/environments/${validEnvironment}?api-version=2020-10-01&$select=properties.linkedEnvironmentMetadata.instanceApiUrl`)) { + if (opts.headers && + opts.headers.accept && + (opts.headers.accept as string).indexOf('application/json') === 0) { + return envResponse; + } + } + + throw `Resource '' does not exist or one of its queried reference-property objects are not present`; + }); + + await assert.rejects(command.action(logger, { options: { environmentName: validEnvironment } }), new CommandError("Resource '' does not exist or one of its queried reference-property objects are not present")); + }); +}); \ No newline at end of file diff --git a/src/m365/pp/commands/website/website-weblink-list.ts b/src/m365/pp/commands/website/website-weblink-list.ts index 245aa863fbf..b129feece6a 100644 --- a/src/m365/pp/commands/website/website-weblink-list.ts +++ b/src/m365/pp/commands/website/website-weblink-list.ts @@ -1,5 +1,7 @@ import { Logger } from '../../../../cli/Logger.js'; -import GlobalOptions from '../../../../GlobalOptions.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import { z } from 'zod'; +import { zod } from '../../../../utils/zod.js'; import request, { CliRequestOptions } from '../../../../request.js'; import { odata } from '../../../../utils/odata.js'; import { powerPlatform } from '../../../../utils/powerPlatform.js'; @@ -8,17 +10,28 @@ import PowerPlatformCommand from '../../../base/PowerPlatformCommand.js'; import commands from '../../commands.js'; import { PpWebSiteOptions } from '../Website.js'; + + +export const options = globalOptionsZod + .extend({ + environmentName: zod.alias('e', z.string()), + websiteId: z.string().refine(id => validation.isValidGuid(id) === true, id => ({ message: `${id} is not a valid GUID` })).optional(), + websiteName: z.string().optional(), + asAdmin: z.boolean().optional() + }) + .refine(options => !(options.websiteId !== undefined && options.websiteName !== undefined), { + message: `Either websiteId or websiteName is required, but not both.` + }) + .refine(options => !(options.websiteId === undefined && options.websiteName === undefined), { + message: `Either websiteId or websiteName is required.` + }); + +declare type Options = z.infer; + interface CommandArgs { options: Options; } -interface Options extends GlobalOptions { - environmentName: string; - websiteId?: string; - websiteName?: string; - asAdmin?: boolean; -} - class PpWebSiteWebLinkListCommand extends PowerPlatformCommand { public get name(): string { return commands.WEBSITE_WEBLINK_LIST; @@ -29,60 +42,11 @@ class PpWebSiteWebLinkListCommand extends PowerPlatformCommand { } public defaultProperties(): string[] | undefined { - return ['mspp_name', 'mspp_webfileid', 'mspp_summary', '_mspp_publishingstateid_value@OData.Community.Display.V1.FormattedValue']; - } - - constructor() { - super(); - - this.#initTelemetry(); - this.#initOptions(); - this.#initValidators(); - this.#initOptionsSets(); - } - - #initTelemetry(): void { - this.telemetry.push((args: CommandArgs) => { - Object.assign(this.telemetryProperties, { - websiteId: typeof args.options.websiteId !== 'undefined', - websiteName: typeof args.options.name !== 'undefined', - asAdmin: !!args.options.asAdmin - }); - }); - } - - #initOptions(): void { - this.options.unshift( - { - option: '-e, --environmentName ' - }, - { - option: '--websiteId [websiteId]' - }, - { - option: '--websiteName [websiteName]' - }, - { - option: '--asAdmin' - } - ); + return ['mspp_name', 'mspp_weblinkid', 'mspp_description', 'statecode']; } - #initOptionsSets(): void { - this.optionSets.push( - { options: ['websiteId', 'websiteName'] } - ); - } - - #initValidators(): void { - this.validators.push( - async (args: CommandArgs) => { - if (args.options.websiteId && !validation.isValidGuid(args.options.websiteId)) { - return `${args.options.websiteId} is not a valid GUID`; - } - return true; - } - ); + public get schema(): z.ZodTypeAny { + return options; } private async getWebSiteId(dynamicsApiUrl: string, args: CommandArgs): Promise { @@ -98,13 +62,13 @@ class PpWebSiteWebLinkListCommand extends PowerPlatformCommand { const requestOptions: CliRequestOptions = { headers: { - accept: 'applciation/json;odata.metadata=none' + accept: 'application/json;odata.metadata=none' }, responseType: 'json' }; if (options.name) { - requestOptions.url = `${dynamicsApiUrl}/api/data/v9.2/weblinks?$filter=name eq '${options.name}'&$select=powerpagesiteid`; + requestOptions.url = `${dynamicsApiUrl}/api/data/v9.2/powerpagesites?$filter=name eq '${options.name}'&$select=powerpagesiteid`; const result = await request.get<{ value: any[] }>(requestOptions); if (result.value.length === 0) { @@ -114,23 +78,39 @@ class PpWebSiteWebLinkListCommand extends PowerPlatformCommand { } } + private async getwebsitelinksets(dynamicsApiUrl: string, websiteId: string): Promise { + + const requestOptions: CliRequestOptions = { + headers: { + accept: 'application/json;odata.metadata=none' + }, + responseType: 'json' + }; + + if (websiteId) { + requestOptions.url = `${dynamicsApiUrl}/api/data/v9.2/mspp_weblinksets?$filter=_mspp_websiteid_value eq '${websiteId}'`; + const result = await request.get<{ value: any[] }>(requestOptions); + if (result.value.length === 0) { + throw `The specified website '${websiteId}' does not have links.`; + } + const weblinksets = result.value.map(linkset => "'" + linkset.mspp_weblinksetid.toString() + "'"); + return weblinksets.join(','); + } + } + public async commandAction(logger: Logger, args: CommandArgs): Promise { if (this.verbose) { - await logger.logToStderr(`Retrieving list of webfiles`); + await logger.logToStderr(`Retrieving list of weblinks from website ${args.options.websiteId || args.options.websiteName} in environment ${args.options.environmentName}...`); } try { const dynamicsApiUrl = await powerPlatform.getDynamicsInstanceApiUrl(args.options.environmentName, args.options.asAdmin); const websiteId = await this.getWebSiteId(dynamicsApiUrl, args); - console.log(websiteId); - + const weblinksets = await this.getwebsitelinksets(dynamicsApiUrl, websiteId); const requestOptions: CliRequestOptions = { - //url: `${dynamicsApiUrl}/api/data/v9.2/mspp_webfiles?$filter=_mspp_websiteid_value eq '${websiteId}'`, - url: `${dynamicsApiUrl}/api/data/v9.2/mspp_weblinks`, + url: `${dynamicsApiUrl}/api/data/v9.2/mspp_weblinks?$filter=Microsoft.Dynamics.CRM.ContainValues(PropertyName=@p1,PropertyValues=@p2)&@p1='mspp_weblinksetid'&@p2=[${weblinksets}]`, headers: { - accept: `application/json;`, - 'odata-version': '4.0', - prefer: `odata.include-annotations="*"` + accept: `application/json;` }, responseType: 'json' }; From 63303022f138da812ffec7439c586f0950f09e90 Mon Sep 17 00:00:00 2001 From: Shantha Kumar T Date: Fri, 25 Oct 2024 20:55:02 +0530 Subject: [PATCH 3/3] Documentation updated --- docs/docs/cmd/pp/website/website-weblink-list.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/cmd/pp/website/website-weblink-list.mdx b/docs/docs/cmd/pp/website/website-weblink-list.mdx index b3384285858..85d772a002b 100644 --- a/docs/docs/cmd/pp/website/website-weblink-list.mdx +++ b/docs/docs/cmd/pp/website/website-weblink-list.mdx @@ -2,14 +2,14 @@ import Global from "/docs/cmd/_global.mdx"; import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; -# pp webiste weblink list +# pp website weblink list List all weblinks for the specified Power Pages website ## Usage ```sh -m365 pp webiste weblink list [options] +m365 pp website weblink list [options] ``` ## Options @@ -35,13 +35,13 @@ m365 pp webiste weblink list [options] List all weblinks for the site Contoso ```sh -m365 pp webiste weblink list --environmentName "Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c" --websiteName "Contoso" +m365 pp website weblink list --environmentName "Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c" --websiteName "Contoso" ``` List all weblinks for the site by id as admin ```sh -m365 pp webiste weblink list --environmentName "Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c" --websiteId "3bbc8102-8ee7-4dac-afbb-807cc5b6f9c2" --asAdmin +m365 pp website weblink list --environmentName "Default-eff8592e-e14a-4ae8-8771-d96d5c549e1c" --websiteId "3bbc8102-8ee7-4dac-afbb-807cc5b6f9c2" --asAdmin ``` ## Response