Skip to content

Commit f5773df

Browse files
authored
Merge pull request #2108 from RedisInsight/feature/RI-4447_bulk_actions_analytics
#RI-4447 - update bulk actions telemetry (#2105)
2 parents cc12e25 + f4c55b1 commit f5773df

27 files changed

+376
-55
lines changed

redisinsight/api/src/__mocks__/analytics.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
import { EventEmitter2 } from '@nestjs/event-emitter';
2+
import { BulkActionsAnalyticsService } from 'src/modules/bulk-actions/bulk-actions-analytics.service';
3+
4+
const mockEmitter = new EventEmitter2();
5+
6+
class AnalyticsService extends BulkActionsAnalyticsService {
7+
constructor(protected eventEmitter: EventEmitter2) {
8+
super(eventEmitter);
9+
}
10+
}
11+
112
export const mockInstancesAnalyticsService = () => ({
213
sendInstanceListReceivedEvent: jest.fn(),
314
sendInstanceAddedEvent: jest.fn(),
@@ -29,6 +40,8 @@ export const mockSettingsAnalyticsService = () => ({
2940
sendSettingsUpdatedEvent: jest.fn(),
3041
});
3142

43+
export const mockBulActionsAnalyticsService = new AnalyticsService(mockEmitter);
44+
3245
export const mockPubSubAnalyticsService = () => ({
3346
sendMessagePublishedEvent: jest.fn(),
3447
sendChannelSubscribeEvent: jest.fn(),

redisinsight/api/src/constants/telemetry-events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export enum TelemetryEvents {
6464
// Bulk Actions
6565
BulkActionsStarted = 'BULK_ACTIONS_STARTED',
6666
BulkActionsStopped = 'BULK_ACTIONS_STOPPED',
67+
BulkActionsSucceed = 'BULK_ACTIONS_SUCCEED',
68+
BulkActionsFailed = 'BULK_ACTIONS_FAILED',
6769
}
6870

6971
export enum CommandType {

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

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Injectable } from '@nestjs/common';
1+
import { HttpException, Injectable } from '@nestjs/common';
22
import { EventEmitter2 } from '@nestjs/event-emitter';
33
import { TelemetryEvents } from 'src/constants';
44
import { TelemetryBaseService } from 'src/modules/analytics/telemetry.base.service';
5+
import { getRangeForNumber, BULK_ACTIONS_BREAKPOINTS } from 'src/utils';
56
import { CommandExecutionStatus } from 'src/modules/cli/dto/cli.dto';
67
import { RedisError, ReplyError } from 'src/models';
78
import { IBulkActionOverview } from 'src/modules/bulk-actions/interfaces/bulk-action-overview.interface';
@@ -20,6 +21,8 @@ export class BulkActionsAnalyticsService extends TelemetryBaseService {
2021
super(eventEmitter);
2122
this.events.set(TelemetryEvents.BulkActionsStarted, this.sendActionStarted.bind(this));
2223
this.events.set(TelemetryEvents.BulkActionsStopped, this.sendActionStopped.bind(this));
24+
this.events.set(TelemetryEvents.BulkActionsSucceed, this.sendActionSucceed.bind(this));
25+
this.events.set(TelemetryEvents.BulkActionsFailed, this.sendActionFailed.bind(this));
2326
}
2427

2528
sendActionStarted(overview: IBulkActionOverview): void {
@@ -28,15 +31,17 @@ export class BulkActionsAnalyticsService extends TelemetryBaseService {
2831
TelemetryEvents.BulkActionsStarted,
2932
{
3033
databaseId: overview.databaseId,
31-
type: overview.type,
34+
action: overview.type,
3235
duration: overview.duration,
3336
filter: {
3437
match: overview.filter?.match === '*' ? '*' : 'PATTERN',
3538
type: overview.filter?.type,
3639
},
3740
progress: {
3841
scanned: overview.progress?.scanned,
42+
scannedRange: getRangeForNumber(overview.progress?.scanned, BULK_ACTIONS_BREAKPOINTS),
3943
total: overview.progress?.total,
44+
totalRange: getRangeForNumber(overview.progress?.total, BULK_ACTIONS_BREAKPOINTS),
4045
},
4146
},
4247
);
@@ -51,20 +56,25 @@ export class BulkActionsAnalyticsService extends TelemetryBaseService {
5156
TelemetryEvents.BulkActionsStopped,
5257
{
5358
databaseId: overview.databaseId,
54-
type: overview.type,
59+
action: overview.type,
5560
duration: overview.duration,
5661
filter: {
5762
match: overview.filter?.match === '*' ? '*' : 'PATTERN',
5863
type: overview.filter?.type,
5964
},
6065
progress: {
6166
scanned: overview.progress?.scanned,
67+
scannedRange: getRangeForNumber(overview.progress?.scanned, BULK_ACTIONS_BREAKPOINTS),
6268
total: overview.progress?.total,
69+
totalRange: getRangeForNumber(overview.progress?.total, BULK_ACTIONS_BREAKPOINTS),
6370
},
6471
summary: {
6572
processed: overview.summary?.processed,
73+
processedRange: getRangeForNumber(overview.summary?.processed, BULK_ACTIONS_BREAKPOINTS),
6674
succeed: overview.summary?.succeed,
75+
succeedRange: getRangeForNumber(overview.summary?.succeed, BULK_ACTIONS_BREAKPOINTS),
6776
failed: overview.summary.failed,
77+
failedRange: getRangeForNumber(overview.summary.failed, BULK_ACTIONS_BREAKPOINTS),
6878
},
6979
},
7080
);
@@ -73,6 +83,48 @@ export class BulkActionsAnalyticsService extends TelemetryBaseService {
7383
}
7484
}
7585

86+
sendActionSucceed(overview: IBulkActionOverview): void {
87+
try {
88+
this.sendEvent(
89+
TelemetryEvents.BulkActionsSucceed,
90+
{
91+
databaseId: overview.databaseId,
92+
action: overview.type,
93+
duration: overview.duration,
94+
filter: {
95+
match: overview.filter?.match === '*' ? '*' : 'PATTERN',
96+
type: overview.filter?.type,
97+
},
98+
summary: {
99+
processed: overview.summary?.processed,
100+
processedRange: getRangeForNumber(overview.summary?.processed, BULK_ACTIONS_BREAKPOINTS),
101+
succeed: overview.summary?.succeed,
102+
succeedRange: getRangeForNumber(overview.summary?.succeed, BULK_ACTIONS_BREAKPOINTS),
103+
failed: overview.summary.failed,
104+
failedRange: getRangeForNumber(overview.summary.failed, BULK_ACTIONS_BREAKPOINTS),
105+
},
106+
},
107+
);
108+
} catch (e) {
109+
// continue regardless of error
110+
}
111+
}
112+
113+
sendActionFailed(overview: IBulkActionOverview, error: HttpException | Error): void {
114+
try {
115+
this.sendEvent(
116+
TelemetryEvents.BulkActionsFailed,
117+
{
118+
databaseId: overview.databaseId,
119+
action: overview.type,
120+
error,
121+
},
122+
);
123+
} catch (e) {
124+
// continue regardless of error
125+
}
126+
}
127+
76128
getEventsEmitters(): Map<TelemetryEvents, Function> {
77129
return this.events;
78130
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as MockedSocket from 'socket.io-mock';
33
import { Test, TestingModule } from '@nestjs/testing';
44
import {
55
MockType,
6+
mockBulActionsAnalyticsService,
67
} from 'src/__mocks__';
78
import { BulkActionsProvider } from 'src/modules/bulk-actions/providers/bulk-actions.provider';
89
import { RedisDataType } from 'src/modules/browser/dto';
@@ -43,6 +44,7 @@ const mockBulkAction = new BulkAction(
4344
mockCreateBulkActionDto.type,
4445
mockBulkActionFilter,
4546
mockSocket1,
47+
mockBulActionsAnalyticsService,
4648
);
4749
const mockOverview = 'mocked overview...';
4850

@@ -51,6 +53,7 @@ mockBulkAction['getOverview'] = jest.fn().mockReturnValue(mockOverview);
5153
describe('BulkActionsService', () => {
5254
let service: BulkActionsService;
5355
let bulkActionProvider: MockType<BulkActionsProvider>;
56+
let analyticsService: MockType<BulkActionsAnalyticsService>;
5457

5558
beforeEach(async () => {
5659
jest.clearAllMocks();
@@ -72,19 +75,23 @@ describe('BulkActionsService', () => {
7275
useFactory: () => ({
7376
sendActionStarted: jest.fn(),
7477
sendActionStopped: jest.fn(),
78+
sendActionSucceed: jest.fn(),
79+
sendActionFailed: jest.fn(),
7580
}),
7681
},
7782
],
7883
}).compile();
7984

8085
service = module.get(BulkActionsService);
8186
bulkActionProvider = module.get(BulkActionsProvider);
87+
analyticsService = module.get(BulkActionsAnalyticsService);
8288
});
8389

8490
describe('create', () => {
8591
it('should create and return overview', async () => {
8692
expect(await service.create(mockCreateBulkActionDto, mockSocket1)).toEqual(mockOverview);
8793
expect(bulkActionProvider.create).toHaveBeenCalledTimes(1);
94+
expect(analyticsService.sendActionStarted).toHaveBeenCalledTimes(1);
8895
});
8996
});
9097
describe('get', () => {

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,8 @@ export class BulkActionsService {
2828

2929
async abort(dto: BulkActionIdDto) {
3030
const bulkAction = await this.bulkActionsProvider.abort(dto.id);
31-
const overview = bulkAction.getOverview();
32-
33-
this.analyticsService.sendActionStopped(overview);
3431

35-
return overview;
32+
return bulkAction.getOverview();
3633
}
3734

3835
disconnect(socketId: string) {

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { BulkActionsAnalyticsService } from 'src/modules/bulk-actions/bulk-actio
1616
import * as fs from 'fs-extra';
1717
import config from 'src/utils/config';
1818
import { join } from 'path';
19+
import { wrapHttpError } from 'src/common/utils';
1920

2021
const PATH_CONFIG = config.get('dir_path');
2122

@@ -34,6 +35,13 @@ const mockSummary: BulkActionSummary = Object.assign(new BulkActionSummary(), {
3435
errors: [],
3536
});
3637

38+
const mockEmptySummary: BulkActionSummary = Object.assign(new BulkActionSummary(), {
39+
processed: 0,
40+
succeed: 0,
41+
failed: 0,
42+
errors: [],
43+
});
44+
3745
const mockSummaryWithErrors = Object.assign(new BulkActionSummary(), {
3846
processed: 100,
3947
succeed: 99,
@@ -44,14 +52,25 @@ const mockSummaryWithErrors = Object.assign(new BulkActionSummary(), {
4452
const mockImportResult: IBulkActionOverview = {
4553
id: 'empty',
4654
databaseId: mockClientMetadata.databaseId,
47-
type: BulkActionType.Import,
55+
type: BulkActionType.Upload,
4856
summary: mockSummary.getOverview(),
4957
progress: null,
5058
filter: null,
5159
status: BulkActionStatus.Completed,
5260
duration: 100,
5361
};
5462

63+
const mockEmptyImportResult: IBulkActionOverview = {
64+
id: 'empty',
65+
databaseId: mockClientMetadata.databaseId,
66+
type: BulkActionType.Upload,
67+
summary: mockEmptySummary.getOverview(),
68+
progress: null,
69+
filter: null,
70+
status: BulkActionStatus.Completed,
71+
duration: 0,
72+
};
73+
5574
const mockUploadImportFileDto = {
5675
file: {
5776
originalname: 'filename',
@@ -88,6 +107,8 @@ describe('BulkImportService', () => {
88107
useFactory: () => ({
89108
sendActionStarted: jest.fn(),
90109
sendActionStopped: jest.fn(),
110+
sendActionSucceed: jest.fn(),
111+
sendActionFailed: jest.fn(),
91112
}),
92113
},
93114
],
@@ -135,7 +156,7 @@ describe('BulkImportService', () => {
135156
...mockImportResult,
136157
duration: jasmine.anything(),
137158
});
138-
expect(analytics.sendActionStopped).toHaveBeenCalledWith({
159+
expect(analytics.sendActionSucceed).toHaveBeenCalledWith({
139160
...mockImportResult,
140161
duration: jasmine.anything(),
141162
});
@@ -220,6 +241,10 @@ describe('BulkImportService', () => {
220241
fail();
221242
} catch (e) {
222243
expect(mockIORedisClient.disconnect).not.toHaveBeenCalled();
244+
expect(analytics.sendActionFailed).toHaveBeenCalledWith(
245+
{ ...mockEmptyImportResult },
246+
wrapHttpError(e),
247+
);
223248
expect(e).toBeInstanceOf(NotFoundException);
224249
}
225250
});

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { join, resolve } from 'path';
1+
import { join } from 'path';
22
import * as fs from 'fs-extra';
33
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
44
import { Readable } from 'stream';
@@ -70,7 +70,7 @@ export class BulkImportService {
7070
const result: IBulkActionOverview = {
7171
id: 'empty',
7272
databaseId: clientMetadata.databaseId,
73-
type: BulkActionType.Import,
73+
type: BulkActionType.Upload,
7474
summary: {
7575
processed: 0,
7676
succeed: 0,
@@ -115,6 +115,7 @@ export class BulkImportService {
115115
rl.on('error', (error) => {
116116
result.summary.errors.push(error);
117117
result.status = BulkActionStatus.Failed;
118+
this.analyticsService.sendActionFailed(result, error);
118119
res(null);
119120
});
120121
rl.on('close', () => {
@@ -133,15 +134,19 @@ export class BulkImportService {
133134
result.summary.processed += parseErrors;
134135
result.summary.failed += parseErrors;
135136

136-
this.analyticsService.sendActionStopped(result);
137+
if (result.status === BulkActionStatus.Completed) {
138+
this.analyticsService.sendActionSucceed(result);
139+
}
137140

138141
client.disconnect();
139142

140143
return result;
141144
} catch (e) {
142145
this.logger.error('Unable to process an import file', e);
146+
const exception = wrapHttpError(e);
147+
this.analyticsService.sendActionFailed(result, exception);
143148
client?.disconnect();
144-
throw wrapHttpError(e);
149+
throw exception;
145150
}
146151
}
147152

redisinsight/api/src/modules/bulk-actions/constants/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export enum BulkActionsServerEvents {
66

77
export enum BulkActionType {
88
Delete = 'delete',
9-
Import = 'import',
9+
Upload = 'upload',
1010
}
1111

1212
export enum BulkActionStatus {

0 commit comments

Comments
 (0)