Skip to content

Commit 07ad307

Browse files
author
arthosofteq
authored
Merge pull request #2504 from RedisInsight/release/2.32.0
Release/2.32.0 to main
2 parents 4935faa + 1155f84 commit 07ad307

File tree

19 files changed

+204
-17
lines changed

19 files changed

+204
-17
lines changed

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,7 @@ jobs:
659659
export RI_CLOUD_IDP_GOOGLE_ID=$RI_CLOUD_IDP_GOOGLE_ID_STAGE
660660
export RI_CLOUD_IDP_GH_ID=$RI_CLOUD_IDP_GH_ID_STAGE
661661
export RI_CLOUD_API_URL=$RI_CLOUD_API_URL_STAGE
662+
export RI_CLOUD_CAPI_URL=$RI_CLOUD_CAPI_URL_STAGE
662663
663664
if [ << parameters.env >> == 'stage' ]; then
664665
UPGRADES_LINK=$UPGRADES_LINK_STAGE SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:stage && yarn package:mas
@@ -724,6 +725,7 @@ jobs:
724725
export RI_CLOUD_IDP_GOOGLE_ID=$RI_CLOUD_IDP_GOOGLE_ID_STAGE
725726
export RI_CLOUD_IDP_GH_ID=$RI_CLOUD_IDP_GH_ID_STAGE
726727
export RI_CLOUD_API_URL=$RI_CLOUD_API_URL_STAGE
728+
export RI_CLOUD_CAPI_URL=$RI_CLOUD_CAPI_URL_STAGE
727729
728730
if [ << parameters.env >> == 'stage' ]; then
729731
UPGRADES_LINK=$UPGRADES_LINK_STAGE SEGMENT_WRITE_KEY=$SEGMENT_WRITE_KEY_STAGE yarn package:stage

redisinsight/api/config/default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export default {
6161
tlsKey: process.env.SERVER_TLS_KEY,
6262
staticContent: !!process.env.SERVER_STATIC_CONTENT || false,
6363
buildType: process.env.BUILD_TYPE || 'ELECTRON',
64-
appVersion: process.env.APP_VERSION || '2.30.0',
64+
appVersion: process.env.APP_VERSION || '2.32.0',
6565
requestTimeout: parseInt(process.env.REQUEST_TIMEOUT, 10) || 25000,
6666
excludeRoutes: [],
6767
excludeAuthRoutes: [],

redisinsight/api/config/features-config.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": 2,
2+
"version": 2.32,
33
"features": {
44
"insightsRecommendations": {
55
"flag": true,
@@ -43,6 +43,18 @@
4343
}
4444
}
4545
}
46+
},
47+
"redisModuleFilter": {
48+
"flag": true,
49+
"perc": [[0, 100]],
50+
"data": {
51+
"hideByName": [
52+
{
53+
"expression": "RedisGraphStub",
54+
"options": "i"
55+
}
56+
]
57+
}
4658
}
4759
}
4860
}

redisinsight/api/config/swagger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const SWAGGER_CONFIG: Omit<OpenAPIObject, 'paths'> = {
55
info: {
66
title: 'RedisInsight Backend API',
77
description: 'RedisInsight Backend API',
8-
version: '2.30.0',
8+
version: '2.32.0',
99
},
1010
tags: [],
1111
};

redisinsight/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "redisinsight-api",
3-
"version": "2.30.0",
3+
"version": "2.32.0",
44
"description": "RedisInsight API",
55
"private": true,
66
"author": {

redisinsight/api/src/__mocks__/feature.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ export const mockFeaturesConfigService = jest.fn(() => ({
223223
}));
224224

225225
export const mockFeatureService = jest.fn(() => ({
226+
getByName: jest.fn().mockResolvedValue(undefined),
226227
isFeatureEnabled: jest.fn().mockResolvedValue(true),
227228
}));
228229

redisinsight/api/src/modules/database/providers/database-info.provider.spec.ts

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import { when } from 'jest-when';
33
import { IRedisClusterNodeAddress, ReplyError } from 'src/models';
44
import {
5+
mockFeatureService,
56
mockIOClusterNode1,
67
mockIOClusterNode2,
78
mockIORedisClient,
@@ -17,14 +18,15 @@ import {
1718
mockSentinelMasterEndpoint,
1819
mockSentinelMasterInDownState,
1920
mockSentinelMasterInOkState,
20-
mockStandaloneRedisInfoReply,
21+
mockStandaloneRedisInfoReply, MockType,
2122
} from 'src/__mocks__';
2223
import { REDIS_MODULES_COMMANDS, AdditionalRedisModuleName } from 'src/constants';
2324
import { DatabaseInfoProvider } from 'src/modules/database/providers/database-info.provider';
2425
import { RedisDatabaseInfoResponse } from 'src/modules/database/dto/redis-info.dto';
2526
import { BadRequestException, ForbiddenException } from '@nestjs/common';
2627
import { SentinelMasterStatus } from 'src/modules/redis-sentinel/models/sentinel-master';
2728
import ERROR_MESSAGES from 'src/constants/error-messages';
29+
import { FeatureService } from 'src/modules/feature/feature.service';
2830

2931
const mockClusterNodeAddresses: IRedisClusterNodeAddress[] = [
3032
{
@@ -78,14 +80,22 @@ const mockSentinelConnectionOptions = {
7880

7981
describe('DatabaseInfoProvider', () => {
8082
let service: DatabaseInfoProvider;
83+
let featureService: MockType<FeatureService>;
8184

8285
beforeEach(async () => {
8386
jest.clearAllMocks();
8487
const module: TestingModule = await Test.createTestingModule({
85-
providers: [DatabaseInfoProvider],
88+
providers: [
89+
DatabaseInfoProvider,
90+
{
91+
provide: FeatureService,
92+
useFactory: mockFeatureService,
93+
},
94+
],
8695
}).compile();
8796

8897
service = await module.get(DatabaseInfoProvider);
98+
featureService = await module.get(FeatureService);
8999
});
90100

91101
describe('isCluster', () => {
@@ -239,7 +249,7 @@ describe('DatabaseInfoProvider', () => {
239249
});
240250

241251
describe('determineDatabaseModules', () => {
242-
it('get modules by using MODULE LIST command', async () => {
252+
it('get modules by using MODULE LIST command (without filters)', async () => {
243253
when(mockIORedisClient.call)
244254
.calledWith('module', ['list'])
245255
.mockResolvedValue(mockRedisModuleList);
@@ -258,7 +268,37 @@ describe('DatabaseInfoProvider', () => {
258268
{ name: 'customModule', version: 10000, semanticVersion: undefined },
259269
]);
260270
});
261-
it('detect all modules by using COMMAND INFO command', async () => {
271+
it('get modules by using MODULE LIST command (with filters applied)', async () => {
272+
when(mockIORedisClient.call)
273+
.calledWith('module', ['list'])
274+
.mockResolvedValue(mockRedisModuleList);
275+
featureService.getByName.mockResolvedValue({
276+
flag: true,
277+
data: {
278+
hideByName: [
279+
{
280+
expression: 'rejSoN',
281+
options: 'i',
282+
},
283+
],
284+
},
285+
});
286+
287+
const result = await service.determineDatabaseModules(mockIORedisClient);
288+
289+
expect(mockIORedisClient.call).not.toHaveBeenCalledWith('command', expect.anything());
290+
expect(result).toEqual([
291+
{ name: AdditionalRedisModuleName.RedisAI, version: 10000, semanticVersion: '1.0.0' },
292+
{ name: AdditionalRedisModuleName.RedisGraph, version: 10000, semanticVersion: '1.0.0' },
293+
{ name: AdditionalRedisModuleName.RedisGears, version: 10000, semanticVersion: '1.0.0' },
294+
{ name: AdditionalRedisModuleName.RedisBloom, version: 10000, semanticVersion: '1.0.0' },
295+
// { name: AdditionalRedisModuleName.RedisJSON, version: 10000, semanticVersion: '1.0.0' }, should be ignored
296+
{ name: AdditionalRedisModuleName.RediSearch, version: 10000, semanticVersion: '1.0.0' },
297+
{ name: AdditionalRedisModuleName.RedisTimeSeries, version: 10000, semanticVersion: '1.0.0' },
298+
{ name: 'customModule', version: 10000, semanticVersion: undefined },
299+
]);
300+
});
301+
it('detect all modules by using COMMAND INFO command (without filter)', async () => {
262302
when(mockIORedisClient.call)
263303
.calledWith('module', ['list'])
264304
.mockRejectedValue(mockUnknownCommandModule);
@@ -282,6 +322,41 @@ describe('DatabaseInfoProvider', () => {
282322
{ name: AdditionalRedisModuleName.RedisTimeSeries },
283323
]);
284324
});
325+
it('detect all modules by using COMMAND INFO command (with filter)', async () => {
326+
when(mockIORedisClient.call)
327+
.calledWith('module', ['list'])
328+
.mockRejectedValue(mockUnknownCommandModule);
329+
when(mockIORedisClient.call)
330+
.calledWith('command', expect.anything())
331+
.mockResolvedValue([
332+
null,
333+
['somecommand', -1, ['readonly'], 0, 0, -1, []],
334+
]);
335+
featureService.getByName.mockResolvedValue({
336+
flag: true,
337+
data: {
338+
hideByName: [
339+
{
340+
expression: 'rejSoN',
341+
options: 'i',
342+
},
343+
],
344+
},
345+
});
346+
347+
const result = await service.determineDatabaseModules(mockIORedisClient);
348+
349+
expect(mockIORedisClient.call).toHaveBeenCalledTimes(REDIS_MODULES_COMMANDS.size + 1);
350+
expect(result).toEqual([
351+
{ name: AdditionalRedisModuleName.RedisAI },
352+
{ name: AdditionalRedisModuleName.RedisGraph },
353+
{ name: AdditionalRedisModuleName.RedisGears },
354+
{ name: AdditionalRedisModuleName.RedisBloom },
355+
// { name: AdditionalRedisModuleName.RedisJSON }, should be ignored
356+
{ name: AdditionalRedisModuleName.RediSearch },
357+
{ name: AdditionalRedisModuleName.RedisTimeSeries },
358+
]);
359+
});
285360
it('detect only RediSearch module by using COMMAND INFO command', async () => {
286361
when(mockIORedisClient.call)
287362
.calledWith('module', ['list'])
@@ -372,7 +447,7 @@ describe('DatabaseInfoProvider', () => {
372447
});
373448
it('should throw an error if no permission to run \'info\' command', async () => {
374449
mockIORedisClient.info.mockRejectedValue({
375-
message: 'NOPERM this user has no permissions to run the \'info\' command'
450+
message: 'NOPERM this user has no permissions to run the \'info\' command',
376451
});
377452

378453
try {

redisinsight/api/src/modules/database/providers/database-info.provider.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ import { SentinelMaster, SentinelMasterStatus } from 'src/modules/redis-sentinel
2121
import ERROR_MESSAGES from 'src/constants/error-messages';
2222
import { Endpoint } from 'src/common/models';
2323
import { RedisDatabaseInfoResponse } from 'src/modules/database/dto/redis-info.dto';
24+
import { FeatureService } from 'src/modules/feature/feature.service';
25+
import { KnownFeatures } from 'src/modules/feature/constants';
2426

2527
@Injectable()
2628
export class DatabaseInfoProvider {
29+
constructor(
30+
private readonly featureService: FeatureService,
31+
) {}
32+
2733
/**
2834
* Check weather current database is a cluster
2935
* @param client
@@ -67,6 +73,27 @@ export class DatabaseInfoProvider {
6773
}));
6874
}
6975

76+
public async filterRawModules(modules: any[]): Promise<any[]> {
77+
let filteredModules = modules;
78+
79+
try {
80+
const filterModules = await this.featureService.getByName(KnownFeatures.RedisModuleFilter);
81+
82+
if (filterModules?.flag && filterModules.data?.hideByName?.length) {
83+
filteredModules = modules.filter(({ name }) => {
84+
const match = filterModules.data.hideByName.find((filter) => filter.expression
85+
&& (new RegExp(filter.expression, filter.options)).test(name));
86+
87+
return !match;
88+
});
89+
}
90+
} catch (e) {
91+
// ignore
92+
}
93+
94+
return filteredModules;
95+
}
96+
7097
/**
7198
* Determine database modules using "module list" command
7299
* In case when "module" command is not available use "command info" approach
@@ -75,7 +102,10 @@ export class DatabaseInfoProvider {
75102
public async determineDatabaseModules(client: any): Promise<AdditionalRedisModule[]> {
76103
try {
77104
const reply = await client.call('module', ['list']);
78-
const modules = reply.map((module: any[]) => convertStringsArrayToObject(module));
105+
const modules = await this.filterRawModules(
106+
reply.map((module: any[]) => convertStringsArrayToObject(module)),
107+
);
108+
79109
return modules.map(({ name, ver }) => ({
80110
name: SUPPORTED_REDIS_MODULES[name] ?? name,
81111
version: ver,
@@ -120,7 +150,8 @@ export class DatabaseInfoProvider {
120150
// continue regardless of error
121151
}
122152
}));
123-
return modules;
153+
154+
return await this.filterRawModules(modules);
124155
}
125156

126157
/**

redisinsight/api/src/modules/feature/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export enum FeatureConfigConfigDestination {
2222
export enum KnownFeatures {
2323
InsightsRecommendations = 'insightsRecommendations',
2424
CloudSso = 'cloudSso',
25+
RedisModuleFilter = 'redisModuleFilter',
2526
}
2627

2728
export interface IFeatureFlag {

redisinsight/api/src/modules/feature/constants/known-features.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ export const knownFeatures: Record<KnownFeatures, IFeatureFlag> = {
1111
storage: FeatureStorage.Database,
1212
factory: CloudSsoFeatureFlag.getFeature,
1313
},
14+
[KnownFeatures.RedisModuleFilter]: {
15+
name: KnownFeatures.RedisModuleFilter,
16+
storage: FeatureStorage.Database,
17+
},
1418
};

0 commit comments

Comments
 (0)