diff --git a/docs/docs/cmd/pp/pipeline/pipeline-list.mdx b/docs/docs/cmd/pp/pipeline/pipeline-list.mdx new file mode 100644 index 00000000000..61382f04d4a --- /dev/null +++ b/docs/docs/cmd/pp/pipeline/pipeline-list.mdx @@ -0,0 +1,105 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# pp pipeline list + +Lists the Power Platform pipelines for the specified environment. + +## Usage + +```sh +m365 pp pipeline list [options] +``` + +## Options + +`-e, --environmentName ` +: The name or id of the environment for which to list the pipelines. + +`--asAdmin` +: Run the command as admin and retrieve pipelines from environments you do not have explicitly assigned permissions to. + + + +## Remarks + +:::warning + +This command is based on an API that is currently in preview and is subject to change once the API reaches general availability. + +Register the CLI for Microsoft 365 or Microsoft Entra application as a management application for the Power Platform using + +m365 pp managementapp add [options] + +::: + +## Examples + +Retrieve the list of deployment pipelines in the specified Power Platform environment. + +```sh +m365 pp pipeline list --environmentName Default-d87a7535-dd31-4437-bfe1-95340acd55c5 +``` + +Retrieve the list of deployment pipelines as admin in the specified Power Platform environment. + +```sh +m365 pp pipeline list --environmentName Default-d87a7535-dd31-4437-bfe1-95340acd55c5 --asAdmin +``` + +## Response + + + + + ```json + [ + { + "name": "Sales Pipeline", + "deploymentpipelineid": "3ef59632-f087-ef11-8a69-7c1e521ba3ec", + "ownerid": "27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8", + "statuscode": 1 + } + ] + ``` + + + + + ```text + deploymentpipelineid: 3ef59632-f087-ef11-8a69-7c1e521ba3ec + name : Sales Pipeline + ownerid : 27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8 + statuscode : 1 + ``` + + + + + ```csv + name,deploymentpipelineid,ownerid,statuscode + Sales Pipeline,3ef59632-f087-ef11-8a69-7c1e521ba3ec,27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8,1 + ``` + + + + + ```md + # m365 pp pipeline list --environmentName Default-d87a7535-dd31-4437-bfe1-95340acd55c5 + + Date: 17/10/2024 + + ## environmentName (default) (/providers/Microsoft.BusinessAppPlatform/environments/Default-e1dd4023-a656-480a-8a0e-c1b1eec51e1d) + + Property | Value + ---------|------- + name | Sales Pipeline + deploymentpipelineid | 3ef59632-f087-ef11-8a69-7c1e521ba3ec + ownerid | 27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8 + statuscode | 1 + ``` + + + + \ No newline at end of file diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 0370e28df09..f4840217bd7 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -1742,6 +1742,15 @@ const sidebars: SidebarsConfig = { } ] }, + { + pipeline: [ + { + type: 'doc', + label: 'pipeline list', + id: 'cmd/pp/pipeline/pipeline-list' + } + ] + }, { solution: [ { diff --git a/src/m365/pp/commands.ts b/src/m365/pp/commands.ts index dae86ad2616..fabf574e7ca 100644 --- a/src/m365/pp/commands.ts +++ b/src/m365/pp/commands.ts @@ -22,6 +22,7 @@ export default { GATEWAY_LIST: `${prefix} gateway list`, MANAGEMENTAPP_ADD: `${prefix} managementapp add`, MANAGEMENTAPP_LIST: `${prefix} managementapp list`, + PIPELINE_LIST: `${prefix} pipeline list`, SOLUTION_GET: `${prefix} solution get`, SOLUTION_LIST: `${prefix} solution list`, SOLUTION_PUBLISH: `${prefix} solution publish`, diff --git a/src/m365/pp/commands/pipeline/pipeline-list.spec.ts b/src/m365/pp/commands/pipeline/pipeline-list.spec.ts new file mode 100644 index 00000000000..06f39a201ab --- /dev/null +++ b/src/m365/pp/commands/pipeline/pipeline-list.spec.ts @@ -0,0 +1,138 @@ +import assert from "assert"; +import commands from "../../commands.js"; +import command from './pipeline-list.js'; +import sinon from 'sinon'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import auth from '../../../../Auth.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from "../../../../utils/session.js"; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import { accessToken } from '../../../../utils/accessToken.js'; +import { CommandError } from "../../../../Command.js"; +import { powerPlatform } from "../../../../utils/powerPlatform.js"; + +describe(commands.PIPELINE_LIST, () => { + const environmentName = 'environmentName'; + const mockPipelineListResponse: any = [ + { + '_ownerid_value': 'owner1', + deploymentpipelineid: 'deploymentpipelineid1', + name: 'pipeline1', + statuscode: 'statuscode1' + } + ]; + const mockEnvironmentResponse = { + "id": `/providers/Microsoft.BusinessAppPlatform/environments/Default-Environment`, + "type": "Microsoft.BusinessAppPlatform/environments", + "location": "unitedstates", + "name": "Default-Environment", + "properties": { + "displayName": "contoso (default)", + "isDefault": true, + linkedEnvironmentMetadata: { + instanceApiUrl: 'https://contoso.crm.dynamics.com' + } + } + }; + + let log: string[]; + let logger: Logger; + + let loggerLogSpy: sinon.SinonSpy; + + 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(); + 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(commands.PIPELINE_LIST, command.name); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('defines correct properties for the default output', () => { + assert.deepStrictEqual(command.defaultProperties(), ['name', 'deploymentpipelineid', '_ownerid_value', 'statuscode']); + }); + + it('retrieves pipelines in the specified Power Platform environment', async () => { + const getEnvironmentStub = await sinon.stub(powerPlatform as any, 'getDynamicsInstanceApiUrl').callsFake(() => Promise.resolve(mockEnvironmentResponse)); + const getPipelineStub = sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf('/api/data/v9.0/deploymentpipelines') > -1) { + return { + value: [ + { + '_ownerid_value': 'owner1', + deploymentpipelineid: 'deploymentpipelineid1', + name: 'pipeline1', + statuscode: 'statuscode1' + } + ] + }; + } + throw new Error('Invalid request'); + }); + + await command.action(logger, { + options: { + environmentName: environmentName, + asAdmin: false + } + }); + + assert(getEnvironmentStub.called); + assert(getPipelineStub.called); + + assert(loggerLogSpy.calledWith(sinon.match(mockPipelineListResponse))); + }); + + it('correctly handles error when retrieving environment details or pipelines', async () => { + const errorMessage = 'An error has occurred'; + sinon.stub(request, 'get').callsFake(async () => { + throw errorMessage; + }); + + await assert.rejects(command.action(logger, { + options: { + environmentName: environmentName, + asAdmin: false + } + }), new CommandError(errorMessage)); + }); + +}); diff --git a/src/m365/pp/commands/pipeline/pipeline-list.ts b/src/m365/pp/commands/pipeline/pipeline-list.ts new file mode 100644 index 00000000000..6a79afdf23e --- /dev/null +++ b/src/m365/pp/commands/pipeline/pipeline-list.ts @@ -0,0 +1,89 @@ + +import { Logger } from '../../../../cli/Logger.js'; +import GlobalOptions from '../../../../GlobalOptions.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { powerPlatform } from '../../../../utils/powerPlatform.js'; +import PowerPlatformCommand from '../../../base/PowerPlatformCommand.js'; +import commands from '../../commands.js'; + +interface Options extends GlobalOptions { + environmentName: string; + asAdmin?: boolean; +} + +interface CommandArgs { + options: Options; +} + +class PpPipelineListCommand extends PowerPlatformCommand { + + constructor() { + super(); + this.#initTelemetry(); + this.#initOptions(); + } + + public get name(): string { + return commands.PIPELINE_LIST; + } + + public get description(): string { + return 'Lists Microsoft Power Platform pipelines in the specified Power Platform environment.'; + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + asAdmin: !!args.options.asAdmin + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { + option: '-e, --environmentName ' + }, + { + option: '--asAdmin' + } + ); + } + + public defaultProperties(): string[] | undefined { + return ['name', 'deploymentpipelineid', '_ownerid_value', 'statuscode']; + } + + public async commandAction(logger: Logger, args: any): Promise { + + try { + const dynamicsApiUrl = await powerPlatform.getDynamicsInstanceApiUrl(args.options.environmentName, args.options.asAdmin); + + const pipelines = await this.listPipelines(dynamicsApiUrl); + + await logger.log(pipelines); + } + catch (ex: any) { + this.handleRejectedODataJsonPromise(ex); + } + + } + + private async listPipelines(instanceUrl: string): Promise { + + const pipelineListRequestOptions: CliRequestOptions = { + url: `${instanceUrl}/api/data/v9.0/deploymentpipelines`, + headers: { + accept: 'application/json' + }, + responseType: 'json' + }; + + const pipelines = await request.get(pipelineListRequestOptions); + + return pipelines.value; + } + +} + +export default new PpPipelineListCommand(); \ No newline at end of file