From 182ba62cb5250259a36519f6a7e87e36c9430328 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 12 Oct 2024 21:09:45 +0200 Subject: [PATCH] Allow case-insensitive column name Id on spo listitem batch remove. Closes #6419 --- .../listitem/listitem-batch-remove.spec.ts | 16 ++++++++++++++ .../listitem/listitem-batch-remove.ts | 21 ++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/m365/spo/commands/listitem/listitem-batch-remove.spec.ts b/src/m365/spo/commands/listitem/listitem-batch-remove.spec.ts index 99b57f71ff1..22da1148b8e 100644 --- a/src/m365/spo/commands/listitem/listitem-batch-remove.spec.ts +++ b/src/m365/spo/commands/listitem/listitem-batch-remove.spec.ts @@ -122,6 +122,22 @@ describe(commands.LISTITEM_BATCH_REMOVE, () => { assert(postStub.calledOnce); }); + it('removes items in batch from a SharePoint list retrieved by id when using a csv file with different casing for the ID column', async () => { + sinonUtil.restore(cli.promptForConfirmation); + sinon.stub(cli, 'promptForConfirmation').resolves(true); + + sinon.stub(fs, 'readFileSync').returns(`id\n1`); + const postStub = sinon.stub(request, 'post').callsFake(async (opts: any) => { + if (opts.url === `${webUrl}/_api/$batch`) { + return mockBatchSuccessfulResponse; + } + throw 'Invalid request'; + }); + + await command.action(logger, { options: { webUrl: webUrl, filePath: filePath, listId: listId, recycle: true, verbose: true } }); + assert(postStub.calledOnce); + }); + it('removes items from a sharepoint list retrieved by id when passing a list of ids via string', async () => { const postStub = sinon.stub(request, 'post').callsFake(async (opts: any) => { if (opts.url === `${webUrl}/_api/$batch`) { diff --git a/src/m365/spo/commands/listitem/listitem-batch-remove.ts b/src/m365/spo/commands/listitem/listitem-batch-remove.ts index 20c91eed6c4..1ed2f22fb84 100644 --- a/src/m365/spo/commands/listitem/listitem-batch-remove.ts +++ b/src/m365/spo/commands/listitem/listitem-batch-remove.ts @@ -109,22 +109,24 @@ class SpoListItemBatchRemoveCommand extends SpoCommand { const fileContent = fs.readFileSync(args.options.filePath, 'utf-8'); const jsonContent: any[] = formatting.parseCsvToJson(fileContent); - if (!jsonContent[0].hasOwnProperty('ID')) { + const idKey = Object.keys(jsonContent[0]).find(key => key.toLowerCase() === 'id'); + + if (!idKey) { return `The file does not contain the required header row with the column name 'ID'.`; } - const nonNumbers = jsonContent.filter(element => isNaN(Number(element['ID'].toString().trim()))).map(element => element['ID']); + const invalidIDs = jsonContent.filter(element => !validation.isValidPositiveInteger(element[idKey].toString().trim())).map(element => element[idKey]); - if (nonNumbers.length > 0) { - return `The specified ids '${nonNumbers.join(', ')}' are invalid numbers.`; + if (invalidIDs.length > 0) { + return `The file contains one or more invalid IDs: '${invalidIDs.join(', ')}'.`; } } if (args.options.ids) { - const nonNumbers = formatting.splitAndTrim(args.options.ids).filter(element => isNaN(Number(element))); + const isValidIntegerArray = validation.isValidPositiveIntegerArray(args.options.ids); - if (nonNumbers.length > 0) { - return `The specified ids '${nonNumbers.join(', ')}' are invalid numbers.`; + if (isValidIntegerArray !== true) { + return `Option 'ids' contains one or more invalid IDs: '${isValidIntegerArray}'.`; } } @@ -167,7 +169,10 @@ class SpoListItemBatchRemoveCommand extends SpoCommand { if (args.options.filePath) { const csvContent = fs.readFileSync(args.options.filePath, 'utf-8'); const jsonContent = formatting.parseCsvToJson(csvContent); - idsToRemove = jsonContent.map((item: { ID: string }) => item['ID']); + + const idKey = Object.keys(jsonContent[0]).find(key => key.toLowerCase() === 'id'); + + idsToRemove = jsonContent.map((item: any) => item[idKey!]); } else { idsToRemove = formatting.splitAndTrim(args.options.ids!);