diff --git a/redisinsight/api/config/features-config.json b/redisinsight/api/config/features-config.json index 4785af0ecc..bfd81316e9 100644 --- a/redisinsight/api/config/features-config.json +++ b/redisinsight/api/config/features-config.json @@ -1,5 +1,5 @@ { - "version": 2.3402, + "version": 2.3403, "features": { "insightsRecommendations": { "flag": true, @@ -47,6 +47,22 @@ } } }, + "cloudSsoRecommendedSettings": { + "flag": true, + "perc": [[0, 50]], + "filters": [ + { + "name": "config.server.buildType", + "value": "ELECTRON", + "cond": "eq" + }, + { + "name": "agreements.analytics", + "value": true, + "cond": "eq" + } + ] + }, "redisModuleFilter": { "flag": true, "perc": [[0, 100]], diff --git a/redisinsight/api/src/__mocks__/feature.ts b/redisinsight/api/src/__mocks__/feature.ts index d60490f0bd..8476cdb5de 100644 --- a/redisinsight/api/src/__mocks__/feature.ts +++ b/redisinsight/api/src/__mocks__/feature.ts @@ -182,12 +182,12 @@ export const mockFeatureSso = Object.assign(new Feature(), { redisStackPreview: [ { provider: 'AWS', - regions: ['us-east-2', 'ap-southeast-1', 'sa-east-1'] + regions: ['us-east-2', 'ap-southeast-1', 'sa-east-1'], }, { provider: 'GCP', - regions: ['asia-northeast1', 'europe-west1', 'us-central1'] - } + regions: ['asia-northeast1', 'europe-west1', 'us-central1'], + }, ], }, }, diff --git a/redisinsight/api/src/modules/cloud/job/cloud-job.factory.ts b/redisinsight/api/src/modules/cloud/job/cloud-job.factory.ts index a256b85b26..92f379e0b5 100644 --- a/redisinsight/api/src/modules/cloud/job/cloud-job.factory.ts +++ b/redisinsight/api/src/modules/cloud/job/cloud-job.factory.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { CloudJob, CreateFreeSubscriptionAndDatabaseCloudJob, - ImportFreeDatabaseCloudJob + ImportFreeDatabaseCloudJob, } from 'src/modules/cloud/job/jobs'; import { CloudJobName } from 'src/modules/cloud/job/constants'; import { CreateFreeDatabaseCloudJob } from 'src/modules/cloud/job/jobs/create-free-database.cloud-job'; @@ -16,6 +16,7 @@ import { DatabaseService } from 'src/modules/database/database.service'; import { CloudDatabaseAnalytics } from 'src/modules/cloud/database/cloud-database.analytics'; import { CloudRequestUtm } from 'src/modules/cloud/common/models'; import { CloudCapiKeyService } from 'src/modules/cloud/capi-key/cloud-capi-key.service'; +import { CloudSubscriptionApiService } from 'src/modules/cloud/subscription/cloud-subscription.api.service'; @Injectable() export class CloudJobFactory { @@ -26,6 +27,7 @@ export class CloudJobFactory { private readonly cloudDatabaseAnalytics: CloudDatabaseAnalytics, private readonly databaseService: DatabaseService, private readonly cloudCapiKeyService: CloudCapiKeyService, + private readonly cloudSubscriptionApiService: CloudSubscriptionApiService, ) {} async create( @@ -53,6 +55,7 @@ export class CloudJobFactory { cloudDatabaseAnalytics: this.cloudDatabaseAnalytics, databaseService: this.databaseService, cloudCapiKeyService: this.cloudCapiKeyService, + cloudSubscriptionApiService: this.cloudSubscriptionApiService, }, ); case CloudJobName.CreateFreeDatabase: diff --git a/redisinsight/api/src/modules/cloud/job/dto/create-subscription-and-database.cloud-job.data.dto.ts b/redisinsight/api/src/modules/cloud/job/dto/create-subscription-and-database.cloud-job.data.dto.ts index 6ff973bf5f..7ce9fb206a 100644 --- a/redisinsight/api/src/modules/cloud/job/dto/create-subscription-and-database.cloud-job.data.dto.ts +++ b/redisinsight/api/src/modules/cloud/job/dto/create-subscription-and-database.cloud-job.data.dto.ts @@ -1,12 +1,21 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, ValidateIf } from 'class-validator'; export class CreateSubscriptionAndDatabaseCloudJobDataDto { @ApiProperty({ description: 'Plan id for create a subscription.', type: Number, }) + @ValidateIf((object) => !object.isRecommendedSettings) @IsNumber() @IsNotEmpty() planId: number; + + @ApiPropertyOptional({ + description: 'Use recommended settings', + type: Boolean, + }) + @IsBoolean() + @IsOptional() + isRecommendedSettings?: boolean; } diff --git a/redisinsight/api/src/modules/cloud/job/jobs/create-free-subscription-and-database.cloud-job.ts b/redisinsight/api/src/modules/cloud/job/jobs/create-free-subscription-and-database.cloud-job.ts index 378b0c15e1..572415c010 100644 --- a/redisinsight/api/src/modules/cloud/job/jobs/create-free-subscription-and-database.cloud-job.ts +++ b/redisinsight/api/src/modules/cloud/job/jobs/create-free-subscription-and-database.cloud-job.ts @@ -1,3 +1,4 @@ +import { sortBy } from 'lodash'; import { CloudJob, CloudJobOptions, CreateFreeDatabaseCloudJob } from 'src/modules/cloud/job/jobs'; import { CloudTaskCapiService } from 'src/modules/cloud/task/cloud-task.capi.service'; import { CloudSubscriptionCapiService } from 'src/modules/cloud/subscription/cloud-subscription.capi.service'; @@ -10,6 +11,8 @@ import { Database } from 'src/modules/database/models/database'; import { CloudDatabaseAnalytics } from 'src/modules/cloud/database/cloud-database.analytics'; import { CloudCapiKeyService } from 'src/modules/cloud/capi-key/cloud-capi-key.service'; import { CloudSubscription } from 'src/modules/cloud/subscription/models'; +import { CloudSubscriptionApiService } from '../../subscription/cloud-subscription.api.service'; +import { CloudSubscriptionPlanResponse } from '../../subscription/dto'; export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { protected name = CloudJobName.CreateFreeDatabase; @@ -17,8 +20,9 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { constructor( readonly options: CloudJobOptions, - private readonly data: { - planId: number, + private data: { + planId?: number, + isRecommendedSettings?: boolean, }, protected readonly dependencies: { @@ -28,12 +32,15 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { cloudDatabaseAnalytics: CloudDatabaseAnalytics, databaseService: DatabaseService, cloudCapiKeyService: CloudCapiKeyService, + cloudSubscriptionApiService: CloudSubscriptionApiService, }, ) { super(options); } async iteration(): Promise { + let planId = this.data?.planId; + this.logger.log('Create free subscription and database'); this.checkSignal(); @@ -42,9 +49,15 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { this.logger.debug('Get or create free subscription'); + if (this.data?.isRecommendedSettings) { + const plans = await this.dependencies.cloudSubscriptionApiService.getSubscriptionPlans(this.options.sessionMetadata); + + planId = this.getRecommendedPlanId(plans); + } + const freeSubscription: CloudSubscription = await this.runChildJob( CreateFreeSubscriptionCloudJob, - this.data, + { planId }, this.options, ); @@ -68,4 +81,9 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { return database; } + + private getRecommendedPlanId(plans: CloudSubscriptionPlanResponse[]) { + const defaultPlan = sortBy(plans, ['details.displayOrder']); + return defaultPlan[0]?.id; + } } diff --git a/redisinsight/api/src/modules/cloud/subscription/cloud-subscription.module.ts b/redisinsight/api/src/modules/cloud/subscription/cloud-subscription.module.ts index 669913a684..bd1e1925ca 100644 --- a/redisinsight/api/src/modules/cloud/subscription/cloud-subscription.module.ts +++ b/redisinsight/api/src/modules/cloud/subscription/cloud-subscription.module.ts @@ -25,6 +25,7 @@ import { CloudSubscriptionCapiProvider } from './providers/cloud-subscription.ca ], exports: [ CloudSubscriptionCapiService, + CloudSubscriptionApiService, ], }) export class CloudSubscriptionModule {} diff --git a/redisinsight/api/src/modules/feature/constants/index.ts b/redisinsight/api/src/modules/feature/constants/index.ts index 1586eb54e0..6912f3f762 100644 --- a/redisinsight/api/src/modules/feature/constants/index.ts +++ b/redisinsight/api/src/modules/feature/constants/index.ts @@ -22,6 +22,7 @@ export enum FeatureConfigConfigDestination { export enum KnownFeatures { InsightsRecommendations = 'insightsRecommendations', CloudSso = 'cloudSso', + CloudSsoRecommendedSettings = 'cloudSsoRecommendedSettings', RedisModuleFilter = 'redisModuleFilter', } diff --git a/redisinsight/api/src/modules/feature/constants/known-features.ts b/redisinsight/api/src/modules/feature/constants/known-features.ts index 36d4910879..de16c17fc2 100644 --- a/redisinsight/api/src/modules/feature/constants/known-features.ts +++ b/redisinsight/api/src/modules/feature/constants/known-features.ts @@ -11,6 +11,10 @@ export const knownFeatures: Record = { storage: FeatureStorage.Database, factory: CloudSsoFeatureFlag.getFeature, }, + [KnownFeatures.CloudSsoRecommendedSettings]: { + name: KnownFeatures.CloudSsoRecommendedSettings, + storage: FeatureStorage.Database, + }, [KnownFeatures.RedisModuleFilter]: { name: KnownFeatures.RedisModuleFilter, storage: FeatureStorage.Database, diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.spec.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.spec.ts index 85dbd87722..0d873e9576 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.spec.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.spec.ts @@ -9,8 +9,8 @@ import { FeatureFlagProvider } from 'src/modules/feature/providers/feature-flag/ import { SettingsService } from 'src/modules/settings/settings.service'; import { KnownFeatures } from 'src/modules/feature/constants'; import { - InsightsRecommendationsFlagStrategy, -} from 'src/modules/feature/providers/feature-flag/strategies/insights-recommendations.flag.strategy'; + CommonFlagStrategy, +} from 'src/modules/feature/providers/feature-flag/strategies/common.flag.strategy'; import { DefaultFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/default.flag.strategy'; import { knownFeatures } from 'src/modules/feature/constants/known-features'; @@ -37,9 +37,13 @@ describe('FeatureFlagProvider', () => { }); describe('getStrategy', () => { - it('should return insights strategy', async () => { + it('should return common strategy', async () => { expect(await service.getStrategy(KnownFeatures.InsightsRecommendations)) - .toBeInstanceOf(InsightsRecommendationsFlagStrategy); + .toBeInstanceOf(CommonFlagStrategy); + }); + it('should return common strategy', async () => { + expect(await service.getStrategy(KnownFeatures.CloudSsoRecommendedSettings)) + .toBeInstanceOf(CommonFlagStrategy); }); it('should return default strategy when directly called', async () => { expect(await service.getStrategy('default')) @@ -54,7 +58,7 @@ describe('FeatureFlagProvider', () => { describe('calculate', () => { it('should calculate ', async () => { jest.spyOn(service, 'getStrategy') - .mockReturnValue(mockInsightsRecommendationsFlagStrategy as unknown as InsightsRecommendationsFlagStrategy); + .mockReturnValue(mockInsightsRecommendationsFlagStrategy as unknown as CommonFlagStrategy); expect(await service.calculate( knownFeatures[KnownFeatures.InsightsRecommendations], diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.ts index 8299f70636..d7ce9cc392 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.ts @@ -1,15 +1,15 @@ import { Injectable } from '@nestjs/common'; import { FeatureFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy'; import { - InsightsRecommendationsFlagStrategy, -} from 'src/modules/feature/providers/feature-flag/strategies/insights-recommendations.flag.strategy'; + CommonFlagStrategy, +} from 'src/modules/feature/providers/feature-flag/strategies/common.flag.strategy'; import { DefaultFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/default.flag.strategy'; import { FeaturesConfigService } from 'src/modules/feature/features-config.service'; import { SettingsService } from 'src/modules/settings/settings.service'; import { IFeatureFlag, KnownFeatures } from 'src/modules/feature/constants'; import { CloudSsoFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/cloud-sso.flag.strategy'; import { Feature } from 'src/modules/feature/model/feature'; -import { SimpleFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/simple.flag.strategy'; +import { WithDataFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/with-data.flag.strategy'; @Injectable() export class FeatureFlagProvider { @@ -23,7 +23,7 @@ export class FeatureFlagProvider { this.featuresConfigService, this.settingsService, )); - this.strategies.set(KnownFeatures.InsightsRecommendations, new InsightsRecommendationsFlagStrategy( + this.strategies.set(KnownFeatures.InsightsRecommendations, new CommonFlagStrategy( this.featuresConfigService, this.settingsService, )); @@ -31,7 +31,11 @@ export class FeatureFlagProvider { this.featuresConfigService, this.settingsService, )); - this.strategies.set(KnownFeatures.RedisModuleFilter, new SimpleFlagStrategy( + this.strategies.set(KnownFeatures.CloudSsoRecommendedSettings, new CommonFlagStrategy( + this.featuresConfigService, + this.settingsService, + )); + this.strategies.set(KnownFeatures.RedisModuleFilter, new WithDataFlagStrategy( this.featuresConfigService, this.settingsService, )); diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/insights-recommendations.flag.strategy.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/common.flag.strategy.ts similarity index 87% rename from redisinsight/api/src/modules/feature/providers/feature-flag/strategies/insights-recommendations.flag.strategy.ts rename to redisinsight/api/src/modules/feature/providers/feature-flag/strategies/common.flag.strategy.ts index bed82daed6..1ad873d55e 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/insights-recommendations.flag.strategy.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/common.flag.strategy.ts @@ -2,7 +2,7 @@ import { FeatureFlagStrategy } from 'src/modules/feature/providers/feature-flag/ import { Feature } from 'src/modules/feature/model/feature'; import { IFeatureFlag } from 'src/modules/feature/constants'; -export class InsightsRecommendationsFlagStrategy extends FeatureFlagStrategy { +export class CommonFlagStrategy extends FeatureFlagStrategy { async calculate(knownFeature: IFeatureFlag, featureConfig: any): Promise { const isInRange = await this.isInTargetRange(featureConfig?.perc); diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy.spec.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy.spec.ts index 6943833acc..69a0a3b30f 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy.spec.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy.spec.ts @@ -12,8 +12,8 @@ import { SettingsService } from 'src/modules/settings/settings.service'; import { FeaturesConfigService } from 'src/modules/feature/features-config.service'; import { FeatureFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy'; import { - InsightsRecommendationsFlagStrategy, -} from 'src/modules/feature/providers/feature-flag/strategies/insights-recommendations.flag.strategy'; + CommonFlagStrategy, +} from 'src/modules/feature/providers/feature-flag/strategies/common.flag.strategy'; import { FeatureConfigFilter, FeatureConfigFilterAnd, @@ -45,7 +45,7 @@ describe('FeatureFlagStrategy', () => { settingsService = module.get(SettingsService); featuresConfigService = module.get(FeaturesConfigService); - service = new InsightsRecommendationsFlagStrategy( + service = new CommonFlagStrategy( featuresConfigService as unknown as FeaturesConfigService, settingsService as unknown as SettingsService, ); diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/simple.flag.strategy.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/with-data.flag.strategy.ts similarity index 90% rename from redisinsight/api/src/modules/feature/providers/feature-flag/strategies/simple.flag.strategy.ts rename to redisinsight/api/src/modules/feature/providers/feature-flag/strategies/with-data.flag.strategy.ts index 0d4c0c0794..02fcf51a89 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/simple.flag.strategy.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/with-data.flag.strategy.ts @@ -2,7 +2,7 @@ import { FeatureFlagStrategy } from 'src/modules/feature/providers/feature-flag/ import { Feature } from 'src/modules/feature/model/feature'; import { IFeatureFlag } from 'src/modules/feature/constants'; -export class SimpleFlagStrategy extends FeatureFlagStrategy { +export class WithDataFlagStrategy extends FeatureFlagStrategy { async calculate(knownFeature: IFeatureFlag, featureConfig: any): Promise { const isInRange = await this.isInTargetRange(featureConfig?.perc);