diff --git a/src/m365/planner/commands/bucket/bucket-add.spec.ts b/src/m365/planner/commands/bucket/bucket-add.spec.ts index b73b69305c0..8f63639f685 100644 --- a/src/m365/planner/commands/bucket/bucket-add.spec.ts +++ b/src/m365/planner/commands/bucket/bucket-add.spec.ts @@ -28,43 +28,7 @@ describe(commands.BUCKET_ADD, () => { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups", "value": [ { - "id": "0d0402ee-970f-4951-90b5-2f24519d2e40", - "deletedDateTime": null, - "classification": null, - "createdDateTime": "2021-06-08T11:04:45Z", - "creationOptions": [], - "description": "My Planner Group", - "displayName": "My Planner Group", - "expirationDateTime": null, - "groupTypes": [ - "Unified" - ], - "isAssignableToRole": null, - "mail": "MyPlannerGroup@contoso.onmicrosoft.com", - "mailEnabled": true, - "mailNickname": "My Planner Group", - "membershipRule": null, - "membershipRuleProcessingState": null, - "onPremisesDomainName": null, - "onPremisesLastSyncDateTime": null, - "onPremisesNetBiosName": null, - "onPremisesSamAccountName": null, - "onPremisesSecurityIdentifier": null, - "onPremisesSyncEnabled": null, - "preferredDataLocation": null, - "preferredLanguage": null, - "proxyAddresses": [ - "SPO:SPO_e13f6193-fb01-43e8-8e8d-557796b82ebf@SPO_cc6fafe9-dd93-497c-b521-1d971b1471c7", - "SMTP:MyPlannerGroup@contoso.onmicrosoft.com" - ], - "renewedDateTime": "2021-06-08T11:04:45Z", - "resourceBehaviorOptions": [], - "resourceProvisioningOptions": [], - "securityEnabled": false, - "securityIdentifier": "S-1-12-1-218366702-1230083855-573552016-1076796785", - "theme": null, - "visibility": "Private", - "onPremisesProvisioningErrors": [] + "id": "0d0402ee-970f-4951-90b5-2f24519d2e40" } ] }; @@ -74,47 +38,22 @@ describe(commands.BUCKET_ADD, () => { "@odata.count": 2, "value": [ { - "@odata.etag": "W/\"JzEtUGxhbiAgQEBAQEBAQEBAQEBAQEBASCc=\"", - "createdDateTime": "2021-06-08T12:24:57.3312829Z", - "owner": "f3f985d0-a4e0-4891-83f6-08d88bf44e5e", "title": "My Planner Plan", - "id": "iVPMIgdku0uFlou-KLNg6MkAE1O2", - "createdBy": { - "user": { - "displayName": null, - "id": "73829066-5f0a-4745-8f72-12a17bacadea" - }, - "application": { - "displayName": null, - "id": "09abbdfd-ed25-47ee-a2d9-a627aa1c90f3" - } - } + "id": "iVPMIgdku0uFlou-KLNg6MkAE1O2" }, { - "@odata.etag": "W/\"JzEtUGxhbiAgQEBAQEBAQEBAQEBAQEBASCc=\"", - "createdDateTime": "2021-06-08T12:25:09.3751058Z", - "owner": "f3f985d0-a4e0-4891-83f6-08d88bf44e5e", "title": "Sample Plan", - "id": "uO1bj3fdekKuMitpeJqaj8kADBxO", - "createdBy": { - "user": { - "displayName": null, - "id": "73829066-5f0a-4745-8f72-12a17bacadea" - }, - "application": { - "displayName": null, - "id": "09abbdfd-ed25-47ee-a2d9-a627aa1c90f3" - } - } + "id": "uO1bj3fdekKuMitpeJqaj8kADBxO" } ] }; const planResponse = { - value: [{ - id: 'iVPMIgdku0uFlou-KLNg6MkAE1O2', - title: 'My Planner Plan' - }] + value: [ + { + id: 'iVPMIgdku0uFlou-KLNg6MkAE1O2' + } + ] }; let log: string[]; @@ -147,13 +86,13 @@ describe(commands.BUCKET_ADD, () => { throw 'Invalid request'; }); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/RuY-PSpdw02drevnYDTCJpgAEfoI/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/RuY-PSpdw02drevnYDTCJpgAEfoI/plans?$select=id`) { return planResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'&$select=id`) { return groupByDisplayNameResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return plansInOwnerGroup; } throw 'Invalid request'; diff --git a/src/m365/planner/commands/bucket/bucket-add.ts b/src/m365/planner/commands/bucket/bucket-add.ts index 8416b65f521..a7b53c0a706 100644 --- a/src/m365/planner/commands/bucket/bucket-add.ts +++ b/src/m365/planner/commands/bucket/bucket-add.ts @@ -116,7 +116,7 @@ class PlannerBucketAddCommand extends GraphCommand { const requestOptions: CliRequestOptions = { url: `${this.resource}/v1.0/planner/buckets`, headers: { - 'accept': 'application/json;odata.metadata=none' + accept: 'application/json;odata.metadata=none' }, responseType: 'json', data: { @@ -141,12 +141,10 @@ class PlannerBucketAddCommand extends GraphCommand { if (args.options.planTitle) { const groupId: string = await this.getGroupId(args); - const plan = await planner.getPlanByTitle(args.options.planTitle, groupId); - return plan.id!; + return planner.getPlanIdByTitle(args.options.planTitle, groupId); } - const plan = await planner.getPlanByRosterId(args.options.rosterId!); - return plan.id!; + return planner.getPlanIdByRosterId(args.options.rosterId!); } private async getGroupId(args: CommandArgs): Promise { @@ -154,8 +152,7 @@ class PlannerBucketAddCommand extends GraphCommand { return args.options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } } diff --git a/src/m365/planner/commands/bucket/bucket-get.spec.ts b/src/m365/planner/commands/bucket/bucket-get.spec.ts index ed8c7725476..1866f92839b 100644 --- a/src/m365/planner/commands/bucket/bucket-get.spec.ts +++ b/src/m365/planner/commands/bucket/bucket-get.spec.ts @@ -37,12 +37,10 @@ describe(commands.BUCKET_GET, () => { const multipleGroupResponse = { "value": [ { - "id": validOwnerGroupId, - "displayName": validOwnerGroupName + "id": validOwnerGroupId }, { - "id": validOwnerGroupId, - "displayName": validOwnerGroupName + "id": validOwnerGroupId } ] }; @@ -59,7 +57,6 @@ describe(commands.BUCKET_GET, () => { const singleBucketByNameResponse = { "value": [ { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": validBucketName, "id": validBucketId } @@ -67,7 +64,6 @@ describe(commands.BUCKET_GET, () => { }; const singleBucketByIdResponse = { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": validBucketName, "id": validBucketId }; @@ -75,12 +71,10 @@ describe(commands.BUCKET_GET, () => { const multipleBucketByNameResponse = { "value": [ { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": validBucketName, "id": validBucketId }, { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": validBucketName, "id": validBucketId } @@ -263,7 +257,7 @@ describe(commands.BUCKET_GET, () => { it('fails validation when no groups found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return { "value": [] }; } @@ -290,7 +284,7 @@ describe(commands.BUCKET_GET, () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return multipleGroupResponse; } @@ -308,7 +302,6 @@ describe(commands.BUCKET_GET, () => { it('fails validation when no buckets found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { return { "value": [] }; } @@ -321,7 +314,7 @@ describe(commands.BUCKET_GET, () => { name: validBucketName, planId: validPlanId } - }), new CommandError(`The specified bucket ${validBucketName} does not exist`)); + }), new CommandError(`The specified bucket '${validBucketName}' does not exist.`)); }); it('fails validation when multiple buckets found', async () => { @@ -352,10 +345,10 @@ describe(commands.BUCKET_GET, () => { it('handles selecting single result when multiple groups with the specified name found and cli is set to prompt', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -399,10 +392,10 @@ describe(commands.BUCKET_GET, () => { it('correctly gets bucket by name', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -427,7 +420,7 @@ describe(commands.BUCKET_GET, () => { it('correctly gets bucket by plan title and owner group ID', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -451,7 +444,7 @@ describe(commands.BUCKET_GET, () => { it('correctly gets bucket by roster id', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans?$select=id`) { return planResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { diff --git a/src/m365/planner/commands/bucket/bucket-get.ts b/src/m365/planner/commands/bucket/bucket-get.ts index 1da2c0f9c83..779241411f1 100644 --- a/src/m365/planner/commands/bucket/bucket-get.ts +++ b/src/m365/planner/commands/bucket/bucket-get.ts @@ -7,8 +7,6 @@ import { planner } from '../../../../utils/planner.js'; import { validation } from '../../../../utils/validation.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; -import { cli } from '../../../../cli/cli.js'; -import { formatting } from '../../../../utils/formatting.js'; interface CommandArgs { options: Options; @@ -137,8 +135,7 @@ class PlannerBucketGetCommand extends GraphCommand { public async commandAction(logger: Logger, args: CommandArgs): Promise { try { - const bucketId = await this.getBucketId(args); - const bucket = await this.getBucketById(bucketId); + const bucket = await this.getBucket(args); await logger.log(bucket); } catch (err: any) { @@ -146,36 +143,14 @@ class PlannerBucketGetCommand extends GraphCommand { } } - private async getBucketId(args: CommandArgs): Promise { + private async getBucket(args: CommandArgs): Promise { const { id, name } = args.options; if (id) { - return id; + return this.getBucketById(id); } const planId = await this.getPlanId(args); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const buckets = await request.get<{ value: PlannerBucket[] }>(requestOptions); - - const filteredBuckets = buckets.value.filter(b => name!.toLowerCase() === b.name!.toLowerCase()); - - if (!filteredBuckets.length) { - throw `The specified bucket ${name} does not exist`; - } - - if (filteredBuckets.length > 1) { - const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', filteredBuckets); - const result = await cli.handleMultipleResultsFound(`Multiple buckets with name '${name}' found.`, resultAsKeyValuePair); - return result.id!.toString(); - } - - return filteredBuckets[0].id!.toString(); + return planner.getBucketByTitle(name!, planId); } private async getPlanId(args: CommandArgs): Promise { @@ -187,12 +162,10 @@ class PlannerBucketGetCommand extends GraphCommand { if (planTitle) { const groupId: string = await this.getGroupId(args); - const plan = await planner.getPlanByTitle(planTitle, groupId); - return plan.id!; + return planner.getPlanIdByTitle(planTitle, groupId); } - const plan = await planner.getPlanByRosterId(rosterId!); - return plan.id!; + return planner.getPlanIdByRosterId(rosterId!); } private async getBucketById(id: string): Promise { @@ -214,8 +187,7 @@ class PlannerBucketGetCommand extends GraphCommand { return ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(ownerGroupName!); } } diff --git a/src/m365/planner/commands/bucket/bucket-list.spec.ts b/src/m365/planner/commands/bucket/bucket-list.spec.ts index 8eecbb17a84..aef2d08b035 100644 --- a/src/m365/planner/commands/bucket/bucket-list.spec.ts +++ b/src/m365/planner/commands/bucket/bucket-list.spec.ts @@ -39,95 +39,29 @@ describe(commands.BUCKET_LIST, () => { }; const groupByDisplayNameResponse: any = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups", "value": [ { - "id": "0d0402ee-970f-4951-90b5-2f24519d2e40", - "deletedDateTime": null, - "classification": null, - "createdDateTime": "2021-06-08T11:04:45Z", - "creationOptions": [], - "description": "My Planner Group", - "displayName": "My Planner Group", - "expirationDateTime": null, - "groupTypes": [ - "Unified" - ], - "isAssignableToRole": null, - "mail": "MyPlannerGroup@contoso.onmicrosoft.com", - "mailEnabled": true, - "mailNickname": "My Planner Group", - "membershipRule": null, - "membershipRuleProcessingState": null, - "onPremisesDomainName": null, - "onPremisesLastSyncDateTime": null, - "onPremisesNetBiosName": null, - "onPremisesSamAccountName": null, - "onPremisesSecurityIdentifier": null, - "onPremisesSyncEnabled": null, - "preferredDataLocation": null, - "preferredLanguage": null, - "proxyAddresses": [ - "SPO:SPO_e13f6193-fb01-43e8-8e8d-557796b82ebf@SPO_cc6fafe9-dd93-497c-b521-1d971b1471c7", - "SMTP:MyPlannerGroup@contoso.onmicrosoft.com" - ], - "renewedDateTime": "2021-06-08T11:04:45Z", - "resourceBehaviorOptions": [], - "resourceProvisioningOptions": [], - "securityEnabled": false, - "securityIdentifier": "S-1-12-1-218366702-1230083855-573552016-1076796785", - "theme": null, - "visibility": "Private", - "onPremisesProvisioningErrors": [] + "id": "0d0402ee-970f-4951-90b5-2f24519d2e40" } ] }; const plansInOwnerGroup: any = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#planner/plans", - "@odata.count": 2, "value": [ { - "@odata.etag": "W/\"JzEtUGxhbiAgQEBAQEBAQEBAQEBAQEBASCc=\"", - "createdDateTime": "2021-06-08T12:24:57.3312829Z", - "owner": "f3f985d0-a4e0-4891-83f6-08d88bf44e5e", "title": "My Planner Plan", - "id": "iVPMIgdku0uFlou-KLNg6MkAE1O2", - "createdBy": { - "user": { - "displayName": null, - "id": "73829066-5f0a-4745-8f72-12a17bacadea" - }, - "application": { - "displayName": null, - "id": "09abbdfd-ed25-47ee-a2d9-a627aa1c90f3" - } - } + "id": "iVPMIgdku0uFlou-KLNg6MkAE1O2" }, { - "@odata.etag": "W/\"JzEtUGxhbiAgQEBAQEBAQEBAQEBAQEBASCc=\"", - "createdDateTime": "2021-06-08T12:25:09.3751058Z", - "owner": "f3f985d0-a4e0-4891-83f6-08d88bf44e5e", "title": "Sample Plan", - "id": "uO1bj3fdekKuMitpeJqaj8kADBxO", - "createdBy": { - "user": { - "displayName": null, - "id": "73829066-5f0a-4745-8f72-12a17bacadea" - }, - "application": { - "displayName": null, - "id": "09abbdfd-ed25-47ee-a2d9-a627aa1c90f3" - } - } + "id": "uO1bj3fdekKuMitpeJqaj8kADBxO" } ] }; const planResponse = { value: [{ - id: 'iVPMIgdku0uFlou-KLNg6MkAE1O2', - title: 'My Planner' + id: 'iVPMIgdku0uFlou-KLNg6MkAE1O2' }] }; @@ -152,13 +86,13 @@ describe(commands.BUCKET_LIST, () => { beforeEach(() => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/RuY-PSpdw02drevnYDTCJpgAEfoI/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/RuY-PSpdw02drevnYDTCJpgAEfoI/plans?$select=id`) { return planResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'&$select=id`) { return groupByDisplayNameResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return plansInOwnerGroup; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/buckets`) { diff --git a/src/m365/planner/commands/bucket/bucket-list.ts b/src/m365/planner/commands/bucket/bucket-list.ts index f7a0ebcc466..5d5d9b0e46c 100644 --- a/src/m365/planner/commands/bucket/bucket-list.ts +++ b/src/m365/planner/commands/bucket/bucket-list.ts @@ -118,13 +118,11 @@ class PlannerBucketListCommand extends GraphCommand { } if (args.options.planTitle) { - const groupId: string = await this.getGroupId(args); - const plan = await planner.getPlanByTitle(args.options.planTitle, groupId); - return plan.id!; + const groupId = await this.getGroupId(args); + return planner.getPlanIdByTitle(args.options.planTitle, groupId); } - const plan = await planner.getPlanByRosterId(args.options.rosterId!); - return plan.id!; + return planner.getPlanIdByRosterId(args.options.rosterId!); } private async getGroupId(args: CommandArgs): Promise { @@ -132,8 +130,7 @@ class PlannerBucketListCommand extends GraphCommand { return args.options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } } diff --git a/src/m365/planner/commands/bucket/bucket-remove.spec.ts b/src/m365/planner/commands/bucket/bucket-remove.spec.ts index 32ef77bb6e6..c856e4aab35 100644 --- a/src/m365/planner/commands/bucket/bucket-remove.spec.ts +++ b/src/m365/planner/commands/bucket/bucket-remove.spec.ts @@ -272,7 +272,7 @@ describe(commands.BUCKET_REMOVE, () => { it('fails validation when no groups found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return { "value": [] }; } @@ -299,7 +299,7 @@ describe(commands.BUCKET_REMOVE, () => { }); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return multipleGroupResponse; } @@ -331,7 +331,7 @@ describe(commands.BUCKET_REMOVE, () => { planId: validPlanId, force: true } - }), new CommandError(`The specified bucket ${validBucketName} does not exist`)); + }), new CommandError(`The specified bucket '${validBucketName}' does not exist.`)); }); it('fails validation when multiple buckets found', async () => { @@ -362,10 +362,10 @@ describe(commands.BUCKET_REMOVE, () => { it('handles selecting single result when multiple buckets with the specified name found and cli is set to prompt', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -421,10 +421,10 @@ describe(commands.BUCKET_REMOVE, () => { it('correctly deletes bucket by name', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -453,7 +453,7 @@ describe(commands.BUCKET_REMOVE, () => { it('correctly deletes bucket by rosterId', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans?$select=id`) { return planResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -485,7 +485,7 @@ describe(commands.BUCKET_REMOVE, () => { it('correctly deletes bucket by name with group id', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -508,7 +508,8 @@ describe(commands.BUCKET_REMOVE, () => { options: { name: validBucketName, planTitle: validPlanTitle, - ownerGroupId: validOwnerGroupId + ownerGroupId: validOwnerGroupId, + verbose: true } })); }); diff --git a/src/m365/planner/commands/bucket/bucket-remove.ts b/src/m365/planner/commands/bucket/bucket-remove.ts index ca622196c7d..f97b7e1a28a 100644 --- a/src/m365/planner/commands/bucket/bucket-remove.ts +++ b/src/m365/planner/commands/bucket/bucket-remove.ts @@ -8,7 +8,6 @@ import { planner } from '../../../../utils/planner.js'; import { validation } from '../../../../utils/validation.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; -import { formatting } from '../../../../utils/formatting.js'; interface CommandArgs { options: Options; @@ -139,6 +138,10 @@ class PlannerBucketRemoveCommand extends GraphCommand { public async commandAction(logger: Logger, args: CommandArgs): Promise { const removeBucket = async (): Promise => { + if (this.verbose) { + await logger.logToStderr(`Removing bucket...`); + } + try { const bucket = await this.getBucket(args); @@ -175,7 +178,7 @@ class PlannerBucketRemoveCommand extends GraphCommand { const requestOptions: CliRequestOptions = { url: `${this.resource}/v1.0/planner/buckets/${args.options.id}`, headers: { - accept: 'application/json' + accept: 'application/json;odata.metadata=minimal' }, responseType: 'json' }; @@ -184,27 +187,7 @@ class PlannerBucketRemoveCommand extends GraphCommand { } const planId = await this.getPlanId(args); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets`, - headers: { - accept: 'application/json' - }, - responseType: 'json' - }; - - const buckets = await request.get<{ value: PlannerBucket[] }>(requestOptions); - const filteredBuckets = buckets.value.filter(b => args.options.name!.toLowerCase() === b.name!.toLowerCase()); - - if (!filteredBuckets.length) { - throw `The specified bucket ${args.options.name} does not exist`; - } - - if (filteredBuckets.length > 1) { - const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', filteredBuckets); - return await cli.handleMultipleResultsFound(`Multiple buckets with name '${args.options.name}' found.`, resultAsKeyValuePair); - } - - return filteredBuckets[0]; + return planner.getBucketByTitle(args.options.name!, planId, 'minimal'); } private async getPlanId(args: CommandArgs): Promise { @@ -215,13 +198,11 @@ class PlannerBucketRemoveCommand extends GraphCommand { } if (planTitle) { - const groupId: string = await this.getGroupId(args); - const plan = await planner.getPlanByTitle(planTitle, groupId); - return plan.id!; + const groupId = await this.getGroupId(args); + return planner.getPlanIdByTitle(planTitle, groupId); } - const plan = await planner.getPlanByRosterId(rosterId!); - return plan.id!; + return planner.getPlanIdByRosterId(rosterId!); } private async getGroupId(args: CommandArgs): Promise { @@ -231,8 +212,7 @@ class PlannerBucketRemoveCommand extends GraphCommand { return ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(ownerGroupName!); } } diff --git a/src/m365/planner/commands/bucket/bucket-set.spec.ts b/src/m365/planner/commands/bucket/bucket-set.spec.ts index 75a3ce6ee45..b5729eefa46 100644 --- a/src/m365/planner/commands/bucket/bucket-set.spec.ts +++ b/src/m365/planner/commands/bucket/bucket-set.spec.ts @@ -110,13 +110,6 @@ describe(commands.BUCKET_SET, () => { expiresOn: new Date() }; commandInfo = cli.getCommandInfo(command); - sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName: string, defaultValue: any) => { - if (settingName === 'prompt') { - return false; - } - - return defaultValue; - }); }); beforeEach(() => { @@ -133,6 +126,13 @@ describe(commands.BUCKET_SET, () => { } }; (command as any).items = []; + sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName: string, defaultValue: any) => { + if (settingName === 'prompt') { + return false; + } + + return defaultValue; + }); }); afterEach(() => { @@ -256,7 +256,7 @@ describe(commands.BUCKET_SET, () => { it('fails validation when no groups found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return { "value": [] }; } @@ -273,6 +273,7 @@ describe(commands.BUCKET_SET, () => { }); it('fails validation when multiple groups found', async () => { + sinonUtil.restore([cli.getSettingWithDefaultValue]); sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.prompt) { return false; @@ -282,7 +283,7 @@ describe(commands.BUCKET_SET, () => { }); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return multipleGroupResponse; } @@ -299,6 +300,7 @@ describe(commands.BUCKET_SET, () => { }); it('fails validation when no buckets found', async () => { + sinonUtil.restore([cli.getSettingWithDefaultValue]); sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { return { "value": [] }; @@ -312,10 +314,11 @@ describe(commands.BUCKET_SET, () => { name: validBucketName, planId: validPlanId } - }), new CommandError(`The specified bucket ${validBucketName} does not exist`)); + }), new CommandError(`The specified bucket '${validBucketName}' does not exist.`)); }); it('fails validation when multiple buckets found', async () => { + sinonUtil.restore([cli.getSettingWithDefaultValue]); sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.prompt) { return false; @@ -342,10 +345,10 @@ describe(commands.BUCKET_SET, () => { it('handles selecting single result when multiple buckets with the specified name found and cli is set to prompt', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -402,10 +405,10 @@ describe(commands.BUCKET_SET, () => { it('correctly updates bucket by name', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -435,7 +438,7 @@ describe(commands.BUCKET_SET, () => { it('correctly updates bucket by name with rosterId', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans?$select=id`) { return planResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -467,7 +470,7 @@ describe(commands.BUCKET_SET, () => { it('correctly updates bucket by name with group ID', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets`) { @@ -490,7 +493,8 @@ describe(commands.BUCKET_SET, () => { planTitle: validPlanTitle, ownerGroupId: validOwnerGroupId, newName: 'New bucket name', - orderHint: validOrderHint + orderHint: validOrderHint, + verbose: true } })); }); diff --git a/src/m365/planner/commands/bucket/bucket-set.ts b/src/m365/planner/commands/bucket/bucket-set.ts index 169f1b99821..aae8e5e5100 100644 --- a/src/m365/planner/commands/bucket/bucket-set.ts +++ b/src/m365/planner/commands/bucket/bucket-set.ts @@ -7,8 +7,6 @@ import { planner } from '../../../../utils/planner.js'; import { validation } from '../../../../utils/validation.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; -import { cli } from '../../../../cli/cli.js'; -import { formatting } from '../../../../utils/formatting.js'; interface CommandArgs { options: Options; @@ -142,6 +140,10 @@ class PlannerBucketSetCommand extends GraphCommand { } public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (this.verbose) { + await logger.logToStderr(`Updating bucket...`); + } + try { const bucket = await this.getBucket(args); @@ -152,17 +154,12 @@ class PlannerBucketSetCommand extends GraphCommand { 'if-match': (bucket as any)['@odata.etag'] }, responseType: 'json', - data: {} + data: { + name: args.options.newName, + orderHint: args.options.orderHint + } }; - const { newName, orderHint } = args.options; - if (newName) { - requestOptions.data.name = newName; - } - if (orderHint) { - requestOptions.data.orderHint = orderHint; - } - await request.patch(requestOptions); } catch (err: any) { @@ -175,7 +172,7 @@ class PlannerBucketSetCommand extends GraphCommand { const requestOptions: CliRequestOptions = { url: `${this.resource}/v1.0/planner/buckets/${args.options.id}`, headers: { - accept: 'application/json' + accept: 'application/json;odata.metadata=minimal' }, responseType: 'json' }; @@ -184,27 +181,7 @@ class PlannerBucketSetCommand extends GraphCommand { } const planId = await this.getPlanId(args); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets`, - headers: { - accept: 'application/json' - }, - responseType: 'json' - }; - - const buckets = await request.get<{ value: PlannerBucket[] }>(requestOptions); - const filteredBuckets = buckets.value.filter(b => args.options.name!.toLowerCase() === b.name!.toLowerCase()); - - if (!filteredBuckets.length) { - throw `The specified bucket ${args.options.name} does not exist`; - } - - if (filteredBuckets.length > 1) { - const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', filteredBuckets); - return await cli.handleMultipleResultsFound(`Multiple buckets with name '${args.options.name}' found.`, resultAsKeyValuePair); - } - - return filteredBuckets[0]; + return planner.getBucketByTitle(args.options.name!, planId, 'minimal'); } private async getPlanId(args: CommandArgs): Promise { @@ -215,13 +192,11 @@ class PlannerBucketSetCommand extends GraphCommand { } if (planTitle) { - const groupId: string = await this.getGroupId(args); - const plan = await planner.getPlanByTitle(planTitle, groupId); - return plan.id!; + const groupId = await this.getGroupId(args); + return planner.getPlanIdByTitle(planTitle, groupId); } - const plan = await planner.getPlanByRosterId(rosterId!); - return plan.id!; + return planner.getPlanIdByRosterId(rosterId!); } private async getGroupId(args: CommandArgs): Promise { @@ -231,8 +206,7 @@ class PlannerBucketSetCommand extends GraphCommand { return ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(ownerGroupName!); } } diff --git a/src/m365/planner/commands/plan/plan-add.ts b/src/m365/planner/commands/plan/plan-add.ts index 96f4249911d..7c64abd5fab 100644 --- a/src/m365/planner/commands/plan/plan-add.ts +++ b/src/m365/planner/commands/plan/plan-add.ts @@ -124,12 +124,14 @@ class PlannerPlanAddCommand extends GraphCommand { }; if (args.options.rosterId) { data.container = { - "url": `https://graph.microsoft.com/v1.0/planner/rosters/${args.options.rosterId}` + url: `https://graph.microsoft.com/v1.0/planner/rosters/${args.options.rosterId}` }; } else { const groupId = await this.getGroupId(args); - data.owner = groupId; + data.container = { + url: `https://graph.microsoft.com/v1.0/groups/${groupId}` + }; } const requestOptions: CliRequestOptions = { @@ -146,7 +148,7 @@ class PlannerPlanAddCommand extends GraphCommand { await logger.log(result); } catch (err: any) { - if (err.error && err.error.error.message === "You do not have the required permissions to access this item, or the item may not exist.") { + if (args.options.rosterId && err.error?.error.message === "You do not have the required permissions to access this item, or the item may not exist.") { throw new CommandError("You can only add 1 plan to a Roster"); } this.handleRejectedODataJsonPromise(err); @@ -154,7 +156,7 @@ class PlannerPlanAddCommand extends GraphCommand { } private async updatePlanDetails(options: Options, newPlan: PlannerPlan): Promise { - const planId = newPlan.id as string; + const planId = newPlan.id!; if (!options.shareWithUserIds && !options.shareWithUserNames) { return newPlan; @@ -245,8 +247,7 @@ class PlannerPlanAddCommand extends GraphCommand { return args.options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } } diff --git a/src/m365/planner/commands/plan/plan-get.ts b/src/m365/planner/commands/plan/plan-get.ts index e7522b6c0bc..7020288c461 100644 --- a/src/m365/planner/commands/plan/plan-get.ts +++ b/src/m365/planner/commands/plan/plan-get.ts @@ -107,30 +107,20 @@ class PlannerPlanGetCommand extends GraphCommand { public async commandAction(logger: Logger, args: CommandArgs): Promise { try { + let plan: PlannerPlan; if (args.options.id) { - const plan = await planner.getPlanById(args.options.id); - const result = await this.getPlanDetails(plan); - await logger.log(result); + plan = await planner.getPlanById(args.options.id); + } + else if (args.options.rosterId) { + plan = await planner.getPlanByRosterId(args.options.rosterId); } else { - let plan: PlannerPlan = {}; - if (args.options.rosterId) { - plan = await planner.getPlanByRosterId(args.options.rosterId); - } - else { - let groupId = undefined; - if (args.options.ownerGroupId || args.options.ownerGroupName) { - groupId = await this.getGroupId(args); - } - plan = await planner.getPlanByTitle(args.options.title!, groupId); - } - - const result = await this.getPlanDetails(plan); - - if (result) { - await logger.log(result); - } + const groupId = await this.getGroupId(args); + plan = await planner.getPlanByTitle(args.options.title!, groupId); } + + const result = await this.getPlanDetails(plan); + await logger.log(result); } catch (err: any) { this.handleRejectedODataJsonPromise(err); @@ -141,8 +131,8 @@ class PlannerPlanGetCommand extends GraphCommand { const requestOptionsTaskDetails: CliRequestOptions = { url: `${this.resource}/v1.0/planner/plans/${plan.id}/details`, headers: { - 'accept': 'application/json;odata.metadata=none', - 'Prefer': 'return=representation' + accept: 'application/json;odata.metadata=none', + Prefer: 'return=representation' }, responseType: 'json' }; @@ -156,8 +146,7 @@ class PlannerPlanGetCommand extends GraphCommand { return args.options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } } diff --git a/src/m365/planner/commands/plan/plan-list.spec.ts b/src/m365/planner/commands/plan/plan-list.spec.ts index 6fa13f05519..d6aaa170769 100644 --- a/src/m365/planner/commands/plan/plan-list.spec.ts +++ b/src/m365/planner/commands/plan/plan-list.spec.ts @@ -257,7 +257,7 @@ describe(commands.PLAN_LIST, () => { it('correctly list planner plans with given ownerGroupName', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${ownerGroupName}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${ownerGroupName}'&$select=id`) { return groupsResponse; } diff --git a/src/m365/planner/commands/plan/plan-list.ts b/src/m365/planner/commands/plan/plan-list.ts index 66106fd4630..f041f790161 100644 --- a/src/m365/planner/commands/plan/plan-list.ts +++ b/src/m365/planner/commands/plan/plan-list.ts @@ -107,8 +107,7 @@ class PlannerPlanListCommand extends GraphCommand { return args.options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } } diff --git a/src/m365/planner/commands/plan/plan-remove.spec.ts b/src/m365/planner/commands/plan/plan-remove.spec.ts index ae3944aecb0..5e4056d1e22 100644 --- a/src/m365/planner/commands/plan/plan-remove.spec.ts +++ b/src/m365/planner/commands/plan/plan-remove.spec.ts @@ -214,7 +214,7 @@ describe(commands.PLAN_REMOVE, () => { it('Correctly deletes plan by title', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupsResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { @@ -262,7 +262,8 @@ describe(commands.PLAN_REMOVE, () => { await command.action(logger, { options: { title: validPlanTitle, - ownerGroupId: validOwnerGroupId + ownerGroupId: validOwnerGroupId, + verbose: true } }); }); diff --git a/src/m365/planner/commands/plan/plan-remove.ts b/src/m365/planner/commands/plan/plan-remove.ts index ade74832ea2..fb212304948 100644 --- a/src/m365/planner/commands/plan/plan-remove.ts +++ b/src/m365/planner/commands/plan/plan-remove.ts @@ -110,6 +110,10 @@ class PlannerPlanRemoveCommand extends GraphCommand { try { const plan = await this.getPlan(args); + if (this.verbose) { + await logger.logToStderr(`Removing plan '${plan.title}' ...`); + } + const requestOptions: CliRequestOptions = { url: `${this.resource}/v1.0/planner/plans/${plan.id}`, headers: { @@ -146,7 +150,7 @@ class PlannerPlanRemoveCommand extends GraphCommand { } const groupId = await this.getGroupId(args); - return await planner.getPlanByTitle(title!, groupId, 'minimal'); + return planner.getPlanByTitle(title!, groupId, 'minimal'); } private async getGroupId(args: CommandArgs): Promise { @@ -156,8 +160,7 @@ class PlannerPlanRemoveCommand extends GraphCommand { return ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(ownerGroupName!); } } diff --git a/src/m365/planner/commands/plan/plan-set.spec.ts b/src/m365/planner/commands/plan/plan-set.spec.ts index f3f8342ba71..802adb897c5 100644 --- a/src/m365/planner/commands/plan/plan-set.spec.ts +++ b/src/m365/planner/commands/plan/plan-set.spec.ts @@ -61,8 +61,7 @@ describe(commands.PLAN_SET, () => { const singleGroupsResponse = { value: [ { - id: ownerGroupId, - displayName: ownerGroupName + id: ownerGroupId } ] }; @@ -72,8 +71,7 @@ describe(commands.PLAN_SET, () => { { '@odata.etag': 'abcdef', id: id, - title: title, - owner: ownerGroupId + title: title } ] }; @@ -337,11 +335,11 @@ describe(commands.PLAN_SET, () => { it('correctly updates planner plan shareWithUserNames with given title and ownerGroupName', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(ownerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(ownerGroupName)}'&$select=id`) { return singleGroupsResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans?$select=id,title`) { return singlePlansResponse; } @@ -385,11 +383,11 @@ describe(commands.PLAN_SET, () => { it('correctly updates planner plan shareWithUserIds with given title and ownerGroupId', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(ownerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(ownerGroupName)}'&$select=id`) { return singleGroupsResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans?$select=id,title`) { return singlePlansResponse; } @@ -425,7 +423,7 @@ describe(commands.PLAN_SET, () => { it('correctly updates planner plan shareWithUserIds with given title and rosterId', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${rosterId}/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${rosterId}/plans?$select=id`) { return singlePlansResponse; } @@ -495,11 +493,11 @@ describe(commands.PLAN_SET, () => { it('fails when an invalid user is specified', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(ownerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(ownerGroupName)}'&$select=id`) { return singleGroupsResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans?$select=id,title`) { return singlePlansResponse; } diff --git a/src/m365/planner/commands/plan/plan-set.ts b/src/m365/planner/commands/plan/plan-set.ts index 034f4ef5f59..63f2e3034c6 100644 --- a/src/m365/planner/commands/plan/plan-set.ts +++ b/src/m365/planner/commands/plan/plan-set.ts @@ -190,8 +190,8 @@ class PlannerPlanSetCommand extends GraphCommand { return ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(ownerGroupName!); - return group.id!; + const id = await entraGroup.getGroupIdByDisplayName(ownerGroupName!); + return id; } private async getPlanId(args: CommandArgs): Promise { @@ -201,16 +201,14 @@ class PlannerPlanSetCommand extends GraphCommand { return id; } - let groupId: string = ''; - if (args.options.rosterId) { - const plan: PlannerPlan = await planner.getPlanByRosterId(args.options.rosterId); - return plan.id!; + const id = await planner.getPlanIdByRosterId(args.options.rosterId); + return id; } else { - groupId = await this.getGroupId(args); - const plan: PlannerPlan = await planner.getPlanByTitle(title!, groupId); - return plan.id!; + const groupId = await this.getGroupId(args); + const id = await planner.getPlanIdByTitle(title!, groupId); + return id; } } diff --git a/src/m365/planner/commands/roster/roster-add.ts b/src/m365/planner/commands/roster/roster-add.ts index 324e7b9e473..f6a7bb933ad 100644 --- a/src/m365/planner/commands/roster/roster-add.ts +++ b/src/m365/planner/commands/roster/roster-add.ts @@ -18,14 +18,12 @@ class PlannerRosterAddCommand extends GraphCommand { } try { - const requestBody: any = {}; - const requestOptions: CliRequestOptions = { url: `${this.resource}/beta/planner/rosters`, headers: { accept: 'application/json;odata.metadata=none' }, - data: requestBody, + data: {}, responseType: 'json' }; diff --git a/src/m365/planner/commands/task/task-add.spec.ts b/src/m365/planner/commands/task/task-add.spec.ts index 640edf5e61c..8339d78b36e 100644 --- a/src/m365/planner/commands/task/task-add.spec.ts +++ b/src/m365/planner/commands/task/task-add.spec.ts @@ -119,46 +119,9 @@ describe(commands.TASK_ADD, () => { }; const groupByDisplayNameResponse: any = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups", "value": [ { - "id": "0d0402ee-970f-4951-90b5-2f24519d2e40", - "deletedDateTime": null, - "classification": null, - "createdDateTime": "2021-06-08T11:04:45Z", - "creationOptions": [], - "description": "My Planner Group", - "displayName": "My Planner Group", - "expirationDateTime": null, - "groupTypes": [ - "Unified" - ], - "isAssignableToRole": null, - "mail": "MyPlannerGroup@contoso.onmicrosoft.com", - "mailEnabled": true, - "mailNickname": "My Planner Group", - "membershipRule": null, - "membershipRuleProcessingState": null, - "onPremisesDomainName": null, - "onPremisesLastSyncDateTime": null, - "onPremisesNetBiosName": null, - "onPremisesSamAccountName": null, - "onPremisesSecurityIdentifier": null, - "onPremisesSyncEnabled": null, - "preferredDataLocation": null, - "preferredLanguage": null, - "proxyAddresses": [ - "SPO:SPO_e13f6193-fb01-43e8-8e8d-557796b82ebf@SPO_cc6fafe9-dd93-497c-b521-1d971b1471c7", - "SMTP:MyPlannerGroup@contoso.onmicrosoft.com" - ], - "renewedDateTime": "2021-06-08T11:04:45Z", - "resourceBehaviorOptions": [], - "resourceProvisioningOptions": [], - "securityEnabled": false, - "securityIdentifier": "S-1-12-1-218366702-1230083855-573552016-1076796785", - "theme": null, - "visibility": "Private", - "onPremisesProvisioningErrors": [] + "id": "0d0402ee-970f-4951-90b5-2f24519d2e40" } ] }; @@ -565,11 +528,10 @@ describe(commands.TASK_ADD, () => { it('correctly adds planner bucket with title, bucketId, planTitle, and ownerGroupName', async () => { sinonUtil.restore(request.get); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return { value: [ { - "owner": "0d0402ee-970f-4951-90b5-2f24519d2e40", "title": "My Planner Plan", "id": "8QZEH7b3wkS_bGQobscsM5gADCBb" } @@ -577,7 +539,7 @@ describe(commands.TASK_ADD, () => { }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'&$select=id`) { return groupByDisplayNameResponse; } @@ -598,20 +560,10 @@ describe(commands.TASK_ADD, () => { it('correctly adds planner task with title, bucketId, planTitle, and ownerGroupId', async () => { sinonUtil.restore(request.get); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return { value: [ { - "createdBy": { - "application": { - "id": "95e27074-6c4a-447a-aa24-9d718a0b86fa" - }, - "user": { - "id": "ebf3b108-5234-4e22-b93d-656d7dae5874" - } - }, - "createdDateTime": "2015-03-30T18:36:49.2407981Z", - "owner": "ebf3b108-5234-4e22-b93d-656d7dae5874", "title": "My Planner Plan", "id": "8QZEH7b3wkS_bGQobscsM5gADCBb" } @@ -636,13 +588,11 @@ describe(commands.TASK_ADD, () => { it('correctly adds planner task with title, planId, and bucketName', async () => { sinonUtil.restore(request.get); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/8QZEH7b3wkS_bGQobscsM5gADCBb/buckets`) { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/8QZEH7b3wkS_bGQobscsM5gADCBb/buckets?$select=id,name`) { return { value: [ { "name": "My Planner Bucket", - "planId": "2txjA-BMZEq-bKi6Wfj5aGQAB1OJ", - "orderHint": "85752723360752+", "id": "IK8tuFTwQEa5vTonM7ZMRZgAKdno" } ] @@ -796,20 +746,18 @@ describe(commands.TASK_ADD, () => { it('correctly adds planner task with title, bucketId, and rosterId', async () => { sinonUtil.restore(request.get); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans?$select=id`) { return { "value": [{ - "id": '8QZEH7b3wkS_bGQobscsM5gADCBb', - "title": 'My Planner Plan' + "id": '8QZEH7b3wkS_bGQobscsM5gADCBb' }] }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return { value: [ { - "owner": "0d0402ee-970f-4951-90b5-2f24519d2e40", "title": "My Planner Plan", "id": "8QZEH7b3wkS_bGQobscsM5gADCBb" } @@ -901,7 +849,7 @@ describe(commands.TASK_ADD, () => { it('fails when no bucket is found', async () => { sinonUtil.restore(request.get); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/8QZEH7b3wkS_bGQobscsM5gADCBb/buckets`) { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/8QZEH7b3wkS_bGQobscsM5gADCBb/buckets?$select=id,name`) { return { value: [] }; @@ -916,7 +864,7 @@ describe(commands.TASK_ADD, () => { bucketName: 'My Planner Bucket' }; - await assert.rejects(command.action(logger, { options: options } as any), new CommandError('The specified bucket does not exist')); + await assert.rejects(command.action(logger, { options: options } as any), new CommandError(`The specified bucket 'My Planner Bucket' does not exist.`)); }); it('fails when an invalid user is specified', async () => { diff --git a/src/m365/planner/commands/task/task-add.ts b/src/m365/planner/commands/task/task-add.ts index 334f57d72cc..337daad90f3 100644 --- a/src/m365/planner/commands/task/task-add.ts +++ b/src/m365/planner/commands/task/task-add.ts @@ -1,4 +1,4 @@ -import { PlannerBucket, PlannerPlan, PlannerTask, PlannerTaskDetails, User } from '@microsoft/microsoft-graph-types'; +import { PlannerTask, PlannerTaskDetails, User } from '@microsoft/microsoft-graph-types'; import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; @@ -312,23 +312,7 @@ class PlannerTaskAddCommand extends GraphCommand { return args.options.bucketId; } - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const response = await request.get<{ value: PlannerBucket[] }>(requestOptions); - - const bucket: PlannerBucket | undefined = response.value.find(val => val.name === args.options.bucketName); - - if (!bucket) { - throw `The specified bucket does not exist`; - } - - return bucket.id!; + return planner.getBucketIdByTitle(args.options.bucketName!, planId); } private async getPlanId(args: CommandArgs): Promise { @@ -337,13 +321,11 @@ class PlannerTaskAddCommand extends GraphCommand { } if (args.options.rosterId) { - const plan: PlannerPlan = await planner.getPlanByRosterId(args.options.rosterId); - return plan.id!; + return planner.getPlanIdByRosterId(args.options.rosterId); } else { - const groupId: string = await this.getGroupId(args); - const plan: PlannerPlan = await planner.getPlanByTitle(args.options.planTitle!, groupId); - return plan.id!; + const groupId = await this.getGroupId(args); + return planner.getPlanIdByTitle(args.options.planTitle!, groupId); } } @@ -352,8 +334,7 @@ class PlannerTaskAddCommand extends GraphCommand { return args.options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } private async getUserIds(options: Options): Promise { diff --git a/src/m365/planner/commands/task/task-checklistitem-add.ts b/src/m365/planner/commands/task/task-checklistitem-add.ts index 46159b28b1d..b277227ae81 100644 --- a/src/m365/planner/commands/task/task-checklistitem-add.ts +++ b/src/m365/planner/commands/task/task-checklistitem-add.ts @@ -42,7 +42,7 @@ class PlannerTaskChecklistItemAddCommand extends GraphCommand { #initTelemetry(): void { this.telemetry.push((args: CommandArgs) => { Object.assign(this.telemetryProperties, { - isChecked: args.options.isChecked || false + isChecked: !!args.options.isChecked }); }); } diff --git a/src/m365/planner/commands/task/task-checklistitem-remove.ts b/src/m365/planner/commands/task/task-checklistitem-remove.ts index 30d83bcd5e5..e663e34c35d 100644 --- a/src/m365/planner/commands/task/task-checklistitem-remove.ts +++ b/src/m365/planner/commands/task/task-checklistitem-remove.ts @@ -37,7 +37,7 @@ class PlannerTaskChecklistItemRemoveCommand extends GraphCommand { #initTelemetry(): void { this.telemetry.push((args: CommandArgs) => { Object.assign(this.telemetryProperties, { - force: (!(!args.options.force)).toString() + force: !!args.options.force }); }); } diff --git a/src/m365/planner/commands/task/task-get.spec.ts b/src/m365/planner/commands/task/task-get.spec.ts index 1da5935fcc2..a06214e6044 100644 --- a/src/m365/planner/commands/task/task-get.spec.ts +++ b/src/m365/planner/commands/task/task-get.spec.ts @@ -34,8 +34,7 @@ describe(commands.TASK_GET, () => { const singleGroupResponse = { "value": [ { - "id": validOwnerGroupId, - "displayName": validOwnerGroupName + "id": validOwnerGroupId } ] }; @@ -368,7 +367,7 @@ describe(commands.TASK_GET, () => { it('fails validation when no groups found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return { "value": [] }; } @@ -395,7 +394,7 @@ describe(commands.TASK_GET, () => { }); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return multipleGroupResponse; } @@ -415,7 +414,7 @@ describe(commands.TASK_GET, () => { it('fails validation when no buckets found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { - return { "value": [{ "id": "" }] }; + return { "value": [] }; } throw 'Invalid Request'; @@ -427,7 +426,7 @@ describe(commands.TASK_GET, () => { bucketName: validBucketName, planId: validPlanId } - }), new CommandError(`The specified bucket ${validBucketName} does not exist`)); + }), new CommandError(`The specified bucket '${validBucketName}' does not exist.`)); }); it('fails validation when multiple buckets found', async () => { @@ -458,10 +457,10 @@ describe(commands.TASK_GET, () => { it('handles selecting single result when multiple buckets with the specified name found and cli is set to prompt', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -537,10 +536,10 @@ describe(commands.TASK_GET, () => { it('handles selecting single result when multiple tasks with the specified name found and cli is set to prompt', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -574,10 +573,10 @@ describe(commands.TASK_GET, () => { it('correctly gets task by name', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -609,7 +608,7 @@ describe(commands.TASK_GET, () => { it('correctly gets task by name with group ID', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -661,7 +660,7 @@ describe(commands.TASK_GET, () => { it('correctly gets task by rosterId', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans?$select=id`) { return { "value": [planResponse] }; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { diff --git a/src/m365/planner/commands/task/task-get.ts b/src/m365/planner/commands/task/task-get.ts index 6924f901cab..f4a19e0fd12 100644 --- a/src/m365/planner/commands/task/task-get.ts +++ b/src/m365/planner/commands/task/task-get.ts @@ -1,4 +1,4 @@ -import { PlannerBucket, PlannerPlan, PlannerTask, PlannerTaskDetails } from '@microsoft/microsoft-graph-types'; +import { PlannerTask, PlannerTaskDetails } from '@microsoft/microsoft-graph-types'; import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; @@ -192,7 +192,7 @@ class PlannerTaskGetCommand extends GraphCommand { if (tasks.length > 1) { const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', tasks); - const result = (await cli.handleMultipleResultsFound(`Multiple tasks with title '${options.title}' found.`, resultAsKeyValuePair)); + const result = await cli.handleMultipleResultsFound(`Multiple tasks with title '${options.title}' found.`, resultAsKeyValuePair); return result.id!; } @@ -205,29 +205,7 @@ class PlannerTaskGetCommand extends GraphCommand { } const planId = await this.getPlanId(options); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets?$select=id,name`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const response = await request.get<{ value: PlannerBucket[] }>(requestOptions); - const bucketName = options.bucketName as string; - const buckets: PlannerBucket[] | undefined = response.value.filter(val => val.name?.toLocaleLowerCase() === bucketName.toLocaleLowerCase()); - - if (!buckets.length) { - throw `The specified bucket ${options.bucketName} does not exist`; - } - - if (buckets.length > 1) { - const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', buckets); - const result = await cli.handleMultipleResultsFound(`Multiple buckets with name '${options.bucketName}' found.`, resultAsKeyValuePair); - return result.id!; - } - - return buckets[0].id as string; + return planner.getBucketIdByTitle(options.bucketName!, planId); } private async getPlanId(options: Options): Promise { @@ -236,13 +214,11 @@ class PlannerTaskGetCommand extends GraphCommand { } if (options.rosterId) { - const plan: PlannerPlan = await planner.getPlanByRosterId(options.rosterId); - return plan.id!; + return planner.getPlanIdByRosterId(options.rosterId); } else { const groupId: string = await this.getGroupId(options); - const plan: PlannerPlan = await planner.getPlanByTitle(options.planTitle!, groupId); - return plan.id!; + return planner.getPlanIdByTitle(options.planTitle!, groupId); } } @@ -251,8 +227,7 @@ class PlannerTaskGetCommand extends GraphCommand { return options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(options.ownerGroupName!); } } diff --git a/src/m365/planner/commands/task/task-list.spec.ts b/src/m365/planner/commands/task/task-list.spec.ts index b73d9811511..8c18b21d66d 100644 --- a/src/m365/planner/commands/task/task-list.spec.ts +++ b/src/m365/planner/commands/task/task-list.spec.ts @@ -141,37 +141,25 @@ describe(commands.TASK_LIST, () => { ]; const taskListResponse: any = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.plannerTask)", - "@odata.count": 2, "value": taskListResponseValue }; const taskListBetaResponse: any = { - "@odata.context": "https://graph.microsoft.com/beta/$metadata#Collection(microsoft.graph.plannerTask)", - "@odata.count": 2, "value": taskListResponseBetaValue }; const bucketListResponseValue = [ { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": "Planner Bucket A", - "planId": "iVPMIgdku0uFlou-KLNg6MkAE1O2", - "orderHint": "8585768731950308408", "id": "FtzysDykv0-9s9toWiZhdskAD67z" }, { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": "Planner Bucket 2", - "planId": "iVPMIgdku0uFlou-KLNg6MkAE1O2", - "orderHint": "8585784565[8", "id": "ZpcnnvS9ZES2pb91RPxQx8kAMLo5" } ]; const bucketListResponse: any = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(microsoft.graph.plannerBucket)", - "@odata.count": 2, "value": bucketListResponseValue }; @@ -179,43 +167,7 @@ describe(commands.TASK_LIST, () => { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#groups", "value": [ { - "id": "0d0402ee-970f-4951-90b5-2f24519d2e40", - "deletedDateTime": null, - "classification": null, - "createdDateTime": "2021-06-08T11:04:45Z", - "creationOptions": [], - "description": "My Planner Group", - "displayName": "My Planner Group", - "expirationDateTime": null, - "groupTypes": [ - "Unified" - ], - "isAssignableToRole": null, - "mail": "MyPlannerGroup@contoso.onmicrosoft.com", - "mailEnabled": true, - "mailNickname": "My Planner Group", - "membershipRule": null, - "membershipRuleProcessingState": null, - "onPremisesDomainName": null, - "onPremisesLastSyncDateTime": null, - "onPremisesNetBiosName": null, - "onPremisesSamAccountName": null, - "onPremisesSecurityIdentifier": null, - "onPremisesSyncEnabled": null, - "preferredDataLocation": null, - "preferredLanguage": null, - "proxyAddresses": [ - "SPO:SPO_e13f6193-fb01-43e8-8e8d-557796b82ebf@SPO_cc6fafe9-dd93-497c-b521-1d971b1471c7", - "SMTP:MyPlannerGroup@contoso.onmicrosoft.com" - ], - "renewedDateTime": "2021-06-08T11:04:45Z", - "resourceBehaviorOptions": [], - "resourceProvisioningOptions": [], - "securityEnabled": false, - "securityIdentifier": "S-1-12-1-218366702-1230083855-573552016-1076796785", - "theme": null, - "visibility": "Private", - "onPremisesProvisioningErrors": [] + "id": "0d0402ee-970f-4951-90b5-2f24519d2e40" } ] }; @@ -228,42 +180,14 @@ describe(commands.TASK_LIST, () => { }; const plansInOwnerGroup: any = { - "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#planner/plans", - "@odata.count": 2, "value": [ { - "@odata.etag": "W/\"JzEtUGxhbiAgQEBAQEBAQEBAQEBAQEBASCc=\"", - "createdDateTime": "2021-06-08T12:24:57.3312829Z", - "owner": "f3f985d0-a4e0-4891-83f6-08d88bf44e5e", "title": "My Planner Plan", - "id": "iVPMIgdku0uFlou-KLNg6MkAE1O2", - "createdBy": { - "user": { - "displayName": null, - "id": "73829066-5f0a-4745-8f72-12a17bacadea" - }, - "application": { - "displayName": null, - "id": "09abbdfd-ed25-47ee-a2d9-a627aa1c90f3" - } - } + "id": "iVPMIgdku0uFlou-KLNg6MkAE1O2" }, { - "@odata.etag": "W/\"JzEtUGxhbiAgQEBAQEBAQEBAQEBAQEBASCc=\"", - "createdDateTime": "2021-06-08T12:25:09.3751058Z", - "owner": "f3f985d0-a4e0-4891-83f6-08d88bf44e5e", "title": "Sample Plan", - "id": "uO1bj3fdekKuMitpeJqaj8kADBxO", - "createdBy": { - "user": { - "displayName": null, - "id": "73829066-5f0a-4745-8f72-12a17bacadea" - }, - "application": { - "displayName": null, - "id": "09abbdfd-ed25-47ee-a2d9-a627aa1c90f3" - } - } + "id": "uO1bj3fdekKuMitpeJqaj8kADBxO" } ] }; @@ -295,16 +219,16 @@ describe(commands.TASK_LIST, () => { beforeEach(() => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'&$select=id`) { return groupByDisplayNameResponse; } - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans?$select=id`) { return planResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return plansInOwnerGroup; } - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/buckets`) { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/buckets?$select=id,name`) { return bucketListResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/tasks`) { @@ -619,13 +543,13 @@ describe(commands.TASK_LIST, () => { it('fails validation when bucketName not found', async () => { sinonUtil.restore(request.get); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'&$select=id`) { return groupByDisplayNameResponse; } - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans?$select=id`) { return planResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return plansInOwnerGroup; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/tasks`) { @@ -634,7 +558,7 @@ describe(commands.TASK_LIST, () => { if (opts.url === `https://graph.microsoft.com/v1.0/planner/buckets/FtzysDykv0-9s9toWiZhdskAD67z/tasks`) { return taskListResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/me/planner/tasks`) { + if (opts.url === `https://graph.microsoft.com/v1.0/me/planner/tasks?$select=id,title`) { return taskListResponse; } if (opts.url === `https://graph.microsoft.com/beta/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/tasks`) { @@ -643,10 +567,10 @@ describe(commands.TASK_LIST, () => { if (opts.url === `https://graph.microsoft.com/beta/planner/buckets/FtzysDykv0-9s9toWiZhdskAD67z/tasks`) { return taskListBetaResponse; } - if (opts.url === `https://graph.microsoft.com/beta/me/planner/tasks`) { + if (opts.url === `https://graph.microsoft.com/beta/me/planner/tasks?$select=id,title`) { return taskListBetaResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/buckets`) { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/iVPMIgdku0uFlou-KLNg6MkAE1O2/buckets?$select=id,name`) { return { value: [] }; } throw 'Invalid Request'; @@ -658,7 +582,7 @@ describe(commands.TASK_LIST, () => { planTitle: 'My Planner Plan', ownerGroupName: 'My Planner Group' } - }), new CommandError(`The specified bucket does not exist`)); + }), new CommandError(`The specified bucket 'foo' does not exist.`)); }); it('lists planner tasks of the current logged in user', async () => { diff --git a/src/m365/planner/commands/task/task-list.ts b/src/m365/planner/commands/task/task-list.ts index 5536b07abea..7cdcfdab3e1 100644 --- a/src/m365/planner/commands/task/task-list.ts +++ b/src/m365/planner/commands/task/task-list.ts @@ -1,7 +1,6 @@ -import { PlannerPlan, PlannerTask } from '@microsoft/microsoft-graph-types'; +import { PlannerTask } from '@microsoft/microsoft-graph-types'; import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; -import request, { CliRequestOptions } from '../../../../request.js'; import { entraGroup } from '../../../../utils/entraGroup.js'; import { formatting } from '../../../../utils/formatting.js'; import { odata } from '../../../../utils/odata.js'; @@ -133,39 +132,30 @@ class PlannerTaskListCommand extends GraphCommand { let planId: string | undefined = args.options.planId; let taskItems: PlannerTask[] = []; - if (bucketId || bucketName) { - try { + try { + if (bucketId || bucketName) { bucketId = await this.getBucketId(args); taskItems = await odata.getAllItems(`${this.resource}/v1.0/planner/buckets/${bucketId}/tasks`); const betaTasks = await odata.getAllItems(`${this.resource}/beta/planner/buckets/${bucketId}/tasks`); await logger.log(this.mergeTaskPriority(taskItems, betaTasks)); } - catch (err: any) { - this.handleRejectedODataJsonPromise(err); - } - } - else if (planId || planTitle) { - try { + else if (planId || planTitle) { planId = await this.getPlanId(args); taskItems = await odata.getAllItems(`${this.resource}/v1.0/planner/plans/${planId}/tasks`); const betaTasks = await odata.getAllItems(`${this.resource}/beta/planner/plans/${planId}/tasks`); await logger.log(this.mergeTaskPriority(taskItems, betaTasks)); } - catch (err: any) { - this.handleRejectedODataJsonPromise(err); - } - } - else { - try { + else { taskItems = await odata.getAllItems(`${this.resource}/v1.0/me/planner/tasks`); const betaTasks = await odata.getAllItems(`${this.resource}/beta/me/planner/tasks`); + await logger.log(this.mergeTaskPriority(taskItems, betaTasks)); } - catch (err: any) { - this.handleRejectedODataJsonPromise(err); - } + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); } } @@ -175,22 +165,7 @@ class PlannerTaskListCommand extends GraphCommand { } const planId = await this.getPlanId(args); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const response = await request.get<{ value: { id: string; name: string; }[] }>(requestOptions); - const bucket: { id: string; name: string; } | undefined = response.value.find(val => val.name === args.options.bucketName); - - if (!bucket) { - throw `The specified bucket does not exist`; - } - - return bucket.id; + return planner.getBucketIdByTitle(args.options.bucketName!, planId); } private async getPlanId(args: CommandArgs): Promise { @@ -199,13 +174,11 @@ class PlannerTaskListCommand extends GraphCommand { } if (args.options.rosterId) { - const plan: PlannerPlan = await planner.getPlanByRosterId(args.options.rosterId); - return plan.id!; + return planner.getPlanIdByRosterId(args.options.rosterId); } else { const groupId: string = await this.getGroupId(args); - const plan: PlannerPlan = await planner.getPlanByTitle(args.options.planTitle!, groupId); - return plan.id!; + return planner.getPlanIdByTitle(args.options.planTitle!, groupId); } } @@ -214,8 +187,7 @@ class PlannerTaskListCommand extends GraphCommand { return formatting.encodeQueryParameter(args.options.ownerGroupId); } - const group = await entraGroup.getGroupByDisplayName(args.options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(args.options.ownerGroupName!); } private mergeTaskPriority(taskItems: PlannerTask[], betaTaskItems: PlannerTask[]): PlannerTask[] { diff --git a/src/m365/planner/commands/task/task-reference-remove.ts b/src/m365/planner/commands/task/task-reference-remove.ts index b847476adcb..1ae227d7dcc 100644 --- a/src/m365/planner/commands/task/task-reference-remove.ts +++ b/src/m365/planner/commands/task/task-reference-remove.ts @@ -42,7 +42,7 @@ class PlannerTaskReferenceRemoveCommand extends GraphCommand { Object.assign(this.telemetryProperties, { url: typeof args.options.url !== 'undefined', alias: typeof args.options.alias !== 'undefined', - force: (!(!args.options.force)).toString() + force: !!args.options.force }); }); } diff --git a/src/m365/planner/commands/task/task-remove.spec.ts b/src/m365/planner/commands/task/task-remove.spec.ts index 7fe41006943..fcd235a1b87 100644 --- a/src/m365/planner/commands/task/task-remove.spec.ts +++ b/src/m365/planner/commands/task/task-remove.spec.ts @@ -35,8 +35,7 @@ describe(commands.TASK_REMOVE, () => { const singleGroupResponse = { "value": [ { - "id": validOwnerGroupId, - "displayName": validOwnerGroupName + "id": validOwnerGroupId } ] }; @@ -44,12 +43,10 @@ describe(commands.TASK_REMOVE, () => { const multipleGroupResponse = { "value": [ { - "id": validOwnerGroupId, - "displayName": validOwnerGroupName + "id": validOwnerGroupId }, { - "id": validOwnerGroupId, - "displayName": validOwnerGroupName + "id": validOwnerGroupId } ] }; @@ -356,7 +353,7 @@ describe(commands.TASK_REMOVE, () => { it('fails validation when no groups found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return { "value": [] }; } @@ -384,7 +381,7 @@ describe(commands.TASK_REMOVE, () => { }); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return multipleGroupResponse; } @@ -418,7 +415,7 @@ describe(commands.TASK_REMOVE, () => { planId: validPlanId, force: true } - }), new CommandError(`The specified bucket ${validBucketName} does not exist`)); + }), new CommandError(`The specified bucket '${validBucketName}' does not exist.`)); }); it('fails validation when multiple buckets found', async () => { @@ -452,10 +449,10 @@ describe(commands.TASK_REMOVE, () => { let removeRequestIssued = false; sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -543,10 +540,10 @@ describe(commands.TASK_REMOVE, () => { let removeRequestIssued = false; sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -636,7 +633,7 @@ describe(commands.TASK_REMOVE, () => { it('correctly deletes task by title with group id', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -674,10 +671,10 @@ describe(commands.TASK_REMOVE, () => { it('correctly deletes task by title', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter(validOwnerGroupName)}'&$select=id`) { return singleGroupResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans?$select=id,title`) { return singlePlanResponse; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -715,7 +712,7 @@ describe(commands.TASK_REMOVE, () => { it('correctly deletes task by rosterId', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${validRosterId}/plans?$select=id`) { return { "value": [planResponse] }; } if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}/buckets?$select=id,name`) { @@ -745,7 +742,8 @@ describe(commands.TASK_REMOVE, () => { options: { title: validTaskTitle, bucketName: validBucketName, - rosterId: validRosterId + rosterId: validRosterId, + verbose: true } }); }); diff --git a/src/m365/planner/commands/task/task-remove.ts b/src/m365/planner/commands/task/task-remove.ts index fd850d83005..6ca95d79d61 100644 --- a/src/m365/planner/commands/task/task-remove.ts +++ b/src/m365/planner/commands/task/task-remove.ts @@ -1,4 +1,4 @@ -import { PlannerBucket, PlannerPlan, PlannerTask } from '@microsoft/microsoft-graph-types'; +import { PlannerTask } from '@microsoft/microsoft-graph-types'; import { cli } from '../../../../cli/cli.js'; import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; @@ -138,6 +138,10 @@ class PlannerTaskRemoveCommand extends GraphCommand { try { const task = await this.getTask(args.options); + if (this.verbose) { + await logger.logToStderr(`Removing task '${task.title}' ...`); + } + const requestOptions: CliRequestOptions = { url: `${this.resource}/v1.0/planner/tasks/${task.id}`, headers: { @@ -185,18 +189,18 @@ class PlannerTaskRemoveCommand extends GraphCommand { // $filter is not working on the buckets/{bucketId}/tasks endpoint, hence it is not being used. const tasks = await odata.getAllItems(`${this.resource}/v1.0/planner/buckets/${bucketId}/tasks?$select=title,id`, 'minimal'); - const filteredtasks = tasks.filter(b => title!.toLocaleLowerCase() === b.title!.toLocaleLowerCase()); + const filteredTasks = tasks.filter(b => title!.toLocaleLowerCase() === b.title!.toLocaleLowerCase()); - if (filteredtasks.length === 0) { + if (filteredTasks.length === 0) { throw `The specified task ${title} does not exist`; } - if (filteredtasks.length > 1) { - const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', filteredtasks); + if (filteredTasks.length > 1) { + const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', filteredTasks); return await cli.handleMultipleResultsFound(`Multiple tasks with title '${title}' found.`, resultAsKeyValuePair); } - return filteredtasks[0]; + return filteredTasks[0]; } private async getBucketId(options: Options): Promise { @@ -207,28 +211,7 @@ class PlannerTaskRemoveCommand extends GraphCommand { } const planId = await this.getPlanId(options); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets?$select=id,name`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const buckets = await request.get<{ value: PlannerBucket[] }>(requestOptions); - const filteredBuckets = buckets.value.filter(b => bucketName!.toLocaleLowerCase() === b.name!.toLocaleLowerCase()); - - if (filteredBuckets.length === 0) { - throw `The specified bucket ${bucketName} does not exist`; - } - - if (filteredBuckets.length > 1) { - const resultAsKeyValuePair = formatting.convertArrayToHashTable('id', filteredBuckets); - const result = await cli.handleMultipleResultsFound(`Multiple buckets with name '${bucketName}' found.`, resultAsKeyValuePair); - return result.id!; - } - - return filteredBuckets[0].id!; + return planner.getBucketIdByTitle(bucketName!, planId); } private async getPlanId(options: Options): Promise { @@ -239,13 +222,11 @@ class PlannerTaskRemoveCommand extends GraphCommand { } if (options.rosterId) { - const plan: PlannerPlan = await planner.getPlanByRosterId(rosterId as string); - return plan.id!; + return planner.getPlanIdByRosterId(rosterId as string); } else { - const groupId: string = await this.getGroupId(options); - const plan: PlannerPlan = await planner.getPlanByTitle(planTitle!, groupId); - return plan.id!; + const groupId = await this.getGroupId(options); + return planner.getPlanIdByTitle(planTitle!, groupId); } } @@ -256,8 +237,7 @@ class PlannerTaskRemoveCommand extends GraphCommand { return ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(ownerGroupName!); } } diff --git a/src/m365/planner/commands/task/task-set.spec.ts b/src/m365/planner/commands/task/task-set.spec.ts index e2fb8a7fa97..8a773674016 100644 --- a/src/m365/planner/commands/task/task-set.spec.ts +++ b/src/m365/planner/commands/task/task-set.spec.ts @@ -575,7 +575,7 @@ describe(commands.TASK_SET, () => { return groupByDisplayNameResponse; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return { value: [ { @@ -615,7 +615,7 @@ describe(commands.TASK_SET, () => { }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups?$filter=displayName eq '${formatting.encodeQueryParameter('My Planner Group')}'&$select=id`) { return groupByDisplayNameResponse; } @@ -661,7 +661,7 @@ describe(commands.TASK_SET, () => { return { "@odata.etag": "TestEtag" }; } - if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/0d0402ee-970f-4951-90b5-2f24519d2e40/planner/plans?$select=id,title`) { return { value: [ { @@ -737,11 +737,10 @@ describe(commands.TASK_SET, () => { }); sinon.stub(request, 'get').callsFake(async (opts) => { - if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/DjL5xiKO10qut8LQgztpKskABWna/plans?$select=id`) { return { "value": [{ - "id": '8QZEH7b3wkS_bGQobscsM5gADCBb', - "title": 'My Planner Plan' + "id": '8QZEH7b3wkS_bGQobscsM5gADCBb' }] }; } @@ -750,7 +749,6 @@ describe(commands.TASK_SET, () => { return { "value": [ { - "@odata.etag": "W/\"JzEtQnVja2V0QEBAQEBAQEBAQEBAQEBARCc=\"", "name": "My Planner Bucket", "id": "IK8tuFTwQEa5vTonM7ZMRZgAKdno" } @@ -767,7 +765,7 @@ describe(commands.TASK_SET, () => { }; } - return Promise.reject('Invalid request'); + throw 'Invalid request'; }); const options: any = { @@ -950,7 +948,7 @@ describe(commands.TASK_SET, () => { planId: '8QZEH7b3wkS_bGQobscsM5gADCBb' }; - await assert.rejects(command.action(logger, { options: options } as any), new CommandError('The specified bucket does not exist')); + await assert.rejects(command.action(logger, { options: options } as any), new CommandError(`The specified bucket 'My Planner Bucket' does not exist.`)); }); it('fails when an invalid user is specified', async () => { diff --git a/src/m365/planner/commands/task/task-set.ts b/src/m365/planner/commands/task/task-set.ts index fdb50812205..c26bb169233 100644 --- a/src/m365/planner/commands/task/task-set.ts +++ b/src/m365/planner/commands/task/task-set.ts @@ -1,4 +1,4 @@ -import { PlannerBucket, PlannerPlan, PlannerTask, PlannerTaskDetails, User } from '@microsoft/microsoft-graph-types'; +import { PlannerTask, PlannerTaskDetails, User } from '@microsoft/microsoft-graph-types'; import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; @@ -354,23 +354,7 @@ class PlannerTaskSetCommand extends GraphCommand { } const planId = await this.getPlanId(options); - const requestOptions: CliRequestOptions = { - url: `${this.resource}/v1.0/planner/plans/${planId}/buckets?$select=id,name`, - headers: { - accept: 'application/json;odata.metadata=none' - }, - responseType: 'json' - }; - - const response = await request.get<{ value: PlannerBucket[] }>(requestOptions); - - const bucket: PlannerBucket | undefined = response.value.find(val => val.name === options.bucketName); - - if (!bucket) { - throw 'The specified bucket does not exist'; - } - - return bucket.id as string; + return planner.getBucketIdByTitle(options.bucketName, planId); } @@ -380,13 +364,11 @@ class PlannerTaskSetCommand extends GraphCommand { } if (options.rosterId) { - const plan: PlannerPlan = await planner.getPlanByRosterId(options.rosterId); - return plan.id!; + return planner.getPlanIdByRosterId(options.rosterId); } else { - const groupId: string = await this.getGroupId(options); - const plan: PlannerPlan = await planner.getPlanByTitle(options.planTitle!, groupId); - return plan.id!; + const groupId = await this.getGroupId(options); + return planner.getPlanIdByTitle(options.planTitle!, groupId); } } @@ -395,8 +377,7 @@ class PlannerTaskSetCommand extends GraphCommand { return options.ownerGroupId; } - const group = await entraGroup.getGroupByDisplayName(options.ownerGroupName!); - return group.id!; + return entraGroup.getGroupIdByDisplayName(options.ownerGroupName!); } private mapRequestBody(options: Options, appliedCategories: AppliedCategories): any { diff --git a/src/utils/planner.spec.ts b/src/utils/planner.spec.ts index 276723027f4..e7922b0f21d 100644 --- a/src/utils/planner.spec.ts +++ b/src/utils/planner.spec.ts @@ -1,130 +1,364 @@ import assert from 'assert'; import sinon from 'sinon'; import request from "../request.js"; -import { PlannerPlan } from '@microsoft/microsoft-graph-types'; +import { PlannerBucket, PlannerPlan } from '@microsoft/microsoft-graph-types'; import { planner } from './planner.js'; import { sinonUtil } from "./sinonUtil.js"; +import { cli } from '../cli/cli.js'; +import { formatting } from './formatting.js'; -const validPlanId = 'oUHpnKBFekqfGE_PS6GGUZcAFY7b'; -const validPlanTitle = 'Plan title'; -const validOwnerGroupId = '00000000-0000-0000-0000-000000000000'; +const planId = 'oUHpnKBFekqfGE_PS6GGUZcAFY7b'; +const planTitle = 'Plan title'; +const ownerGroupId = '00000000-0000-0000-0000-000000000000'; +const rosterId = 'tYqYlNd6eECmsNhN_fcq85cAGAnd'; +const bucketName = 'To Do'; +const bucketId = 'iTpUXFFYlkyyUsoWmILQbJgAMCbR'; const singlePlanResponse: PlannerPlan = { - id: validPlanId, - title: validPlanTitle, - owner: validOwnerGroupId + id: planId, + title: planTitle, + container: { + containerId: ownerGroupId + } +}; + +const singlePlanResponseTrimmed: PlannerPlan = { + id: planId, + title: planTitle }; const multiplePlanResponse = { value: [ + { + id: 'RnZkyGveikyfG2u817hodJgADNwq', + title: 'Another plan', + container: { + containerId: '00000000-0000-0000-0000-000000000000' + } + }, singlePlanResponse ] as PlannerPlan[] }; +const multiplePlanResponseTrimmed = { + value: multiplePlanResponse.value.map(p => ({ id: p.id, title: p.title })) +}; + +const singleBucketResponse: PlannerBucket = { + name: bucketName, + planId: planId, + orderHint: '8584914112[Y', + id: bucketId +}; + +const multipleBucketResponse = { + value: [ + { + name: 'In Progress', + planId: planId, + orderHint: '8584914113862517879P=', + id: 'X5vwdkKh70-5hsFT1MJ8-pgAL4Si' + }, + singleBucketResponse + ] +}; + +const multipleBucketResponseTrimmed = { + value: multipleBucketResponse.value.map(b => ({ name: b.name, id: b.id })) +}; + describe('utils/planner', () => { afterEach(() => { sinonUtil.restore([ - request.get + request.get, + cli.handleMultipleResultsFound ]); }); - it('correctly get all plans related to a specific group.', async () => { + after(() => { + sinon.restore(); + }); + + it('correctly get all plans related to a specific group', async () => { sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { return multiplePlanResponse; } - return 'Invalid Request'; + return 'Invalid Request ' + opts.url; }); - const actual = await planner.getPlansByGroupId(validOwnerGroupId); - assert.strictEqual(actual, multiplePlanResponse.value); + const actual = await planner.getPlansByGroupId(ownerGroupId); + assert.deepStrictEqual(actual, multiplePlanResponse.value); }); - it('correctly get a single plan by id.', async () => { + it('correctly get the plan from a roster', async () => { sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}`) { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${rosterId}/plans`) { + return { value: [singlePlanResponse] }; + } + + return 'Invalid Request: ' + opts.url; + }); + + const actual = await planner.getPlanByRosterId(rosterId); + assert.deepStrictEqual(actual, singlePlanResponse); + }); + + it('correctly throws an error when a roster does not have a plan', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${rosterId}/plans`) { + return { value: [] }; + } + + return 'Invalid Request: ' + opts.url; + }); + + await assert.rejects(planner.getPlanByRosterId(rosterId), Error(`The specified roster '${rosterId}' does not have a plan.`)); + }); + + it('correctly get the plan ID from a roster', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${rosterId}/plans?$select=id`) { + return { value: [{ id: planId }] }; + } + + return 'Invalid Request: ' + opts.url; + }); + + const actual = await planner.getPlanIdByRosterId(rosterId); + assert.deepStrictEqual(actual, planId); + }); + + it('correctly throws an error when a roster does not have a plan when getting the plan ID', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/beta/planner/rosters/${rosterId}/plans?$select=id`) { + return { value: [] }; + } + + return 'Invalid Request: ' + opts.url; + }); + + await assert.rejects(planner.getPlanIdByRosterId(rosterId), Error(`The specified roster '${rosterId}' does not have a plan.`)); + }); + + it('correctly get a single plan by id', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}`) { return singlePlanResponse; } - return 'Invalid Request'; + return 'Invalid Request ' + opts.url; }); - const actual = await planner.getPlanById(validPlanId); - assert.strictEqual(actual, singlePlanResponse); + const actual = await planner.getPlanById(planId); + assert.deepStrictEqual(actual, singlePlanResponse); }); - it('display error message when plan is not found.', async () => { + it('display error message when plan is not found', async () => { sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${validPlanId}`) { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}`) { throw Error('Plan not found.'); } - return 'Invalid Request'; + return 'Invalid Request ' + opts.url; + }); + + await assert.rejects(planner.getPlanById(planId), Error(`Planner plan with id '${planId}' was not found.`)); + }); + + it('correctly get plan by title', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { + return multiplePlanResponse; + } + + return 'Invalid Request ' + opts.url; + }); + + const actual = await planner.getPlanByTitle(planTitle, ownerGroupId); + assert.deepStrictEqual(actual, singlePlanResponse); + }); + + it('fails to get plan when plan does not exist', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { + return multiplePlanResponse; + } + + return 'Invalid Request ' + opts.url; + }); + + await assert.rejects(planner.getPlanByTitle('Wrong title', ownerGroupId), + Error(`The specified plan 'Wrong title' does not exist.`)); + }); + + it('correctly returns a plan when multiple plans have the same title with prompt', async () => { + const stubMultiResults = sinon.stub(cli, 'handleMultipleResultsFound').resolves(singlePlanResponse); + + const requestResult = [ + singlePlanResponse, + { ...singlePlanResponse, id: 'RnZkyGveikyfG2u817hodJgADNwq' } + ]; + + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans`) { + return { + value: requestResult + }; + } + + return 'Invalid Request ' + opts.url; + }); + + const actual = await planner.getPlanByTitle(planTitle, ownerGroupId); + + const plansKeyValuePair = formatting.convertArrayToHashTable('id', requestResult); + assert(stubMultiResults.calledOnceWithExactly(`Multiple plans with title '${planTitle}' found.`, plansKeyValuePair)); + assert.deepStrictEqual(actual, singlePlanResponse); + }); + + it('correctly get plan ID by title.', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans?$select=id,title`) { + return multiplePlanResponseTrimmed; + } + + return 'Invalid Request ' + opts.url; }); - try { - await planner.getPlanById(validPlanId); - assert.fail('No error message thrown.'); - } - catch (ex) { - assert.deepStrictEqual(ex, Error(`Planner plan with id '${validPlanId}' was not found.`)); - } + const actual = await planner.getPlanIdByTitle(planTitle, ownerGroupId); + assert.deepStrictEqual(actual, planId); }); - it('correctly get plan by title.', async () => { + it('fails to get plan ID when plan does not exist', async () => { sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans?$select=id,title`) { return multiplePlanResponse; } - return 'Invalid Request'; + return 'Invalid Request ' + opts.url; }); - const actual = await planner.getPlanByTitle(validPlanTitle, validOwnerGroupId); - assert.strictEqual(actual, singlePlanResponse); + await assert.rejects(planner.getPlanIdByTitle('Wrong title', ownerGroupId), + Error(`The specified plan 'Wrong title' does not exist.`)); }); - it('fails to get plan when plan doesn not exist', async () => { + it('correctly returns a plan ID when multiple plans have the same title with prompt', async () => { + const stubMultiResults = sinon.stub(cli, 'handleMultipleResultsFound').resolves(singlePlanResponse); + sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { - const response = { ...multiplePlanResponse }; - response.value[0].title = "Wrong title"; - return response; + if (opts.url === `https://graph.microsoft.com/v1.0/groups/${ownerGroupId}/planner/plans?$select=id,title`) { + return { + value: [ + singlePlanResponseTrimmed, + { title: singlePlanResponseTrimmed.title, id: 'RnZkyGveikyfG2u817hodJgADNwq' } + ] + }; } - return 'Invalid Request'; + return 'Invalid Request ' + opts.url; }); - try { - await planner.getPlanByTitle(validPlanTitle, validOwnerGroupId); - assert.fail('No error message thrown.'); - } - catch (ex) { - assert.deepStrictEqual(ex, Error(`The specified plan '${validPlanTitle}' does not exist.`)); - } + const actual = await planner.getPlanIdByTitle(planTitle, ownerGroupId); + assert.deepStrictEqual(actual, singlePlanResponse.id); + assert(stubMultiResults.calledOnce); }); - it('fails to get plan when multiple plans have the same title', async () => { + it('correctly retrieves a bucket by title', async () => { sinon.stub(request, 'get').callsFake(async opts => { - if (opts.url === `https://graph.microsoft.com/v1.0/groups/${validOwnerGroupId}/planner/plans`) { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets`) { + return multipleBucketResponse; + } + + return 'Invalid Request ' + opts.url; + }); + + const actual = await planner.getBucketByTitle(bucketName, planId); + assert.deepStrictEqual(actual, singleBucketResponse); + }); + + it('fails to get bucket by title when it does not exist', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets`) { + return multipleBucketResponse; + } + + return 'Invalid Request ' + opts.url; + }); + + await assert.rejects(planner.getBucketByTitle('Wrong name', planId), + Error(`The specified bucket 'Wrong name' does not exist.`)); + }); + + it('correctly returns a bucket when multiple buckets have the same title with prompt', async () => { + const stubMultiResults = sinon.stub(cli, 'handleMultipleResultsFound').resolves(singleBucketResponse); + + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets`) { return { value: [ - { title: validPlanTitle, id: validPlanId }, - { title: validPlanTitle, id: validPlanId } + singleBucketResponse, + { ...singleBucketResponse, id: 'X5vwdkKh70-5hsFT1MJ8-pgAL4Si' } ] }; } - return 'Invalid Request'; + return 'Invalid Request ' + opts.url; + }); + + const actual = await planner.getBucketByTitle(bucketName, planId); + assert.deepStrictEqual(actual, singleBucketResponse); + assert(stubMultiResults.calledOnce); + }); + + it('correctly retrieves a bucket ID by title', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets?$select=id,name`) { + return multipleBucketResponseTrimmed; + } + + return 'Invalid Request ' + opts.url; + }); + + const actual = await planner.getBucketIdByTitle(bucketName, planId); + assert.deepStrictEqual(actual, bucketId); + }); + + it('fails to get bucket ID by title when it does not exist', async () => { + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets?$select=id,name`) { + return multipleBucketResponseTrimmed; + } + + return 'Invalid Request ' + opts.url; + }); + + await assert.rejects(planner.getBucketIdByTitle('Wrong name', planId), + Error(`The specified bucket 'Wrong name' does not exist.`)); + }); + + it('correctly returns a bucket ID when multiple buckets have the same title with prompt', async () => { + const stubMultiResults = sinon.stub(cli, 'handleMultipleResultsFound').resolves(singleBucketResponse); + + const requestResult = [ + singleBucketResponse, + { ...singleBucketResponse, id: 'X5vwdkKh70-5hsFT1MJ8-pgAL4Si' } + ]; + + sinon.stub(request, 'get').callsFake(async opts => { + if (opts.url === `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets?$select=id,name`) { + return { + value: requestResult + }; + } + + return 'Invalid Request ' + opts.url; }); - try { - await planner.getPlanByTitle(validPlanTitle, validOwnerGroupId); - assert.fail('No error message thrown.'); - } - catch (ex) { - assert.deepStrictEqual(ex, Error(`Multiple plans with title '${validPlanTitle}' found: ${[validPlanId, validPlanId]}.`)); - } + const actual = await planner.getBucketIdByTitle(bucketName, planId); + const plansKeyValuePair = formatting.convertArrayToHashTable('id', requestResult); + assert(stubMultiResults.calledOnceWithExactly(`Multiple buckets with name '${bucketName}' found.`, plansKeyValuePair)); + assert.deepStrictEqual(actual, bucketId); }); }); \ No newline at end of file diff --git a/src/utils/planner.ts b/src/utils/planner.ts index 706c99ef7f7..33d7f97082c 100644 --- a/src/utils/planner.ts +++ b/src/utils/planner.ts @@ -1,6 +1,8 @@ +import { cli } from "../cli/cli.js"; import request, { CliRequestOptions } from "../request.js"; +import { formatting } from "./formatting.js"; import { odata } from "./odata.js"; -import { PlannerPlan } from "@microsoft/microsoft-graph-types"; +import { PlannerBucket, PlannerPlan } from "@microsoft/microsoft-graph-types"; const graphResource = 'https://graph.microsoft.com'; @@ -16,7 +18,8 @@ export const planner = { /** * Get Planner plan by ID. * @param id Planner ID. - * @param metadata OData metadata level. Default is none + * @param metadata OData metadata level. Default is none. + * @throws Error when the plan is not found. */ async getPlanById(id: string, metadata: 'none' | 'minimal' | 'full' = 'none'): Promise { const requestOptions = getRequestOptions(`${graphResource}/v1.0/planner/plans/${id}`, metadata); @@ -32,6 +35,7 @@ export const planner = { /** * Get all Planner plans for a specific group. * @param groupId Group ID. + * @param metadata OData metadata level. Default is none. */ getPlansByGroupId(groupId: string, metadata: 'none' | 'minimal' | 'full' = 'none'): Promise { return odata.getAllItems(`${graphResource}/v1.0/groups/${groupId}/planner/plans`, metadata); @@ -40,33 +44,125 @@ export const planner = { /** * Get the Planner plan for a specific Roster. * @param rosterId Roster ID. + * @param metadata OData metadata level. Default is none. + * @throws Error when the roster has no plan. */ async getPlanByRosterId(rosterId: string, metadata: 'none' | 'minimal' | 'full' = 'none'): Promise { const plans = await odata.getAllItems(`${graphResource}/beta/planner/rosters/${rosterId}/plans`, metadata); + + if (plans.length === 0) { + throw Error(`The specified roster '${rosterId}' does not have a plan.`); + } + return plans[0]; }, + /** + * Get the Planner plan ID for a specific Roster. + * @param rosterId Roster ID. + * @throws Error when the roster has no plan. + */ + async getPlanIdByRosterId(rosterId: string): Promise { + const plans = await odata.getAllItems(`${graphResource}/beta/planner/rosters/${rosterId}/plans?$select=id`); + + if (plans.length === 0) { + throw Error(`The specified roster '${rosterId}' does not have a plan.`); + } + + return plans[0].id!; + }, + /** * Get Planner plan by title in a specific group. * @param title Title of the Planner plan. Case insensitive. * @param groupId Owner group ID. - * @param rosterId Roster ID. + * @param metadata OData metadata level. Default is none. + * @throws Error when the plan is not found. */ - async getPlanByTitle(title: string, groupId?: string, metadata: 'none' | 'minimal' | 'full' = 'none'): Promise { - let plans: PlannerPlan[] = []; - if (groupId) { - plans = await this.getPlansByGroupId(groupId, metadata); - } + async getPlanByTitle(title: string, groupId: string, metadata: 'none' | 'minimal' | 'full' = 'none'): Promise { + const plans = await this.getPlansByGroupId(groupId, metadata); const filteredPlans = plans.filter(p => p.title && p.title.toLowerCase() === title.toLowerCase()); - if (!filteredPlans.length) { + if (filteredPlans.length === 0) { throw Error(`The specified plan '${title}' does not exist.`); } if (filteredPlans.length > 1) { - throw Error(`Multiple plans with title '${title}' found: ${filteredPlans.map(x => x.id)}.`); + const plansKeyValuePair = formatting.convertArrayToHashTable('id', filteredPlans); + const plan = await cli.handleMultipleResultsFound(`Multiple plans with title '${title}' found.`, plansKeyValuePair); + return plan; } return filteredPlans[0]; + }, + + /** + * Get Planner plan ID by title in a specific group. + * @param title Title of the Planner plan. Case insensitive. + * @param groupId Owner group ID. + * @throws Error when the plan is not found. + */ + async getPlanIdByTitle(title: string, groupId: string): Promise { + const plans = await odata.getAllItems(`${graphResource}/v1.0/groups/${groupId}/planner/plans?$select=id,title`); + const filteredPlans = plans.filter(p => p.title && p.title.toLowerCase() === title.toLowerCase()); + + if (filteredPlans.length === 0) { + throw Error(`The specified plan '${title}' does not exist.`); + } + + if (filteredPlans.length > 1) { + const plansKeyValuePair = formatting.convertArrayToHashTable('id', filteredPlans); + const plan = await cli.handleMultipleResultsFound(`Multiple plans with title '${title}' found.`, plansKeyValuePair); + return plan.id!; + } + + return filteredPlans[0].id!; + }, + + /** + * Get Planner bucket by title in a specific plan. + * @param title Title of the Planner bucket. Case insensitive. + * @param planId ID of the plan that contains the bucket. + * @param metadata OData metadata level. Default is none. + * @throws Error when the bucket is not found. + */ + async getBucketByTitle(title: string, planId: string, metadata: 'none' | 'minimal' | 'full' = 'none'): Promise { + const buckets = await odata.getAllItems(`${graphResource}/v1.0/planner/plans/${planId}/buckets`, metadata); + const filteredBuckets = buckets.filter(b => b.name && b.name.toLowerCase() === title.toLowerCase()); + + if (filteredBuckets.length === 0) { + throw Error(`The specified bucket '${title}' does not exist.`); + } + + if (filteredBuckets.length > 1) { + const bucketsKeyValuePair = formatting.convertArrayToHashTable('id', filteredBuckets); + const bucket = await cli.handleMultipleResultsFound(`Multiple buckets with name '${title}' found.`, bucketsKeyValuePair); + return bucket; + } + + return filteredBuckets[0]; + }, + + /** + * Get Planner bucket ID by title in a specific plan. + * @param title Title of the Planner bucket. Case insensitive. + * @param planId ID of the plan that contains the bucket. + * @throws Error when the bucket is not found. + */ + async getBucketIdByTitle(title: string, planId: string): Promise { + const buckets = await odata.getAllItems(`${graphResource}/v1.0/planner/plans/${planId}/buckets?$select=id,name`); + const filteredBuckets = buckets.filter(b => b.name && b.name.toLowerCase() === title.toLowerCase()); + + if (filteredBuckets.length === 0) { + throw Error(`The specified bucket '${title}' does not exist.`); + } + + if (filteredBuckets.length > 1) { + const bucketsKeyValuePair = formatting.convertArrayToHashTable('id', filteredBuckets); + const bucket = await cli.handleMultipleResultsFound(`Multiple buckets with name '${title}' found.`, bucketsKeyValuePair); + return bucket.id!; + } + + return filteredBuckets[0].id!; } }; \ No newline at end of file