-
Notifications
You must be signed in to change notification settings - Fork 330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: ✨ add new command 'm365 pp pipeline list' to list Powe… #6434
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <environment>` | ||
: 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. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A closing ``` is necessary just below the last option and before the |
||
<Global /> | ||
|
||
## 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 | ||
|
||
<Tabs> | ||
<TabItem value="JSON"> | ||
|
||
```json | ||
[ | ||
{ | ||
"name": "Sales Pipeline", | ||
"deploymentpipelineid": "3ef59632-f087-ef11-8a69-7c1e521ba3ec", | ||
"ownerid": "27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8", | ||
"statuscode": 1 | ||
} | ||
] | ||
``` | ||
</TabItem> | ||
|
||
<TabItem value="Text"> | ||
|
||
```text | ||
deploymentpipelineid: 3ef59632-f087-ef11-8a69-7c1e521ba3ec | ||
name : Sales Pipeline | ||
ownerid : 27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8 | ||
statuscode : 1 | ||
``` | ||
</TabItem> | ||
|
||
<TabItem value="CSV"> | ||
|
||
```csv | ||
name,deploymentpipelineid,ownerid,statuscode | ||
Sales Pipeline,3ef59632-f087-ef11-8a69-7c1e521ba3ec,27f5bb85-6b7a-ef11-ac22-000d3a4e3cc8,1 | ||
``` | ||
</TabItem> | ||
|
||
<TabItem value="Markdown"> | ||
|
||
```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 | ||
``` | ||
|
||
</TabItem> | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove superfluous line breaks like this one. |
||
</Tabs> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
}); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this linebreak as well.. and i see another couple of line breaks that can be removed here. |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
DevPio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
options: Options; | ||
} | ||
|
||
class PpPipelineListCommand extends PowerPlatformCommand { | ||
|
||
constructor() { | ||
super(); | ||
this.#initTelemetry(); | ||
this.#initOptions(); | ||
} | ||
|
||
public get name(): string { | ||
DevPio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return commands.PIPELINE_LIST; | ||
} | ||
|
||
public get description(): string { | ||
return 'Lists Microsoft Power Platform pipelines in the specified Power Platform environment.'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the exact same description as is in the docs file. |
||
} | ||
|
||
#initTelemetry(): void { | ||
this.telemetry.push((args: CommandArgs) => { | ||
Object.assign(this.telemetryProperties, { | ||
asAdmin: !!args.options.asAdmin | ||
}); | ||
}); | ||
} | ||
|
||
#initOptions(): void { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I overlooked this the previous review, but you're actually using the older setup of options and validation. We've since moved to zod, which is a tool to help parse options and validate the schema. Would you mind reworking this command so that it aligns with the new way of working on this count? You can find a good example in recyclebinitem-restore.ts. |
||
this.options.unshift( | ||
{ | ||
option: '-e, --environmentName <environmentName>' | ||
}, | ||
{ | ||
option: '--asAdmin' | ||
} | ||
); | ||
} | ||
|
||
public defaultProperties(): string[] | undefined { | ||
return ['name', 'deploymentpipelineid', '_ownerid_value', 'statuscode']; | ||
} | ||
|
||
public async commandAction(logger: Logger, args: any): Promise<void> { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this superfluous line break |
||
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); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this superfluous line break |
||
} | ||
|
||
private async listPipelines(instanceUrl: string): Promise<any> { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this superfluous line break |
||
const pipelineListRequestOptions: CliRequestOptions = { | ||
url: `${instanceUrl}/api/data/v9.0/deploymentpipelines`, | ||
headers: { | ||
accept: 'application/json' | ||
}, | ||
responseType: 'json' | ||
}; | ||
|
||
DevPio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const pipelines = await request.get<any>(pipelineListRequestOptions); | ||
|
||
return pipelines.value; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove this sup... you get the gist of it :-) |
||
} | ||
|
||
export default new PpPipelineListCommand(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're missing the line ```md definition-list above this line. Do compare with other mdx files to see. And if you want you can build the docs and serve them locally to see if the page looks correctly...