diff --git a/docs/docs/cmd/teams/user/user-app-add.mdx b/docs/docs/cmd/teams/user/user-app-add.mdx index e81d6baf867..34a602b7a06 100644 --- a/docs/docs/cmd/teams/user/user-app-add.mdx +++ b/docs/docs/cmd/teams/user/user-app-add.mdx @@ -16,8 +16,11 @@ m365 teams user app add [options] `--id ` : The ID of the app to install. -`--userId ` -: The ID of the user to install the app for. +`--userId [userId]` +: The ID of the user to install the app for. Specify either `userId` or `userName` but not both. + +`--userName [userName]` +: The UPN of the user to install the app for. Specify either `userId` or `userName` but not both. ``` @@ -28,12 +31,18 @@ The `id` has to be the ID of the app from the Microsoft Teams App Catalog. Do no ## Examples -Install an app from the catalog for the specified user. +Install an app from the catalog for the specified user by id. ```sh m365 teams user app add --id 4440558e-8c73-4597-abc7-3644a64c4bce --userId 2609af39-7775-4f94-a3dc-0dd67657e900 ``` +Install an app from the catalog for the specified user by name. + +```sh +m365 teams user app add --id 4440558e-8c73-4597-abc7-3644a64c4bce --userName admin@contoso.com +``` + ## Response The command won't return a response on success. diff --git a/docs/docs/cmd/teams/user/user-app-remove.mdx b/docs/docs/cmd/teams/user/user-app-remove.mdx index 30c0ee16798..ed3fb280be6 100644 --- a/docs/docs/cmd/teams/user/user-app-remove.mdx +++ b/docs/docs/cmd/teams/user/user-app-remove.mdx @@ -14,16 +14,16 @@ m365 teams user app remove [options] ```md definition-list `--id [id]` -: The unique id of the app instance installed for the user. Specify either `id` or `name`. +: The unique id of the app instance installed for the user. Specify either `id` or `name` but not both. `--name [name]` -: Name of the app instance installed for the user. Specify either `id` or `name`. +: Name of the app instance installed for the user. Specify either `id` or `name` but not both. `--userId [userId]` -: The ID of the user to uninstall the app for. Specify `userId` or `userName` but not both. +: The ID of the user to uninstall the app for. Specify either `userId` or `userName` but not both. `--userName [userName]` -: The UPN of the user to uninstall the app for. Specify `userId` or `userName` but not both. +: The UPN of the user to uninstall the app for. Specify either `userId` or `userName` but not both. `-f, --force` : Confirm removal of app for user. diff --git a/src/m365/teams/commands/user/user-app-add.spec.ts b/src/m365/teams/commands/user/user-app-add.spec.ts index ff4c09d0f9d..7b0ef2374ce 100644 --- a/src/m365/teams/commands/user/user-app-add.spec.ts +++ b/src/m365/teams/commands/user/user-app-add.spec.ts @@ -72,6 +72,16 @@ describe(commands.USER_APP_ADD, () => { assert.notStrictEqual(actual, true); }); + it('fails validation if the userName is not a valid UPN.', async () => { + const actual = await command.validate({ + options: { + userName: "no-an-email", + id: '15d7a78e-fd77-4599-97a5-dbb6372846c5' + } + }, commandInfo); + assert.notStrictEqual(actual, true); + }); + it('fails validation if the id is not a valid guid.', async () => { const actual = await command.validate({ options: { @@ -92,7 +102,17 @@ describe(commands.USER_APP_ADD, () => { assert.strictEqual(actual, true); }); - it('adds app from the catalog for the specified user', async () => { + it('passes validation when the input is correct (userName)', async () => { + const actual = await command.validate({ + options: { + id: '15d7a78e-fd77-4599-97a5-dbb6372846c5', + userName: 'admin@contoso.com' + } + }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('adds app from the catalog for the specified user by id', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { if (opts.url === `https://graph.microsoft.com/v1.0/users/c527a470-a882-481c-981c-ee6efaba85c7/teamwork/installedApps` && JSON.stringify(opts.data) === `{"teamsApp@odata.bind":"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/4440558e-8c73-4597-abc7-3644a64c4bce"}`) { @@ -110,6 +130,24 @@ describe(commands.USER_APP_ADD, () => { } as any); }); + it('adds app from the catalog for the specified user by name', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://graph.microsoft.com/v1.0/users/admin%40contoso.com/teamwork/installedApps` && + JSON.stringify(opts.data) === `{"teamsApp@odata.bind":"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/4440558e-8c73-4597-abc7-3644a64c4bce"}`) { + return; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + userName: 'admin@contoso.com', + id: '4440558e-8c73-4597-abc7-3644a64c4bce' + } + } as any); + }); + it('correctly handles error while installing teams app', async () => { const error = { "error": { diff --git a/src/m365/teams/commands/user/user-app-add.ts b/src/m365/teams/commands/user/user-app-add.ts index 6b7e3674920..525fb4c6747 100644 --- a/src/m365/teams/commands/user/user-app-add.ts +++ b/src/m365/teams/commands/user/user-app-add.ts @@ -1,6 +1,7 @@ import { Logger } from '../../../../cli/Logger.js'; import GlobalOptions from '../../../../GlobalOptions.js'; import request, { CliRequestOptions } from '../../../../request.js'; +import { formatting } from '../../../../utils/formatting.js'; import { validation } from '../../../../utils/validation.js'; import GraphCommand from '../../../base/GraphCommand.js'; import commands from '../../commands.js'; @@ -11,7 +12,8 @@ interface CommandArgs { interface Options extends GlobalOptions { id: string; - userId: string; + userId?: string; + userName?: string; } class TeamsUserAppAddCommand extends GraphCommand { @@ -26,8 +28,19 @@ class TeamsUserAppAddCommand extends GraphCommand { constructor() { super(); + this.#initTelemetry(); this.#initOptions(); this.#initValidators(); + this.#initOptionSets(); + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + userId: typeof args.options.userId !== 'undefined', + userName: typeof args.options.userName !== 'undefined' + }); + }); } #initOptions(): void { @@ -36,7 +49,10 @@ class TeamsUserAppAddCommand extends GraphCommand { option: '--id ' }, { - option: '--userId ' + option: '--userId [userId]' + }, + { + option: '--userName [userName]' } ); } @@ -48,20 +64,29 @@ class TeamsUserAppAddCommand extends GraphCommand { return `${args.options.id} is not a valid GUID`; } - if (!validation.isValidGuid(args.options.userId)) { + if (args.options.userId && !validation.isValidGuid(args.options.userId)) { return `${args.options.userId} is not a valid GUID`; } + if (args.options.userName && !validation.isValidUserPrincipalName(args.options.userName)) { + return `${args.options.userName} is not a valid userName`; + } + return true; } ); } + #initOptionSets(): void { + this.optionSets.push({ options: ['userId', 'userName'] }); + } + public async commandAction(logger: Logger, args: CommandArgs): Promise { + const userId: string = (args.options.userId ?? args.options.userName) as string; const endpoint: string = `${this.resource}/v1.0`; const requestOptions: CliRequestOptions = { - url: `${endpoint}/users/${args.options.userId}/teamwork/installedApps`, + url: `${endpoint}/users/${formatting.encodeQueryParameter(userId)}/teamwork/installedApps`, headers: { 'content-type': 'application/json;odata=nometadata', 'accept': 'application/json;odata.metadata=none'