Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions redisinsight/api/src/__mocks__/ssh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const mockSshOptionsBasic = Object.assign(new SshOptions(), {
port: 22,
username: mockSshOptionsUsernamePlain,
password: mockSshOptionsPasswordPlain,
privateKey: null,
passphrase: null,
privateKey: undefined,
passphrase: undefined,
});

export const mockSshOptionsBasicEntity = Object.assign(new SshOptionsEntity(), {
Expand All @@ -32,7 +32,7 @@ export const mockSshOptionsBasicEntity = Object.assign(new SshOptionsEntity(), {

export const mockSshOptionsPrivateKey = Object.assign(new SshOptions(), {
...mockSshOptionsBasic,
password: null,
password: undefined,
privateKey: mockSshOptionsPrivateKeyPlain,
passphrase: mockSshOptionsPassphrasePlain,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Transform } from 'class-transformer';

export function HiddenField(field: any): PropertyDecorator {
return Transform((value: string) => (value ? field : undefined), {
toPlainOnly: true,
});
}
1 change: 1 addition & 0 deletions redisinsight/api/src/common/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './certificate-import.util';
export * from './errors.util';
export * from './merge.util';
106 changes: 106 additions & 0 deletions redisinsight/api/src/common/utils/merge.util.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { deepMerge } from 'src/common/utils/merge.util';

const deepMergeTests = [
{ obj1: {}, obj2: {}, result: {} },
{ obj1: { value: 1 }, obj2: { value: 2 }, result: { value: 2 } },
{ obj1: { value: 1 }, obj2: { value: 1.0000001 }, result: { value: 1.0000001 } },
{ obj1: { value: 1 }, obj2: { value: '2' }, result: { value: '2' } },
{ obj1: { value: 1 }, obj2: { value: undefined }, result: { value: 1 } },
{ obj1: { value: 1 }, obj2: { value: null }, result: { value: null } },
{ obj1: { value: 0 }, obj2: { value: null }, result: { value: null } },
{ obj1: { value: false }, obj2: { value: 1 }, result: { value: 1 } },
{ obj1: { value: false }, obj2: { value: true }, result: { value: true } },
{ obj1: { value: false }, obj2: { value: '1' }, result: { value: '1' } },
{ obj1: { value: false }, obj2: { value: false }, result: { value: false } },
{ obj1: { value: false }, obj2: { value: 1 }, result: { value: 1 } },
{ obj1: { value: false }, obj2: { value: true }, result: { value: true } },
{ obj1: { value: false }, obj2: { value: '1' }, result: { value: '1' } },
{ obj1: { value: '1' }, obj2: { value: '2' }, result: { value: '2' } },
{ obj1: { value: undefined }, obj2: { value: 2 }, result: { value: 2 } },
{ obj1: { value: undefined }, obj2: { value: null }, result: { value: null } },
{ obj1: { value: null }, obj2: { value: null }, result: { value: null } },
{ obj1: {}, obj2: { value: 2 }, result: { value: 2 } },
{ obj1: { value: {} }, obj2: { value: 1 }, result: { value: 1 } },
{ obj1: { value: [] }, obj2: { value: 0 }, result: { value: 0 } },
{ obj1: { value: [] }, obj2: { value: [1] }, result: { value: [1] } },
{ obj1: { value: [] }, obj2: { value: undefined }, result: { value: [] } },
{ obj1: { value: { name: 1 } }, obj2: [], result: { value: { name: 1 } } },
{ obj1: { value: [] }, obj2: { value: { name: 1 } }, result: { value: { name: 1 } } },
{ obj1: [1, 2, 3], obj2: [3, 5, 6], result: [3, 5, 6] },

{
obj1: {
value: 1, value2: 'string', value3: null, value4: undefined, nested: { nestedValue1: 1, nestedValue2: 2 },
},
obj2: {
value2: undefined, value3: 1, value4: null, value5: 'new', nested: { nestedValue2: 4, nestedValue3: 'value' },
},
result: {
value: 1,
value2: 'string',
value3: 1,
value4: null,
value5: 'new',
nested: { nestedValue1: 1, nestedValue2: 4, nestedValue3: 'value' },
},
},
{
obj1: {
value: 0,
value2: '',
value3: [],
value4: {},
nested: {
nestedValue1: undefined,
nestedValue2: {
key1: null, key2: undefined, key3: {}, key4: [],
},
nestedValue3: 'value',
},
},
obj2: {
value: 'value',
value3: { name: 1 },
value4: [1, 2, 3],
value5: null,
nested: {
nestedValue1: { key1: 0, key2: undefined, key3: null },
nestedValue2: {
key1: 'value', key3: { name: 1 }, key5: null, key6: 1,
},
nestedValue4: 1.2,
},
},
result: {
value: 'value',
value2: '',
value3: { name: 1 },
value4: [1, 2, 3],
value5: null,
nested: {
nestedValue1: { key1: 0, key2: undefined, key3: null },
nestedValue2: {
key1: 'value', key2: undefined, key3: { name: 1 }, key4: [], key5: null, key6: 1,
},
nestedValue3: 'value',
nestedValue4: 1.2,
},
},
},
];

describe('deepMerge', () => {
test.each(deepMergeTests)('%j', ({ obj1, obj2, result }) => {
expect(
JSON.parse(
JSON.stringify(
deepMerge(obj1, obj2),
),
),
).toStrictEqual(
JSON.parse(
JSON.stringify(result),
),
);
});
});
15 changes: 15 additions & 0 deletions redisinsight/api/src/common/utils/merge.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
isObjectLike, isUndefined, mergeWith, isArray,
} from 'lodash';

export const deepMerge = (target: object, source: object) => mergeWith(target, source, (targetValue, sourceValue) => {
if (isUndefined(sourceValue)) {
return targetValue;
}

if (isObjectLike(sourceValue) && !isArray(sourceValue) && !isArray(targetValue)) {
return deepMerge(targetValue, sourceValue);
}

return sourceValue;
});
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ describe('DatabaseConnectionService', () => {
});

describe('createClient', () => {
it('should create client for standalone datbaase', async () => {
it('should create client for standalone database', async () => {
expect(await service.createClient(mockCommonClientMetadata)).toEqual(mockIORedisClient);
});
it('should throw Unauthorized error in case of NOAUTH', async () => {
Expand Down
6 changes: 3 additions & 3 deletions redisinsight/api/src/modules/database/database.analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class DatabaseAnalytics extends TelemetryBaseService {

sendInstanceAddedEvent(
instance: Database,
additionalInfo: RedisDatabaseInfoResponse,
additionalInfo?: RedisDatabaseInfoResponse,
): void {
try {
const modulesSummary = getRedisModulesSummary(instance.modules);
Expand All @@ -45,8 +45,8 @@ export class DatabaseAnalytics extends TelemetryBaseService {
version: additionalInfo?.version,
numberOfKeys: additionalInfo?.totalKeys,
numberOfKeysRange: getRangeForNumber(additionalInfo?.totalKeys, TOTAL_KEYS_BREAKPOINTS),
totalMemory: additionalInfo.usedMemory,
numberedDatabases: additionalInfo.databases,
totalMemory: additionalInfo?.usedMemory,
numberedDatabases: additionalInfo?.databases,
numberOfModules: instance.modules?.length || 0,
timeout: instance.timeout / 1_000, // milliseconds to seconds
databaseIndex: instance.db || 0,
Expand Down
65 changes: 45 additions & 20 deletions redisinsight/api/src/modules/database/database.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
Param,
Patch,
Post,
Put,
UseInterceptors,
UsePipes,
ValidationPipe,
Expand All @@ -26,9 +25,10 @@ import { DeleteDatabasesDto } from 'src/modules/database/dto/delete.databases.dt
import { DeleteDatabasesResponse } from 'src/modules/database/dto/delete.databases.response';
import { ClientMetadataParam } from 'src/common/decorators';
import { ClientMetadata } from 'src/common/models';
import { ModifyDatabaseDto } from 'src/modules/database/dto/modify.database.dto';
import { ExportDatabasesDto } from 'src/modules/database/dto/export.databases.dto';
import { ExportDatabase } from 'src/modules/database/models/export-database';
import { DatabaseResponse } from 'src/modules/database/dto/database.response';
import { classToClass } from 'src/utils';

@ApiTags('Database')
@Controller('databases')
Expand Down Expand Up @@ -64,14 +64,14 @@ export class DatabaseController {
{
status: 200,
description: 'Database instance',
type: Database,
type: DatabaseResponse,
},
],
})
async get(
@Param('id') id: string,
): Promise<Database> {
return await this.service.get(id);
): Promise<DatabaseResponse> {
return classToClass(DatabaseResponse, await this.service.get(id));
}

@UseInterceptors(ClassSerializerInterceptor)
Expand All @@ -84,7 +84,7 @@ export class DatabaseController {
{
status: 201,
description: 'Created database instance',
type: Database,
type: DatabaseResponse,
},
],
})
Expand All @@ -97,21 +97,21 @@ export class DatabaseController {
)
async create(
@Body() dto: CreateDatabaseDto,
): Promise<Database> {
return await this.service.create(dto, true);
): Promise<DatabaseResponse> {
return classToClass(DatabaseResponse, await this.service.create(dto, true));
}

@UseInterceptors(ClassSerializerInterceptor)
@UseInterceptors(new TimeoutInterceptor(ERROR_MESSAGES.CONNECTION_TIMEOUT))
@Put(':id')
@Patch(':id')
@ApiEndpoint({
description: 'Update database instance by id',
statusCode: 200,
responses: [
{
status: 200,
description: 'Updated database instance\' response',
type: Database,
type: DatabaseResponse,
},
],
})
Expand All @@ -125,21 +125,21 @@ export class DatabaseController {
async update(
@Param('id') id: string,
@Body() database: UpdateDatabaseDto,
): Promise<Database> {
return await this.service.update(id, database, true);
): Promise<DatabaseResponse> {
return classToClass(DatabaseResponse, await this.service.update(id, database, true));
}

@UseInterceptors(ClassSerializerInterceptor)
@UseInterceptors(new TimeoutInterceptor(ERROR_MESSAGES.CONNECTION_TIMEOUT))
@Patch(':id')
@Post('clone/:id')
@ApiEndpoint({
description: 'Update database instance by id',
statusCode: 200,
responses: [
{
status: 200,
description: 'Updated database instance\' response',
type: Database,
type: DatabaseResponse,
},
],
})
Expand All @@ -150,11 +150,11 @@ export class DatabaseController {
forbidNonWhitelisted: true,
}),
)
async modify(
async clone(
@Param('id') id: string,
@Body() database: ModifyDatabaseDto,
): Promise<Database> {
return await this.service.update(id, database, true);
@Body() database: UpdateDatabaseDto,
): Promise<DatabaseResponse> {
return classToClass(DatabaseResponse, await this.service.clone(id, database));
}

@UseInterceptors(ClassSerializerInterceptor)
Expand All @@ -175,9 +175,34 @@ export class DatabaseController {
}),
)
async testConnection(
@Body() database: CreateDatabaseDto,
@Body() dto: CreateDatabaseDto,
): Promise<void> {
return await this.service.testConnection(dto);
}

@UseInterceptors(ClassSerializerInterceptor)
@Post('/test/:id')
@ApiEndpoint({
description: 'Test connection',
statusCode: 200,
responses: [
{
status: 200,
},
],
})
@UsePipes(
new ValidationPipe({
transform: true,
whitelist: true,
forbidNonWhitelisted: true,
}),
)
async testExistConnection(
@Param('id') id: string,
@Body() dto: UpdateDatabaseDto,
): Promise<void> {
return await this.service.testConnection(database);
return this.service.testConnection(dto, id);
}

@Delete('/:id')
Expand Down
Loading