Skip to content

Commit 29ef589

Browse files
authored
Merge pull request #1149 from RedisInsight/feature/RI-2509-wb-group-mode
Feature/ri 2509 wb group mode
2 parents 8700076 + 2bc4a8e commit 29ef589

File tree

53 files changed

+1021
-205
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1021
-205
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class workbenchGroupMode1663093411715 implements MigrationInterface {
4+
name = 'workbenchGroupMode1663093411715'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`DROP INDEX "IDX_5cd90dd6def1fd7c521e53fb2c"`);
8+
await queryRunner.query(`CREATE TABLE "temporary_command_execution" ("id" varchar PRIMARY KEY NOT NULL, "databaseId" varchar NOT NULL, "command" text NOT NULL, "result" text NOT NULL, "role" varchar, "nodeOptions" varchar, "encryption" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "mode" varchar, "resultsMode" varchar, "summary" varchar, CONSTRAINT "FK_ea8adfe9aceceb79212142206b8" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
9+
await queryRunner.query(`INSERT INTO "temporary_command_execution"("id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode") SELECT "id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode" FROM "command_execution"`);
10+
await queryRunner.query(`DROP TABLE "command_execution"`);
11+
await queryRunner.query(`ALTER TABLE "temporary_command_execution" RENAME TO "command_execution"`);
12+
await queryRunner.query(`CREATE INDEX "IDX_5cd90dd6def1fd7c521e53fb2c" ON "command_execution" ("createdAt") `);
13+
}
14+
15+
public async down(queryRunner: QueryRunner): Promise<void> {
16+
await queryRunner.query(`DROP INDEX "IDX_5cd90dd6def1fd7c521e53fb2c"`);
17+
await queryRunner.query(`ALTER TABLE "command_execution" RENAME TO "temporary_command_execution"`);
18+
await queryRunner.query(`CREATE TABLE "command_execution" ("id" varchar PRIMARY KEY NOT NULL, "databaseId" varchar NOT NULL, "command" text NOT NULL, "result" text NOT NULL, "role" varchar, "nodeOptions" varchar, "encryption" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "mode" varchar, CONSTRAINT "FK_ea8adfe9aceceb79212142206b8" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
19+
await queryRunner.query(`INSERT INTO "command_execution"("id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode") SELECT "id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode" FROM "temporary_command_execution"`);
20+
await queryRunner.query(`DROP TABLE "temporary_command_execution"`);
21+
await queryRunner.query(`CREATE INDEX "IDX_5cd90dd6def1fd7c521e53fb2c" ON "command_execution" ("createdAt") `);
22+
}
23+
24+
}

redisinsight/api/migration/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { sni1650278664000 } from "./1650278664000-sni";
1616
import { notification1655821010349 } from './1655821010349-notification';
1717
import { notificationCategory1659687030433 } from './1659687030433-notification-category';
1818
import { workbenchMode1660664717573 } from './1660664717573-workbench-mode';
19+
import { workbenchGroupMode1663093411715 } from './1663093411715-workbench-group-mode';
1920

2021
export default [
2122
initialMigration1614164490968,
@@ -36,4 +37,5 @@ export default [
3637
notification1655821010349,
3738
notificationCategory1659687030433,
3839
workbenchMode1660664717573,
40+
workbenchGroupMode1663093411715,
3941
];

redisinsight/api/src/modules/workbench/dto/create-command-execution.dto.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export enum RunQueryMode {
1616
ASCII = 'ASCII',
1717
}
1818

19+
export enum ResultsMode {
20+
Default = 'DEFAULT',
21+
GroupMode = 'GROUP_MODE',
22+
}
23+
1924
export class CreateCommandExecutionDto {
2025
@ApiProperty({
2126
type: String,
@@ -38,6 +43,19 @@ export class CreateCommandExecutionDto {
3843
})
3944
mode?: RunQueryMode = RunQueryMode.ASCII;
4045

46+
@ApiPropertyOptional({
47+
description: 'Workbench group mode',
48+
default: ResultsMode.Default,
49+
enum: ResultsMode,
50+
})
51+
@IsOptional()
52+
@IsEnum(ResultsMode, {
53+
message: `resultsMode must be a valid enum value. Valid values: ${Object.values(
54+
ResultsMode,
55+
)}.`,
56+
})
57+
resultsMode?: ResultsMode;
58+
4159
@ApiPropertyOptional({
4260
description: 'Execute command for nodes with defined role',
4361
default: ClusterNodeRole.All,

redisinsight/api/src/modules/workbench/dto/create-command-executions.dto.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
} from 'class-validator';
55
import { Type } from 'class-transformer';
66
import { ClusterSingleNodeOptions } from 'src/modules/cli/dto/cli.dto';
7-
import { ClusterNodeRole, RunQueryMode } from './create-command-execution.dto';
7+
import { ClusterNodeRole, RunQueryMode, ResultsMode } from './create-command-execution.dto';
88

99
export class CreateCommandExecutionsDto {
1010
@ApiProperty({
@@ -29,6 +29,14 @@ export class CreateCommandExecutionsDto {
2929
})
3030
mode?: RunQueryMode = RunQueryMode.ASCII;
3131

32+
@IsOptional()
33+
@IsEnum(ResultsMode, {
34+
message: `resultsMode must be a valid enum value. Valid values: ${Object.values(
35+
ResultsMode,
36+
)}.`,
37+
})
38+
resultsMode?: ResultsMode;
39+
3240
@ApiPropertyOptional({
3341
description: 'Execute command for nodes with defined role',
3442
default: ClusterNodeRole.All,

redisinsight/api/src/modules/workbench/entities/command-execution.entity.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, JoinColumn, Index,
33
} from 'typeorm';
44
import { DatabaseInstanceEntity } from 'src/modules/core/models/database-instance.entity';
5-
import { RunQueryMode } from 'src/modules/workbench/dto/create-command-execution.dto';
5+
import { RunQueryMode, ResultsMode } from 'src/modules/workbench/dto/create-command-execution.dto';
66
import { Transform } from 'class-transformer';
77

88
@Entity('command_execution')
@@ -43,6 +43,20 @@ export class CommandExecutionEntity {
4343
@Column({ nullable: true })
4444
role?: string;
4545

46+
@Column({ nullable: true })
47+
resultsMode?: string = ResultsMode.Default;
48+
49+
@Column({ nullable: true })
50+
@Transform((object) => JSON.stringify(object), { toClassOnly: true })
51+
@Transform((string) => {
52+
try {
53+
return JSON.parse(string);
54+
} catch (e) {
55+
return undefined;
56+
}
57+
}, { toPlainOnly: true })
58+
summary?: string;
59+
4660
@Column({ nullable: true })
4761
@Transform((object) => JSON.stringify(object), { toClassOnly: true })
4862
@Transform((string) => {

redisinsight/api/src/modules/workbench/models/command-execution.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,33 @@
11
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
2+
import { IsDefined } from 'class-validator';
23
import { CommandExecutionResult } from 'src/modules/workbench/models/command-execution-result';
3-
import { ClusterNodeRole, RunQueryMode } from 'src/modules/workbench/dto/create-command-execution.dto';
4+
import { ClusterNodeRole, RunQueryMode, ResultsMode } from 'src/modules/workbench/dto/create-command-execution.dto';
45
import { ClusterSingleNodeOptions } from 'src/modules/cli/dto/cli.dto';
56
import { Expose } from 'class-transformer';
67

8+
export class ResultsSummary {
9+
@ApiProperty({
10+
description: 'Total number of commands executed',
11+
type: Number,
12+
})
13+
@IsDefined()
14+
total: number;
15+
16+
@ApiProperty({
17+
description: 'Total number of successful commands executed',
18+
type: Number,
19+
})
20+
@IsDefined()
21+
success: number;
22+
23+
@ApiProperty({
24+
description: 'Total number of failed commands executed',
25+
type: Number,
26+
})
27+
@IsDefined()
28+
fail: number;
29+
}
30+
731
export class CommandExecution {
832
@ApiProperty({
933
description: 'Command execution id',
@@ -34,6 +58,21 @@ export class CommandExecution {
3458
@Expose()
3559
mode?: RunQueryMode = RunQueryMode.ASCII;
3660

61+
@ApiPropertyOptional({
62+
description: 'Workbench result mode',
63+
default: ResultsMode.Default,
64+
enum: ResultsMode,
65+
})
66+
@Expose()
67+
resultsMode?: ResultsMode = ResultsMode.Default;
68+
69+
@ApiPropertyOptional({
70+
description: 'Workbench executions summary',
71+
type: () => ResultsSummary,
72+
})
73+
@Expose()
74+
summary?: ResultsSummary;
75+
3776
@ApiProperty({
3877
description: 'Command execution result',
3978
type: () => CommandExecutionResult,
@@ -42,6 +81,13 @@ export class CommandExecution {
4281
@Expose()
4382
result: CommandExecutionResult[];
4483

84+
@ApiPropertyOptional({
85+
description: 'Result did not stored in db',
86+
type: Boolean,
87+
})
88+
@Expose()
89+
isNotStored?: boolean;
90+
4591
@ApiPropertyOptional({
4692
description: 'Nodes roles where command was executed',
4793
default: ClusterNodeRole.All,

redisinsight/api/src/modules/workbench/plugins.service.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
ClusterNodeRole,
88
CreateCommandExecutionDto,
99
RunQueryMode,
10+
ResultsMode,
1011
} from 'src/modules/workbench/dto/create-command-execution.dto';
1112
import { CommandExecutionResult } from 'src/modules/workbench/models/command-execution-result';
1213
import { CommandExecutionStatus } from 'src/modules/cli/dto/cli.dto';
@@ -34,6 +35,7 @@ const mockCreateCommandExecutionDto: CreateCommandExecutionDto = {
3435
},
3536
role: ClusterNodeRole.All,
3637
mode: RunQueryMode.ASCII,
38+
resultsMode: ResultsMode.Default,
3739
};
3840

3941
const mockCommandExecutionResults: CommandExecutionResult[] = [

redisinsight/api/src/modules/workbench/providers/command-execution.provider.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,33 @@ describe('CommandExecutionProvider', () => {
140140
}),
141141
]));
142142
});
143+
it('should return with flag isNotStored="true" even if size limit exceeded', async () => {
144+
repository.save.mockReturnValueOnce([mockCommandExecutionEntity]);
145+
encryptionService.encrypt.mockReturnValue(mockEncryptResult);
146+
147+
const executionResult = [new CommandExecutionResult({
148+
status: CommandExecutionStatus.Success,
149+
response: `${Buffer.alloc(WORKBENCH_CONFIG.maxResultSize, 'a').toString()}`,
150+
})];
151+
152+
expect(await service.createMany([{
153+
...mockCommandExecutionPartial,
154+
result: executionResult,
155+
}])).toEqual([new CommandExecution({
156+
...mockCommandExecutionPartial,
157+
id: mockCommandExecutionEntity.id,
158+
createdAt: mockCommandExecutionEntity.createdAt,
159+
result: executionResult,
160+
isNotStored: true,
161+
})]);
162+
163+
expect(encryptionService.encrypt).toHaveBeenLastCalledWith(JSON.stringify([
164+
new CommandExecutionResult({
165+
status: CommandExecutionStatus.Success,
166+
response: 'Results have been deleted since they exceed 1 MB. Re-run the command to see new results.',
167+
}),
168+
]));
169+
})
143170
});
144171
describe('getList', () => {
145172
it('should return list (2) of command execution', async () => {

redisinsight/api/src/modules/workbench/providers/command-execution.provider.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class CommandExecutionProvider {
3131
*/
3232
async createMany(commandExecutions: Partial<CommandExecution>[]): Promise<CommandExecution[]> {
3333
// todo: limit by 30 max to insert
34-
let entities = await Promise.all(commandExecutions.map(async (commandExecution) => {
34+
let entities = await Promise.all(commandExecutions.map(async (commandExecution, idx) => {
3535
const entity = plainToClass(CommandExecutionEntity, commandExecution);
3636

3737
// Do not store command execution result that exceeded limitation
@@ -42,6 +42,8 @@ export class CommandExecutionProvider {
4242
response: ERROR_MESSAGES.WORKBENCH_RESPONSE_TOO_BIG(),
4343
},
4444
]);
45+
// Hack, do not store isNotStored. Send once to show warning
46+
entity['isNotStored'] = true;
4547
}
4648

4749
return this.encryptEntity(entity);
@@ -58,6 +60,7 @@ export class CommandExecutionProvider {
5860
mode: commandExecutions[idx].mode,
5961
result: commandExecutions[idx].result,
6062
nodeOptions: commandExecutions[idx].nodeOptions,
63+
summary: commandExecutions[idx].summary,
6164
},
6265
)),
6366
);
@@ -81,7 +84,18 @@ export class CommandExecutionProvider {
8184
const entities = await this.commandExecutionRepository
8285
.createQueryBuilder('e')
8386
.where({ databaseId })
84-
.select(['e.id', 'e.command', 'e.databaseId', 'e.createdAt', 'e.encryption', 'e.role', 'e.nodeOptions', 'e.mode'])
87+
.select([
88+
'e.id',
89+
'e.command',
90+
'e.databaseId',
91+
'e.createdAt',
92+
'e.encryption',
93+
'e.role',
94+
'e.nodeOptions',
95+
'e.mode',
96+
'e.summary',
97+
'e.resultsMode',
98+
])
8599
.orderBy('e.createdAt', 'DESC')
86100
.limit(WORKBENCH_CONFIG.maxItemsPerDb)
87101
.getMany();

redisinsight/api/src/modules/workbench/workbench.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import { PluginCommandsWhitelistProvider } from 'src/modules/workbench/providers
2020
import { PluginsController } from 'src/modules/workbench/plugins.controller';
2121
import { PluginStateProvider } from 'src/modules/workbench/providers/plugin-state.provider';
2222
import { PluginStateEntity } from 'src/modules/workbench/entities/plugin-state.entity';
23-
import { WorkbenchAnalyticsService } from './services/workbench-analytics/workbench-analytics.service';
2423
import config from 'src/utils/config';
24+
import { WorkbenchAnalyticsService } from './services/workbench-analytics/workbench-analytics.service';
2525

2626
const COMMANDS_CONFIGS = config.get('commands');
2727

@@ -52,7 +52,7 @@ const COMMANDS_CONFIGS = config.get('commands');
5252
provide: CommandsService,
5353
useFactory: () => new CommandsService(
5454
COMMANDS_CONFIGS.map(({ name, url }) => new CommandsJsonProvider(name, url)),
55-
)
55+
),
5656
},
5757
PluginsService,
5858
PluginCommandsWhitelistProvider,

0 commit comments

Comments
 (0)