diff --git a/redisinsight/api/migration/1673934231410-workbench_and_analysis_db.ts b/redisinsight/api/migration/1673934231410-workbench_and_analysis_db.ts new file mode 100644 index 0000000000..71f4fa194c --- /dev/null +++ b/redisinsight/api/migration/1673934231410-workbench_and_analysis_db.ts @@ -0,0 +1,40 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class workbenchAndAnalysisDbIndex1673934231410 implements MigrationInterface { + name = 'workbenchAndAnalysisDbIndex1673934231410' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_5cd90dd6def1fd7c521e53fb2c"`); + 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, "executionTime" integer, "db" integer, CONSTRAINT "FK_ea8adfe9aceceb79212142206b8" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_command_execution"("id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary", "executionTime") SELECT "id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary", "executionTime" FROM "command_execution"`); + await queryRunner.query(`DROP TABLE "command_execution"`); + await queryRunner.query(`ALTER TABLE "temporary_command_execution" RENAME TO "command_execution"`); + await queryRunner.query(`CREATE INDEX "IDX_5cd90dd6def1fd7c521e53fb2c" ON "command_execution" ("createdAt") `); + await queryRunner.query(`DROP INDEX "IDX_d174a8edc2201d6c5781f0126a"`); + await queryRunner.query(`DROP INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb"`); + 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, "db" integer, CONSTRAINT "FK_d174a8edc2201d6c5781f0126ae" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`); + 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"`); + await queryRunner.query(`DROP TABLE "database_analysis"`); + await queryRunner.query(`ALTER TABLE "temporary_database_analysis" RENAME TO "database_analysis"`); + await queryRunner.query(`CREATE INDEX "IDX_d174a8edc2201d6c5781f0126a" ON "database_analysis" ("databaseId") `); + await queryRunner.query(`CREATE INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb" ON "database_analysis" ("createdAt") `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb"`); + await queryRunner.query(`DROP INDEX "IDX_d174a8edc2201d6c5781f0126a"`); + await queryRunner.query(`ALTER TABLE "database_analysis" RENAME TO "temporary_database_analysis"`); + 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)`); + 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"`); + await queryRunner.query(`DROP TABLE "temporary_database_analysis"`); + await queryRunner.query(`CREATE INDEX "IDX_fdd0daeb4d8f226cf1ff79bebb" ON "database_analysis" ("createdAt") `); + await queryRunner.query(`CREATE INDEX "IDX_d174a8edc2201d6c5781f0126a" ON "database_analysis" ("databaseId") `); + await queryRunner.query(`DROP INDEX "IDX_5cd90dd6def1fd7c521e53fb2c"`); + await queryRunner.query(`ALTER TABLE "command_execution" RENAME TO "temporary_command_execution"`); + 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, "resultsMode" varchar, "summary" varchar, "executionTime" integer, CONSTRAINT "FK_ea8adfe9aceceb79212142206b8" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "command_execution"("id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary", "executionTime") SELECT "id", "databaseId", "command", "result", "role", "nodeOptions", "encryption", "createdAt", "mode", "resultsMode", "summary", "executionTime" FROM "temporary_command_execution"`); + await queryRunner.query(`DROP TABLE "temporary_command_execution"`); + await queryRunner.query(`CREATE INDEX "IDX_5cd90dd6def1fd7c521e53fb2c" ON "command_execution" ("createdAt") `); + } + +} diff --git a/redisinsight/api/migration/index.ts b/redisinsight/api/migration/index.ts index c5964662cc..26caa5f803 100644 --- a/redisinsight/api/migration/index.ts +++ b/redisinsight/api/migration/index.ts @@ -23,6 +23,7 @@ import { workbenchExecutionTime1667368983699 } from './1667368983699-workbench-e import { database1667477693934 } from './1667477693934-database'; import { databaseNew1670252337342 } from './1670252337342-database-new'; import { sshOptions1673035852335 } from './1673035852335-ssh-options'; +import { workbenchAndAnalysisDbIndex1673934231410 } from './1673934231410-workbench_and_analysis_db'; export default [ initialMigration1614164490968, @@ -50,4 +51,5 @@ export default [ database1667477693934, databaseNew1670252337342, sshOptions1673035852335, + workbenchAndAnalysisDbIndex1673934231410, ]; diff --git a/redisinsight/api/src/modules/database-analysis/database-analysis.service.ts b/redisinsight/api/src/modules/database-analysis/database-analysis.service.ts index 258bd135da..9d828341f4 100644 --- a/redisinsight/api/src/modules/database-analysis/database-analysis.service.ts +++ b/redisinsight/api/src/modules/database-analysis/database-analysis.service.ts @@ -52,6 +52,7 @@ export class DatabaseAnalysisService { const analysis = plainToClass(DatabaseAnalysis, await this.analyzer.analyze({ databaseId: clientMetadata.databaseId, + db: client?.options?.db || 0, ...dto, progress, }, [].concat(...scanResults.map((nodeResult) => nodeResult.keys)))); diff --git a/redisinsight/api/src/modules/database-analysis/entities/database-analysis.entity.ts b/redisinsight/api/src/modules/database-analysis/entities/database-analysis.entity.ts index 1874261ad7..13794a372d 100644 --- a/redisinsight/api/src/modules/database-analysis/entities/database-analysis.entity.ts +++ b/redisinsight/api/src/modules/database-analysis/entities/database-analysis.entity.ts @@ -1,6 +1,7 @@ import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, JoinColumn, Index, } from 'typeorm'; +import { IsInt, Min } from 'class-validator'; import { Expose, Transform } from 'class-transformer'; import { DataAsJsonString } from 'src/common/decorators'; import { DatabaseEntity } from 'src/modules/database/entities/database.entity'; @@ -118,6 +119,12 @@ export class DatabaseAnalysisEntity { @Column({ nullable: true }) encryption: string; + @Column({ nullable: true }) + @Expose() + @IsInt() + @Min(0) + db?: number; + @CreateDateColumn() @Index() @Expose() diff --git a/redisinsight/api/src/modules/database-analysis/models/database-analysis.ts b/redisinsight/api/src/modules/database-analysis/models/database-analysis.ts index 9579ab2013..4c63c66435 100644 --- a/redisinsight/api/src/modules/database-analysis/models/database-analysis.ts +++ b/redisinsight/api/src/modules/database-analysis/models/database-analysis.ts @@ -1,8 +1,9 @@ import { NspSummary } from 'src/modules/database-analysis/models/nsp-summary'; import { Key } from 'src/modules/database-analysis/models/key'; +import { IsInt, IsOptional, Min } from 'class-validator'; import { Expose, Type } from 'class-transformer'; import { SimpleSummary } from 'src/modules/database-analysis/models/simple-summary'; -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ScanFilter } from 'src/modules/database-analysis/models/scan-filter'; import { AnalysisProgress } from 'src/modules/database-analysis/models/analysis-progress'; import { SumGroup } from 'src/modules/database-analysis/models/sum-group'; @@ -114,4 +115,14 @@ export class DatabaseAnalysis { @Expose() @Type(() => SumGroup) expirationGroups: SumGroup[]; + + @ApiPropertyOptional({ + description: 'Logical database number.', + type: Number, + }) + @Expose() + @IsInt() + @Min(0) + @IsOptional() + db?: number; } diff --git a/redisinsight/api/src/modules/database-analysis/models/short-database-analysis.ts b/redisinsight/api/src/modules/database-analysis/models/short-database-analysis.ts index ce8c796bd3..f99f014b31 100644 --- a/redisinsight/api/src/modules/database-analysis/models/short-database-analysis.ts +++ b/redisinsight/api/src/modules/database-analysis/models/short-database-analysis.ts @@ -2,5 +2,5 @@ import { PartialType, PickType } from '@nestjs/swagger'; import { DatabaseAnalysis } from 'src/modules/database-analysis/models/database-analysis'; export class ShortDatabaseAnalysis extends PartialType( - PickType(DatabaseAnalysis, ['id', 'createdAt'] as const), + PickType(DatabaseAnalysis, ['id', 'createdAt', 'db'] as const), ) {} diff --git a/redisinsight/api/src/modules/database-analysis/providers/database-analysis.provider.ts b/redisinsight/api/src/modules/database-analysis/providers/database-analysis.provider.ts index 8731ba4f97..89ef9fd082 100644 --- a/redisinsight/api/src/modules/database-analysis/providers/database-analysis.provider.ts +++ b/redisinsight/api/src/modules/database-analysis/providers/database-analysis.provider.ts @@ -76,7 +76,7 @@ export class DatabaseAnalysisProvider { const entities = await this.repository .createQueryBuilder('a') .where({ databaseId }) - .select(['a.id', 'a.createdAt']) + .select(['a.id', 'a.createdAt', 'a.db']) .orderBy('a.createdAt', 'DESC') .limit(DATABASE_ANALYSIS_CONFIG.maxItemsPerDb) .getMany(); diff --git a/redisinsight/api/src/modules/workbench/entities/command-execution.entity.ts b/redisinsight/api/src/modules/workbench/entities/command-execution.entity.ts index e71e96e970..f7bf2589a7 100644 --- a/redisinsight/api/src/modules/workbench/entities/command-execution.entity.ts +++ b/redisinsight/api/src/modules/workbench/entities/command-execution.entity.ts @@ -4,6 +4,7 @@ import { import { DatabaseEntity } from 'src/modules/database/entities/database.entity'; import { RunQueryMode, ResultsMode } from 'src/modules/workbench/dto/create-command-execution.dto'; import { Expose, Transform } from 'class-transformer'; +import { IsInt, Min } from 'class-validator'; @Entity('command_execution') export class CommandExecutionEntity { @@ -84,6 +85,12 @@ export class CommandExecutionEntity { @Expose() executionTime?: number; + @Column({ nullable: true }) + @Expose() + @IsInt() + @Min(0) + db?: number; + @CreateDateColumn() @Index() @Expose() diff --git a/redisinsight/api/src/modules/workbench/models/command-execution.ts b/redisinsight/api/src/modules/workbench/models/command-execution.ts index 388aa96d7b..f768786cdd 100644 --- a/redisinsight/api/src/modules/workbench/models/command-execution.ts +++ b/redisinsight/api/src/modules/workbench/models/command-execution.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsDefined } from 'class-validator'; +import { IsDefined, IsInt, IsOptional, Min } from 'class-validator'; import { CommandExecutionResult } from 'src/modules/workbench/models/command-execution-result'; import { ClusterNodeRole, RunQueryMode, ResultsMode } from 'src/modules/workbench/dto/create-command-execution.dto'; import { ClusterSingleNodeOptions } from 'src/modules/cli/dto/cli.dto'; @@ -117,6 +117,16 @@ export class CommandExecution { @Expose() executionTime?: number; + @ApiPropertyOptional({ + description: 'Logical database number.', + type: Number, + }) + @Expose() + @IsInt() + @Min(0) + @IsOptional() + db?: number; + constructor(partial: Partial = {}) { Object.assign(this, partial); } diff --git a/redisinsight/api/src/modules/workbench/providers/command-execution.provider.ts b/redisinsight/api/src/modules/workbench/providers/command-execution.provider.ts index 18da177da3..26fa987dcc 100644 --- a/redisinsight/api/src/modules/workbench/providers/command-execution.provider.ts +++ b/redisinsight/api/src/modules/workbench/providers/command-execution.provider.ts @@ -97,6 +97,7 @@ export class CommandExecutionProvider { 'e.summary', 'e.resultsMode', 'e.executionTime', + 'e.db', ]) .orderBy('e.createdAt', 'DESC') .limit(WORKBENCH_CONFIG.maxItemsPerDb) diff --git a/redisinsight/api/src/modules/workbench/workbench.service.spec.ts b/redisinsight/api/src/modules/workbench/workbench.service.spec.ts index fccaa2efca..408b8b8cb5 100644 --- a/redisinsight/api/src/modules/workbench/workbench.service.spec.ts +++ b/redisinsight/api/src/modules/workbench/workbench.service.spec.ts @@ -85,6 +85,7 @@ const mockCommandExecutionResults: CommandExecutionResult[] = [ const mockCommandExecutionToRun: CommandExecution = new CommandExecution({ ...mockCreateCommandExecutionDto, databaseId: mockDatabase.id, + db: 0, }); const mockCommandExecution: CommandExecution = new CommandExecution({ @@ -177,6 +178,12 @@ describe('WorkbenchService', () => { expect(result).toMatchObject(mockCommandExecutionToRun); expect(result.executionTime).toBeGreaterThan(0); }); + it('should save db index', async () => { + const db = 2 + const result = await service.createCommandExecution(mockWorkbenchClientMetadata, mockCreateCommandExecutionDto, db); + expect(result).toMatchObject({...mockCommandExecutionToRun, db}); + expect(result.db).toBe(db); + }); it('should save result as unsupported command message', async () => { workbenchCommandsExecutor.sendCommand.mockResolvedValueOnce(mockCommandExecutionResults); @@ -188,6 +195,7 @@ describe('WorkbenchService', () => { expect(await service.createCommandExecution(mockWorkbenchClientMetadata, dto)).toEqual({ ...dto, + db: 0, databaseId: mockWorkbenchClientMetadata.databaseId, result: [ { diff --git a/redisinsight/api/src/modules/workbench/workbench.service.ts b/redisinsight/api/src/modules/workbench/workbench.service.ts index c579cc7852..0ce1a9b11b 100644 --- a/redisinsight/api/src/modules/workbench/workbench.service.ts +++ b/redisinsight/api/src/modules/workbench/workbench.service.ts @@ -28,13 +28,16 @@ export class WorkbenchService { * * @param clientMetadata * @param dto + * @param db */ async createCommandExecution( clientMetadata: ClientMetadata, dto: CreateCommandExecutionDto, + db: number = 0, ): Promise> { const commandExecution: Partial = { ...omit(dto, 'commands'), + db, databaseId: clientMetadata.databaseId, }; @@ -63,16 +66,19 @@ export class WorkbenchService { * @param clientMetadata * @param dto * @param commands + * @param db * @param onlyErrorResponse */ async createCommandsExecution( clientMetadata: ClientMetadata, dto: Partial, commands: string[], + db: number = 0, onlyErrorResponse: boolean = false, ): Promise> { const commandExecution: Partial = { ...dto, + db, databaseId: clientMetadata.databaseId, }; let executionTimeInNanoseconds = BigInt(0); @@ -134,17 +140,19 @@ export class WorkbenchService { ): Promise { // todo: handle concurrent client creation on RedisModule side // temporary workaround. Just create client before any command execution precess - await this.databaseConnectionService.getOrCreateClient(clientMetadata); + const client = await this.databaseConnectionService.getOrCreateClient(clientMetadata); if (dto.resultsMode === ResultsMode.GroupMode || dto.resultsMode === ResultsMode.Silent) { return this.commandExecutionProvider.createMany( - [await this.createCommandsExecution(clientMetadata, dto, dto.commands, dto.resultsMode === ResultsMode.Silent)], + [await this.createCommandsExecution(clientMetadata, dto, dto.commands, client?.options?.db, dto.resultsMode === ResultsMode.Silent)], ); } // todo: rework to support pipeline // prepare and execute commands const commandExecutions = await Promise.all( - dto.commands.map(async (command) => await this.createCommandExecution(clientMetadata, { ...dto, command })), + dto.commands.map( + async (command) => await this.createCommandExecution(clientMetadata, { ...dto, command }, client?.options?.db), + ), ); // save history diff --git a/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis-id.test.ts b/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis-id.test.ts index f6826fded6..7f4c5c7602 100644 --- a/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis-id.test.ts +++ b/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis-id.test.ts @@ -27,6 +27,7 @@ describe('GET /databases/:id/analysis/:id', () => { expect(body).to.deep.eq({ id: constants.TEST_DATABASE_ANALYSIS_ID_1, databaseId: constants.TEST_INSTANCE_ID, + db: constants.TEST_DATABASE_ANALYSIS_DB_1, createdAt: constants.TEST_DATABASE_ANALYSIS_CREATED_AT_1.toISOString(), delimiter: constants.TEST_DATABASE_ANALYSIS_DELIMITER_1, filter: constants.TEST_DATABASE_ANALYSIS_FILTER_1, diff --git a/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis.test.ts b/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis.test.ts index 11aca36a00..a9674567da 100644 --- a/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis.test.ts +++ b/redisinsight/api/test/api/database-analysis/GET-databases-id-analysis.test.ts @@ -10,6 +10,7 @@ const endpoint = ( const responseSchema = Joi.array().items(Joi.object({ createdAt: Joi.date().required(), id: Joi.string().required(), + db: Joi.number().integer().allow(null), })).required().max(5); const mainCheckFn = getMainCheckFn(endpoint); diff --git a/redisinsight/api/test/api/database-analysis/POST-databases-id-analysis.test.ts b/redisinsight/api/test/api/database-analysis/POST-databases-id-analysis.test.ts index 8d38826fff..4edf9b4f0a 100644 --- a/redisinsight/api/test/api/database-analysis/POST-databases-id-analysis.test.ts +++ b/redisinsight/api/test/api/database-analysis/POST-databases-id-analysis.test.ts @@ -44,6 +44,7 @@ describe('POST /databases/:instanceId/analysis', () => { expect(body.topKeysLength.length).to.gt(0); expect(body.topKeysMemory.length).to.gt(0); expect(body.expirationGroups.length).to.gt(0); + expect(body.db).to.gte(0); }, after: async () => { expect(await repository.count()).to.eq(5); @@ -64,6 +65,7 @@ describe('POST /databases/:instanceId/analysis', () => { expect(body.topKeysLength.length).to.gt(0); expect(body.topKeysMemory.length).to.gt(0); expect(body.expirationGroups.length).to.gt(0); + expect(body.db).to.gte(0); }, after: async () => { expect(await repository.count()).to.eq(5); @@ -142,6 +144,7 @@ describe('POST /databases/:instanceId/analysis', () => { expect(body.expirationGroups[0].label).to.eq('No Expiry'); expect(body.expirationGroups[0].total).to.gt(0); expect(body.expirationGroups[0].threshold).to.eq(0); + expect(body.db).to.eq(0); }, after: async () => { expect(await repository.count()).to.eq(5); diff --git a/redisinsight/api/test/api/database-analysis/constants.ts b/redisinsight/api/test/api/database-analysis/constants.ts index a2f587db60..a529cef10a 100644 --- a/redisinsight/api/test/api/database-analysis/constants.ts +++ b/redisinsight/api/test/api/database-analysis/constants.ts @@ -50,6 +50,7 @@ export const analysisSchema = Joi.object().keys({ }).required(), totalKeys: typedTotalSchema.required(), totalMemory: typedTotalSchema.required(), + db: Joi.number().integer().allow(null), topKeysNsp: Joi.array().items(nspSummarySchema).required().max(15), topMemoryNsp: Joi.array().items(nspSummarySchema).required().max(15), topKeysLength: Joi.array().items(keySchema).required().max(15), diff --git a/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions-id.test.ts b/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions-id.test.ts index 64c1210931..1bf8e13145 100644 --- a/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions-id.test.ts +++ b/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions-id.test.ts @@ -38,6 +38,7 @@ const responseSchema = Joi.object().keys({ port: Joi.number().required(), enableRedirection: Joi.boolean().required(), }).allow(null), + db: Joi.number().integer().allow(null), createdAt: Joi.date().required(), }).required(); diff --git a/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions.test.ts b/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions.test.ts index 999128f931..f705326087 100644 --- a/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions.test.ts +++ b/redisinsight/api/test/api/workbench/GET-databases-id-workbench-command_executions.test.ts @@ -26,6 +26,7 @@ const responseSchema = Joi.array().items(Joi.object().keys({ port: Joi.number().required(), enableRedirection: Joi.boolean().required(), }).allow(null), + db: Joi.number().integer().allow(null), createdAt: Joi.date().required(), })).required().max(30); diff --git a/redisinsight/api/test/api/workbench/POST-databases-id-workbench-command_executions.test.ts b/redisinsight/api/test/api/workbench/POST-databases-id-workbench-command_executions.test.ts index e0aadaff31..77a157c1c7 100644 --- a/redisinsight/api/test/api/workbench/POST-databases-id-workbench-command_executions.test.ts +++ b/redisinsight/api/test/api/workbench/POST-databases-id-workbench-command_executions.test.ts @@ -72,6 +72,7 @@ const responseSchema = Joi.array().items(Joi.object().keys({ }).allow(null), createdAt: Joi.date().required(), executionTime: Joi.number().integer(), + db: Joi.number().integer().allow(null), isNotStored: Joi.boolean(), summary: Joi.object({ total: Joi.number(), @@ -133,6 +134,7 @@ describe('POST /databases/:instanceId/workbench/command-executions', () => { expect(body[0].command).to.eql(`get ${constants.TEST_STRING_KEY_1}`); expect(body[0].role).to.eql(null); expect(body[0].executionTime).to.be.a('number'); + expect(body[0].db).to.be.eql(0); expect(body[0].role).to.eql(null); expect(body[0].result.length).to.eql(1); expect(body[0].result[0].response).to.eql(bigStringValue); @@ -1236,6 +1238,8 @@ describe('POST /databases/:instanceId/workbench/command-executions', () => { expect(entity_2.encryption).to.eql(constants.TEST_ENCRYPTION_STRATEGY); expect(body[0].executionTime).to.eql(entity_1.executionTime); expect(body[1].executionTime).to.eql(entity_2.executionTime); + expect(body[0].db).to.eql(entity_1.db); + expect(body[1].db).to.eql(entity_2.db); expect(localDb.encryptData(body[0].command)).to.eql(entity_1.command); expect(localDb.encryptData(body[1].command)).to.eql(entity_2.command); expect(body[0].result[0].status).to.eql('success'); @@ -1264,6 +1268,8 @@ describe('POST /databases/:instanceId/workbench/command-executions', () => { expect(entity_2.encryption).to.eql(constants.TEST_ENCRYPTION_STRATEGY); expect(body[0].executionTime).to.eql(entity_1.executionTime); expect(body[1].executionTime).to.eql(entity_2.executionTime); + expect(body[0].db).to.eql(entity_1.db); + expect(body[1].db).to.eql(entity_2.db); expect(localDb.encryptData(body[0].command)).to.eql(entity_1.command); expect(localDb.encryptData(body[1].command)).to.eql(entity_2.command); expect(body[0].result[0].status).to.eql('success'); @@ -1299,6 +1305,7 @@ describe('POST /databases/:instanceId/workbench/command-executions', () => { expect(entity.encryption).to.eql(constants.TEST_ENCRYPTION_STRATEGY); expect(body[0].executionTime).to.eql(entity.executionTime); + expect(body[0].db).to.eql(entity.db); // group mode should always return success expect(body[0].result[0].status).to.eql('success'); expect(body[0].result[0].response[0].status).to.eql('success'); @@ -1346,6 +1353,8 @@ describe('POST /databases/:instanceId/workbench/command-executions', () => { ]); expect(body[0].result[0].response[1].response).to.include('ERR unknown command'); expect(localDb.encryptData(JSON.stringify(body[0].result))).to.eql(entity.result); + expect(body[0].db).to.be.a('number') + expect(body[0].db).to.eql(entity.db); } }, ].map(mainCheckFn); diff --git a/redisinsight/api/test/helpers/constants.ts b/redisinsight/api/test/helpers/constants.ts index 433d32bb36..c0eac54f1e 100644 --- a/redisinsight/api/test/helpers/constants.ts +++ b/redisinsight/api/test/helpers/constants.ts @@ -384,6 +384,7 @@ export const constants = { TEST_DATABASE_ANALYSIS_ID_1: uuidv4(), TEST_DATABASE_ANALYSIS_CREATED_AT_1: new Date(), TEST_DATABASE_ANALYSIS_DELIMITER_1: ':', + TEST_DATABASE_ANALYSIS_DB_1: 2, TEST_DATABASE_ANALYSIS_FILTER_1: { type: null, match: '*', diff --git a/redisinsight/api/test/helpers/local-db.ts b/redisinsight/api/test/helpers/local-db.ts index 7e79f96d40..2961fe3bcd 100644 --- a/redisinsight/api/test/helpers/local-db.ts +++ b/redisinsight/api/test/helpers/local-db.ts @@ -145,6 +145,7 @@ export const generateNDatabaseAnalysis = async ( result.push(await rep.save({ id: uuidv4(), databaseId: uuidv4(), + db: constants.TEST_DATABASE_ANALYSIS_DB_1, delimiter: constants.TEST_DATABASE_ANALYSIS_DELIMITER_1, filter: encryptData(JSON.stringify(constants.TEST_DATABASE_ANALYSIS_FILTER_1)), progress: encryptData(JSON.stringify(constants.TEST_DATABASE_ANALYSIS_PROGRESS_1)), diff --git a/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx b/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx index 47c06ec486..40de023fde 100644 --- a/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-header/CliHeader.tsx @@ -22,6 +22,7 @@ import { sessionStorageService } from 'uiSrc/services' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { outputSelector, resetOutputLoading } from 'uiSrc/slices/cli/cli-output' +import { getDbIndex } from 'uiSrc/utils' import styles from './styles.module.scss' @@ -32,7 +33,7 @@ const CliHeader = () => { const { host, port } = useSelector(connectedInstanceSelector) const { db } = useSelector(outputSelector) - const endpoint = db > 0 ? `${host}:${port}[${db}]` : `${host}:${port}` + const endpoint = `${host}:${port}${getDbIndex(db)}` const removeCliClient = () => { const cliClientUuid = sessionStorageService.get(BrowserStorageItem.cliClientUuid) ?? '' diff --git a/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.spec.tsx b/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.spec.tsx index 2a3d9efd04..faa4f5dda4 100644 --- a/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.spec.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.spec.tsx @@ -23,7 +23,7 @@ describe('CliInput', () => { const dbIndexEl = queryByTestId('cli-db-index') expect(dbIndexEl).toBeInTheDocument() - expect(dbIndexEl).toHaveTextContent('[1]') + expect(dbIndexEl).toHaveTextContent('[db1]') }) it('should not render db index if it is 0', () => { diff --git a/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.tsx b/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.tsx index 645e342436..23255d582a 100644 --- a/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.tsx +++ b/redisinsight/ui/src/components/cli/components/cli-input/CliInput/CliInput.tsx @@ -3,6 +3,7 @@ import { ContentEditableEvent } from 'react-contenteditable' import { ContentEditable } from 'uiSrc/components' import { parseContentEditableChangeHtml } from 'uiSrc/components/ContentEditable' +import { getDbIndex } from 'uiSrc/utils' import styles from './styles.module.scss' @@ -28,7 +29,7 @@ const CliInput = (props: Props) => { return ( <> - {dbIndex !== 0 && {`[${dbIndex}] `}} + {dbIndex !== 0 && {`${getDbIndex(dbIndex)} `}} >  void onQueryReRun: () => void onQueryOpen: () => void @@ -79,6 +80,7 @@ const QueryCard = (props: Props) => { emptyCommand, isNotStored, executionTime, + db, } = props const { visualizations = [] } = useSelector(appPluginsSelector) @@ -178,6 +180,7 @@ const QueryCard = (props: Props) => { summary={summary} summaryText={getSummaryText(summary, resultsMode)} executionTime={executionTime} + db={db} toggleOpen={toggleOpen} toggleFullScreen={toggleFullScreen} setSelectedValue={changeViewTypeSelected} @@ -194,6 +197,7 @@ const QueryCard = (props: Props) => { isFullScreen?: boolean + db?: number } const QueryCardCliGroupResult = (props: Props) => { - const { result = [], isFullScreen } = props + const { result = [], isFullScreen, db } = props return (
@@ -23,7 +24,7 @@ const QueryCardCliGroupResult = (props: Props) => { if (React.isValidElement(commonError)) { return ([wbSummaryCommand(item.command), commonError]) } - return flatten(cliParseCommandsGroupResult(item)) + return flatten(cliParseCommandsGroupResult(item, db)) }))} />
diff --git a/redisinsight/ui/src/components/query-card/QueryCardCliResultWrapper/QueryCardCliResultWrapper.tsx b/redisinsight/ui/src/components/query-card/QueryCardCliResultWrapper/QueryCardCliResultWrapper.tsx index a215240720..bfa1525816 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardCliResultWrapper/QueryCardCliResultWrapper.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardCliResultWrapper/QueryCardCliResultWrapper.tsx @@ -20,10 +20,11 @@ export interface Props { resultsMode?: ResultsMode isNotStored?: boolean isFullScreen?: boolean + db?: number } const QueryCardCliResultWrapper = (props: Props) => { - const { result = [], query, loading, resultsMode, isNotStored, isFullScreen } = props + const { result = [], query, loading, resultsMode, isNotStored, isFullScreen, db } = props return (
@@ -36,7 +37,7 @@ const QueryCardCliResultWrapper = (props: Props) => { )} {isGroupResults(resultsMode) && isArray(result[0]?.response) - ? + ? : ( void toggleFullScreen: () => void setSelectedValue: (type: WBQueryType, value: string) => void @@ -108,6 +109,7 @@ const QueryCardHeader = (props: Props) => { setSelectedValue, onQueryDelete, onQueryReRun, + db, } = props const { visualizations = [] } = useSelector(appPluginsSelector) @@ -267,7 +269,7 @@ const QueryCardHeader = (props: Props) => { >
- + { expect(render()).toBeTruthy() }) + it('should show db index', () => { + const { queryByTestId } = render( + + ) + + expect(queryByTestId('query-card-tooltip-anchor')).toHaveTextContent('[db2]') + }) + it(`should show ${EMPTY_COMMAND} if command=null and summary=`, () => { const { queryByTestId } = render( diff --git a/redisinsight/ui/src/components/query-card/QueryCardTooltip/QueryCardTooltip.tsx b/redisinsight/ui/src/components/query-card/QueryCardTooltip/QueryCardTooltip.tsx index e59c45671d..d7183a6543 100644 --- a/redisinsight/ui/src/components/query-card/QueryCardTooltip/QueryCardTooltip.tsx +++ b/redisinsight/ui/src/components/query-card/QueryCardTooltip/QueryCardTooltip.tsx @@ -3,14 +3,17 @@ import { EuiToolTip } from '@elastic/eui' import { take } from 'lodash' import cx from 'classnames' -import { Nullable, truncateText } from 'uiSrc/utils' +import { Nullable, getDbIndex, isGroupResults, truncateText } from 'uiSrc/utils' import { EMPTY_COMMAND } from 'uiSrc/constants' +import { ResultsMode } from 'uiSrc/slices/interfaces' import styles from './styles.module.scss' export interface Props { query: Nullable summary?: Nullable maxLinesNumber?: number + resultsMode?: ResultsMode + db?: number } interface IQueryLine { @@ -20,7 +23,8 @@ interface IQueryLine { } const QueryCardTooltip = (props: Props) => { - const { query = '', maxLinesNumber = 20, summary = '' } = props + const { query = '', maxLinesNumber = 20, summary = '', resultsMode, db } = props + const command = summary || query || EMPTY_COMMAND let queryLines: IQueryLine[] = (query || EMPTY_COMMAND).split('\n') .map((query: string, i) => ({ @@ -39,13 +43,14 @@ const QueryCardTooltip = (props: Props) => { const contentItems = queryLines .map((item: IQueryLine) => { const { value, index, isFolding } = item - return !isMultilineCommand ? {value} : ( + const command = `${getDbIndex(db)} ${value}` + return !isMultilineCommand ? {command} : (
           
{`${index + 1}`}
- {value} + {command}
) }) @@ -57,7 +62,9 @@ const QueryCardTooltip = (props: Props) => { content={<>{contentItems}} position="bottom" > - {summary || query || EMPTY_COMMAND} + + {`${!isGroupResults(resultsMode) ? getDbIndex(db) : ''} ${command}`.trim()} + ) } diff --git a/redisinsight/ui/src/constants/cliOutput.tsx b/redisinsight/ui/src/constants/cliOutput.tsx index cfbce3d247..5b0c212546 100644 --- a/redisinsight/ui/src/constants/cliOutput.tsx +++ b/redisinsight/ui/src/constants/cliOutput.tsx @@ -1,6 +1,7 @@ import { EuiLink, EuiTextColor } from '@elastic/eui' import React, { Fragment } from 'react' import { getRouterLinkProps } from 'uiSrc/services' +import { getDbIndex } from 'uiSrc/utils' export const ClearCommand = 'clear' export const SelectCommand = 'select' @@ -38,8 +39,7 @@ export const InitOutputText = ( '\n\n', 'Pinging Redis server on ', - {`${host}:${port}`} - {dbIndex > 0 && `[${dbIndex}]`} + {`${host}:${port}${getDbIndex(dbIndex)}`} , ] diff --git a/redisinsight/ui/src/pages/databaseAnalysis/components/header/Header.tsx b/redisinsight/ui/src/pages/databaseAnalysis/components/header/Header.tsx index 4a59633088..e9b42476fb 100644 --- a/redisinsight/ui/src/pages/databaseAnalysis/components/header/Header.tsx +++ b/redisinsight/ui/src/pages/databaseAnalysis/components/header/Header.tsx @@ -21,7 +21,7 @@ import { appContextDbConfig } from 'uiSrc/slices/app/context' import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances' import { ConnectionType } from 'uiSrc/slices/interfaces' import AnalyticsTabs from 'uiSrc/components/analytics-tabs' -import { Nullable } from 'uiSrc/utils' +import { Nullable, getDbIndex } from 'uiSrc/utils' import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry' import { ShortDatabaseAnalysis } from 'apiSrc/modules/database-analysis/models' import { AnalysisProgress } from 'apiSrc/modules/database-analysis/models/analysis-progress' @@ -57,11 +57,13 @@ const Header = (props: Props) => { const { treeViewDelimiter: delimiter = '' } = useSelector(appContextDbConfig) const analysisOptions: EuiSuperSelectOption[] = items.map((item) => { - const { createdAt, id } = item + const { createdAt, id, db } = item return { value: id, inputDisplay: ( - {format(new Date(createdAt ?? ''), dateFormat)} + + {`${getDbIndex(db)} ${format(new Date(createdAt ?? ''), dateFormat)}`} + ), 'data-test-subj': `items-report-${id}`, } diff --git a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx b/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx index 2b6ffb77c6..207270b79a 100644 --- a/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx +++ b/redisinsight/ui/src/pages/home/components/DatabaseAlias/DatabaseAlias.tsx @@ -12,9 +12,10 @@ import { } from '@elastic/eui' import cx from 'classnames' import { useSelector } from 'react-redux' +import { toNumber } from 'lodash' import { BuildType } from 'uiSrc/constants/env' import { appInfoSelector } from 'uiSrc/slices/app/info' -import { Nullable } from 'uiSrc/utils' +import { Nullable, getDbIndex } from 'uiSrc/utils' import { Theme } from 'uiSrc/constants' import { ThemeContext } from 'uiSrc/contexts/themeContext' import InlineItemEditor from 'uiSrc/components/inline-item-editor/InlineItemEditor' @@ -177,7 +178,7 @@ const DatabaseAlias = (props: Props) => { {!isCloneMode && ({alias})} - {database ? `[${database}]` : ''} + {getDbIndex(toNumber(database))} {!isCloneMode && ()} diff --git a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx index 0a191d3ed0..1de1cbc119 100644 --- a/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx +++ b/redisinsight/ui/src/pages/workbench/components/wb-results/WBResults/WBResults.tsx @@ -65,6 +65,7 @@ const WBResults = (props: Props) => { emptyCommand, isNotStored, executionTime, + db, } ) => ( { mode={mode} activeResultsMode={activeResultsMode} resultsMode={resultsMode} + db={db} onQueryOpen={() => onQueryOpen(id)} onQueryReRun={() => onQueryReRun( command, diff --git a/redisinsight/ui/src/utils/cliHelper.tsx b/redisinsight/ui/src/utils/cliHelper.tsx index 2776fd7c4f..6c27189848 100644 --- a/redisinsight/ui/src/utils/cliHelper.tsx +++ b/redisinsight/ui/src/utils/cliHelper.tsx @@ -13,6 +13,7 @@ import { ClusterNode, RedisDefaultModules, REDISEARCH_MODULES } from 'uiSrc/slic import { AdditionalRedisModule } from 'apiSrc/modules/database/models/additional.redis.module' import { Nullable } from './types' import formatToText from './transformers/cliTextFormatter' +import { getDbIndex } from './longNames' export enum CliPrefix { Cli = 'cli', @@ -71,7 +72,7 @@ const cliParseTextResponse = ( const cliCommandOutput = (command: string, dbIndex = 0) => ['\n', bashTextValue(dbIndex), cliCommandWrapper(command), '\n'] -const bashTextValue = (dbIndex = 0) => (dbIndex > 0 ? `[${dbIndex}] > ` : '> ') +const bashTextValue = (dbIndex = 0) => `${getDbIndex(dbIndex)} > ` const cliCommandWrapper = (command: string) => ( @@ -79,12 +80,12 @@ const cliCommandWrapper = (command: string) => ( ) -const wbSummaryCommand = (command: string) => ( +const wbSummaryCommand = (command: string, db?: number) => ( - {`> ${command} \n`} + {`${getDbIndex(db)} > ${command} \n`} ) @@ -93,9 +94,10 @@ const clearOutput = (dispatch: any) => { } const cliParseCommandsGroupResult = ( - result: IGroupModeCommand + result: IGroupModeCommand, + db?: number, ) => { - const executionCommand = wbSummaryCommand(result.command) + const executionCommand = wbSummaryCommand(result.command, db) let executionResult = [] if (result.status === CommandExecutionStatus.Success) { @@ -198,5 +200,5 @@ export { checkUnsupportedModuleCommand, getDbIndexFromSelectQuery, getCommandNameFromQuery, - wbSummaryCommand + wbSummaryCommand, } diff --git a/redisinsight/ui/src/utils/longNames.tsx b/redisinsight/ui/src/utils/longNames.tsx index bba008fb2c..3fc03356d2 100644 --- a/redisinsight/ui/src/utils/longNames.tsx +++ b/redisinsight/ui/src/utils/longNames.tsx @@ -24,7 +24,7 @@ export function formatNameShort(name = '') { } export function getDbIndex(db: number = 0) { - return db ? `[${db}]` : '' + return db ? `[db${db}]` : '' } export const truncateText = (text = '', maxLength = 0, separator = '...') => diff --git a/redisinsight/ui/src/utils/tests/cliHelper.spec.ts b/redisinsight/ui/src/utils/tests/cliHelper.spec.ts index a60ecf6a6f..515fe6a891 100644 --- a/redisinsight/ui/src/utils/tests/cliHelper.spec.ts +++ b/redisinsight/ui/src/utils/tests/cliHelper.spec.ts @@ -2,7 +2,8 @@ import { getDbIndexFromSelectQuery, getCommandNameFromQuery, cliParseCommandsGroupResult, - CliPrefix + CliPrefix, + wbSummaryCommand } from 'uiSrc/utils' import { MOCK_COMMANDS_SPEC } from 'uiSrc/constants' import { render, screen } from 'uiSrc/utils/test-utils' @@ -82,3 +83,19 @@ describe('cliParseCommandsGroupResult error status', () => { expect(screen.queryByTestId(`${CliPrefix.Cli}-output-response-fail`)).toBeInTheDocument() }) + +const wbSummaryCommandTests: any[] = [ + ['SET', 0, '> SET'], + ['iueigc h pb32 ueo', 0, '> iueigc h pb32 ueo'], + ['SET', 1, '[db1] > SET'], + ['INFO', 10, '[db10] > INFO'], + ['aoeuaoeu', 10, '[db10] > aoeuaoeu'], +] + +describe('wbSummaryCommand', () => { + it.each(wbSummaryCommandTests)('for input: %s (command), should be output: %s', + (command, db, expected) => { + const { container } = render(wbSummaryCommand(command, db)) + expect(container).toHaveTextContent(expected) + }) +}) diff --git a/redisinsight/ui/src/utils/tests/longNames.spec.ts b/redisinsight/ui/src/utils/tests/longNames.spec.ts index 865d613e88..773318a7e4 100644 --- a/redisinsight/ui/src/utils/tests/longNames.spec.ts +++ b/redisinsight/ui/src/utils/tests/longNames.spec.ts @@ -15,8 +15,8 @@ describe('formatLongName', () => { describe('getDbIndex', () => { it('should format long names', () => { expect(getDbIndex(0)).toEqual('') - expect(getDbIndex(1)).toEqual('[1]') - expect(getDbIndex(10)).toEqual('[10]') + expect(getDbIndex(1)).toEqual('[db1]') + expect(getDbIndex(10)).toEqual('[db10]') }) }) diff --git a/tests/e2e/pageObjects/my-redis-databases-page.ts b/tests/e2e/pageObjects/my-redis-databases-page.ts index 594d581de3..dd7fa2f9b2 100644 --- a/tests/e2e/pageObjects/my-redis-databases-page.ts +++ b/tests/e2e/pageObjects/my-redis-databases-page.ts @@ -1,4 +1,5 @@ import { t, Selector } from 'testcafe'; +import { getDatabaseByName } from '../helpers/api/api-database'; export class MyRedisDatabasePage { //------------------------------------------------------------------------------------------- @@ -187,18 +188,24 @@ export class MyRedisDatabasePage { /** * Verify database status is visible + * @param databaseName The name of the database */ - async verifyDatabaseStatusIsVisible(): Promise { - await t.expect(Selector('div').withAttribute('data-testid', /database-status-new-*/).visible) - .ok('Database status is not visible'); + async verifyDatabaseStatusIsVisible(databaseName: string): Promise { + const databaseId = await getDatabaseByName(databaseName); + const databaseEditBtn = Selector(`[data-testid=database-status-new-${databaseId}]`); + + await t.expect(databaseEditBtn.exists).ok(`Database status is not visible for ${databaseName}`); } /** * Verify database status is not visible + * @param databaseName The name of the database */ - async verifyDatabaseStatusIsNotVisible(): Promise { - await t.expect(Selector('div').withAttribute('data-testid', /database-status-new-*/).visible) - .notOk('Database status is still visible'); + async verifyDatabaseStatusIsNotVisible(databaseName: string): Promise { + const databaseId = await getDatabaseByName(databaseName); + const databaseEditBtn = Selector(`[data-testid=database-status-new-${databaseId}]`); + + await t.expect(databaseEditBtn.exists).notOk(`Database status is still visible for ${databaseName}`); } /** diff --git a/tests/e2e/pageObjects/workbench-page.ts b/tests/e2e/pageObjects/workbench-page.ts index dab9fc0eaf..7e62eff21a 100644 --- a/tests/e2e/pageObjects/workbench-page.ts +++ b/tests/e2e/pageObjects/workbench-page.ts @@ -214,9 +214,9 @@ export class WorkbenchPage { async checkWorkbenchCommandResult(command: string, result: string, childNum = 0): Promise { // Compare the command with executed command const actualCommand = await this.queryCardContainer.nth(childNum).find(this.cssQueryCardCommand).textContent; - await t.expect(actualCommand).eql(command, 'Actual command is not equal to executed'); + await t.expect(actualCommand).contains(command, 'Actual command is not equal to executed'); // Compare the command result with executed command const actualCommandResult = await this.queryCardContainer.nth(childNum).find(this.cssQueryTextResult).textContent; - await t.expect(actualCommandResult).eql(result, 'Actual command result is not equal to executed'); + await t.expect(actualCommandResult).contains(result, 'Actual command result is not equal to executed'); } } diff --git a/tests/e2e/tests/critical-path/database-overview/database-index.e2e.ts b/tests/e2e/tests/critical-path/database-overview/database-index.e2e.ts index cd4187197f..121bd68f1c 100644 --- a/tests/e2e/tests/critical-path/database-overview/database-index.e2e.ts +++ b/tests/e2e/tests/critical-path/database-overview/database-index.e2e.ts @@ -113,6 +113,9 @@ test('Switching between indexed databases', async t => { // Open Workbench page await t.click(myRedisDatabasePage.workbenchButton); await workbenchPage.sendCommandInWorkbench(command); + // Verify that user can see the database index before the command name executed in Workbench + await workbenchPage.checkWorkbenchCommandResult(`[db1] ${command}`, '8'); + // Open Browser page await t.click(myRedisDatabasePage.browserButton); // Clear filter @@ -134,9 +137,15 @@ test('Switching between indexed databases', async t => { // Verify that data changed for indexed db on Database analysis page await t.expect(memoryEfficiencyPage.topKeysKeyName.withExactText(keyNames[0]).exists).ok('Keys from current db index not displayed in report'); await t.expect(memoryEfficiencyPage.topKeysKeyName.withExactText(logicalDbKey).exists).notOk('Keys from other db index displayed in report'); + await t.expect(memoryEfficiencyPage.selectedReport.textContent).notContains('[db', 'Index displayed for 0 index in report name'); // Change index to logical db await databaseOverviewPage.changeDbIndex(1); await t.click(memoryEfficiencyPage.newReportBtn); + await t.expect(memoryEfficiencyPage.selectedReport.textContent).contains('[db1]', 'Index not displayed in report name'); await t.expect(memoryEfficiencyPage.topKeysKeyName.withExactText(logicalDbKey).exists).ok('Keys from current db index not displayed in report'); await t.expect(memoryEfficiencyPage.topKeysKeyName.withExactText(keyNames[0]).exists).notOk('Keys from other db index displayed in report'); + + // Verify that user can see the database index before the report date in Database Analysis + await t.click(memoryEfficiencyPage.selectedReport); + await t.expect(memoryEfficiencyPage.reportItem.withText('[db1]').count).eql(1, 'Index not displayed in report name'); }); diff --git a/tests/e2e/tests/critical-path/database/clone-databases.e2e.ts b/tests/e2e/tests/critical-path/database/clone-databases.e2e.ts index dd292f834c..ac0a87d832 100644 --- a/tests/e2e/tests/critical-path/database/clone-databases.e2e.ts +++ b/tests/e2e/tests/critical-path/database/clone-databases.e2e.ts @@ -21,12 +21,12 @@ fixture `Clone databases` .meta({ type: 'critical_path' }) .page(commonUrl); test - .before(async () => { + .before(async() => { await acceptLicenseTerms(); await addNewStandaloneDatabaseApi(ossStandaloneConfig); await common.reloadPage(); }) - .after(async () => { + .after(async() => { // Delete databases const dbNumber = await myRedisDatabasePage.dbNameList.withExactText(ossStandaloneConfig.databaseName).count; for (let i = 0; i < dbNumber; i++) { @@ -37,10 +37,6 @@ test await clickOnEditDatabaseByName(ossStandaloneConfig.databaseName); // Verify that user can cancel the Clone by clicking the “Cancel” or the “x” button await t.click(addRedisDatabasePage.cloneDatabaseButton); - - // Verify new connection badge for cloned database - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); - await t.click(addRedisDatabasePage.cancelButton); await t.expect(myRedisDatabasePage.editAliasButton.withText('Clone ').exists).notOk('Clone panel is still displayed', { timeout: 2000 }); await clickOnEditDatabaseByName(ossStandaloneConfig.databaseName); @@ -55,14 +51,17 @@ test // Verify that user can confirm the creation of the database by clicking “Clone Database” await t.click(addRedisDatabasePage.addRedisDatabaseButton); await t.expect(myRedisDatabasePage.dbNameList.withExactText(ossStandaloneConfig.databaseName).count).eql(2, 'DB was not cloned'); + + // Verify new connection badge for cloned database + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(ossStandaloneConfig.databaseName); }); test - .before(async () => { + .before(async() => { await acceptLicenseTerms(); await addNewOSSClusterDatabaseApi(ossClusterConfig); await common.reloadPage(); }) - .after(async () => { + .after(async() => { // Delete database await deleteOSSClusterDatabaseApi(ossClusterConfig); await myRedisDatabasePage.deleteDatabaseByName(newOssDatabaseAlias); @@ -70,10 +69,6 @@ test .meta({ rte: rte.ossCluster })('Verify that user can clone OSS Cluster', async t => { await clickOnEditDatabaseByName(ossClusterConfig.ossClusterDatabaseName); await t.click(addRedisDatabasePage.cloneDatabaseButton); - - // New connections indicator - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); - await t .expect(myRedisDatabasePage.editAliasButton.withText('Clone ').exists).ok('Clone panel is not displayed') .expect(addRedisDatabasePage.portInput.getAttribute('value')).eql(ossClusterConfig.ossClusterPort, 'Wrong port value') @@ -83,15 +78,18 @@ test await t.click(addRedisDatabasePage.addRedisDatabaseButton); await t.expect(myRedisDatabasePage.dbNameList.withExactText(newOssDatabaseAlias).exists).ok('DB was not closed'); await t.expect(myRedisDatabasePage.dbNameList.withExactText(ossClusterConfig.ossClusterDatabaseName).exists).ok('Original DB is not displayed'); + + // New connections indicator + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(ossClusterConfig.ossClusterDatabaseName); }); test - .before(async () => { + .before(async() => { await acceptLicenseTerms(); // Add Sentinel databases await discoverSentinelDatabaseApi(ossSentinelConfig); await common.reloadPage(); }) - .after(async () => { + .after(async() => { // Delete all primary groups const sentinelCopy = ossSentinelConfig; sentinelCopy.masters.push(ossSentinelConfig.masters[1]); @@ -103,9 +101,6 @@ test await clickOnEditDatabaseByName(ossSentinelConfig.name[1]); await t.click(addRedisDatabasePage.cloneDatabaseButton); - // Verify new connection badge for Sentinel db - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); - // Verify that for Sentinel Host and Port fields are replaced with editable Primary Group Name field await t .expect(myRedisDatabasePage.editAliasButton.withText('Clone ').exists).ok('Clone panel is not displayed') @@ -123,4 +118,7 @@ test // Clone Sentinel Primary Group await t.click(addRedisDatabasePage.addRedisDatabaseButton); await t.expect(myRedisDatabasePage.dbNameList.withExactText(ossSentinelConfig.masters[1].name).count).gt(1, 'Primary Group was not cloned'); + + // Verify new connection badge for Sentinel db + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(ossSentinelConfig.name[1]); }); diff --git a/tests/e2e/tests/critical-path/database/import-databases.e2e.ts b/tests/e2e/tests/critical-path/database/import-databases.e2e.ts index ce46029b7f..b031c90396 100644 --- a/tests/e2e/tests/critical-path/database/import-databases.e2e.ts +++ b/tests/e2e/tests/critical-path/database/import-databases.e2e.ts @@ -42,7 +42,7 @@ const dbData = [ { type: 'racompass', path: path.join('..', '..', '..', 'test-data', 'import-databases', racompassValidJson), - dbNames: ['racompassCluster', 'racompassDbWithIndex:8100 [1]'] + dbNames: ['racompassCluster', 'racompassDbWithIndex:8100 [db1]'] }, { type: 'ardm', diff --git a/tests/e2e/tests/critical-path/database/logical-databases.e2e.ts b/tests/e2e/tests/critical-path/database/logical-databases.e2e.ts index b3b536cd48..85ccc474fb 100644 --- a/tests/e2e/tests/critical-path/database/logical-databases.e2e.ts +++ b/tests/e2e/tests/critical-path/database/logical-databases.e2e.ts @@ -5,7 +5,6 @@ import { commonUrl, ossStandaloneConfig } from '../../../helpers/conf'; const addRedisDatabasePage = new AddRedisDatabasePage(); const myRedisDatabasePage = new MyRedisDatabasePage(); -const indexDbMessage = 'When the database is added, you can select logical databases only in CLI. To work with other logical databases in Browser and Workbench, add another database with the same host and port, but a different database index.'; fixture `Logical databases` .meta({ type: 'critical_path', rte: rte.standalone }) @@ -33,5 +32,6 @@ test('Verify that user can add DB with logical index via host and port from Add // Verify that the database is in the list await t.expect(myRedisDatabasePage.dbNameList.withText(ossStandaloneConfig.databaseName).exists).ok('Database not exist', { timeout: 10000 }); // Verify that if user adds DB with logical DB > 0, DB name contains postfix "space+[{database index}]" - await t.expect(myRedisDatabasePage.dbNameList.textContent).eql(`${ossStandaloneConfig.databaseName} [${index}]`, 'The postfix is not added to the database name', { timeout: 10000 }); + // Verify that user can see the db{index} instead of {index} in database alias + await t.expect(myRedisDatabasePage.dbNameList.textContent).eql(`${ossStandaloneConfig.databaseName} [db${index}]`, 'The postfix is not added to the database name', { timeout: 10000 }); }); diff --git a/tests/e2e/tests/regression/cli/cli-logical-db.e2e.ts b/tests/e2e/tests/regression/cli/cli-logical-db.e2e.ts index 1ca0fc5e20..f7a3578a32 100644 --- a/tests/e2e/tests/regression/cli/cli-logical-db.e2e.ts +++ b/tests/e2e/tests/regression/cli/cli-logical-db.e2e.ts @@ -48,36 +48,37 @@ test }); test('Verify that working with logical DBs, user can see N DB index in CLI', async t => { index = '1'; - databaseEndpoint = `${ossStandaloneConfig.host}:${ossStandaloneConfig.port}[${index}]`; + databaseEndpoint = `${ossStandaloneConfig.host}:${ossStandaloneConfig.port}[db${index}]`; await addRedisDatabasePage.addLogicalRedisDatabase(ossStandaloneConfig, index); - await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneConfig.databaseName } [${index}]`); + await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneConfig.databaseName } [db${index}]`); // Open CLI await t.click(cliPage.cliExpandButton); // Verify that user can see DB index in CLI + // Verify that user can see the db{index} instead of {index} in CLI input and endpoint for (const text of cliMessage) { await t.expect(cliPage.cliArea.textContent).contains(text, 'DB index is not displayed in the CLI message'); } - await t.expect(cliPage.cliDbIndex.textContent).eql(`[${index}] `, 'DB index before the > character in CLI is not displayed'); + await t.expect(cliPage.cliDbIndex.textContent).eql(`[db${index}] `, 'DB index before the > character in CLI is not displayed'); await t.expect(cliPage.cliEndpoint.textContent).eql(databaseEndpoint, 'Database index is not displayed in the CLI endpoint'); }); test('Verify that user can see DB index in the endpoint in CLI header is automatically changed when switched to another logical DB', async t => { index = '2'; const indexAfter = '3'; - databaseEndpoint = `${ossStandaloneConfig.host}:${ossStandaloneConfig.port}[${index}]`; - const databaseEndpointAfter = `${ossStandaloneConfig.host}:${ossStandaloneConfig.port}[${indexAfter}]`; + databaseEndpoint = `${ossStandaloneConfig.host}:${ossStandaloneConfig.port}[db${index}]`; + const databaseEndpointAfter = `${ossStandaloneConfig.host}:${ossStandaloneConfig.port}[db${indexAfter}]`; await addRedisDatabasePage.addLogicalRedisDatabase(ossStandaloneConfig, index); - await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneConfig.databaseName } [${index}]`); + await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneConfig.databaseName } [db${index}]`); // Open CLI and verify that user can see DB index in CLI await t.click(cliPage.cliExpandButton); - await t.expect(cliPage.cliDbIndex.textContent).eql(`[${index}] `, 'DB index before the > character in CLI is not displayed'); + await t.expect(cliPage.cliDbIndex.textContent).eql(`[db${index}] `, 'DB index before the > character in CLI is not displayed'); // Re-creates client in CLI await t.click(cliPage.cliCollapseButton); await t.click(cliPage.cliExpandButton); // Verify that when user re-creates client in CLI the new client is connected to the DB index selected for the DB by default - await t.expect(cliPage.cliDbIndex.textContent).eql(`[${index}] `, 'The new client is not connected to the DB index selected for the DB by default'); + await t.expect(cliPage.cliDbIndex.textContent).eql(`[db${index}] `, 'The new client is not connected to the DB index selected for the DB by default'); // Open CLI and verify the database index in the endpoint await t.expect(cliPage.cliEndpoint.textContent).eql(databaseEndpoint, `The endpoint in CLI header not contains ${index} index`); diff --git a/tests/e2e/tests/regression/database-overview/database-overview-keys.e2e.ts b/tests/e2e/tests/regression/database-overview/database-overview-keys.e2e.ts index a178ef51b7..366ab94a1f 100644 --- a/tests/e2e/tests/regression/database-overview/database-overview-keys.e2e.ts +++ b/tests/e2e/tests/regression/database-overview/database-overview-keys.e2e.ts @@ -1,4 +1,3 @@ -import { t } from 'testcafe'; import { acceptLicenseTermsAndAddDatabase, acceptLicenseTermsAndAddRECloudDatabase, deleteCustomDatabase, deleteDatabase } from '../../../helpers/database'; import { MyRedisDatabasePage, @@ -35,16 +34,16 @@ fixture `Database overview` await browserPage.addStringKey(keyName); await t.click(myRedisDatabasePage.myRedisDBButton); await addRedisDatabasePage.addLogicalRedisDatabase(ossStandaloneRedisearch, index); - await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneRedisearch.databaseName} [${index}]`); + await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneRedisearch.databaseName} [db${index}]`); keys = await common.createArrayWithKeyValue(keysAmount); await cliPage.sendCommandInCli(`MSET ${keys.join(' ')}`); }) .afterEach(async t => { // Clear and delete databases await t.click(myRedisDatabasePage.myRedisDBButton); - await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneRedisearch.databaseName} [${index}]`); + await myRedisDatabasePage.clickOnDBByName(`${ossStandaloneRedisearch.databaseName} [db${index}]`); await cliPage.sendCommandInCli(`DEL ${keys.join(' ')}`); - await deleteCustomDatabase(`${ossStandaloneRedisearch.databaseName} [${index}]`); + await deleteCustomDatabase(`${ossStandaloneRedisearch.databaseName} [db${index}]`); await myRedisDatabasePage.clickOnDBByName(ossStandaloneRedisearch.databaseName); await browserPage.deleteKeyByName(keyName); await deleteStandaloneDatabaseApi(ossStandaloneRedisearch); diff --git a/tests/e2e/tests/regression/workbench/group-mode.e2e.ts b/tests/e2e/tests/regression/workbench/group-mode.e2e.ts index c7195d4f9d..939e81179c 100644 --- a/tests/e2e/tests/regression/workbench/group-mode.e2e.ts +++ b/tests/e2e/tests/regression/workbench/group-mode.e2e.ts @@ -31,7 +31,7 @@ test('Verify that user can run the commands from the Editor in the group mode', // Verify that user can run a command with quantifier and see results in group(10 info) await workbenchPage.sendCommandInWorkbench(`${counter} ${command}`); // Verify that user can see number of total commands in group, success commands, number of failed commands in header summary in Workbench - await t.expect(workbenchPage.queryCardCommand.textContent).eql(`${counter} Command(s) - ${counter} success, 0 error(s)`, 'Not valid summary'); + await t.expect(workbenchPage.queryCardCommand.textContent).contains(`${counter} Command(s) - ${counter} success, 0 error(s)`, 'Not valid summary'); // Verify that if users execute commands in group mode, they see summary of the commands execution await t.expect(workbenchPage.executionCommandTime.exists).ok('Execution time is not displayed'); await t.expect(workbenchPage.executionCommandIcon.exists).ok('Execution time icon is not displayed'); diff --git a/tests/e2e/tests/smoke/database/add-standalone-db.e2e.ts b/tests/e2e/tests/smoke/database/add-standalone-db.e2e.ts index 5038380f5f..2f32689d35 100644 --- a/tests/e2e/tests/smoke/database/add-standalone-db.e2e.ts +++ b/tests/e2e/tests/smoke/database/add-standalone-db.e2e.ts @@ -33,11 +33,11 @@ test })('Verify that user can add Standalone Database', async() => { await addNewStandaloneDatabase(ossStandaloneConfig); // Verify that user can see an indicator of databases that are added manually and not opened yet - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(ossStandaloneConfig.databaseName); await myRedisDatabasePage.clickOnDBByName(ossStandaloneConfig.databaseName); await t.click(browserPage.myRedisDbIcon); // Verify that user can't see an indicator of databases that were opened - await myRedisDatabasePage.verifyDatabaseStatusIsNotVisible(); + await myRedisDatabasePage.verifyDatabaseStatusIsNotVisible(ossStandaloneConfig.databaseName); }); test .meta({ rte: rte.reCluster }) @@ -47,7 +47,7 @@ test await addNewREClusterDatabase(redisEnterpriseClusterConfig); // Verify that user can see an indicator of databases that are added using autodiscovery and not opened yet // Verify new connection badge for RE cluster - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(redisEnterpriseClusterConfig.databaseName); }); test .meta({ env: env.web, rte: rte.ossCluster }) @@ -56,7 +56,7 @@ test })('Verify that user can add OSS Cluster DB', async() => { await addOSSClusterDatabase(ossClusterConfig); // Verify new connection badge for OSS cluster - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(ossClusterConfig.ossClusterDatabaseName); }); test @@ -66,7 +66,7 @@ test })('Verify that user can add database from RE Cloud via auto-discover flow', async() => { await addRECloudDatabase(cloudDatabaseConfig); // Verify new connection badge for RE cloud - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); + await myRedisDatabasePage.verifyDatabaseStatusIsVisible(cloudDatabaseConfig.databaseName); // Verify redis stack icon for RE Cloud with all 5 modules await t.expect(myRedisDatabasePage.redisStackIcon.visible).ok('Redis Stack icon not found for RE Cloud db with all 5 modules'); }); diff --git a/tests/e2e/tests/smoke/database/connecting-to-the-db.e2e.ts b/tests/e2e/tests/smoke/database/connecting-to-the-db.e2e.ts index 785134bb3b..3d5aa6ab6f 100644 --- a/tests/e2e/tests/smoke/database/connecting-to-the-db.e2e.ts +++ b/tests/e2e/tests/smoke/database/connecting-to-the-db.e2e.ts @@ -28,12 +28,14 @@ test // Add OSS Sentinel DB await discoverSentinelDatabase(ossSentinelConfig); - // Verify new connection badge for Sentinel db - await myRedisDatabasePage.verifyDatabaseStatusIsVisible(); - // Get groups & their count - const sentinelGroups = myRedisDatabasePage.dbNameList; + const sentinelGroups = myRedisDatabasePage.dbNameList.withText('primary-group'); const sentinelGroupsCount = await sentinelGroups.count; + + // Verify new connection badge for Sentinel db + await myRedisDatabasePage.verifyDatabaseStatusIsVisible('primary-group-1'); + await myRedisDatabasePage.verifyDatabaseStatusIsVisible('primary-group-2'); + // Verify all groups for connection for (let i = 0; i < sentinelGroupsCount; i++) { const groupSelector = sentinelGroups.nth(i);