From 04320fd23920a47ba1912ef6dd41788a06d5170b Mon Sep 17 00:00:00 2001 From: Joseff <40164689+joseffffff@users.noreply.github.com> Date: Thu, 23 May 2024 19:47:24 +0200 Subject: [PATCH] DeleteAll method (#9) --- README.md | 18 ++++++ src/GoogleSpreadsheetsOrm.ts | 88 +++++++++++++------------- tests/GoogleSpreadsheetsOrm.test.ts | 98 +++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index e738fc6..5582e0b 100644 --- a/README.md +++ b/README.md @@ -154,3 +154,21 @@ await orm.delete(entityToDelete); - This method deletes the row in which the entity was persisted. - It internally fetches the sheet data to find which row needs to delete. - Quota retries are automatically handled to manage API rate limits. + +### `deleteAll(entities: T[])` + +Deletes the provided entities from the spreadsheet. + +```typescript +const myEntities: YourEntity[] = await orm.all(); + +const entitiesToDelete: YourEntity = myEntities.filter(e => e.shouldBeDeleted()); + +await orm.delete(entitiesToDelete); +``` + +- **Parameters**: + - `entities`: The entity array to delete in the sheet. +- **Remarks**: + - It internally fetches the sheet data to find which row needs to delete. + - Quota retries are automatically handled to manage API rate limits. diff --git a/src/GoogleSpreadsheetsOrm.ts b/src/GoogleSpreadsheetsOrm.ts index aa3b11e..0350f2d 100644 --- a/src/GoogleSpreadsheetsOrm.ts +++ b/src/GoogleSpreadsheetsOrm.ts @@ -75,30 +75,7 @@ export class GoogleSpreadsheetsOrm { * @returns A Promise that resolves when the row deletion process is completed successfully. */ public async delete(entity: T): Promise { - const { data } = await this.findTableData(); - const rowNumber = this.rowNumber(data, entity); - - const sheetId = await this.fetchSheetDetails().then(sheetDetails => sheetDetails.properties?.sheetId); - - await this.sheetsClientProvider.handleQuotaRetries(sheetsClient => - sheetsClient.spreadsheets.batchUpdate({ - spreadsheetId: this.options.spreadsheetId, - requestBody: { - requests: [ - { - deleteDimension: { - range: { - sheetId, - dimension: 'ROWS', - startIndex: rowNumber - 1, // index, not a rowNumber here - endIndex: rowNumber, // exclusive, to delete just one row - }, - }, - }, - ], - }, - }), - ); + return this.deleteAll([entity]); } /** @@ -141,6 +118,50 @@ export class GoogleSpreadsheetsOrm { ); } + /** + * Deletes the rows associated with the provided entities in the specified sheet. + * + * @param entities - An array of entities objects to delete + * + * @remarks + * @remarks + * It internally retrieves all data from the specified sheet. + * Quota retries are automatically handled to manage API rate limits. + * + * @returns A Promise that resolves when all the row deletion processes are completed successfully. + */ + public async deleteAll(entities: T[]): Promise { + if (entities.length === 0) { + return; + } + + const { data } = await this.findTableData(); + const rowNumbers = entities + .map(entity => this.rowNumber(data, entity)) + // rows are deleted from bottom to top + .sort((a, b) => b - a); + + const sheetId = await this.fetchSheetDetails().then(sheetDetails => sheetDetails.properties?.sheetId); + + await this.sheetsClientProvider.handleQuotaRetries(sheetsClient => + sheetsClient.spreadsheets.batchUpdate({ + spreadsheetId: this.options.spreadsheetId, + requestBody: { + requests: rowNumbers.map(rowNumber => ({ + deleteDimension: { + range: { + sheetId, + dimension: 'ROWS', + startIndex: rowNumber - 1, // index, not a rowNumber here, so -1 + endIndex: rowNumber, // exclusive, to delete just one row + }, + }, + })), + }, + }), + ); + } + // public updateAll(entities: T[]): boolean { // if (entities.length === 0) { // return true; @@ -182,25 +203,6 @@ export class GoogleSpreadsheetsOrm { return sheetDetails; } - // public deleteAll(entities: T[]): boolean { - // if (entities.length === 0) { - // return true; - // } - // - // const sheet = this.sheet(); - // - // const data = this.allSheetDataFromSheet(sheet); - // data.shift(); // Delete headers - // - // const rowNumbers = entities.map(entity => this.rowNumber(data, entity)).sort((a, b) => b - a); - // - // this.ioTimingsReporter.measureTime(InputOutputOperation.DB_DELETE_ALL, () => - // rowNumbers.forEach(rowNumber => sheet.deleteRow(rowNumber)), - // ); - // - // return true; - // } - // private rowNumber(data: ParsedSpreadsheetCellValue[][], entity: T): number { const index = data.findIndex(row => row[0] === entity.id); diff --git a/tests/GoogleSpreadsheetsOrm.test.ts b/tests/GoogleSpreadsheetsOrm.test.ts index 277e414..2e2a003 100644 --- a/tests/GoogleSpreadsheetsOrm.test.ts +++ b/tests/GoogleSpreadsheetsOrm.test.ts @@ -359,6 +359,104 @@ describe(GoogleSpreadsheetsOrm.name, () => { ); }); + test('deleteAll method should correctly delete many rows', async () => { + mockValuesResponse([ + ['id', 'createdAt', 'name', 'jsonField', 'current', 'year'], + [ + 'ae222b54-182f-4958-b77f-26a3a04dff34', // id + '29/12/2023 17:47:04', // createdAt + 'John Doe', // name + // language=json + '{"a":"b","c":[1,2,3]}', // jsonField + 'true', // current + '2023', // year + ], + [ + 'ae222b54-182f-4958-b77f-26a3a04dff35', // id + '29/12/2023 17:47:04', // createdAt + 'John Doe', // name + // language=json + '{"a":"b","c":[1,2,3]}', // jsonField + 'true', // current + '2023', // year + ], + ]); + + mockSpreadsheetDetailsResponse({ + data: { + sheets: [ + { + properties: { + title: SHEET, + sheetId: 1234, + }, + }, + ], + }, + } as never); + + const entitiesToDelete: TestEntity[] = [ + { + id: 'ae222b54-182f-4958-b77f-26a3a04dff34', + createdAt: new Date('2023-12-29 17:47:04'), + name: 'John Doe', + jsonField: { + a: 'b', + c: [1, 2, 3], + }, + current: true, + year: 2023, + }, + { + id: 'ae222b54-182f-4958-b77f-26a3a04dff35', + createdAt: new Date('2023-12-29 17:47:04'), + name: 'John Doe', + jsonField: { + a: 'b', + c: [1, 2, 3], + }, + current: true, + year: 2023, + }, + ]; + + await sut.deleteAll(entitiesToDelete); + + expect(getBatchUpdateUsedSheetClient()?.spreadsheets.batchUpdate).toHaveBeenCalledWith({ + spreadsheetId: SPREADSHEET_ID, + requestBody: { + requests: [ + { + deleteDimension: { + range: { + sheetId: 1234, + dimension: 'ROWS', + startIndex: 2, // row 3 + endIndex: 3, + }, + }, + }, + { + deleteDimension: { + range: { + sheetId: 1234, + dimension: 'ROWS', + startIndex: 1, // row 3 + endIndex: 2, + }, + }, + }, + ], + }, + }); + }); + + test('deleteAll does not delete anything if no entities are passed', async () => { + await sut.deleteAll([]); + // @ts-ignore + expect(sheetClients.every(client => client.spreadsheets.batchUpdate.mock.calls.length === 0)).toBeTruthy(); + }); + function mockValuesResponse(rawValues: string[][]): void { sheetClients .map(s => s.spreadsheets.values as MockProxy)