Skip to content

Commit d2fc07c

Browse files
Merge branch 'main' into feature/e2e-tree-view
2 parents 3952a3c + c3b7263 commit d2fc07c

File tree

9 files changed

+75
-21
lines changed

9 files changed

+75
-21
lines changed

.github/redisinsight_browser.png

463 KB
Loading

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ RedisInsight is an intuitive and efficient GUI for Redis, allowing you to intera
2323
* Browse, filter and visualise your key-value Redis data structures
2424
* CRUD support for Lists, Hashes, Strings, Sets, Sorted Sets
2525
* CRUD support for [RedisJSON](https://oss.redis.com/redisjson/)
26+
* Profiler - analyze every command sent to Redis in real-time
2627
* Introducing Workbench - advanced command line interface with intelligent command auto-complete and complex data visualizations
2728
* Command auto-complete support for [RediSearch](https://oss.redis.com/redisearch/), [RedisJSON](https://oss.redis.com/redisjson/), [RedisGraph](https://oss.redis.com/redisgraph/), [RedisTimeSeries](https://oss.redis.com/redistimeseries/), [RedisAI](https://oss.redis.com/redisai/)
2829
* Visualizations of your [RediSearch](https://oss.redis.com/redisearch/) index, queries, and aggregations

redisinsight/api/src/main.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export default async function bootstrap() {
1515
const port = process.env.API_PORT || serverConfig.port;
1616
const logger = WinstonModule.createLogger(LOGGER_CONFIG);
1717

18-
const options: NestApplicationOptions = {};
18+
const options: NestApplicationOptions = {
19+
logger,
20+
};
21+
1922
if (serverConfig.tls && serverConfig.tlsCert && serverConfig.tlsKey) {
2023
options.httpsOptions = {
2124
key: JSON.parse(`"${serverConfig.tlsKey}"`),
@@ -29,7 +32,6 @@ export default async function bootstrap() {
2932
app.use(bodyParser.urlencoded({ limit: '512mb', extended: true }));
3033
app.enableCors();
3134
app.setGlobalPrefix(serverConfig.globalPrefix);
32-
app.useLogger(logger);
3335

3436
if (process.env.APP_ENV !== 'electron') {
3537
SwaggerModule.setup(

redisinsight/api/src/modules/cli/services/cli-business/output-formatter/strategies/raw-formatter.strategy.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('Cli RawFormatterStrategy', () => {
4444
Buffer.from('"quoted key"'),
4545
],
4646
];
47-
const mockResponse = ['0', ['key', '"quoted""key"', '"quoted key"']];
47+
const mockResponse = ['0', ['key', '\\"quoted\\"\\"key\\"', '\\"quoted key\\"']];
4848
const output = strategy.format(input);
4949

5050
expect(output).toEqual(mockResponse);
@@ -69,7 +69,7 @@ describe('Cli RawFormatterStrategy', () => {
6969
const input = Buffer.from(JSON.stringify(object));
7070
const output = strategy.format(input);
7171

72-
expect(output).toEqual(JSON.stringify(object));
72+
expect(output).toEqual('{\\"key\\":\\"value\\"}');
7373
});
7474
});
7575
});

redisinsight/api/src/modules/cli/services/cli-business/output-formatter/strategies/raw-formatter.strategy.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { isArray, isObject } from 'lodash';
2+
import { getASCIISafeStringFromBuffer } from 'src/utils/cli-helper';
23
import { IOutputFormatterStrategy } from '../output-formatter.interface';
34

45
export class RawFormatterStrategy implements IOutputFormatterStrategy {
56
public format(reply: any): any {
67
if (reply instanceof Buffer) {
7-
return this.formatRedisBufferReply(reply);
8+
return getASCIISafeStringFromBuffer(reply);
89
}
910
if (isArray(reply)) {
1011
return this.formatRedisArrayReply(reply);
@@ -29,10 +30,6 @@ export class RawFormatterStrategy implements IOutputFormatterStrategy {
2930
return result;
3031
}
3132

32-
private formatRedisBufferReply(reply: Buffer): string {
33-
return reply.toString();
34-
}
35-
3633
private formatRedisObjectReply(reply: Object): object {
3734
const result = {};
3835
Object.keys(reply).forEach((key) => {

redisinsight/api/src/modules/workbench/providers/workbench-commands.executor.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ import {
1616
import { CommandExecutionResult } from 'src/modules/workbench/models/command-execution-result';
1717
import { CreateCommandExecutionDto } from 'src/modules/workbench/dto/create-command-execution.dto';
1818
import { RedisToolService } from 'src/modules/shared/services/base/redis-tool.service';
19+
import { RawFormatterStrategy } from 'src/modules/cli/services/cli-business/output-formatter/strategies/raw-formatter.strategy';
1920
import { WorkbenchAnalyticsService } from '../services/workbench-analytics/workbench-analytics.service';
2021

2122
@Injectable()
2223
export class WorkbenchCommandsExecutor {
2324
private logger = new Logger('WorkbenchCommandsExecutor');
2425

26+
private formatter = new RawFormatterStrategy();
27+
2528
constructor(
2629
private redisTool: RedisToolService,
2730
private analyticsService: WorkbenchAnalyticsService,
@@ -60,7 +63,10 @@ export class WorkbenchCommandsExecutor {
6063

6164
try {
6265
const [command, ...args] = splitCliCommandLine(commandLine);
63-
const response = await this.redisTool.execCommand(clientOptions, command, args, 'utf-8');
66+
const response = this.formatter.format(
67+
await this.redisTool.execCommand(clientOptions, command, args),
68+
);
69+
6470
this.logger.log('Succeed to execute workbench command.');
6571

6672
const result = { response, status: CommandExecutionStatus.Success };
@@ -100,7 +106,6 @@ export class WorkbenchCommandsExecutor {
100106
args,
101107
role,
102108
nodeAddress,
103-
'utf-8',
104109
);
105110
if (result.error && checkRedirectionError(result.error) && nodeOptions.enableRedirection) {
106111
const { slot, address } = parseRedirectionError(result.error);
@@ -110,7 +115,6 @@ export class WorkbenchCommandsExecutor {
110115
args,
111116
role,
112117
address,
113-
'utf-8',
114118
);
115119
result.slot = parseInt(slot, 10);
116120
}
@@ -119,7 +123,12 @@ export class WorkbenchCommandsExecutor {
119123
const {
120124
host, port, error, slot, ...rest
121125
} = result;
122-
return { ...rest, node: { host, port, slot } };
126+
127+
return {
128+
...rest,
129+
response: this.formatter.format(rest.response),
130+
node: { host, port, slot },
131+
};
123132
} catch (error) {
124133
this.logger.error('Failed to execute redis.cluster CLI command.', error);
125134
const result = { response: error.message, status: CommandExecutionStatus.Fail };
@@ -148,12 +157,16 @@ export class WorkbenchCommandsExecutor {
148157
const [command, ...args] = splitCliCommandLine(commandLine);
149158

150159
return (
151-
await this.redisTool.execCommandForNodes(clientOptions, command, args, role, 'utf-8')
160+
await this.redisTool.execCommandForNodes(clientOptions, command, args, role)
152161
).map((nodeExecReply) => {
153162
const {
154163
response, status, host, port,
155164
} = nodeExecReply;
156-
const result = { response, status, node: { host, port } };
165+
const result = {
166+
response: this.formatter.format(response),
167+
status,
168+
node: { host, port },
169+
};
157170
this.analyticsService.sendCommandExecutedEvent(clientOptions.instanceId, result, { command });
158171
return result;
159172
});

redisinsight/api/src/utils/cli-helper.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ERROR_MESSAGES from 'src/constants/error-messages';
44
import { CommandParsingError, RedirectionParsingError } from 'src/modules/cli/constants/errors';
55
import { ReplyError } from 'src/models';
66
import { IRedirectionInfo } from 'src/modules/cli/services/cli-business/output-formatter/output-formatter.interface';
7+
import { IS_NON_PRINTABLE_ASCII_CHARACTER } from 'src/constants';
78

89
const LOGGER_CONFIG = config.get('logger');
910
const BLANK_LINE_REGEX = /^\s*\n/gm;
@@ -219,3 +220,43 @@ export const multilineCommandToOneLine = (text: string = '') => text
219220
.split(/(\r\n|\n|\r)+\s+/gm)
220221
.filter((line: string) => !(BLANK_LINE_REGEX.test(line) || isEmpty(line)))
221222
.join(' ');
223+
224+
/**
225+
* Produces an escaped string representation of a byte string.
226+
* Ported from sdscatrepr() function in sds.c from Redis source code.
227+
* This is the function redis-cli uses to escape strings for output.
228+
* @param reply
229+
*/
230+
export const getASCIISafeStringFromBuffer = (reply: Buffer): string => {
231+
let result = '';
232+
reply.forEach((byte: number) => {
233+
const char = Buffer.from([byte]).toString();
234+
if (IS_NON_PRINTABLE_ASCII_CHARACTER.test(char)) {
235+
result += `\\x${decimalToHexString(byte)}`;
236+
} else {
237+
switch (char) {
238+
case '\u0007': // Bell character
239+
result += '\\a';
240+
break;
241+
case '"':
242+
result += `\\${char}`;
243+
break;
244+
case '\b':
245+
result += '\\b';
246+
break;
247+
case '\t':
248+
result += '\\t';
249+
break;
250+
case '\n':
251+
result += '\\n';
252+
break;
253+
case '\r':
254+
result += '\\r';
255+
break;
256+
default:
257+
result += char;
258+
}
259+
}
260+
});
261+
return result;
262+
};

redisinsight/api/test/api/workbench/POST-instance-id-workbench-command_executions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ describe('POST /instance/:instanceId/workbench/command-executions', () => {
439439
expect(body.result).to.eql([
440440
{
441441
status: 'success',
442-
response: '"value"'
442+
response: '\\"value\\"'
443443
},
444444
]);
445445
}
@@ -750,7 +750,7 @@ describe('POST /instance/:instanceId/workbench/command-executions', () => {
750750
for (let i = 0; i < 10; i++) {
751751
response.push(
752752
`${constants.TEST_SEARCH_JSON_KEY_PREFIX_1}${i}`,
753-
['$', `{"user":{"name":"John Smith${i}"}}`],
753+
['$', `{\\"user\\":{\\"name\\":\\"John Smith${i}\\"}}`],
754754
);
755755
}
756756

redisinsight/ui/src/utils/cliTextFormatter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ const formatToText = (reply: any, command: string = ''): string => {
1414
} else if (isObject(reply)) {
1515
result = formatRedisArrayReply(flattenDeep(Object.entries(reply)))
1616
} else if (isFormattedCommand(command)) {
17-
result = JSON.stringify(reply)
18-
} else {
1917
result = reply
18+
} else {
19+
result = `"${reply}"`
2020
}
2121

2222
return result
2323
}
2424

25-
const isFormattedCommand = (commandLine: string = '') => !bulkReplyCommands?.find((command) =>
25+
const isFormattedCommand = (commandLine: string = '') => !!bulkReplyCommands?.find((command) =>
2626
commandLine?.trim().toUpperCase().startsWith(command))
2727

2828
const formatRedisArrayReply = (reply: any | any[], level = 0): string => {
@@ -41,7 +41,7 @@ const formatRedisArrayReply = (reply: any | any[], level = 0): string => {
4141
.join('\n')
4242
}
4343
} else {
44-
result = JSON.stringify(reply)
44+
result = `"${reply}"`
4545
}
4646
return result
4747
}

0 commit comments

Comments
 (0)