Skip to content
41 changes: 34 additions & 7 deletions redisinsight/api/src/__mocks__/database-import.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DatabaseImportResponse } from 'src/modules/database-import/dto/database-import.response';
import { DatabaseImportResponse, DatabaseImportStatus } from 'src/modules/database-import/dto/database-import.response';
import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { mockDatabase } from 'src/__mocks__/databases';
import { ValidationError } from 'class-validator';
import { ValidationException } from 'src/common/exceptions';

export const mockDatabasesToImportArray = new Array(10).fill(mockDatabase);

Expand All @@ -12,23 +12,50 @@ export const mockDatabaseImportFile = {
buffer: Buffer.from(JSON.stringify(mockDatabasesToImportArray)),
};

export const mockDatabaseImportResultSuccess = {
index: 0,
status: DatabaseImportStatus.Success,
host: mockDatabase.host,
port: mockDatabase.port,
};

export const mockDatabaseImportResultFail = {
index: 0,
status: DatabaseImportStatus.Fail,
host: mockDatabase.host,
port: mockDatabase.port,
errors: [new BadRequestException()],
};

export const mockDatabaseImportResponse = Object.assign(new DatabaseImportResponse(), {
total: 10,
success: 7,
errors: [new ValidationError(), new BadRequestException(), new ForbiddenException()],
success: (new Array(7).fill(mockDatabaseImportResultSuccess)).map((v, index) => ({
...v,
index: index + 3,
})),
partial: [],
fail: [
new ValidationException('Bad request'),
new BadRequestException(),
new ForbiddenException(),
].map((error, index) => ({
...mockDatabaseImportResultFail,
index,
errors: [error],
})),
});

export const mockDatabaseImportParseFailedAnalyticsPayload = {

};

export const mockDatabaseImportFailedAnalyticsPayload = {
failed: mockDatabaseImportResponse.errors.length,
errors: ['ValidationError', 'BadRequestException', 'ForbiddenException'],
failed: mockDatabaseImportResponse.fail.length,
errors: ['ValidationException', 'BadRequestException', 'ForbiddenException'],
};

export const mockDatabaseImportSucceededAnalyticsPayload = {
succeed: mockDatabaseImportResponse.success,
succeed: mockDatabaseImportResponse.success.length,
};

export const mockDatabaseImportAnalytics = jest.fn(() => ({
Expand Down
1 change: 1 addition & 0 deletions redisinsight/api/src/common/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './validation.exception';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BadRequestException } from '@nestjs/common';

export class ValidationException extends BadRequestException {}
1 change: 1 addition & 0 deletions redisinsight/api/src/constants/telemetry-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export enum TelemetryEvents {
DatabaseImportParseFailed = 'CONFIG_DATABASES_REDIS_IMPORT_PARSE_FAILED',
DatabaseImportFailed = 'CONFIG_DATABASES_REDIS_IMPORT_FAILED',
DatabaseImportSucceeded = 'CONFIG_DATABASES_REDIS_IMPORT_SUCCEEDED',
DatabaseImportPartiallySucceeded = 'CONFIG_DATABASES_REDIS_IMPORT_PARTIALLY_SUCCEEDED',

// Events for autodiscovery flows
REClusterDiscoverySucceed = 'CONFIG_DATABASES_RE_CLUSTER_AUTODISCOVERY_SUCCEEDED',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { uniq } from 'lodash';
import { Injectable } from '@nestjs/common';
import { TelemetryBaseService } from 'src/modules/analytics/telemetry.base.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { TelemetryEvents } from 'src/constants';
import { DatabaseImportResponse } from 'src/modules/database-import/dto/database-import.response';
import { DatabaseImportResponse, DatabaseImportResult } from 'src/modules/database-import/dto/database-import.response';

@Injectable()
export class DatabaseImportAnalytics extends TelemetryBaseService {
Expand All @@ -11,21 +12,31 @@ export class DatabaseImportAnalytics extends TelemetryBaseService {
}

sendImportResults(importResult: DatabaseImportResponse): void {
if (importResult.success) {
if (importResult.success?.length) {
this.sendEvent(
TelemetryEvents.DatabaseImportSucceeded,
{
succeed: importResult.success,
succeed: importResult.success.length,
},
);
}

if (importResult.errors?.length) {
if (importResult.fail?.length) {
this.sendEvent(
TelemetryEvents.DatabaseImportFailed,
{
failed: importResult.errors.length,
errors: importResult.errors.map((e) => (e?.constructor?.name || 'UncaughtError')),
failed: importResult.fail.length,
errors: DatabaseImportAnalytics.getUniqueErrorNamesFromResults(importResult.fail),
},
);
}

if (importResult.partial?.length) {
this.sendEvent(
TelemetryEvents.DatabaseImportPartiallySucceeded,
{
partially: importResult.partial.length,
errors: DatabaseImportAnalytics.getUniqueErrorNamesFromResults(importResult.partial),
},
);
}
Expand All @@ -39,4 +50,16 @@ export class DatabaseImportAnalytics extends TelemetryBaseService {
},
);
}

static getUniqueErrorNamesFromResults(results: DatabaseImportResult[]) {
return uniq(
[].concat(
...results.map(
(res) => (res?.errors || []).map(
(error) => error?.constructor?.name || 'UncaughtError',
),
),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ClassSerializerInterceptor,
Controller, HttpCode, Post, UploadedFile,
UseInterceptors, UsePipes, ValidationPipe,
UseInterceptors, UsePipes, ValidationPipe
} from '@nestjs/common';
import {
ApiBody, ApiConsumes, ApiResponse, ApiTags,
Expand All @@ -10,6 +11,7 @@ import { FileInterceptor } from '@nestjs/platform-express';
import { DatabaseImportResponse } from 'src/modules/database-import/dto/database-import.response';

@UsePipes(new ValidationPipe({ transform: true }))
@UseInterceptors(ClassSerializerInterceptor)
@ApiTags('Database')
@Controller('/databases')
export class DatabaseImportController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,7 @@ describe('DatabaseImportService', () => {
it('should import databases from json', async () => {
const response = await service.import(mockDatabaseImportFile);

expect(response).toEqual({
...mockDatabaseImportResponse,
errors: undefined, // errors omitted from response
});
expect(response).toEqual(mockDatabaseImportResponse);
expect(analytics.sendImportResults).toHaveBeenCalledWith(mockDatabaseImportResponse);
});

Expand All @@ -75,7 +72,6 @@ describe('DatabaseImportService', () => {

expect(response).toEqual({
...mockDatabaseImportResponse,
errors: undefined, // errors omitted from response
});
expect(analytics.sendImportResults).toHaveBeenCalledWith(mockDatabaseImportResponse);
});
Expand Down Expand Up @@ -138,7 +134,7 @@ describe('DatabaseImportService', () => {
it('should create standalone database', async () => {
await service['createDatabase']({
...mockDatabase,
});
}, 0);

expect(databaseRepository.create).toHaveBeenCalledWith({
...pick(mockDatabase, ['host', 'port', 'name', 'connectionType']),
Expand All @@ -149,7 +145,7 @@ describe('DatabaseImportService', () => {
await service['createDatabase']({
...mockDatabase,
name: undefined,
});
}, 0);

expect(databaseRepository.create).toHaveBeenCalledWith({
...pick(mockDatabase, ['host', 'port', 'name', 'connectionType']),
Expand All @@ -161,7 +157,7 @@ describe('DatabaseImportService', () => {
await service['createDatabase']({
...mockDatabase,
cluster: true,
});
}, 0);

expect(databaseRepository.create).toHaveBeenCalledWith({
...pick(mockDatabase, ['host', 'port', 'name']),
Expand Down
Loading