Skip to content

Commit 67f4245

Browse files
authored
RI-6564: Download bulk delete report (#4427)
* bulk delete keys to the frontend * remove dummy logging * add BulkDeleteSummaryButton * styling * this is better * more styling * fix tests * add test for UI * add tests * add more testss * use proper type * use proper type in UI * address PR comments * change children type * report the key type * add tests * use consistent naming of key type
1 parent dacde95 commit 67f4245

17 files changed

+435
-27
lines changed

redisinsight/api/src/__mocks__/bulk-actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const mockBulkActionOverview = {
2323
processed: 0,
2424
succeed: 0,
2525
errors: [],
26+
keys: [],
2627
},
2728
};
2829

redisinsight/api/src/modules/bulk-actions/bulk-import.service.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ describe('BulkImportService', () => {
188188
succeed: 100_000,
189189
failed: 0,
190190
errors: [],
191+
keys: [],
191192
},
192193
duration: expect.anything(),
193194
});
@@ -207,6 +208,7 @@ describe('BulkImportService', () => {
207208
succeed: 10_000,
208209
failed: 0,
209210
errors: [],
211+
keys: [],
210212
},
211213
duration: expect.anything(),
212214
});
@@ -228,6 +230,7 @@ describe('BulkImportService', () => {
228230
succeed: 0,
229231
failed: 2,
230232
errors: [],
233+
keys: [],
231234
},
232235
duration: expect.anything(),
233236
});

redisinsight/api/src/modules/bulk-actions/bulk-import.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export class BulkImportService {
8080
succeed: 0,
8181
failed: 0,
8282
errors: [],
83+
keys: [],
8384
},
8485
progress: null,
8586
filter: null,
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import { RedisString } from 'src/common/constants';
2+
13
export interface IBulkActionSummaryOverview {
2-
processed: number,
3-
succeed: number,
4-
failed: number,
5-
errors: Array<Record<string, string>>
4+
processed: number;
5+
succeed: number;
6+
failed: number;
7+
errors: Array<Record<string, string>>;
8+
keys: Array<RedisString>;
69
}

redisinsight/api/src/modules/bulk-actions/models/bulk-action-summary.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ describe('BulkActionSummary', () => {
100100
succeed: 500,
101101
failed: 1000,
102102
errors: generateErrors(500),
103+
keys: [],
103104
});
104105

105106
expect(summary['processed']).toEqual(1500);

redisinsight/api/src/modules/bulk-actions/models/bulk-action-summary.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RedisString } from 'src/common/constants';
12
import { IBulkActionSummaryOverview } from 'src/modules/bulk-actions/interfaces/bulk-action-summary-overview.interface';
23

34
export class BulkActionSummary {
@@ -9,6 +10,8 @@ export class BulkActionSummary {
910

1011
private errors: Array<Record<string, string>> = [];
1112

13+
private keys: Array<RedisString> = [];
14+
1215
addProcessed(count: number) {
1316
this.processed += count;
1417
}
@@ -29,12 +32,17 @@ export class BulkActionSummary {
2932
}
3033
}
3134

35+
addKeys(keys: Array<RedisString>) {
36+
this.keys.push(...keys);
37+
}
38+
3239
getOverview(): IBulkActionSummaryOverview {
3340
const overview = {
3441
processed: this.processed,
3542
succeed: this.succeed,
3643
failed: this.failed,
3744
errors: this.errors,
45+
keys: this.keys,
3846
};
3947

4048
this.errors = [];

redisinsight/api/src/modules/bulk-actions/models/bulk-action.spec.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ describe('AbstractBulkActionSimpleRunner', () => {
114114
succeed: 0,
115115
failed: 0,
116116
errors: [],
117+
keys: [],
117118
});
118119

119120
await new Promise((res) => setTimeout(res, 100));
@@ -141,6 +142,7 @@ describe('AbstractBulkActionSimpleRunner', () => {
141142
succeed: 0,
142143
failed: 0,
143144
errors: [],
145+
keys: [],
144146
});
145147

146148
await new Promise((res) => setTimeout(res, 100));
@@ -168,6 +170,7 @@ describe('AbstractBulkActionSimpleRunner', () => {
168170
succeed: 0,
169171
failed: 0,
170172
errors: [],
173+
keys: [],
171174
});
172175

173176
await new Promise((res) => setTimeout(res, 100));
@@ -207,6 +210,7 @@ describe('AbstractBulkActionSimpleRunner', () => {
207210
succeed: 900_000,
208211
failed: 100_000,
209212
errors: generateMockBulkActionErrors(500, false),
213+
keys: [],
210214
});
211215
});
212216
it('should return overview for cluster', async () => {
@@ -227,10 +231,96 @@ describe('AbstractBulkActionSimpleRunner', () => {
227231
succeed: 2_700_000,
228232
failed: 300_000,
229233
errors: generateMockBulkActionErrors(500, false),
234+
keys: [],
230235
});
231236
});
232237
});
233238

239+
describe('getOverview - keys aggregation', () => {
240+
let mockSummary1;
241+
let mockSummary2;
242+
let mockSummary3;
243+
let mockRunner1;
244+
let mockRunner2;
245+
let mockRunner3;
246+
let mockProgress1;
247+
let mockProgress2;
248+
let mockProgress3;
249+
250+
beforeEach(() => {
251+
mockProgress1 = {
252+
getOverview: jest.fn().mockReturnValue({ total: 500, scanned: 400 }),
253+
};
254+
mockProgress2 = {
255+
getOverview: jest.fn().mockReturnValue({ total: 1000, scanned: 800 }),
256+
};
257+
mockProgress3 = {
258+
getOverview: jest.fn().mockReturnValue({ total: 1500, scanned: 1200 }),
259+
};
260+
261+
mockSummary1 = {
262+
getOverview: jest.fn().mockReturnValue({
263+
processed: 100,
264+
succeed: 90,
265+
failed: 10,
266+
errors: [],
267+
keys: ['key1', 'key2'],
268+
}),
269+
};
270+
mockSummary2 = {
271+
getOverview: jest.fn().mockReturnValue({
272+
processed: 200,
273+
succeed: 180,
274+
failed: 20,
275+
errors: [],
276+
keys: ['key3'],
277+
}),
278+
};
279+
mockSummary3 = {
280+
getOverview: jest.fn().mockReturnValue({
281+
processed: 300,
282+
succeed: 270,
283+
failed: 30,
284+
errors: [],
285+
keys: ['key4', 'key5', 'key6'],
286+
}),
287+
};
288+
289+
mockRunner1 = {
290+
getSummary: jest.fn().mockReturnValue(mockSummary1),
291+
getProgress: jest.fn().mockReturnValue(mockProgress1),
292+
};
293+
mockRunner2 = {
294+
getSummary: jest.fn().mockReturnValue(mockSummary2),
295+
getProgress: jest.fn().mockReturnValue(mockProgress2),
296+
};
297+
mockRunner3 = {
298+
getSummary: jest.fn().mockReturnValue(mockSummary3),
299+
getProgress: jest.fn().mockReturnValue(mockProgress3),
300+
};
301+
302+
bulkAction['runners'] = [mockRunner1, mockRunner2, mockRunner3];
303+
bulkAction['status'] = BulkActionStatus.Completed;
304+
});
305+
306+
it('should correctly concatenate keys from all runners', async () => {
307+
const overview = bulkAction.getOverview();
308+
309+
// Convert to a Set to make the test order-independent
310+
const expectedKeys = new Set([
311+
'key1',
312+
'key2',
313+
'key3',
314+
'key4',
315+
'key5',
316+
'key6',
317+
]);
318+
const actualKey = new Set(overview.summary.keys);
319+
320+
expect(actualKey).toEqual(expectedKeys);
321+
});
322+
});
323+
234324
describe('setStatus', () => {
235325
const testCases = [
236326
{ input: BulkActionStatus.Completed, affect: true },

redisinsight/api/src/modules/bulk-actions/models/bulk-action.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,24 @@ export class BulkAction implements IBulkAction {
102102
scanned: 0,
103103
});
104104

105-
const summary = this.runners.map((runner) => runner.getSummary().getOverview())
106-
.reduce((cur, prev) => ({
107-
processed: prev.processed + cur.processed,
108-
succeed: prev.succeed + cur.succeed,
109-
failed: prev.failed + cur.failed,
110-
errors: prev.errors.concat(cur.errors),
111-
}), {
112-
processed: 0,
113-
succeed: 0,
114-
failed: 0,
115-
errors: [],
116-
});
105+
const summary = this.runners
106+
.map((runner) => runner.getSummary().getOverview())
107+
.reduce(
108+
(cur, prev) => ({
109+
processed: prev.processed + cur.processed,
110+
succeed: prev.succeed + cur.succeed,
111+
failed: prev.failed + cur.failed,
112+
errors: prev.errors.concat(cur.errors),
113+
keys: [...prev.keys, ...cur.keys],
114+
}),
115+
{
116+
processed: 0,
117+
succeed: 0,
118+
failed: 0,
119+
errors: [],
120+
keys: [],
121+
},
122+
);
117123

118124
summary.errors = summary.errors.slice(0, 500).map((error) => ({
119125
key: error.key.toString(),

redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,45 @@ describe('AbstractBulkActionSimpleRunner', () => {
226226
expect(runIterationSpy).toHaveBeenCalledTimes(2);
227227
});
228228
});
229+
230+
describe('processIterationResults', () => {
231+
let addProcessedSpy;
232+
let addKeysSpy;
233+
let addSuccessSpy;
234+
let addErrorsSpy;
235+
236+
beforeEach(() => {
237+
addProcessedSpy = jest.spyOn(deleteRunner['summary'], 'addProcessed');
238+
addKeysSpy = jest.spyOn(deleteRunner['summary'], 'addKeys');
239+
addSuccessSpy = jest.spyOn(deleteRunner['summary'], 'addSuccess');
240+
addErrorsSpy = jest.spyOn(deleteRunner['summary'], 'addErrors');
241+
});
242+
243+
it('should add keys to the summary and correctly process results', () => {
244+
const keys = [
245+
Buffer.from('key1'),
246+
Buffer.from('key2'),
247+
Buffer.from('key3'),
248+
];
249+
const results: [Error | null, number | null][] = [
250+
[null, 1], // Success
251+
[mockReplyError, null], // Error
252+
[null, 1], // Success
253+
];
254+
255+
deleteRunner.processIterationResults(keys, results);
256+
257+
expect(addProcessedSpy).toHaveBeenCalledWith(3);
258+
expect(addKeysSpy).toHaveBeenCalledWith(keys);
259+
260+
expect(addSuccessSpy).toHaveBeenNthCalledWith(1, 1); // first call
261+
expect(addSuccessSpy).toHaveBeenNthCalledWith(2, 1); // second call
262+
expect(addErrorsSpy).toHaveBeenCalledWith([
263+
{
264+
key: Buffer.from('key2'),
265+
error: mockRESPError,
266+
},
267+
]);
268+
});
269+
});
229270
});

redisinsight/api/src/modules/bulk-actions/models/runners/simple/abstract.bulk-action.simple.runner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export abstract class AbstractBulkActionSimpleRunner extends AbstractBulkActionR
7777
*/
7878
processIterationResults(keys, res: [Error | null, RedisClientCommandReply][]) {
7979
this.summary.addProcessed(res.length);
80+
this.summary.addKeys(keys);
8081

8182
const errors = [];
8283

0 commit comments

Comments
 (0)