Skip to content

Commit 02eb496

Browse files
authored
Merge pull request #1386 from RedisInsight/feature/RI-3527_lua_scripts
Feature/ri 3527 lua scripts
2 parents 9936d2c + 939cde8 commit 02eb496

File tree

89 files changed

+5144
-153
lines changed

Some content is hidden

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

89 files changed

+5144
-153
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class databaseAnalysisRecommendations1668420950002 implements MigrationInterface {
4+
name = 'databaseAnalysisRecommendations1668420950002'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`DROP INDEX "IDX_d174a8edc2201d6c5781f0126a"`);
8+
await queryRunner.query(`DROP INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb"`);
9+
await queryRunner.query(`CREATE TABLE "temporary_database_analysis" ("id" varchar PRIMARY KEY NOT NULL, "databaseId" varchar NOT NULL, "filter" blob, "delimiter" varchar NOT NULL, "progress" blob, "totalKeys" blob, "totalMemory" blob, "topKeysNsp" blob, "topMemoryNsp" blob, "topKeysLength" blob, "topKeysMemory" blob, "encryption" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "expirationGroups" blob, "recommendations" blob, CONSTRAINT "FK_d174a8edc2201d6c5781f0126ae" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
10+
await queryRunner.query(`INSERT INTO "temporary_database_analysis"("id", "databaseId", "filter", "delimiter", "progress", "totalKeys", "totalMemory", "topKeysNsp", "topMemoryNsp", "topKeysLength", "topKeysMemory", "encryption", "createdAt", "expirationGroups") SELECT "id", "databaseId", "filter", "delimiter", "progress", "totalKeys", "totalMemory", "topKeysNsp", "topMemoryNsp", "topKeysLength", "topKeysMemory", "encryption", "createdAt", "expirationGroups" FROM "database_analysis"`);
11+
await queryRunner.query(`DROP TABLE "database_analysis"`);
12+
await queryRunner.query(`ALTER TABLE "temporary_database_analysis" RENAME TO "database_analysis"`);
13+
await queryRunner.query(`CREATE INDEX "IDX_d174a8edc2201d6c5781f0126a" ON "database_analysis" ("databaseId") `);
14+
await queryRunner.query(`CREATE INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb" ON "database_analysis" ("createdAt") `);
15+
}
16+
17+
public async down(queryRunner: QueryRunner): Promise<void> {
18+
await queryRunner.query(`DROP INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb"`);
19+
await queryRunner.query(`DROP INDEX "IDX_d174a8edc2201d6c5781f0126a"`);
20+
await queryRunner.query(`ALTER TABLE "database_analysis" RENAME TO "temporary_database_analysis"`);
21+
await queryRunner.query(`CREATE TABLE "database_analysis" ("id" varchar PRIMARY KEY NOT NULL, "databaseId" varchar NOT NULL, "filter" blob, "delimiter" varchar NOT NULL, "progress" blob, "totalKeys" blob, "totalMemory" blob, "topKeysNsp" blob, "topMemoryNsp" blob, "topKeysLength" blob, "topKeysMemory" blob, "encryption" varchar, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "expirationGroups" blob, CONSTRAINT "FK_d174a8edc2201d6c5781f0126ae" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
22+
await queryRunner.query(`INSERT INTO "database_analysis"("id", "databaseId", "filter", "delimiter", "progress", "totalKeys", "totalMemory", "topKeysNsp", "topMemoryNsp", "topKeysLength", "topKeysMemory", "encryption", "createdAt", "expirationGroups") SELECT "id", "databaseId", "filter", "delimiter", "progress", "totalKeys", "totalMemory", "topKeysNsp", "topMemoryNsp", "topKeysLength", "topKeysMemory", "encryption", "createdAt", "expirationGroups" FROM "temporary_database_analysis"`);
23+
await queryRunner.query(`DROP TABLE "temporary_database_analysis"`);
24+
await queryRunner.query(`CREATE INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb" ON "database_analysis" ("createdAt") `);
25+
await queryRunner.query(`CREATE INDEX "IDX_d174a8edc2201d6c5781f0126a" ON "database_analysis" ("databaseId") `);
26+
}
27+
28+
}

redisinsight/api/migration/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { databaseAnalysis1664785208236 } from './1664785208236-database-analysis
2121
import { databaseAnalysisExpirationGroups1664886479051 } from './1664886479051-database-analysis-expiration-groups';
2222
import { workbenchExecutionTime1667368983699 } from './1667368983699-workbench-execution-time';
2323
import { database1667477693934 } from './1667477693934-database';
24+
import { databaseAnalysisRecommendations1668420950002 } from './1668420950002-database-analysis-recommendations';
2425
import { databaseNew1670252337342 } from './1670252337342-database-new';
2526
import { sshOptions1673035852335 } from './1673035852335-ssh-options';
2627
import { workbenchAndAnalysisDbIndex1673934231410 } from './1673934231410-workbench_and_analysis_db';
@@ -49,6 +50,7 @@ export default [
4950
databaseAnalysisExpirationGroups1664886479051,
5051
workbenchExecutionTime1667368983699,
5152
database1667477693934,
53+
databaseAnalysisRecommendations1668420950002,
5254
databaseNew1670252337342,
5355
sshOptions1673035852335,
5456
workbenchAndAnalysisDbIndex1673934231410,

redisinsight/api/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"body-parser": "^1.19.0",
5454
"class-transformer": "^0.2.3",
5555
"class-validator": "^0.12.2",
56+
"date-fns": "^2.29.3",
5657
"detect-port": "^1.5.1",
5758
"dotenv": "^16.0.0",
5859
"express": "^4.17.1",
@@ -63,6 +64,7 @@
6364
"lodash": "^4.17.20",
6465
"nest-router": "^1.0.9",
6566
"nest-winston": "^1.4.0",
67+
"node-version-compare": "^1.0.3",
6668
"reflect-metadata": "^0.1.13",
6769
"rxjs": "^7.5.6",
6870
"socket.io": "^4.4.0",
@@ -105,7 +107,6 @@
105107
"mocha": "^8.4.0",
106108
"mocha-junit-reporter": "^2.0.0",
107109
"mocha-multi-reporters": "^1.5.1",
108-
"node-version-compare": "^1.0.3",
109110
"nyc": "^15.1.0",
110111
"object-diff": "^0.0.4",
111112
"rimraf": "^3.0.2",

redisinsight/api/src/__mocks__/errors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ export const mockRedisNoAuthError: ReplyError = {
66
message: 'NOAUTH authentication is required',
77
};
88

9+
export const mockRedisNoPasswordError: ReplyError = {
10+
name: 'ReplyError',
11+
command: 'AUTH',
12+
message: 'ERR Client sent AUTH, but no password is set',
13+
};
14+
915
export const mockRedisNoPermError: ReplyError = {
1016
name: 'ReplyError',
1117
command: 'GET',

redisinsight/api/src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './redis-commands';
99
export * from './telemetry-events';
1010
export * from './app-events';
1111
export * from './redis-connection';
12+
export * from './recommendations';
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export const RECOMMENDATION_NAMES = Object.freeze({
2+
LUA_SCRIPT: 'luaScript',
3+
BIG_HASHES: 'bigHashes',
4+
BIG_STRINGS: 'bigStrings',
5+
BIG_SETS: 'bigSets',
6+
BIG_AMOUNT_OF_CONNECTED_CLIENTS: 'bigAmountOfConnectedClients',
7+
USE_SMALLER_KEYS: 'useSmallerKeys',
8+
AVOID_LOGICAL_DATABASES: 'avoidLogicalDatabases',
9+
COMBINE_SMALL_STRINGS_TO_HASHES: 'combineSmallStringsToHashes',
10+
INCREASE_SET_MAX_INTSET_ENTRIES: 'increaseSetMaxIntsetEntries',
11+
HASH_HASHTABLE_TO_ZIPLIST: 'hashHashtableToZiplist',
12+
COMPRESS_HASH_FIELD_NAMES: 'compressHashFieldNames',
13+
COMPRESSION_FOR_LIST: 'compressionForList',
14+
ZSET_HASHTABLE_TO_ZIPLIST: 'zSetHashtableToZiplist',
15+
SET_PASSWORD: 'setPassword',
16+
RTS: 'RTS',
17+
REDIS_VERSION: 'redisVersion',
18+
REDIS_SEARCH: 'redisSearch',
19+
SEARCH_INDEXES: 'searchIndexes',
20+
DANGEROUS_COMMANDS: 'dangerousCommands',
21+
});
22+
23+
export const ONE_NODE_RECOMMENDATIONS = [
24+
RECOMMENDATION_NAMES.LUA_SCRIPT,
25+
RECOMMENDATION_NAMES.DANGEROUS_COMMANDS,
26+
RECOMMENDATION_NAMES.AVOID_LOGICAL_DATABASES,
27+
RECOMMENDATION_NAMES.RTS,
28+
RECOMMENDATION_NAMES.REDIS_VERSION,
29+
];
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export const ARG_IN_QUOTATION_MARKS_REGEX = /"[^"]*|'[^']*'|"+/g;
22
export const IS_INTEGER_NUMBER_REGEX = /^\d+$/;
3+
export const IS_NUMBER_REGEX = /^-?\d*(\.\d+)?$/;
34
export const IS_NON_PRINTABLE_ASCII_CHARACTER = /[^ -~\u0007\b\t\n\r]/;
45
export const IP_ADDRESS_REGEX = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
56
export const PRIVATE_IP_ADDRESS_REGEX = /(^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)/;
7+
export const IS_TIMESTAMP = /^(\d{10}|\d{13}|\d{16}|\d{19})$/;

redisinsight/api/src/modules/database-analysis/database-analysis.controller.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import {
22
Body,
3-
Controller, Get, Param, Post, UseInterceptors, UsePipes, ValidationPipe,
3+
Controller, Get, Param, Post, Patch, UseInterceptors, UsePipes, ValidationPipe,
44
} from '@nestjs/common';
55
import { ApiEndpoint } from 'src/decorators/api-endpoint.decorator';
66
import { ApiTags } from '@nestjs/swagger';
77
import { DatabaseAnalysisService } from 'src/modules/database-analysis/database-analysis.service';
88
import { DatabaseAnalysis, ShortDatabaseAnalysis } from 'src/modules/database-analysis/models';
99
import { BrowserSerializeInterceptor } from 'src/common/interceptors';
1010
import { ApiQueryRedisStringEncoding, ClientMetadataParam } from 'src/common/decorators';
11-
import { CreateDatabaseAnalysisDto } from 'src/modules/database-analysis/dto';
11+
import { CreateDatabaseAnalysisDto, RecommendationVoteDto } from 'src/modules/database-analysis/dto';
1212
import { ClientMetadata } from 'src/common/models';
1313

1414
@UseInterceptors(BrowserSerializeInterceptor)
@@ -72,4 +72,30 @@ export class DatabaseAnalysisController {
7272
): Promise<ShortDatabaseAnalysis[]> {
7373
return this.service.list(databaseId);
7474
}
75+
76+
@Patch(':id')
77+
@ApiEndpoint({
78+
description: 'Update database instance by id',
79+
statusCode: 200,
80+
responses: [
81+
{
82+
status: 200,
83+
description: 'Updated database instance\' response',
84+
type: DatabaseAnalysis,
85+
},
86+
],
87+
})
88+
@UsePipes(
89+
new ValidationPipe({
90+
transform: true,
91+
whitelist: true,
92+
forbidNonWhitelisted: true,
93+
}),
94+
)
95+
async modify(
96+
@Param('id') id: string,
97+
@Body() dto: RecommendationVoteDto,
98+
): Promise<DatabaseAnalysis> {
99+
return await this.service.vote(id, dto);
100+
}
75101
}

redisinsight/api/src/modules/database-analysis/database-analysis.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { DatabaseAnalyzer } from 'src/modules/database-analysis/providers/databa
55
import { DatabaseAnalysisProvider } from 'src/modules/database-analysis/providers/database-analysis.provider';
66
import { KeysScanner } from 'src/modules/database-analysis/scanner/keys-scanner';
77
import { KeyInfoProvider } from 'src/modules/database-analysis/scanner/key-info/key-info.provider';
8+
import { RecommendationModule } from 'src/modules/recommendation/recommendation.module';
89

910
@Module({
11+
imports: [RecommendationModule],
1012
controllers: [DatabaseAnalysisController],
1113
providers: [
1214
DatabaseAnalysisService,

redisinsight/api/src/modules/database-analysis/database-analysis.service.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { HttpException, Injectable, Logger } from '@nestjs/common';
2+
import { isNull, flatten, concat } from 'lodash';
3+
import { RecommendationService } from 'src/modules/recommendation/recommendation.service';
24
import { catchAclError } from 'src/utils';
5+
import { ONE_NODE_RECOMMENDATIONS } from 'src/constants';
36
import { DatabaseAnalyzer } from 'src/modules/database-analysis/providers/database-analyzer';
47
import { plainToClass } from 'class-transformer';
58
import { DatabaseAnalysis, ShortDatabaseAnalysis } from 'src/modules/database-analysis/models';
69
import { DatabaseAnalysisProvider } from 'src/modules/database-analysis/providers/database-analysis.provider';
7-
import { CreateDatabaseAnalysisDto } from 'src/modules/database-analysis/dto';
10+
import { CreateDatabaseAnalysisDto, RecommendationVoteDto } from 'src/modules/database-analysis/dto';
811
import { KeysScanner } from 'src/modules/database-analysis/scanner/keys-scanner';
912
import { DatabaseConnectionService } from 'src/modules/database/database-connection.service';
1013
import { ClientMetadata } from 'src/common/models';
@@ -15,6 +18,7 @@ export class DatabaseAnalysisService {
1518

1619
constructor(
1720
private readonly databaseConnectionService: DatabaseConnectionService,
21+
private readonly recommendationService: RecommendationService,
1822
private readonly analyzer: DatabaseAnalyzer,
1923
private readonly databaseAnalysisProvider: DatabaseAnalysisProvider,
2024
private readonly scanner: KeysScanner,
@@ -50,11 +54,34 @@ export class DatabaseAnalysisService {
5054
progress.total += nodeResult.progress.total;
5155
});
5256

57+
let recommendationToExclude = [];
58+
59+
const recommendations = await scanResults.reduce(async (previousPromise, nodeResult, idx) => {
60+
const jobsArray = await previousPromise;
61+
const nodeRecommendations = await this.recommendationService.getRecommendations({
62+
client: nodeResult.client,
63+
keys: nodeResult.keys,
64+
total: progress.total,
65+
globalClient: client,
66+
exclude: recommendationToExclude,
67+
});
68+
if (idx === 0) {
69+
recommendationToExclude = concat(recommendationToExclude, ONE_NODE_RECOMMENDATIONS);
70+
}
71+
const foundedRecommendations = nodeRecommendations.filter((recommendation) => !isNull(recommendation));
72+
const foundedRecommendationNames = foundedRecommendations.map(({ name }) => name);
73+
recommendationToExclude = concat(recommendationToExclude, foundedRecommendationNames);
74+
recommendationToExclude.push(...foundedRecommendationNames);
75+
jobsArray.push(foundedRecommendations);
76+
return flatten(jobsArray);
77+
}, Promise.resolve([]));
78+
5379
const analysis = plainToClass(DatabaseAnalysis, await this.analyzer.analyze({
5480
databaseId: clientMetadata.databaseId,
5581
db: client?.options?.db || 0,
5682
...dto,
5783
progress,
84+
recommendations,
5885
}, [].concat(...scanResults.map((nodeResult) => nodeResult.keys))));
5986

6087
client.disconnect();
@@ -86,4 +113,13 @@ export class DatabaseAnalysisService {
86113
async list(databaseId: string): Promise<ShortDatabaseAnalysis[]> {
87114
return this.databaseAnalysisProvider.list(databaseId);
88115
}
116+
117+
/**
118+
* Set user vote for recommendation
119+
* @param id
120+
* @param recommendation
121+
*/
122+
async vote(id: string, recommendation: RecommendationVoteDto): Promise<DatabaseAnalysis> {
123+
return this.databaseAnalysisProvider.recommendationVote(id, recommendation);
124+
}
89125
}

0 commit comments

Comments
 (0)