Skip to content

Commit 9cede76

Browse files
authored
Merge pull request #1110 from RedisInsight/bugfix/RI-3432-fix-commands-order
Bugfix/ri 3432 fix commands order
2 parents 79dbe91 + 8d55a2c commit 9cede76

File tree

9 files changed

+150
-101
lines changed

9 files changed

+150
-101
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,33 +105,33 @@ describe('CommandExecutionProvider', () => {
105105

106106
describe('create', () => {
107107
it('should process new entity', async () => {
108-
repository.save.mockReturnValueOnce(mockCommandExecutionEntity);
108+
repository.save.mockReturnValueOnce([mockCommandExecutionEntity]);
109109
encryptionService.encrypt.mockReturnValue(mockEncryptResult);
110110

111-
expect(await service.create(mockCommandExecutionPartial)).toEqual(new CommandExecution({
111+
expect(await service.createMany([mockCommandExecutionPartial])).toEqual([new CommandExecution({
112112
...mockCommandExecutionPartial,
113113
id: mockCommandExecutionEntity.id,
114114
createdAt: mockCommandExecutionEntity.createdAt,
115-
}));
115+
})]);
116116
});
117117
it('should return full result even if size limit exceeded', async () => {
118-
repository.save.mockReturnValueOnce(mockCommandExecutionEntity);
118+
repository.save.mockReturnValueOnce([mockCommandExecutionEntity]);
119119
encryptionService.encrypt.mockReturnValue(mockEncryptResult);
120120

121121
const executionResult = [new CommandExecutionResult({
122122
status: CommandExecutionStatus.Success,
123123
response: `${Buffer.alloc(WORKBENCH_CONFIG.maxResultSize, 'a').toString()}`,
124124
})];
125125

126-
expect(await service.create({
126+
expect(await service.createMany([{
127127
...mockCommandExecutionPartial,
128128
result: executionResult,
129-
})).toEqual(new CommandExecution({
129+
}])).toEqual([new CommandExecution({
130130
...mockCommandExecutionPartial,
131131
id: mockCommandExecutionEntity.id,
132132
createdAt: mockCommandExecutionEntity.createdAt,
133133
result: executionResult,
134-
}));
134+
})]);
135135

136136
expect(encryptionService.encrypt).toHaveBeenLastCalledWith(JSON.stringify([
137137
new CommandExecutionResult({

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

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,50 @@ export class CommandExecutionProvider {
2525
) {}
2626

2727
/**
28-
* Encrypt command execution and save entire entity
28+
* Encrypt command executions and save entire entities
2929
* Should always throw and error in case when unable to encrypt for some reason
30-
* @param commandExecution
30+
* @param commandExecutions
3131
*/
32-
async create(commandExecution: Partial<CommandExecution>): Promise<CommandExecution> {
33-
const entity = plainToClass(CommandExecutionEntity, commandExecution);
32+
async createMany(commandExecutions: Partial<CommandExecution>[]): Promise<CommandExecution[]> {
33+
// todo: limit by 30 max to insert
34+
let entities = await Promise.all(commandExecutions.map(async (commandExecution) => {
35+
const entity = plainToClass(CommandExecutionEntity, commandExecution);
36+
37+
// Do not store command execution result that exceeded limitation
38+
if (JSON.stringify(entity.result).length > WORKBENCH_CONFIG.maxResultSize) {
39+
entity.result = JSON.stringify([
40+
{
41+
status: CommandExecutionStatus.Success,
42+
response: ERROR_MESSAGES.WORKBENCH_RESPONSE_TOO_BIG(),
43+
},
44+
]);
45+
}
46+
47+
return this.encryptEntity(entity);
48+
}));
49+
50+
entities = await this.commandExecutionRepository.save(entities);
3451

35-
// Do not store command execution result that exceeded limitation
36-
if (JSON.stringify(entity.result).length > WORKBENCH_CONFIG.maxResultSize) {
37-
entity.result = JSON.stringify([
52+
const response = await Promise.all(
53+
entities.map((entity, idx) => classToClass(
54+
CommandExecution,
3855
{
39-
status: CommandExecutionStatus.Success,
40-
response: ERROR_MESSAGES.WORKBENCH_RESPONSE_TOO_BIG(),
56+
...entity,
57+
command: commandExecutions[idx].command,
58+
mode: commandExecutions[idx].mode,
59+
result: commandExecutions[idx].result,
60+
nodeOptions: commandExecutions[idx].nodeOptions,
4161
},
42-
]);
43-
}
44-
45-
const response = await classToClass(
46-
CommandExecution,
47-
{
48-
...await this.commandExecutionRepository.save(await this.encryptEntity(entity)),
49-
command: commandExecution.command,
50-
mode: commandExecution.mode,
51-
result: commandExecution.result,
52-
nodeOptions: commandExecution.nodeOptions,
53-
},
62+
)),
5463
);
5564

5665
// cleanup history and ignore error if any
5766
try {
58-
await this.cleanupDatabaseHistory(entity.databaseId);
67+
await this.cleanupDatabaseHistory(entities[0].databaseId);
5968
} catch (e) {
6069
this.logger.error('Error when trying to cleanup history after insert', e);
6170
}
71+
6272
return response;
6373
}
6474

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class WorkbenchController {
2626
},
2727
],
2828
})
29-
@Post('/commands-execution')
29+
@Post('/command-executions')
3030
@UseInterceptors(ClassSerializerInterceptor)
3131
@ApiRedisParams()
3232
async sendCommands(

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

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { CommandExecutionResult } from 'src/modules/workbench/models/command-exe
1515
import { CommandExecutionStatus } from 'src/modules/cli/dto/cli.dto';
1616
import { BadRequestException, InternalServerErrorException } from '@nestjs/common';
1717
import ERROR_MESSAGES from 'src/constants/error-messages';
18+
import { CreateCommandExecutionsDto } from 'src/modules/workbench/dto/create-command-executions.dto';
1819
import { WorkbenchAnalyticsService } from './services/workbench-analytics/workbench-analytics.service';
1920

2021
const mockClientOptions: IFindRedisClientInstanceByOptions = {
@@ -31,6 +32,13 @@ const mockCreateCommandExecutionDto: CreateCommandExecutionDto = {
3132
role: ClusterNodeRole.All,
3233
mode: RunQueryMode.ASCII,
3334
};
35+
const mockCreateCommandExecutionsDto: CreateCommandExecutionsDto = {
36+
commands: [
37+
mockCreateCommandExecutionDto.command,
38+
mockCreateCommandExecutionDto.command,
39+
],
40+
...mockCreateCommandExecutionDto,
41+
};
3442

3543
const mockCommandExecutionResults: CommandExecutionResult[] = [
3644
new CommandExecutionResult({
@@ -43,16 +51,20 @@ const mockCommandExecutionResults: CommandExecutionResult[] = [
4351
},
4452
}),
4553
];
46-
const mockCommandExecution: CommandExecution = new CommandExecution({
54+
const mockCommandExecutionToRun: CommandExecution = new CommandExecution({
4755
...mockCreateCommandExecutionDto,
4856
databaseId: mockStandaloneDatabaseEntity.id,
57+
});
58+
59+
const mockCommandExecution: CommandExecution = new CommandExecution({
60+
...mockCommandExecutionToRun,
4961
id: uuidv4(),
5062
createdAt: new Date(),
5163
result: mockCommandExecutionResults,
5264
});
5365

5466
const mockCommandExecutionProvider = () => ({
55-
create: jest.fn(),
67+
createMany: jest.fn(),
5668
getList: jest.fn(),
5769
getOne: jest.fn(),
5870
delete: jest.fn(),
@@ -91,13 +103,8 @@ describe('WorkbenchService', () => {
91103

92104
describe('createCommandExecution', () => {
93105
it('should successfully execute command and save it', async () => {
94-
workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce(mockCommandExecutionResults);
95-
commandExecutionProvider.create.mockResolvedValueOnce(mockCommandExecution);
96-
97-
const result = await service.createCommandExecution(mockClientOptions, mockCreateCommandExecutionDto);
98-
99-
expect(result).toBeInstanceOf(CommandExecution);
100-
expect(result).toEqual(mockCommandExecution);
106+
expect(await service.createCommandExecution(mockClientOptions, mockCreateCommandExecutionDto))
107+
.toEqual(mockCommandExecutionToRun);
101108
});
102109
it('should save result as unsupported command message', async () => {
103110
workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce(mockCommandExecutionResults);
@@ -108,9 +115,7 @@ describe('WorkbenchService', () => {
108115
mode: RunQueryMode.ASCII,
109116
};
110117

111-
await service.createCommandExecution(mockClientOptions, dto);
112-
113-
expect(commandExecutionProvider.create).toHaveBeenCalledWith({
118+
expect(await service.createCommandExecution(mockClientOptions, dto)).toEqual({
114119
...dto,
115120
databaseId: mockClientOptions.instanceId,
116121
result: [
@@ -137,18 +142,35 @@ describe('WorkbenchService', () => {
137142
expect(e).toBeInstanceOf(BadRequestException);
138143
}
139144
});
140-
it('should throw an error from command execution provider (create)', async () => {
141-
workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce(mockCommandExecutionResults);
142-
commandExecutionProvider.create.mockRejectedValueOnce(new InternalServerErrorException('db error'));
145+
});
143146

144-
const dto = {
145-
...mockCommandExecutionResults,
146-
command: 'scan 0',
147-
mode: RunQueryMode.ASCII,
148-
};
147+
describe('createCommandExecutions', () => {
148+
it('should successfully execute commands and save them', async () => {
149+
workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce(
150+
[mockCommandExecutionResults, mockCommandExecutionResults],
151+
);
152+
commandExecutionProvider.createMany.mockResolvedValueOnce([mockCommandExecution, mockCommandExecution]);
153+
154+
const result = await service.createCommandExecutions(mockClientOptions, mockCreateCommandExecutionsDto);
155+
156+
expect(result).toEqual([mockCommandExecution, mockCommandExecution]);
157+
});
158+
it('should throw an error when command execution failed', async () => {
159+
workbenchCommandsExecutor.sendCommand.mockRejectedValueOnce(new BadRequestException('error'));
149160

150161
try {
151-
await service.createCommandExecution(mockClientOptions, dto);
162+
await service.createCommandExecutions(mockClientOptions, mockCreateCommandExecutionsDto);
163+
fail();
164+
} catch (e) {
165+
expect(e).toBeInstanceOf(BadRequestException);
166+
}
167+
});
168+
it('should throw an error from command execution provider (create)', async () => {
169+
workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce([mockCommandExecutionResults]);
170+
commandExecutionProvider.createMany.mockRejectedValueOnce(new InternalServerErrorException('db error'));
171+
172+
try {
173+
await service.createCommandExecutions(mockClientOptions, mockCreateCommandExecutionsDto);
152174
fail();
153175
} catch (e) {
154176
expect(e).toBeInstanceOf(InternalServerErrorException);

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Injectable } from '@nestjs/common';
2+
import { omit } from 'lodash';
23
import { IFindRedisClientInstanceByOptions } from 'src/modules/core/services/redis/redis.service';
34
import { WorkbenchCommandsExecutor } from 'src/modules/workbench/providers/workbench-commands.executor';
45
import { CommandExecutionProvider } from 'src/modules/workbench/providers/command-execution.provider';
@@ -29,9 +30,9 @@ export class WorkbenchService {
2930
async createCommandExecution(
3031
clientOptions: IFindRedisClientInstanceByOptions,
3132
dto: CreateCommandExecutionDto,
32-
): Promise<CommandExecution> {
33+
): Promise<Partial<CommandExecution>> {
3334
const commandExecution: Partial<CommandExecution> = {
34-
...dto,
35+
...omit(dto, 'commands'),
3536
databaseId: clientOptions.instanceId,
3637
};
3738

@@ -48,7 +49,7 @@ export class WorkbenchService {
4849
commandExecution.result = await this.commandsExecutor.sendCommand(clientOptions, { ...dto, command });
4950
}
5051

51-
return this.commandExecutionProvider.create(commandExecution);
52+
return commandExecution;
5253
}
5354

5455
/**
@@ -61,9 +62,15 @@ export class WorkbenchService {
6162
clientOptions: IFindRedisClientInstanceByOptions,
6263
dto: CreateCommandExecutionsDto,
6364
): Promise<CommandExecution[]> {
64-
return Promise.all(
65+
// todo: rework to support pipeline
66+
// prepare and execute commands
67+
const commandExecutions = await Promise.all(
6568
dto.commands.map(async (command) => await this.createCommandExecution(clientOptions, { ...dto, command })),
6669
);
70+
71+
// save history
72+
// todo: rework
73+
return this.commandExecutionProvider.createMany(commandExecutions);
6774
}
6875

6976
/**

redisinsight/ui/src/components/query-card/QueryCardHeader/styles.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ $marginIcon: 12px;
4040
}
4141

4242
.titleWrapper {
43-
width: calc(100% - 350px);
43+
width: calc(100% - 380px);
4444

4545
@media (min-width: $breakpoint-m) {
4646
width: calc(100% - 420px);

redisinsight/ui/src/pages/workbench/components/wb-view/WBViewWrapper.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux'
33
import { decode } from 'html-entities'
44
import { useParams } from 'react-router-dom'
55
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'
6-
import { chunk, reverse, without } from 'lodash'
6+
import { chunk, without } from 'lodash'
77

88
import {
99
Nullable,
@@ -145,21 +145,18 @@ const WBViewWrapper = () => {
145145
) => {
146146
const { loading, batchSize } = state
147147
const isNewCommand = () => !commandId
148-
const [commands, ...rest] = chunk(splitMonacoValuePerLines(commandInit), batchSize > 1 ? batchSize : 1)
148+
const commandsForExecuting = splitMonacoValuePerLines(removeMonacoComments(commandInit))
149+
.map((command) => removeMonacoComments(decode(command).trim()))
150+
const [commands, ...rest] = chunk(commandsForExecuting, batchSize > 1 ? batchSize : 1)
149151
const multiCommands = rest.map((command) => getMultiCommands(command))
150-
const commandLine = without(
151-
commands.map((command) => removeMonacoComments(decode(command).trim())),
152-
''
153-
)
154-
155-
if (!commandLine.length || loading) {
152+
if (!commands.length || loading) {
156153
setMultiCommands(multiCommands)
157154
return
158155
}
159156

160157
isNewCommand() && scrollResults('start')
161158

162-
sendCommand(reverse(commandLine), multiCommands)
159+
sendCommand(commands, multiCommands)
163160
}
164161

165162
const sendCommand = (

0 commit comments

Comments
 (0)