diff --git a/docs/docs/cmd/spo/field/field-get.mdx b/docs/docs/cmd/spo/field/field-get.mdx index 130e7fc1206..fa77496a0a6 100644 --- a/docs/docs/cmd/spo/field/field-get.mdx +++ b/docs/docs/cmd/spo/field/field-get.mdx @@ -28,10 +28,14 @@ m365 spo field get [options] : Server- or web-relative URL of the list where the field is located. Specify either `listTitle`, `listId` or `listUrl`. `-i, --id [id]` -: The ID of the field to retrieve. Specify `id` or `title` but not both. +: The ID of the field to retrieve. Specify either `id`, `title` or `internalName`. `-t, --title [title]` -: The display name (case-sensitive) of the field to retrieve. Specify `id` or `title` but not both. +: The display name (case-sensitive) of the field to retrieve. Specify either `id`, `title` or `internalName`. +``` + +`--internalName [internalName]` +: The internal name (case-sensitive) of the field to retrieve. Specify either `id`, `title` or `internalName`. ``` @@ -56,6 +60,12 @@ Retrieves list column by display name located in the specified site. Retrieves t m365 spo field get --webUrl https://contoso.sharepoint.com/sites/contoso-sales --listUrl "Lists/Events" --title "Title" ``` +Retrieves list column by internal name located in the specified site. Retrieves the list by its url. + +```sh +m365 spo field get --webUrl https://contoso.sharepoint.com/sites/contoso-sales --listUrl "Lists/Events" --internalName "Title" +``` + ## Response diff --git a/docs/docs/cmd/spo/field/field-remove.mdx b/docs/docs/cmd/spo/field/field-remove.mdx index f1e3b39fbd9..193c8ab6fa2 100644 --- a/docs/docs/cmd/spo/field/field-remove.mdx +++ b/docs/docs/cmd/spo/field/field-remove.mdx @@ -26,13 +26,16 @@ m365 spo field remove [options] : Server- or web-relative URL of the list where the field is located. Specify either `listTitle`, `listId` or `listUrl`. `-i, --id [id]` -: The ID of the field to remove. Specify either `id`, `title`, or `group`. +: The ID of the field to remove. Specify either `id`, `title`, `internalName`, or `group`. `-t, --title [title]` -: The display name (case-sensitive) of the field to remove. Specify either `id`, `title`, or `group`. +: The display name (case-sensitive) of the field to remove. Specify either `id`, `title`, `internalName`, or `group`. + +`--internalName [internalName]` +: The internal name (case-sensitive) of the field to remove. Specify either `id`, `title`, `internalName`, or `group`. `-g, --group [group]` -: Delete all fields from this group (case-sensitive). Specify either `id`, `title`, or `group`. +: Delete all fields from this group (case-sensitive). Specify either `id`, `title`, `internalName`, or `group`. `-f, --force` : Don't prompt for confirming removing the field. @@ -60,6 +63,12 @@ Remove the list column with the specified display name, located in the specified m365 spo field remove --webUrl https://contoso.sharepoint.com/sites/contoso-sales --listUrl "Lists/Events" --title "Title" ``` +Remove the list column with the specified display name, located in the specified site. Retrieves the list by its url. + +```sh +m365 spo field remove --webUrl https://contoso.sharepoint.com/sites/contoso-sales --listUrl "Lists/Events" --internalName "Title" +``` + Remove all site columns from the specified group. ```sh diff --git a/docs/docs/cmd/spo/field/field-set.mdx b/docs/docs/cmd/spo/field/field-set.mdx index 14d532bfb64..cb449bc81a0 100644 --- a/docs/docs/cmd/spo/field/field-set.mdx +++ b/docs/docs/cmd/spo/field/field-set.mdx @@ -26,10 +26,13 @@ m365 spo field set [options] : Server- or site-relative URL of the list where the field is located (if list column). Specify either `listTitle`, `listId` or `listUrl`. `-i, --id [id]` -: ID of the field to update. Specify either `id` or `title` but not both. +: ID of the field to update. Specify either `id`, `title`, or 'internalName' but not all. `-t, --title [title]` -: Title or internal name of the field to update. Specify either `id` or `title` but not both. +: Title of the field to update. Specify either `id`, `title`, or 'internalName' but not all. + +`--internalName [internalName]` +: Internal name of the field to update. Specify either `id`, `title`, or 'internalName' but not all. `--updateExistingLists` : Set, to push the update to existing lists. Otherwise, the changes will apply to new lists only. @@ -51,6 +54,12 @@ When updating column formatting for a field with the `--CustomFormatter` option, Update the title of the site column specified by its internal name and push changes to existing lists. +```sh +m365 spo field set --webUrl https://contoso.sharepoint.com/sites/project-x --internalName 'MyColumn' --updateExistingLists --Title 'My column' +``` + +Update the title of the site column specified by its title and push changes to existing lists. + ```sh m365 spo field set --webUrl https://contoso.sharepoint.com/sites/project-x --title 'MyColumn' --updateExistingLists --Title 'My column' ``` @@ -67,11 +76,16 @@ Update the description of a column specified by the ID on a list retrieved by th m365 spo field set --webUrl https://contoso.sharepoint.com/sites/project-x --listUrl '/sites/project-x/Lists/My List' --id 330f29c5-5c4c-465f-9f4b-7903020ae1ce --Description 'My column Description' ``` -Update column formatting of the specified list column. +Update column formatting of the specified list column based on title. ```sh m365 spo field set --webUrl https://contoso.sharepoint.com/sites/project-x --listTitle 'My List' --title 'MyColumn' --CustomFormatter '{"schema":"https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json", "elmType": "div", "txtContent": "@currentField"}' ``` +Update column formatting of the specified list column based on internalName. + +```sh +m365 spo field set --webUrl https://contoso.sharepoint.com/sites/project-x --listTitle 'My List' --internalName 'MyColumn' --CustomFormatter '{"schema":"https://developer.microsoft.com/json-schemas/sp/column-formatting.schema.json", "elmType": "div", "txtContent": "@currentField"}' +``` ## Response diff --git a/src/m365/spo/commands/field/field-get.spec.ts b/src/m365/spo/commands/field/field-get.spec.ts index 5f7fbc806d8..0521ff16064 100644 --- a/src/m365/spo/commands/field/field-get.spec.ts +++ b/src/m365/spo/commands/field/field-get.spec.ts @@ -86,7 +86,7 @@ describe(commands.FIELD_GET, () => { it('gets information about a site column', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid('5ee2dd25-d941-455a-9bdb-7f2c54aed11b')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('5ee2dd25-d941-455a-9bdb-7f2c54aed11b')`) { return { "AutoIndexed": false, "CanBeDeleted": true, @@ -177,7 +177,7 @@ describe(commands.FIELD_GET, () => { it('gets information about a list column', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { return { "AutoIndexed": false, "CanBeDeleted": false, @@ -262,7 +262,7 @@ describe(commands.FIELD_GET, () => { it('should call the correct GET url when id and list url specified', async () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/GetList('%2Fsites%2Fportal%2FLists%2FEvents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { return { "Id": "03e45e84-1992-4d42-9116-26f756012634" }; @@ -270,14 +270,13 @@ describe(commands.FIELD_GET, () => { throw 'Invalid request'; }); - - await assert.rejects(command.action(logger, { options: { verbose: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', id: '03e45e84-1992-4d42-9116-26f756012634', listUrl: 'Lists/Events' } })); + await command.action(logger, { options: { debug: true, verbose: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', id: '03e45e84-1992-4d42-9116-26f756012634', listUrl: 'Lists/Events' } }); assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/GetList(\'%2Fsites%2Fportal%2FLists%2FEvents\')/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012634\')'); }); it('should call the correct GET url when field title and list title specified (verbose)', async () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { return { "Id": "03e45e84-1992-4d42-9116-26f756012634" }; @@ -292,7 +291,7 @@ describe(commands.FIELD_GET, () => { it('should call the correct GET url when field title and list title specified', async () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { return { "Id": "03e45e84-1992-4d42-9116-26f756012634" }; @@ -306,8 +305,9 @@ describe(commands.FIELD_GET, () => { }); it('should call the correct GET url when field title and list url specified', async () => { + const webUrl = 'https://contoso.sharepoint.com/sites/portal'; const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `${webUrl}/_api/web/lists(guid'03e45e84-1992-4d42-9116-26f756012634')/fields/getbyinternalnameortitle('Title')`) { return { "Id": "03e45e84-1992-4d42-9116-26f756012634" }; @@ -320,16 +320,63 @@ describe(commands.FIELD_GET, () => { assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists(guid\'03e45e84-1992-4d42-9116-26f756012634\')/fields/getbyinternalnameortitle(\'Title\')'); }); + it('should call the correct GET url when field internalName and list title specified (verbose)', async () => { + const webUrl = 'https://contoso.sharepoint.com/sites/portal'; + const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${webUrl}/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { + return { + "Id": "03e45e84-1992-4d42-9116-26f756012634" + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, verbose: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', internalName: 'Title', listTitle: 'Documents' } }); + assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle(\'Documents\')/fields/getbyinternalnameortitle(\'Title\')'); + }); + + it('should call the correct GET url when field internalName and list title specified', async () => { + const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { + return { + "Id": "03e45e84-1992-4d42-9116-26f756012634" + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/portal', internalName: 'Title', listTitle: 'Documents' } }); + assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle(\'Documents\')/fields/getbyinternalnameortitle(\'Title\')'); + }); + + it('should call the correct GET url when field internalName and list url specified', async () => { + const webUrl = 'https://contoso.sharepoint.com/sites/portal'; + const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { + if (opts.url === `${webUrl}/_api/web/lists(guid'03e45e84-1992-4d42-9116-26f756012634')/fields/getbyinternalnameortitle('Title')`) { + return { + 'Id': '03e45e84-1992-4d42-9116-26f756012634' + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', internalName: 'Title', listId: '03e45e84-1992-4d42-9116-26f756012634' } }); + assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists(guid\'03e45e84-1992-4d42-9116-26f756012634\')/fields/getbyinternalnameortitle(\'Title\')'); + }); + it('correctly handles site column not found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw { error: { "odata.error": { "code": "-2147024809, System.ArgumentException", "message": { "lang": "en-US", - "value": "Invalid field name. {03e45e84-1992-4d42-9116-26f756012634} https://m365x526922.sharepoint.com/sites/portal " + "value": "Invalid field name. {03e45e84-1992-4d42-9116-26f756012634} https://contoso.sharepoint.com/sites/portal " } } } @@ -340,12 +387,12 @@ describe(commands.FIELD_GET, () => { }); await assert.rejects(command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', id: '03e45e84-1992-4d42-9116-26f756012634' } } as any), - new CommandError('Invalid field name. {03e45e84-1992-4d42-9116-26f756012634} https://m365x526922.sharepoint.com/sites/portal ')); + new CommandError('Invalid field name. {03e45e84-1992-4d42-9116-26f756012634} https://contoso.sharepoint.com/sites/portal ')); }); it('correctly handles list column not found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw { error: { "odata.error": { @@ -368,7 +415,7 @@ describe(commands.FIELD_GET, () => { it('correctly handles list not found', async () => { sinon.stub(request, 'get').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw { error: { "odata.error": { diff --git a/src/m365/spo/commands/field/field-get.ts b/src/m365/spo/commands/field/field-get.ts index 9fd81b4458c..40e55719ea4 100644 --- a/src/m365/spo/commands/field/field-get.ts +++ b/src/m365/spo/commands/field/field-get.ts @@ -18,6 +18,7 @@ interface Options extends GlobalOptions { listUrl?: string; id?: string; title?: string; + internalName?: string; } class SpoFieldGetCommand extends SpoCommand { @@ -45,7 +46,8 @@ class SpoFieldGetCommand extends SpoCommand { listTitle: typeof args.options.listTitle !== 'undefined', listUrl: typeof args.options.listUrl !== 'undefined', id: typeof args.options.id !== 'undefined', - title: typeof args.options.title !== 'undefined' + title: typeof args.options.title !== 'undefined', + internalName: typeof args.options.internalName !== 'undefined' }); }); } @@ -69,6 +71,9 @@ class SpoFieldGetCommand extends SpoCommand { }, { option: '-t, --title [title]' + }, + { + option: '--internalName [internalName]' } ); } @@ -95,7 +100,7 @@ class SpoFieldGetCommand extends SpoCommand { } #initOptionSets(): void { - this.optionSets.push({ options: ['id', 'title'] }); + this.optionSets.push({ options: ['id', 'title', 'internalName'] }); } public async commandAction(logger: Logger, args: CommandArgs): Promise { @@ -118,7 +123,7 @@ class SpoFieldGetCommand extends SpoCommand { fieldRestUrl = `/getbyid('${formatting.encodeQueryParameter(args.options.id)}')`; } else { - fieldRestUrl = `/getbyinternalnameortitle('${formatting.encodeQueryParameter(args.options.title as string)}')`; + fieldRestUrl = `/getbyinternalnameortitle('${formatting.encodeQueryParameter((args.options.title || args.options.internalName) as string)}')`; } const requestOptions: CliRequestOptions = { diff --git a/src/m365/spo/commands/field/field-remove.spec.ts b/src/m365/spo/commands/field/field-remove.spec.ts index de7e5156484..a88bd3152c6 100644 --- a/src/m365/spo/commands/field/field-remove.spec.ts +++ b/src/m365/spo/commands/field/field-remove.spec.ts @@ -94,6 +94,18 @@ describe(commands.FIELD_REMOVE, () => { assert(promptIssued); }); + it('prompts before removing field when confirmation argument not passed (internalName)', async () => { + await command.action(logger, { options: { internalName: 'myfield1', webUrl: 'https://contoso.sharepoint.com' } }); + + assert(promptIssued); + }); + + it('prompts before removing list column when confirmation argument not passed (internalName)', async () => { + await command.action(logger, { options: { internalName: 'myfield1', webUrl: 'https://contoso.sharepoint.com', listTitle: 'My List' } }); + + assert(promptIssued); + }); + it('aborts removing field when prompt not confirmed', async () => { sinonUtil.restore(cli.promptForConfirmation); sinon.stub(cli, 'promptForConfirmation').resolves(false); @@ -113,8 +125,7 @@ describe(commands.FIELD_REMOVE, () => { it('removes the field when prompt confirmed', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { requests.push(opts); - - if ((opts.url as string).indexOf(`/_api/web/fields(guid'`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields(guid'b2307a39-e878-458b-bc90-03bc578531d6')`) { if (opts.headers && opts.headers.accept && (opts.headers.accept as string).indexOf('application/json') === 0) { @@ -142,7 +153,7 @@ describe(commands.FIELD_REMOVE, () => { it('command correctly handles field get reject request', async () => { const err = 'Invalid request'; sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf('/_api/web/fields/getbyinternalnameortitle(') > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyinternalnameortitle('field1')`) { throw err; } @@ -161,38 +172,18 @@ describe(commands.FIELD_REMOVE, () => { }), new CommandError(err)); }); - it('uses correct API url when id option is passed', async () => { - sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf('/_api/web/fields/getbyid(\'') > -1) { - return 'Correct Url'; - } - - throw 'Invalid request'; - }); - - const actionId: string = '0CD891EF-AFCE-4E55-B836-FCE03286CCCF'; - - await command.action(logger, { - options: { - id: actionId, - webUrl: 'https://contoso.sharepoint.com', - force: true - } - }); - }); - it('calls the correct remove url when id and list url specified', async () => { const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/GetList('%2Fsites%2Fportal%2FLists%2FEvents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } throw 'Invalid request'; }); - await assert.rejects(command.action(logger, { options: { verbose: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', id: '03e45e84-1992-4d42-9116-26f756012634', listUrl: 'Lists/Events', force: true } })); + await command.action(logger, { options: { debug: true, verbose: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', id: '03e45e84-1992-4d42-9116-26f756012634', listUrl: 'Lists/Events', force: true } }); assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/GetList(\'%2Fsites%2Fportal%2FLists%2FEvents\')/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012634\')'); }); @@ -203,17 +194,17 @@ describe(commands.FIELD_REMOVE, () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/GetList(\'%2Fsites%2Fportal%2FLists%2FEvents\')/fields`) { return { - "value": [{ - "Id": "03e45e84-1992-4d42-9116-26f756012634", - "Group": "MyGroup" + 'value': [{ + 'Id': '03e45e84-1992-4d42-9116-26f756012634', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012635", - "Group": "MyGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012635', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012636", - "Group": "DifferentGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012636', + 'Group': 'DifferentGroup' }] }; } @@ -221,15 +212,14 @@ describe(commands.FIELD_REMOVE, () => { }); const deletion = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/GetList(\'%2Fsites%2Fportal%2FLists%2FEvents\')/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012634\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/GetList('%2Fsites%2Fportal%2FLists%2FEvents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } - - if ((opts.url as string).indexOf(`/_api/web/GetList(\'%2Fsites%2Fportal%2FLists%2FEvents\')/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012635\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/GetList('%2Fsites%2Fportal%2FLists%2FEvents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012635')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012635" + 'Id': '03e45e84-1992-4d42-9116-26f756012635' }; } @@ -247,17 +237,17 @@ describe(commands.FIELD_REMOVE, () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields`) { return { - "value": [{ - "Id": "03e45e84-1992-4d42-9116-26f756012634", - "Group": "MyGroup" + 'value': [{ + 'Id': '03e45e84-1992-4d42-9116-26f756012634', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012635", - "Group": "MyGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012635', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012636", - "Group": "DifferentGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012636', + 'Group': 'DifferentGroup' }] }; } @@ -265,15 +255,14 @@ describe(commands.FIELD_REMOVE, () => { }); const deletion = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012634\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } - - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012635\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012635')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012635" + 'Id': '03e45e84-1992-4d42-9116-26f756012635' }; } @@ -291,17 +280,17 @@ describe(commands.FIELD_REMOVE, () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields`) { return { - "value": [{ - "Id": "03e45e84-1992-4d42-9116-26f756012634", - "Group": "MyGroup" + 'value': [{ + 'Id': '03e45e84-1992-4d42-9116-26f756012634', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012635", - "Group": "MyGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012635', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012636", - "Group": "DifferentGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012636', + 'Group': 'DifferentGroup' }] }; } @@ -311,7 +300,7 @@ describe(commands.FIELD_REMOVE, () => { const deletion = sinon.stub(request, 'post').callsFake(async (opts) => { if ((opts.url as string).indexOf(`/_api/web/fields`) > -1) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } @@ -338,13 +327,12 @@ describe(commands.FIELD_REMOVE, () => { const getStub = sinon.stub(request, 'get').rejects(error); const deletion = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012635\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012635')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012635" + 'Id': '03e45e84-1992-4d42-9116-26f756012635' }; } - - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012634\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw error; } @@ -371,17 +359,17 @@ describe(commands.FIELD_REMOVE, () => { const getStub = sinon.stub(request, 'get').callsFake(async (opts) => { if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields`) { return { - "value": [{ - "Id": "03e45e84-1992-4d42-9116-26f756012634", - "Group": "MyGroup" + 'value': [{ + 'Id': '03e45e84-1992-4d42-9116-26f756012634', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012635", - "Group": "MyGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012635', + 'Group': 'MyGroup' }, { - "Id": "03e45e84-1992-4d42-9116-26f756012636", - "Group": "DifferentGroup" + 'Id': '03e45e84-1992-4d42-9116-26f756012636', + 'Group': 'DifferentGroup' }] }; } @@ -389,13 +377,12 @@ describe(commands.FIELD_REMOVE, () => { }); const deletion = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012635\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012635')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012635" + 'Id': '03e45e84-1992-4d42-9116-26f756012635' }; } - - if ((opts.url as string).indexOf(`/_api/web/fields/getbyid(\'03e45e84-1992-4d42-9116-26f756012634\')`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw error; } @@ -411,9 +398,9 @@ describe(commands.FIELD_REMOVE, () => { it('calls the correct get url when field title and list title specified (verbose)', async () => { const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } @@ -426,9 +413,9 @@ describe(commands.FIELD_REMOVE, () => { it('calls the correct get url when field title and list title specified', async () => { const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } @@ -441,9 +428,9 @@ describe(commands.FIELD_REMOVE, () => { it('calls the correct get url when field title and list url specified', async () => { const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists(guid'03e45e84-1992-4d42-9116-26f756012634')/fields/getbyinternalnameortitle('Title')`) { return { - "Id": "03e45e84-1992-4d42-9116-26f756012634" + 'Id': '03e45e84-1992-4d42-9116-26f756012634' }; } @@ -454,6 +441,51 @@ describe(commands.FIELD_REMOVE, () => { assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists(guid\'03e45e84-1992-4d42-9116-26f756012634\')/fields/getbyinternalnameortitle(\'Title\')'); }); + it('calls the correct get url when field internalName and list title specified (verbose)', async () => { + const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { + return { + 'Id': '03e45e84-1992-4d42-9116-26f756012634' + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, verbose: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', internalName: 'Title', listTitle: 'Documents', force: true } }); + assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle(\'Documents\')/fields/getbyinternalnameortitle(\'Title\')'); + }); + + it('calls the correct get url when field internalName and list title specified', async () => { + const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyinternalnameortitle('Title')`) { + return { + 'Id': '03e45e84-1992-4d42-9116-26f756012634' + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/portal', internalName: 'Title', listTitle: 'Documents', force: true } }); + assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle(\'Documents\')/fields/getbyinternalnameortitle(\'Title\')'); + }); + + it('calls the correct get url when field internalName and list url specified', async () => { + const getStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists(guid'03e45e84-1992-4d42-9116-26f756012634')/fields/getbyinternalnameortitle('Title')`) { + return { + 'Id': '03e45e84-1992-4d42-9116-26f756012634' + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', internalName: 'Title', listId: '03e45e84-1992-4d42-9116-26f756012634', force: true } }); + assert.strictEqual(getStub.lastCall.args[0].url, 'https://contoso.sharepoint.com/sites/portal/_api/web/lists(guid\'03e45e84-1992-4d42-9116-26f756012634\')/fields/getbyinternalnameortitle(\'Title\')'); + }); + it('correctly handles site column not found', async () => { const error = { error: { @@ -466,7 +498,7 @@ describe(commands.FIELD_REMOVE, () => { } }; sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf('/_api/web/fields/getbyinternalnameortitle(') > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/fields/getbyinternalnameortitle('actionTitle')`) { throw error; } throw 'Invalid request'; @@ -479,14 +511,14 @@ describe(commands.FIELD_REMOVE, () => { it('correctly handles list column not found', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists/getByTitle('Documents')/fields/getbyid(`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw { error: { - "odata.error": { - "code": "-2147024809, System.ArgumentException", - "message": { - "lang": "en-US", - "value": "Invalid field name. {03e45e84-1992-4d42-9116-26f756012634} /sites/portal/Shared Documents" + 'odata.error': { + 'code': '-2147024809, System.ArgumentException', + 'message': { + 'lang': 'en-US', + 'value': 'Invalid field name. {03e45e84-1992-4d42-9116-26f756012634} /sites/portal/Shared Documents' } } } @@ -502,14 +534,14 @@ describe(commands.FIELD_REMOVE, () => { it('correctly handles list not found', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_api/web/lists/getByTitle('Documents')/fields/getbyid(`) > -1) { + if (opts.url === `https://contoso.sharepoint.com/sites/portal/_api/web/lists/getByTitle('Documents')/fields/getbyid('03e45e84-1992-4d42-9116-26f756012634')`) { throw { error: { - "odata.error": { - "code": "-1, System.ArgumentException", - "message": { - "lang": "en-US", - "value": "List 'Documents' does not exist at site with URL 'https://contoso.sharepoint.com/sites/portal'." + 'odata.error': { + 'code': '-1, System.ArgumentException', + 'message': { + 'lang': 'en-US', + 'value': 'List \'Documents\' does not exist at site with URL \'https://contoso.sharepoint.com/sites/portal\'.' } } } @@ -520,7 +552,7 @@ describe(commands.FIELD_REMOVE, () => { }); await assert.rejects(command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/portal', id: '03e45e84-1992-4d42-9116-26f756012634', listTitle: 'Documents', force: true } } as any), - new CommandError("List 'Documents' does not exist at site with URL 'https://contoso.sharepoint.com/sites/portal'.")); + new CommandError('List \'Documents\' does not exist at site with URL \'https://contoso.sharepoint.com/sites/portal\'.')); }); it('supports specifying URL', () => { @@ -534,7 +566,7 @@ describe(commands.FIELD_REMOVE, () => { assert(containsTypeOption); }); - it('fails validation if both id and title options are not passed', async () => { + it('fails validation if either of id, title and internalName options are not passed', async () => { sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.prompt) { return false; diff --git a/src/m365/spo/commands/field/field-remove.ts b/src/m365/spo/commands/field/field-remove.ts index fb8efabb529..1fb8a700d2d 100644 --- a/src/m365/spo/commands/field/field-remove.ts +++ b/src/m365/spo/commands/field/field-remove.ts @@ -19,6 +19,7 @@ interface Options extends GlobalOptions { group?: string; listTitle?: string; title?: string; + internalName?: string; listUrl?: string; webUrl: string; } @@ -50,6 +51,7 @@ class SpoFieldRemoveCommand extends SpoCommand { id: typeof args.options.id !== 'undefined', group: typeof args.options.group !== 'undefined', title: typeof args.options.title !== 'undefined', + internalName: typeof args.options.internalName !== 'undefined', force: (!(!args.options.force)).toString() }); }); @@ -75,6 +77,9 @@ class SpoFieldRemoveCommand extends SpoCommand { { option: '-t, --title [title]' }, + { + option: '--internalName [internalName]' + }, { option: '-g, --group [group]' }, @@ -106,7 +111,7 @@ class SpoFieldRemoveCommand extends SpoCommand { } #initOptionSets(): void { - this.optionSets.push({ options: ['id', 'title', 'group'] }); + this.optionSets.push({ options: ['id', 'title', 'internalName', 'group'] }); } public async commandAction(logger: Logger, args: CommandArgs): Promise { @@ -191,7 +196,8 @@ class SpoFieldRemoveCommand extends SpoCommand { } else { try { - await removeField(listRestUrl, args.options.id, args.options.title); + const columnName: string | undefined = args.options.title ? args.options.title : args.options.internalName; + await removeField(listRestUrl, args.options.id, columnName); // REST post call doesn't return anything } catch (err: any) { @@ -204,7 +210,7 @@ class SpoFieldRemoveCommand extends SpoCommand { await prepareRemoval(); } else { - const confirmMessage: string = `Are you sure you want to remove the ${args.options.group ? 'fields' : 'field'} ${args.options.id || args.options.title || 'from group ' + args.options.group} ${messageEnd}?`; + const confirmMessage: string = `Are you sure you want to remove the ${args.options.group ? 'fields' : 'field'} ${args.options.id || args.options.title || args.options.internalName || 'from group ' + args.options.group} ${messageEnd}?`; const result = await cli.promptForConfirmation({ message: confirmMessage }); diff --git a/src/m365/spo/commands/field/field-set.spec.ts b/src/m365/spo/commands/field/field-set.spec.ts index adeb18a8efd..4a87e64672c 100644 --- a/src/m365/spo/commands/field/field-set.spec.ts +++ b/src/m365/spo/commands/field/field-set.spec.ts @@ -76,7 +76,7 @@ describe(commands.FIELD_SET, () => { it('updates site column specified by title', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -112,9 +112,47 @@ describe(commands.FIELD_SET, () => { assert(loggerLogSpy.notCalled); }); + it('updates site column specified by internalName', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { + if (opts.headers && + opts.headers['X-RequestDigest'] !== 'ABC') { + throw 'Invalid request'; + } + + if (opts.data === `MyColumn`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": null, + "TraceCorrelationId": "7c0aa19e-1058-0000-37ae-14170affbedb" + }, 664, { + "IsNull": false + }, 665, { + "_ObjectType_": "SP.FieldText", + "_ObjectIdentity_": "7c0aa19e-1058-0000-37ae-14170affbedb|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:ff7a8065-9120-4c0a-982a-163ab9014179:web:e781d3dc-238d-44f7-8724-5e3e9eabcd6e:field:5d021339-4d62-4fe9-9d2a-c99bc56a157a" + }]); + } + + if (opts.data === `My columnfalse`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.8231.1213", "ErrorInfo": null, "TraceCorrelationId": "b909a19e-5020-0000-37ae-17f800b4ea4c" + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com', internalName: 'MyColumn', Description: 'My column' } }); + assert(loggerLogSpy.notCalled); + }); + it('updates site column specified by id, pushing the changes to existing lists', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -151,7 +189,7 @@ describe(commands.FIELD_SET, () => { it('updates list column specified by id, list specified by id', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -204,7 +242,7 @@ describe(commands.FIELD_SET, () => { it('updates list column specified by title, list specified by title', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -254,6 +292,58 @@ describe(commands.FIELD_SET, () => { await command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com', listTitle: 'My List', title: 'MyColumn', Description: 'My column' } }); }); + it('updates list column specified by internalName, list specified by title', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { + if (opts.headers && + opts.headers['X-RequestDigest'] !== 'ABC') { + throw 'Invalid request'; + } + + if (opts.data === `My List`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": null, + "TraceCorrelationId": "270fa19e-f0f7-0000-37ae-1733ad1b6703" + }, 664, { + "IsNull": false + }, 665, { + "_ObjectType_": "SP.List", + "_ObjectIdentity_": "270fa19e-f0f7-0000-37ae-1733ad1b6703|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:ff7a8065-9120-4c0a-982a-163ab9014179:web:e781d3dc-238d-44f7-8724-5e3e9eabcd6e:list:03cef05c-ba50-4dcf-a876-304f0626085c", + "_ObjectVersion_": "6" + }]); + } + + if (opts.data === `MyColumn`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": null, + "TraceCorrelationId": "7c0aa19e-1058-0000-37ae-14170affbedb" + }, 664, { + "IsNull": false + }, 665, { + "_ObjectType_": "SP.FieldText", + "_ObjectIdentity_": "fe0ea19e-7022-0000-37ae-1357e77e046c|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:ff7a8065-9120-4c0a-982a-163ab9014179:web:e781d3dc-238d-44f7-8724-5e3e9eabcd6e:list:03cef05c-ba50-4dcf-a876-304f0626085c:field:5d021339-4d62-4fe9-9d2a-c99bc56a157a" + }]); + } + + if (opts.data === `My columnfalse`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.8231.1213", "ErrorInfo": null, "TraceCorrelationId": "b909a19e-5020-0000-37ae-17f800b4ea4c" + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com', listTitle: 'My List', internalName: 'MyColumn', Description: 'My column' } }); + }); + it('updates list column specified by name, list specified by url', async () => { const webUrl = 'https://contoso.sharepoint.com'; const listUrl = '/lists/TestList'; @@ -309,9 +399,64 @@ describe(commands.FIELD_SET, () => { await command.action(logger, { options: { debug: true, webUrl: webUrl, listUrl: listUrl, title: 'MyColumn', Description: 'My column Description' } }); }); + it('updates list column specified by internalName, list specified by url', async () => { + const webUrl = 'https://contoso.sharepoint.com'; + const listUrl = '/lists/TestList'; + const listServerRelativeUrl: string = urlUtil.getServerRelativePath(webUrl, listUrl); + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { + if (opts.headers && + opts.headers['X-RequestDigest'] !== 'ABC') { + throw 'Invalid request'; + } + + if (opts.data === `${listServerRelativeUrl}`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": null, + "TraceCorrelationId": "270fa19e-f0f7-0000-37ae-1733ad1b6703" + }, 664, { + "IsNull": false + }, 665, { + "_ObjectType_": "SP.List", + "_ObjectIdentity_": "270fa19e-f0f7-0000-37ae-1733ad1b6703|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:ff7a8065-9120-4c0a-982a-163ab9014179:web:e781d3dc-238d-44f7-8724-5e3e9eabcd6e:list:03cef05c-ba50-4dcf-a876-304f0626085c", + "_ObjectVersion_": "6" + }]); + } + + if (opts.data === `MyColumn`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": null, + "TraceCorrelationId": "7c0aa19e-1058-0000-37ae-14170affbedb" + }, 664, { + "IsNull": false + }, 665, { + "_ObjectType_": "SP.FieldText", + "_ObjectIdentity_": "fe0ea19e-7022-0000-37ae-1357e77e046c|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:ff7a8065-9120-4c0a-982a-163ab9014179:web:e781d3dc-238d-44f7-8724-5e3e9eabcd6e:list:03cef05c-ba50-4dcf-a876-304f0626085c:field:5d021339-4d62-4fe9-9d2a-c99bc56a157a" + }]); + } + + if (opts.data === `My column Descriptionfalse`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.8231.1213", "ErrorInfo": null, "TraceCorrelationId": "b909a19e-5020-0000-37ae-17f800b4ea4c" + } + ]); + } + } + + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, webUrl: webUrl, listUrl: listUrl, internalName: 'MyColumn', Description: 'My column Description' } }); + }); + it('correctly escapes XML in list title', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -363,7 +508,7 @@ describe(commands.FIELD_SET, () => { it('ignores global options when creating request data', async () => { const postStub: Sinon.SinonStub = sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -416,7 +561,7 @@ describe(commands.FIELD_SET, () => { it('correctly escapes XML in field title', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'abc') { throw 'Invalid request'; @@ -452,9 +597,47 @@ describe(commands.FIELD_SET, () => { assert(loggerLogSpy.notCalled); }); + it('correctly escapes XML in field internalName', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { + if (opts.headers && + opts.headers['X-RequestDigest'] !== 'abc') { + throw 'Invalid request'; + } + + if (opts.data === `MyColumn>`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": null, + "TraceCorrelationId": "7c0aa19e-1058-0000-37ae-14170affbedb" + }, 664, { + "IsNull": false + }, 665, { + "_ObjectType_": "SP.FieldText", + "_ObjectIdentity_": "7c0aa19e-1058-0000-37ae-14170affbedb|740c6a0b-85e2-48a0-a494-e0f1759d4aa7:site:ff7a8065-9120-4c0a-982a-163ab9014179:web:e781d3dc-238d-44f7-8724-5e3e9eabcd6e:field:5d021339-4d62-4fe9-9d2a-c99bc56a157a" + }]); + } + + if (opts.data === `My columnfalse`) { + return JSON.stringify([ + { + "SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.8231.1213", "ErrorInfo": null, "TraceCorrelationId": "b909a19e-5020-0000-37ae-17f800b4ea4c" + } + ]); + } + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com', internalName: 'MyColumn>', Description: 'My column' } })); + assert(loggerLogSpy.notCalled); + }); + it('correctly escapes XML in field properties', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'abc') { throw 'Invalid request'; @@ -492,7 +675,7 @@ describe(commands.FIELD_SET, () => { it('correctly handles an error when the field specified by id doesn\'t exist', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -523,7 +706,7 @@ describe(commands.FIELD_SET, () => { it('correctly handles an error when the field specified by title doesn\'t exist', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -552,9 +735,40 @@ describe(commands.FIELD_SET, () => { new CommandError(`Column 'MyColumn' does not exist. It may have been deleted by another user.`)); }); + it('correctly handles an error when the field specified by internalName doesn\'t exist', async () => { + sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { + if (opts.headers && + opts.headers['X-RequestDigest'] !== 'ABC') { + throw 'Invalid request'; + } + + if (opts.data === `MyColumn`) { + return JSON.stringify([{ + "SchemaVersion": "15.0.0.0", + "LibraryVersion": "16.0.8231.1213", + "ErrorInfo": { + "ErrorMessage": "Column 'MyColumn' does not exist. It may have been deleted by another user.", + "ErrorValue": null, + "TraceCorrelationId": "4c0fa19e-b007-0000-37ae-1d177693b378", + "ErrorCode": -2147024809, + "ErrorTypeName": "System.ArgumentException" + }, + "TraceCorrelationId": "4c0fa19e-b007-0000-37ae-1d177693b378" + }]); + } + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com', internalName: 'MyColumn', Description: 'My column' } } as any), + new CommandError(`Column 'MyColumn' does not exist. It may have been deleted by another user.`)); + }); + it('correctly handles an error when the list specified by id doesn\'t exist', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -585,7 +799,7 @@ describe(commands.FIELD_SET, () => { it('correctly handles an error when the list specified by title doesn\'t exist', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -616,7 +830,7 @@ describe(commands.FIELD_SET, () => { it('correctly handles an error when updating the field failed', async () => { sinon.stub(request, 'post').callsFake(async (opts) => { - if ((opts.url as string).indexOf(`/_vti_bin/client.svc/ProcessQuery`) > -1) { + if (opts.url === 'https://contoso.sharepoint.com/_vti_bin/client.svc/ProcessQuery') { if (opts.headers && opts.headers['X-RequestDigest'] !== 'ABC') { throw 'Invalid request'; @@ -669,7 +883,7 @@ describe(commands.FIELD_SET, () => { assert.notStrictEqual(actual, true); }); - it('fails validation if neither id nor title are specified', async () => { + it('fails validation if either id, title, or internalName are not specified', async () => { sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { if (settingName === settingsNames.prompt) { return false; @@ -695,6 +909,45 @@ describe(commands.FIELD_SET, () => { assert.notStrictEqual(actual, true); }); + it('fails validation if both id and internalName are specified', async () => { + sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { + if (settingName === settingsNames.prompt) { + return false; + } + + return defaultValue; + }); + + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', id: '330f29c5-5c4c-465f-9f4b-7903020ae1ce', internalName: 'MyColumn' } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('fails validation if both title and internalName are specified', async () => { + sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { + if (settingName === settingsNames.prompt) { + return false; + } + + return defaultValue; + }); + + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', title: 'MyColumn', internalName: 'MyColumn' } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('fails validation if id, title and internalName are specified', async () => { + sinon.stub(cli, 'getSettingWithDefaultValue').callsFake((settingName, defaultValue) => { + if (settingName === settingsNames.prompt) { + return false; + } + + return defaultValue; + }); + + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', id: '330f29c5-5c4c-465f-9f4b-7903020ae1ce', title: 'MyColumn', internalName: 'MyColumn' } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + it('fails validation if id is specified and is not a valid GUID', async () => { const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', id: 'invalid' } }, commandInfo); assert.notStrictEqual(actual, true); @@ -720,6 +973,11 @@ describe(commands.FIELD_SET, () => { assert.strictEqual(actual, true); }); + it('passes validation when webUrl, listId and internalName are specified', async () => { + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', listId: '330f29c5-5c4c-465f-9f4b-7903020ae1ce', internalName: 'MyColumn' } }, commandInfo); + assert.strictEqual(actual, true); + }); + it('passes validation when webUrl, listTitle and id are specified', async () => { const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', listTitle: 'My List', id: '330f29c5-5c4c-465f-9f4b-7903020ae1ce' } }, commandInfo); assert.strictEqual(actual, true); diff --git a/src/m365/spo/commands/field/field-set.ts b/src/m365/spo/commands/field/field-set.ts index f95eeb0f7f1..64fd67f10e8 100644 --- a/src/m365/spo/commands/field/field-set.ts +++ b/src/m365/spo/commands/field/field-set.ts @@ -16,6 +16,7 @@ interface CommandArgs { interface Options extends GlobalOptions { id?: string; title?: string; + internalName?: string; listId?: string; listTitle?: string; listUrl?: string; @@ -46,6 +47,7 @@ class SpoFieldSetCommand extends SpoCommand { Object.assign(this.telemetryProperties, { id: typeof args.options.id !== 'undefined', title: typeof args.options.title !== 'undefined', + internalName: typeof args.options.internalName !== 'undefined', listId: typeof args.options.listId !== 'undefined', listTitle: typeof args.options.listTitle !== 'undefined', listUrl: typeof args.options.listUrl !== 'undefined', @@ -74,6 +76,9 @@ class SpoFieldSetCommand extends SpoCommand { { option: '-t, --title [title]' }, + { + option: '--internalName [internalName]' + }, { option: '--updateExistingLists' } @@ -109,7 +114,7 @@ class SpoFieldSetCommand extends SpoCommand { } #initOptionSets(): void { - this.optionSets.push({ options: ['id', 'title'] }); + this.optionSets.push({ options: ['id', 'title', 'internalName'] }); } public allowUnknownOptions(): boolean | undefined { @@ -158,7 +163,7 @@ class SpoFieldSetCommand extends SpoCommand { // retrieve column CSOM object id const fieldQuery: string = args.options.id ? `${formatting.escapeXml(args.options.id)}` : - `${formatting.escapeXml(args.options.name || args.options.title)}`; + `${formatting.escapeXml(args.options.name || args.options.title || args.options.internalName)}`; let requestOptions: CliRequestOptions = { url: `${args.options.webUrl}/_vti_bin/client.svc/ProcessQuery`, @@ -206,6 +211,7 @@ class SpoFieldSetCommand extends SpoCommand { 'listUrl', 'id', 'title', + 'internalName', 'updateExistingLists', 'debug', 'verbose',