From 691c7c65e7cdf2e7609d77781bf3566f3c53eaba Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 7 Dec 2023 14:32:05 +0400 Subject: [PATCH 01/11] #RI-5226 - initial implementation --- redisinsight/api/config/default.ts | 26 +++++++++---------- redisinsight/api/config/features-config.json | 18 ++++++++++++- .../modules/cloud/job/cloud-job.factory.ts | 5 +++- ...ription-and-database.cloud-job.data.dto.ts | 14 +++++++--- ...ree-subscription-and-database.cloud-job.ts | 19 +++++++++++--- .../subscription/cloud-subscription.module.ts | 1 + .../src/modules/feature/constants/index.ts | 1 + .../feature/constants/known-features.ts | 4 +++ .../feature-flag/feature-flag.provider.ts | 5 ++++ .../cloud-sso-select-plan.strategy.spec.ts | 0 .../cloud-sso-select-plan.strategy.ts | 15 +++++++++++ 11 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.spec.ts create mode 100644 redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts diff --git a/redisinsight/api/config/default.ts b/redisinsight/api/config/default.ts index 10ba5e09c9..00e003f06b 100644 --- a/redisinsight/api/config/default.ts +++ b/redisinsight/api/config/default.ts @@ -215,7 +215,7 @@ export default { }, cloud: { apiUrl: process.env.RI_CLOUD_API_URL || 'https://app-sm.k8s-cloudapi.sm-qa.qa.redislabs.com/api/v1', - apiToken: process.env.RI_CLOUD_API_TOKEN || 'token', + apiToken: process.env.RI_CLOUD_API_TOKEN || 'bmu7yk54bd6a263sfwk3ytu4wc28te4acn9hdnyk35dbk8b2etxmaj8ryuwq57nn', capiUrl: process.env.RI_CLOUD_CAPI_URL || 'https://api-k8s-cloudapi.qa.redislabs.com/v1', capiKeyName: process.env.RI_CLOUD_CAPI_KEY_NAME || 'RedisInsight', freeSubscriptionName: process.env.RI_CLOUD_FREE_SUBSCRIPTION_NAME || 'My free subscription', @@ -226,20 +226,20 @@ export default { databaseConnectionTimeout: parseInt(process.env.RI_CLOUD_DATABASE_CONNECTION_TIMEOUT, 10) || 30 * 1000, idp: { google: { - authorizeUrl: process.env.RI_CLOUD_IDP_GOOGLE_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL, - tokenUrl: process.env.RI_CLOUD_IDP_GOOGLE_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL, - issuer: process.env.RI_CLOUD_IDP_GOOGLE_ISSUER || process.env.RI_CLOUD_IDP_ISSUER, - clientId: process.env.RI_CLOUD_IDP_GOOGLE_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID, - redirectUri: process.env.RI_CLOUD_IDP_GOOGLE_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI, - idp: process.env.RI_CLOUD_IDP_GOOGLE_ID, + authorizeUrl: process.env.RI_CLOUD_IDP_GOOGLE_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL || 'oauth2/default/v1/authorize', + tokenUrl: process.env.RI_CLOUD_IDP_GOOGLE_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL || 'oauth2/default/v1/token', + issuer: process.env.RI_CLOUD_IDP_GOOGLE_ISSUER || process.env.RI_CLOUD_IDP_ISSUER || 'https://auth.qa.redislabs.com', + clientId: process.env.RI_CLOUD_IDP_GOOGLE_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID || '0oa7ku3x9aALuh3uK1d7', + redirectUri: process.env.RI_CLOUD_IDP_GOOGLE_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI || 'https://auth.redisinsight.redis.com/callback.html', + idp: process.env.RI_CLOUD_IDP_GOOGLE_ID || '0oa8in2sir3kD2jdN1d7', }, github: { - authorizeUrl: process.env.RI_CLOUD_IDP_GH_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL, - tokenUrl: process.env.RI_CLOUD_IDP_GH_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL, - issuer: process.env.RI_CLOUD_IDP_GH_ISSUER || process.env.RI_CLOUD_IDP_ISSUER, - clientId: process.env.RI_CLOUD_IDP_GH_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID, - redirectUri: process.env.RI_CLOUD_IDP_GH_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI, - idp: process.env.RI_CLOUD_IDP_GH_ID, + authorizeUrl: process.env.RI_CLOUD_IDP_GH_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL || 'oauth2/default/v1/authorize', + tokenUrl: process.env.RI_CLOUD_IDP_GH_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL || 'oauth2/default/v1/token', + issuer: process.env.RI_CLOUD_IDP_GH_ISSUER || process.env.RI_CLOUD_IDP_ISSUER || 'https://auth.qa.redislabs.com', + clientId: process.env.RI_CLOUD_IDP_GH_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID || '0oa7ku3x9aALuh3uK1d7', + redirectUri: process.env.RI_CLOUD_IDP_GH_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI || 'https://auth.redisinsight.redis.com/callback.html', + idp: process.env.RI_CLOUD_IDP_GH_ID || '0oa8pxbcc1PwnhZ3R1d7', }, }, }, diff --git a/redisinsight/api/config/features-config.json b/redisinsight/api/config/features-config.json index 4785af0ecc..5fcad5f7cd 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 @@ } } }, + "cloudSsoSelectPlan": { + "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/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..b1948071d3 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,20 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty, IsNumber } from 'class-validator'; export class CreateSubscriptionAndDatabaseCloudJobDataDto { - @ApiProperty({ + @ApiPropertyOptional({ description: 'Plan id for create a subscription.', type: Number, }) @IsNumber() @IsNotEmpty() planId: number; + + @ApiPropertyOptional({ + description: 'Auto select plan id', + type: Boolean, + }) + @IsBoolean() + @IsNotEmpty() + isAutoCreate: 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..01197fe954 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 @@ -10,6 +10,7 @@ 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'; export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { protected name = CloudJobName.CreateFreeDatabase; @@ -17,8 +18,9 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { constructor( readonly options: CloudJobOptions, - private readonly data: { - planId: number, + private data: { + planId?: number, + isAutoCreate?: boolean, }, protected readonly dependencies: { @@ -28,12 +30,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 +47,17 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { this.logger.debug('Get or create free subscription'); + if (this.data?.isAutoCreate) { + const plans = await this.dependencies.cloudSubscriptionApiService.getSubscriptionPlans(this.options.sessionMetadata); + + planId = plans[0].id + } + + const freeSubscription: CloudSubscription = await this.runChildJob( CreateFreeSubscriptionCloudJob, - this.data, + // this.data, + { planId }, this.options, ); 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..f977c39c68 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', + CloudSsoSelectPlan = 'cloudSsoSelectPlan', 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..2cacfe786d 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.CloudSsoSelectPlan]: { + name: KnownFeatures.CloudSsoSelectPlan, + 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.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/feature-flag.provider.ts index 8299f70636..297ddc5cdd 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 @@ -8,6 +8,7 @@ import { FeaturesConfigService } from 'src/modules/feature/features-config.servi 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 { CloudSsoSelectPlanFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy'; import { Feature } from 'src/modules/feature/model/feature'; import { SimpleFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/simple.flag.strategy'; @@ -31,6 +32,10 @@ export class FeatureFlagProvider { this.featuresConfigService, this.settingsService, )); + this.strategies.set(KnownFeatures.CloudSsoSelectPlan, new CloudSsoSelectPlanFlagStrategy( + this.featuresConfigService, + this.settingsService, + )); this.strategies.set(KnownFeatures.RedisModuleFilter, new SimpleFlagStrategy( this.featuresConfigService, this.settingsService, diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.spec.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts new file mode 100644 index 0000000000..78b279edc5 --- /dev/null +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts @@ -0,0 +1,15 @@ +import { FeatureFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy'; +import { Feature } from 'src/modules/feature/model/feature'; +import { IFeatureFlag } from 'src/modules/feature/constants'; + +export class CloudSsoFlagStrategy extends FeatureFlagStrategy { + async calculate(knownFeature: IFeatureFlag, featureConfig: any): Promise { + const isInRange = await this.isInTargetRange(featureConfig?.perc); + + return { + name: knownFeature.name, + flag: isInRange + && await this.filter(featureConfig?.filters) ? !!featureConfig?.flag : !featureConfig?.flag, + }; + } +} From 55650aa22a16f14dacde6b2843240a5a0b4a74a7 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 7 Dec 2023 14:34:54 +0400 Subject: [PATCH 02/11] #RI-5226 - remove deprecated code --- redisinsight/api/config/default.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/redisinsight/api/config/default.ts b/redisinsight/api/config/default.ts index 00e003f06b..10ba5e09c9 100644 --- a/redisinsight/api/config/default.ts +++ b/redisinsight/api/config/default.ts @@ -215,7 +215,7 @@ export default { }, cloud: { apiUrl: process.env.RI_CLOUD_API_URL || 'https://app-sm.k8s-cloudapi.sm-qa.qa.redislabs.com/api/v1', - apiToken: process.env.RI_CLOUD_API_TOKEN || 'bmu7yk54bd6a263sfwk3ytu4wc28te4acn9hdnyk35dbk8b2etxmaj8ryuwq57nn', + apiToken: process.env.RI_CLOUD_API_TOKEN || 'token', capiUrl: process.env.RI_CLOUD_CAPI_URL || 'https://api-k8s-cloudapi.qa.redislabs.com/v1', capiKeyName: process.env.RI_CLOUD_CAPI_KEY_NAME || 'RedisInsight', freeSubscriptionName: process.env.RI_CLOUD_FREE_SUBSCRIPTION_NAME || 'My free subscription', @@ -226,20 +226,20 @@ export default { databaseConnectionTimeout: parseInt(process.env.RI_CLOUD_DATABASE_CONNECTION_TIMEOUT, 10) || 30 * 1000, idp: { google: { - authorizeUrl: process.env.RI_CLOUD_IDP_GOOGLE_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL || 'oauth2/default/v1/authorize', - tokenUrl: process.env.RI_CLOUD_IDP_GOOGLE_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL || 'oauth2/default/v1/token', - issuer: process.env.RI_CLOUD_IDP_GOOGLE_ISSUER || process.env.RI_CLOUD_IDP_ISSUER || 'https://auth.qa.redislabs.com', - clientId: process.env.RI_CLOUD_IDP_GOOGLE_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID || '0oa7ku3x9aALuh3uK1d7', - redirectUri: process.env.RI_CLOUD_IDP_GOOGLE_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI || 'https://auth.redisinsight.redis.com/callback.html', - idp: process.env.RI_CLOUD_IDP_GOOGLE_ID || '0oa8in2sir3kD2jdN1d7', + authorizeUrl: process.env.RI_CLOUD_IDP_GOOGLE_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL, + tokenUrl: process.env.RI_CLOUD_IDP_GOOGLE_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL, + issuer: process.env.RI_CLOUD_IDP_GOOGLE_ISSUER || process.env.RI_CLOUD_IDP_ISSUER, + clientId: process.env.RI_CLOUD_IDP_GOOGLE_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID, + redirectUri: process.env.RI_CLOUD_IDP_GOOGLE_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI, + idp: process.env.RI_CLOUD_IDP_GOOGLE_ID, }, github: { - authorizeUrl: process.env.RI_CLOUD_IDP_GH_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL || 'oauth2/default/v1/authorize', - tokenUrl: process.env.RI_CLOUD_IDP_GH_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL || 'oauth2/default/v1/token', - issuer: process.env.RI_CLOUD_IDP_GH_ISSUER || process.env.RI_CLOUD_IDP_ISSUER || 'https://auth.qa.redislabs.com', - clientId: process.env.RI_CLOUD_IDP_GH_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID || '0oa7ku3x9aALuh3uK1d7', - redirectUri: process.env.RI_CLOUD_IDP_GH_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI || 'https://auth.redisinsight.redis.com/callback.html', - idp: process.env.RI_CLOUD_IDP_GH_ID || '0oa8pxbcc1PwnhZ3R1d7', + authorizeUrl: process.env.RI_CLOUD_IDP_GH_AUTHORIZE_URL || process.env.RI_CLOUD_IDP_AUTHORIZE_URL, + tokenUrl: process.env.RI_CLOUD_IDP_GH_TOKEN_URL || process.env.RI_CLOUD_IDP_TOKEN_URL, + issuer: process.env.RI_CLOUD_IDP_GH_ISSUER || process.env.RI_CLOUD_IDP_ISSUER, + clientId: process.env.RI_CLOUD_IDP_GH_CLIENT_ID || process.env.RI_CLOUD_IDP_CLIENT_ID, + redirectUri: process.env.RI_CLOUD_IDP_GH_REDIRECT_URI || process.env.RI_CLOUD_IDP_REDIRECT_URI, + idp: process.env.RI_CLOUD_IDP_GH_ID, }, }, }, From ff2a059c7d2f7bc358bb0b2ab1e19aab2025ad33 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 7 Dec 2023 14:42:58 +0400 Subject: [PATCH 03/11] update --- .../feature-flag/strategies/cloud-sso-select-plan.strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts index 78b279edc5..4829108a43 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.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 CloudSsoFlagStrategy extends FeatureFlagStrategy { +export class CloudSsoSelectPlanFlagStrategy extends FeatureFlagStrategy { async calculate(knownFeature: IFeatureFlag, featureConfig: any): Promise { const isInRange = await this.isInTargetRange(featureConfig?.perc); From 8fb990a19784da5b2c9b95d792d9f8c63570bed7 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Thu, 7 Dec 2023 15:00:39 +0400 Subject: [PATCH 04/11] #RI-5226 - fix dto --- .../create-subscription-and-database.cloud-job.data.dto.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 b1948071d3..e95046e3c8 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,11 +1,12 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; export class CreateSubscriptionAndDatabaseCloudJobDataDto { @ApiPropertyOptional({ description: 'Plan id for create a subscription.', type: Number, }) + @IsOptional() @IsNumber() @IsNotEmpty() planId: number; @@ -15,6 +16,6 @@ export class CreateSubscriptionAndDatabaseCloudJobDataDto { type: Boolean, }) @IsBoolean() - @IsNotEmpty() + @IsOptional() isAutoCreate: boolean; } From 6e7e2a190519e652ae30ca830ea9d5213616565e Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Fri, 8 Dec 2023 02:08:50 +0400 Subject: [PATCH 05/11] #RI-5226 - resolve comments --- redisinsight/api/config/features-config.json | 2 +- redisinsight/api/src/__mocks__/feature.ts | 6 +++--- ...ate-subscription-and-database.cloud-job.data.dto.ts | 10 +++++----- .../create-free-subscription-and-database.cloud-job.ts | 4 ++-- .../api/src/modules/feature/constants/index.ts | 2 +- .../src/modules/feature/constants/known-features.ts | 4 ++-- .../providers/feature-flag/feature-flag.provider.ts | 4 ++-- .../strategies/cloud-sso-select-plan.strategy.spec.ts | 0 ... => cloud-sso-use-recommended-settings.strategy.ts} | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.spec.ts rename redisinsight/api/src/modules/feature/providers/feature-flag/strategies/{cloud-sso-select-plan.strategy.ts => cloud-sso-use-recommended-settings.strategy.ts} (87%) diff --git a/redisinsight/api/config/features-config.json b/redisinsight/api/config/features-config.json index 5fcad5f7cd..bfd81316e9 100644 --- a/redisinsight/api/config/features-config.json +++ b/redisinsight/api/config/features-config.json @@ -47,7 +47,7 @@ } } }, - "cloudSsoSelectPlan": { + "cloudSsoRecommendedSettings": { "flag": true, "perc": [[0, 50]], "filters": [ 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/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 e95046e3c8..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,21 +1,21 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, ValidateIf } from 'class-validator'; export class CreateSubscriptionAndDatabaseCloudJobDataDto { - @ApiPropertyOptional({ + @ApiProperty({ description: 'Plan id for create a subscription.', type: Number, }) - @IsOptional() + @ValidateIf((object) => !object.isRecommendedSettings) @IsNumber() @IsNotEmpty() planId: number; @ApiPropertyOptional({ - description: 'Auto select plan id', + description: 'Use recommended settings', type: Boolean, }) @IsBoolean() @IsOptional() - isAutoCreate: boolean; + 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 01197fe954..e4f4f45221 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 @@ -20,7 +20,7 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { private data: { planId?: number, - isAutoCreate?: boolean, + isRecommendedSettings?: boolean, }, protected readonly dependencies: { @@ -47,7 +47,7 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { this.logger.debug('Get or create free subscription'); - if (this.data?.isAutoCreate) { + if (this.data?.isRecommendedSettings) { const plans = await this.dependencies.cloudSubscriptionApiService.getSubscriptionPlans(this.options.sessionMetadata); planId = plans[0].id diff --git a/redisinsight/api/src/modules/feature/constants/index.ts b/redisinsight/api/src/modules/feature/constants/index.ts index f977c39c68..6912f3f762 100644 --- a/redisinsight/api/src/modules/feature/constants/index.ts +++ b/redisinsight/api/src/modules/feature/constants/index.ts @@ -22,7 +22,7 @@ export enum FeatureConfigConfigDestination { export enum KnownFeatures { InsightsRecommendations = 'insightsRecommendations', CloudSso = 'cloudSso', - CloudSsoSelectPlan = 'cloudSsoSelectPlan', + 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 2cacfe786d..de16c17fc2 100644 --- a/redisinsight/api/src/modules/feature/constants/known-features.ts +++ b/redisinsight/api/src/modules/feature/constants/known-features.ts @@ -11,8 +11,8 @@ export const knownFeatures: Record = { storage: FeatureStorage.Database, factory: CloudSsoFeatureFlag.getFeature, }, - [KnownFeatures.CloudSsoSelectPlan]: { - name: KnownFeatures.CloudSsoSelectPlan, + [KnownFeatures.CloudSsoRecommendedSettings]: { + name: KnownFeatures.CloudSsoRecommendedSettings, storage: FeatureStorage.Database, }, [KnownFeatures.RedisModuleFilter]: { 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 297ddc5cdd..0e7db26255 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 @@ -8,7 +8,7 @@ import { FeaturesConfigService } from 'src/modules/feature/features-config.servi 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 { CloudSsoSelectPlanFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy'; +import { CloudSsoRecommendedSettingsFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy'; import { Feature } from 'src/modules/feature/model/feature'; import { SimpleFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/simple.flag.strategy'; @@ -32,7 +32,7 @@ export class FeatureFlagProvider { this.featuresConfigService, this.settingsService, )); - this.strategies.set(KnownFeatures.CloudSsoSelectPlan, new CloudSsoSelectPlanFlagStrategy( + this.strategies.set(KnownFeatures.CloudSsoRecommendedSettings, new CloudSsoRecommendedSettingsFlagStrategy( this.featuresConfigService, this.settingsService, )); diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.spec.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.spec.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy.ts similarity index 87% rename from redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts rename to redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy.ts index 4829108a43..351748803f 100644 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-select-plan.strategy.ts +++ b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.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 CloudSsoSelectPlanFlagStrategy extends FeatureFlagStrategy { +export class CloudSsoRecommendedSettingsFlagStrategy extends FeatureFlagStrategy { async calculate(knownFeature: IFeatureFlag, featureConfig: any): Promise { const isInRange = await this.isInTargetRange(featureConfig?.perc); From 11198e2d325baad49fe5469707ef62cd5fe98fb1 Mon Sep 17 00:00:00 2001 From: Roman Sergeenko Date: Fri, 8 Dec 2023 11:30:21 +0100 Subject: [PATCH 06/11] #RI-5227 - auto create free db --- .../OAuthSelectAccountDialog.tsx | 24 ++- .../oauth/oauth-social/OAuthSocial.spec.tsx | 80 ++++++++- .../oauth/oauth-social/OAuthSocial.tsx | 55 ++++++- .../oauth/oauth-social/styles.module.scss | 35 ++++ redisinsight/ui/src/constants/featureFlags.ts | 1 + .../ConfigOAuth/ConfigOAuth.spec.tsx | 155 +++++++++++++++++- .../components/ConfigOAuth/ConfigOAuth.tsx | 30 +++- redisinsight/ui/src/slices/app/features.ts | 5 +- redisinsight/ui/src/slices/instances/cloud.ts | 5 + .../ui/src/slices/interfaces/instances.ts | 1 + redisinsight/ui/src/slices/oauth/cloud.ts | 1 + .../src/slices/tests/instances/cloud.spec.ts | 23 +++ 12 files changed, 393 insertions(+), 22 deletions(-) diff --git a/redisinsight/ui/src/components/oauth/oauth-select-account-dialog/OAuthSelectAccountDialog.tsx b/redisinsight/ui/src/components/oauth/oauth-select-account-dialog/OAuthSelectAccountDialog.tsx index 7dcf44cb67..14dd3c1eda 100644 --- a/redisinsight/ui/src/components/oauth/oauth-select-account-dialog/OAuthSelectAccountDialog.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-select-account-dialog/OAuthSelectAccountDialog.tsx @@ -15,6 +15,7 @@ import { useHistory } from 'react-router-dom' import { activateAccount, + createFreeDbJob, fetchPlans, oauthCloudPlanSelector, oauthCloudSelector, @@ -26,8 +27,9 @@ import { Nullable } from 'uiSrc/utils' import { cloudSelector, fetchSubscriptionsRedisCloud } from 'uiSrc/slices/instances/cloud' import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry' import { Pages } from 'uiSrc/constants' -import { removeInfiniteNotification } from 'uiSrc/slices/app/notifications' -import { InfiniteMessagesIds } from 'uiSrc/components/notifications/components' +import { addInfiniteNotification, removeInfiniteNotification } from 'uiSrc/slices/app/notifications' +import { INFINITE_MESSAGES, InfiniteMessagesIds } from 'uiSrc/components/notifications/components' +import { CloudJobName, CloudJobStep } from 'uiSrc/electron/constants' import styles from './styles.module.scss' @@ -36,7 +38,7 @@ interface FormValues { } const OAuthSelectAccountDialog = () => { - const { isAutodiscoverySSO } = useSelector(cloudSelector) + const { isAutodiscoverySSO, isRecommendedSettings } = useSelector(cloudSelector) const { accounts = [], currentAccountId } = useSelector(oauthCloudUserDataSelector) ?? {} const { isOpenSelectAccountDialog } = useSelector(oauthCloudSelector) const { loading } = useSelector(oauthCloudUserSelector) @@ -75,6 +77,20 @@ const OAuthSelectAccountDialog = () => { } )) dispatch(setSelectAccountDialogState(false)) + } else if (isRecommendedSettings) { + dispatch(createFreeDbJob({ + name: CloudJobName.CreateFreeSubscriptionAndDatabase, + resources: { + isRecommendedSettings + }, + onSuccessAction: () => { + dispatch(setSelectAccountDialogState(false)) + dispatch(addInfiniteNotification(INFINITE_MESSAGES.PENDING_CREATE_DB(CloudJobStep.Credentials))) + }, + onFailAction: () => { + dispatch(removeInfiniteNotification(InfiniteMessagesIds.oAuthProgress)) + } + })) } else { dispatch(fetchPlans()) } @@ -86,7 +102,7 @@ const OAuthSelectAccountDialog = () => { accountsCount: accounts.length }, }) - }, [isAutodiscoverySSO, accounts]) + }, [isAutodiscoverySSO, isRecommendedSettings, accounts]) const onActivateAccountFail = useCallback((error: string) => { sendEventTelemetry({ diff --git a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx index a06c5fc860..49b6c24467 100644 --- a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx @@ -4,8 +4,10 @@ import { cleanup, fireEvent, mockedStore, render, waitForEuiToolTipVisible, act import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry' import { CloudAuthSocial, IpcInvokeEvent } from 'uiSrc/electron/constants' import { setOAuthCloudSource, signIn, oauthCloudPAgreementSelector } from 'uiSrc/slices/oauth/cloud' -import { setIsAutodiscoverySSO } from 'uiSrc/slices/instances/cloud' +import { setIsAutodiscoverySSO, setIsRecommendedSettingsSSO } from 'uiSrc/slices/instances/cloud' import { OAuthSocialSource } from 'uiSrc/slices/interfaces' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' +import { FeatureFlags } from 'uiSrc/constants' import OAuthSocial, { OAuthSocialType } from './OAuthSocial' jest.mock('uiSrc/telemetry', () => ({ @@ -21,6 +23,12 @@ jest.mock('uiSrc/slices/oauth/cloud', () => ({ oauthCloudPAgreementSelector: jest.fn().mockReturnValue(true), })) +jest.mock('uiSrc/slices/app/features', () => ({ + ...jest.requireActual('uiSrc/slices/app/features'), + appFeatureFlagsFeaturesSelector: jest.fn().mockReturnValue({ + }), +})) + let store: typeof mockedStore const invokeMock = jest.fn() beforeEach(() => { @@ -56,7 +64,7 @@ describe('OAuthSocial', () => { expect(invokeMock).toBeCalledTimes(1) expect(invokeMock).toBeCalledWith(IpcInvokeEvent.cloudOauth, { action: 'create', strategy: CloudAuthSocial.Google }) - const expectedActions = [signIn(), setIsAutodiscoverySSO(false)] + const expectedActions = [signIn(), setIsAutodiscoverySSO(false), setIsRecommendedSettingsSSO(undefined)] expect(store.getActions()).toEqual(expectedActions) invokeMock.mockRestore(); @@ -83,11 +91,75 @@ describe('OAuthSocial', () => { expect(invokeMock).toBeCalledWith(IpcInvokeEvent.cloudOauth, { action: 'create', strategy: CloudAuthSocial.Github }) invokeMock.mockRestore() - const expectedActions = [signIn(), setIsAutodiscoverySSO(false)] + const expectedActions = [signIn(), setIsAutodiscoverySSO(false), setIsRecommendedSettingsSSO(undefined)] expect(store.getActions()).toEqual(expectedActions) invokeMock.mockRestore(); - (sendEventTelemetry as jest.Mock).mockRestore() + (sendEventTelemetry as jest.Mock).mockRegstore() + }) + + describe('Recommended Settings Enabled', () => { + beforeEach(() => { + (appFeatureFlagsFeaturesSelector as jest.Mock).mockReturnValue({ + [FeatureFlags.cloudSsoRecommendedSettings]: { + flag: true + } + }) + }) + it('should send telemetry after click on google btn', async () => { + const sendEventTelemetryMock = jest.fn(); + (sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock) + + const { queryByTestId } = render() + + fireEvent.click(queryByTestId('google-oauth') as HTMLButtonElement) + + expect(sendEventTelemetry).toBeCalledWith({ + event: TelemetryEvent.CLOUD_SIGN_IN_SOCIAL_ACCOUNT_SELECTED, + eventData: { + accountOption: 'Google', + action: 'create', + recommendedSettings: 'enabled' + } + }) + + expect(invokeMock).toBeCalledTimes(1) + expect(invokeMock).toBeCalledWith(IpcInvokeEvent.cloudOauth, { action: 'create', strategy: CloudAuthSocial.Google }) + + const expectedActions = [signIn(), setIsAutodiscoverySSO(false), setIsRecommendedSettingsSSO(true)] + expect(store.getActions()).toEqual(expectedActions) + + invokeMock.mockRestore(); + (sendEventTelemetry as jest.Mock).mockRestore() + }) + + it('should send telemetry after click on github btn', async () => { + const sendEventTelemetryMock = jest.fn(); + (sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock) + + const { queryByTestId } = render() + + fireEvent.click(queryByTestId('github-oauth') as HTMLButtonElement) + + expect(sendEventTelemetry).toBeCalledWith({ + event: TelemetryEvent.CLOUD_SIGN_IN_SOCIAL_ACCOUNT_SELECTED, + eventData: { + accountOption: 'GitHub', + action: 'create', + recommendedSettings: 'enabled' + } + }) + + expect(invokeMock).toBeCalledTimes(1) + expect(invokeMock).toBeCalledWith(IpcInvokeEvent.cloudOauth, { action: 'create', strategy: CloudAuthSocial.Github }) + invokeMock.mockRestore() + + const expectedActions = [signIn(), setIsAutodiscoverySSO(false), setIsRecommendedSettingsSSO(true)] + expect(store.getActions()).toEqual(expectedActions) + + invokeMock.mockRestore(); + (sendEventTelemetry as jest.Mock).mockRestore() + }) }) describe('Autodiscovery', () => { diff --git a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx index e0a2afe6f7..780692d518 100644 --- a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx @@ -1,20 +1,22 @@ -import React from 'react' -import { EuiButtonIcon, EuiText, EuiTitle, EuiToolTip } from '@elastic/eui' +import React, { useState } from 'react' +import { EuiButtonIcon, EuiCheckbox, EuiIcon, EuiText, EuiTitle, EuiToolTip } from '@elastic/eui' import cx from 'classnames' import { useDispatch, useSelector } from 'react-redux' import { ipcAuthGithub, ipcAuthGoogle } from 'uiSrc/electron/utils' import { TelemetryEvent, sendEventTelemetry } from 'uiSrc/telemetry' import { setOAuthCloudSource, signIn, oauthCloudPAgreementSelector } from 'uiSrc/slices/oauth/cloud' -import { OAuthAgreement } from 'uiSrc/components' +import { FeatureFlagComponent, OAuthAgreement } from 'uiSrc/components' +import { setIsRecommendedSettingsSSO, setIsAutodiscoverySSO } from 'uiSrc/slices/instances/cloud' +import { OAuthSocialSource } from 'uiSrc/slices/interfaces' +import { FeatureFlags } from 'uiSrc/constants' +import { appFeatureFlagsFeaturesSelector } from 'uiSrc/slices/app/features' import { ReactComponent as GoogleIcon } from 'uiSrc/assets/img/oauth/google.svg' import { ReactComponent as GithubIcon } from 'uiSrc/assets/img/oauth/github.svg' import { ReactComponent as GoogleSmallIcon } from 'uiSrc/assets/img/oauth/google_small.svg' import { ReactComponent as GithubSmallIcon } from 'uiSrc/assets/img/oauth/github_small.svg' -import { setIsAutodiscoverySSO } from 'uiSrc/slices/instances/cloud' -import { OAuthSocialSource } from 'uiSrc/slices/interfaces' import styles from './styles.module.scss' export enum OAuthSocialType { @@ -29,6 +31,10 @@ interface Props { const OAuthSocial = ({ type = OAuthSocialType.Modal, hideTitle = false }: Props) => { const agreement = useSelector(oauthCloudPAgreementSelector) + const { + [FeatureFlags.cloudSsoRecommendedSettings]: isRecommendedFeatureEnabled + } = useSelector(appFeatureFlagsFeaturesSelector) + const [isRecommended, setIsRecommended] = useState(isRecommendedFeatureEnabled?.flag ? true : undefined) const dispatch = useDispatch() const isAutodiscovery = type === OAuthSocialType.Autodiscovery @@ -39,9 +45,21 @@ const OAuthSocial = ({ type = OAuthSocialType.Modal, hideTitle = false }: Props) eventData: { accountOption, action: getAction(), + recommendedSettings: isAutodiscovery || !isRecommendedFeatureEnabled?.flag + ? undefined + : (isRecommended ? 'enabled' : 'disabled') } }) + const handleClickSso = () => { + dispatch(signIn()) + dispatch(setIsAutodiscoverySSO(isAutodiscovery)) + isAutodiscovery && dispatch(setOAuthCloudSource(OAuthSocialSource.Autodiscovery)) + if (!isAutodiscovery) { + dispatch(setIsRecommendedSettingsSSO(isRecommended)) + } + } + const socialLinks = [ { className: styles.googleButton, @@ -76,9 +94,7 @@ const OAuthSocial = ({ type = OAuthSocialType.Modal, hideTitle = false }: Props) disabled={!agreement} className={cx(styles.button, className)} onClick={() => { - dispatch(signIn()) - dispatch(setIsAutodiscoverySSO(isAutodiscovery)) - isAutodiscovery && dispatch(setOAuthCloudSource(OAuthSocialSource.Autodiscovery)) + handleClickSso() onButtonClick() }} data-testid={label} @@ -87,11 +103,34 @@ const OAuthSocial = ({ type = OAuthSocialType.Modal, hideTitle = false }: Props) )) + const RecommendedSettingsCheckBox = () => ( + +
+ setIsRecommended(e.target.checked)} + data-testid="oauth-recommended-settings-checkbox" + /> + + + +
+
+ ) + if (!isAutodiscovery) { return (
{buttons}
+
diff --git a/redisinsight/ui/src/components/oauth/oauth-social/styles.module.scss b/redisinsight/ui/src/components/oauth/oauth-social/styles.module.scss index 2122845eeb..b503b0bc4c 100644 --- a/redisinsight/ui/src/components/oauth/oauth-social/styles.module.scss +++ b/redisinsight/ui/src/components/oauth/oauth-social/styles.module.scss @@ -76,3 +76,38 @@ margin-top: 16px; text-align: left; } + +.recommendedSettings { + display: flex; + align-items: center; + + .recommendedSettingsToolTip { + display: inline-flex; + margin-left: 4px; + margin-bottom: 4px; + + :global { + svg { + width: 14px; + height: 14px; + } + } + } + + :global(.euiCheckbox) { + margin-bottom: 6px; + + :global(.euiCheckbox__label) { + font: normal normal normal 10px/15px Graphik, sans-serif !important; + color: var(--htmlColor) !important; + padding-left: 16px !important; + } + + :global(.euiCheckbox__square) { + width: 12px; + height: 12px; + padding: 0 !important; + border-width: 1px !important; + } + } +} diff --git a/redisinsight/ui/src/constants/featureFlags.ts b/redisinsight/ui/src/constants/featureFlags.ts index 7a6fd066a8..7bd18a8346 100644 --- a/redisinsight/ui/src/constants/featureFlags.ts +++ b/redisinsight/ui/src/constants/featureFlags.ts @@ -1,4 +1,5 @@ export enum FeatureFlags { insightsRecommendations = 'insightsRecommendations', cloudSso = 'cloudSso', + cloudSsoRecommendedSettings = 'cloudSsoRecommendedSettings', } diff --git a/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.spec.tsx b/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.spec.tsx index 82892a8d6b..d87484796d 100644 --- a/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.spec.tsx +++ b/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.spec.tsx @@ -1,10 +1,163 @@ import React from 'react' -import { render } from 'uiSrc/utils/test-utils' +import { cloneDeep } from 'lodash' +import { cleanup, mockedStore, render } from 'uiSrc/utils/test-utils' +import { CloudAuthStatus, CloudJobName, CloudJobStep } from 'uiSrc/electron/constants' +import { + addFreeDb, + fetchUserInfo, + getPlans, + getUserInfo, + setJob, + setOAuthCloudSource, + setSignInDialogState, + setSocialDialogState, + showOAuthProgress, + signInFailure +} from 'uiSrc/slices/oauth/cloud' +import { cloudSelector, loadSubscriptionsRedisCloud, setIsAutodiscoverySSO } from 'uiSrc/slices/instances/cloud' +import { addErrorNotification, addInfiniteNotification } from 'uiSrc/slices/app/notifications' +import { INFINITE_MESSAGES } from 'uiSrc/components/notifications/components' import ConfigOAuth from './ConfigOAuth' +jest.mock('uiSrc/slices/oauth/cloud', () => ({ + ...jest.requireActual('uiSrc/slices/oauth/cloud'), + fetchUserInfo: jest.fn().mockImplementation( + jest.requireActual('uiSrc/slices/oauth/cloud').fetchUserInfo + ) +})) + +jest.mock('uiSrc/slices/instances/cloud', () => ({ + ...jest.requireActual('uiSrc/slices/instances/cloud'), + cloudSelector: jest.fn().mockReturnValue({ + ...jest.requireActual('uiSrc/slices/instances/cloud').initialState + }) +})) + +let store: typeof mockedStore +beforeEach(() => { + cleanup() + store = cloneDeep(mockedStore) + store.clearActions() + window.app = { + cloudOauthCallback: jest.fn() + } as any +}) + describe('ConfigOAuth', () => { it('should render', () => { expect(render()).toBeTruthy() }) + + it('should call proper actions on success', () => { + window.app?.cloudOauthCallback.mockImplementation((cb: any) => cb(undefined, { status: CloudAuthStatus.Succeed })) + render() + + const expectedActions = [ + setJob({ id: '', name: CloudJobName.CreateFreeSubscriptionAndDatabase, status: '' }), + showOAuthProgress(true), + addInfiniteNotification(INFINITE_MESSAGES.PENDING_CREATE_DB(CloudJobStep.Credentials)), + setSignInDialogState(null), + setSocialDialogState(null), + getUserInfo() + ] + expect(store.getActions()).toEqual(expectedActions) + }) + + it('should call proper actions on failed', () => { + window.app?.cloudOauthCallback.mockImplementation((cb: any) => + cb( + undefined, { + status: CloudAuthStatus.Failed, + error: 'error' + } + )) + render() + + const expectedActions = [ + setOAuthCloudSource(null), + signInFailure('error'), + addErrorNotification({ + response: { + data: { + message: 'error' + }, + status: 500 + } + } as any), + setIsAutodiscoverySSO(false) + ] + expect(store.getActions()).toEqual(expectedActions) + }) + + it('should fetch plans by defaul', () => { + const fetchUserInfoMock = jest.fn().mockImplementation((onSuccessAction: () => void) => () => onSuccessAction()); + (fetchUserInfo as jest.Mock).mockImplementation(fetchUserInfoMock) + + window.app?.cloudOauthCallback.mockImplementation((cb: any) => cb(undefined, { status: CloudAuthStatus.Succeed })) + render() + + const afterCallbackActions = [ + setJob({ id: '', name: CloudJobName.CreateFreeSubscriptionAndDatabase, status: '' }), + showOAuthProgress(true), + addInfiniteNotification(INFINITE_MESSAGES.PENDING_CREATE_DB(CloudJobStep.Credentials)), + setSignInDialogState(null), + setSocialDialogState(null), + ] + + const expectedActions = [ + getPlans() + ] + expect(store.getActions()).toEqual([...afterCallbackActions, ...expectedActions]) + }) + + it('should call fetch subscriptions with autodiscovery flow', () => { + (cloudSelector as jest.Mock).mockReturnValue({ + isAutodiscoverySSO: true + }) + + const fetchUserInfoMock = jest.fn().mockImplementation((onSuccessAction: () => void) => () => onSuccessAction()); + (fetchUserInfo as jest.Mock).mockImplementation(fetchUserInfoMock) + + window.app?.cloudOauthCallback.mockImplementation((cb: any) => cb(undefined, { status: CloudAuthStatus.Succeed })) + render() + + const afterCallbackActions = [ + setJob({ id: '', name: CloudJobName.CreateFreeSubscriptionAndDatabase, status: '' }), + showOAuthProgress(true), + addInfiniteNotification(INFINITE_MESSAGES.PENDING_CREATE_DB(CloudJobStep.Credentials)), + setSignInDialogState(null), + setSocialDialogState(null), + ] + + const expectedActions = [ + loadSubscriptionsRedisCloud() + ] + expect(store.getActions()).toEqual([...afterCallbackActions, ...expectedActions]) + }) + + it('should call create free job after success with recommended settings', () => { + (cloudSelector as jest.Mock).mockReturnValue({ + isRecommendedSettings: true + }) + + const fetchUserInfoMock = jest.fn().mockImplementation((onSuccessAction: () => void) => () => onSuccessAction()); + (fetchUserInfo as jest.Mock).mockImplementation(fetchUserInfoMock) + + window.app?.cloudOauthCallback.mockImplementation((cb: any) => cb(undefined, { status: CloudAuthStatus.Succeed })) + render() + + const afterCallbackActions = [ + setJob({ id: '', name: CloudJobName.CreateFreeSubscriptionAndDatabase, status: '' }), + showOAuthProgress(true), + addInfiniteNotification(INFINITE_MESSAGES.PENDING_CREATE_DB(CloudJobStep.Credentials)), + setSignInDialogState(null), + setSocialDialogState(null), + ] + + const expectedActions = [ + addFreeDb() + ] + expect(store.getActions()).toEqual([...afterCallbackActions, ...expectedActions]) + }) }) diff --git a/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.tsx b/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.tsx index 31a2d37b9e..635be4b236 100644 --- a/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.tsx +++ b/redisinsight/ui/src/electron/components/ConfigOAuth/ConfigOAuth.tsx @@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' import { + createFreeDbJob, fetchPlans, fetchUserInfo, setJob, @@ -20,9 +21,10 @@ import { parseCloudOAuthError } from 'uiSrc/utils' import { INFINITE_MESSAGES, InfiniteMessagesIds } from 'uiSrc/components/notifications/components' const ConfigOAuth = () => { - const { isAutodiscoverySSO } = useSelector(cloudSelector) + const { isAutodiscoverySSO, isRecommendedSettings } = useSelector(cloudSelector) const isAutodiscoverySSORef = useRef(isAutodiscoverySSO) + const isRecommendedSettingsRef = useRef(isRecommendedSettings) const history = useHistory() const dispatch = useDispatch() @@ -38,6 +40,10 @@ const ConfigOAuth = () => { isAutodiscoverySSORef.current = isAutodiscoverySSO }, [isAutodiscoverySSO]) + useEffect(() => { + isRecommendedSettingsRef.current = isRecommendedSettings + }, [isRecommendedSettings]) + const fetchUserInfoSuccess = (isMultiAccount: boolean) => { if (isMultiAccount) return @@ -50,9 +56,25 @@ const ConfigOAuth = () => { }, closeInfinityNotification, )) - } else { - dispatch(fetchPlans()) + return + } + + if (isRecommendedSettingsRef.current) { + dispatch(createFreeDbJob({ + name: CloudJobName.CreateFreeSubscriptionAndDatabase, + resources: { + isRecommendedSettings: isRecommendedSettingsRef.current + }, + onSuccessAction: () => { + dispatch(addInfiniteNotification(INFINITE_MESSAGES.PENDING_CREATE_DB(CloudJobStep.Credentials))) + }, + onFailAction: closeInfinityNotification + })) + + return } + + dispatch(fetchPlans()) } const closeInfinityNotification = () => { @@ -72,7 +94,7 @@ const ConfigOAuth = () => { if (status === CloudAuthStatus.Failed) { const err = parseCloudOAuthError(error || message || '') dispatch(setOAuthCloudSource(null)) - dispatch(signInFailure(err?.message)) + dispatch(signInFailure(err?.response?.data?.message || message)) dispatch(addErrorNotification(err)) dispatch(setIsAutodiscoverySSO(false)) } diff --git a/redisinsight/ui/src/slices/app/features.ts b/redisinsight/ui/src/slices/app/features.ts index 767eeca39c..820f5f2dde 100644 --- a/redisinsight/ui/src/slices/app/features.ts +++ b/redisinsight/ui/src/slices/app/features.ts @@ -29,6 +29,9 @@ export const initialState: StateAppFeatures = { [FeatureFlags.cloudSso]: { flag: false }, + [FeatureFlags.cloudSsoRecommendedSettings]: { + flag: false + }, } } } @@ -98,7 +101,7 @@ const appFeaturesSlice = createSlice({ }, getFeatureFlagsSuccess: (state, { payload }) => { state.featureFlags.loading = false - state.featureFlags.features = payload.features + // state.featureFlags.features = payload.features }, getFeatureFlagsFailure: (state) => { state.featureFlags.loading = false diff --git a/redisinsight/ui/src/slices/instances/cloud.ts b/redisinsight/ui/src/slices/instances/cloud.ts index 67806dd498..a297c0c192 100644 --- a/redisinsight/ui/src/slices/instances/cloud.ts +++ b/redisinsight/ui/src/slices/instances/cloud.ts @@ -30,6 +30,7 @@ export const initialState: InitialStateCloud = { subscriptions: null, credentials: null, isAutodiscoverySSO: false, + isRecommendedSettings: undefined, account: { error: '', data: null, @@ -151,6 +152,9 @@ const cloudSlice = createSlice({ setIsAutodiscoverySSO: (state, { payload }: PayloadAction) => { state.isAutodiscoverySSO = payload }, + setIsRecommendedSettingsSSO: (state, { payload }: PayloadAction>) => { + state.isRecommendedSettings = payload + }, }, }) @@ -172,6 +176,7 @@ export const { resetSubscriptionsRedisCloud, resetLoadedRedisCloud, setIsAutodiscoverySSO, + setIsRecommendedSettingsSSO } = cloudSlice.actions // A selector diff --git a/redisinsight/ui/src/slices/interfaces/instances.ts b/redisinsight/ui/src/slices/interfaces/instances.ts index a52c6f83c5..028f16ee78 100644 --- a/redisinsight/ui/src/slices/interfaces/instances.ts +++ b/redisinsight/ui/src/slices/interfaces/instances.ts @@ -384,6 +384,7 @@ export interface InitialStateCloud { credentials: Nullable subscriptions: Nullable isAutodiscoverySSO: boolean + isRecommendedSettings: Maybe account: { data: Nullable error: string diff --git a/redisinsight/ui/src/slices/oauth/cloud.ts b/redisinsight/ui/src/slices/oauth/cloud.ts index e666c3dbdf..86965d6190 100644 --- a/redisinsight/ui/src/slices/oauth/cloud.ts +++ b/redisinsight/ui/src/slices/oauth/cloud.ts @@ -313,6 +313,7 @@ export function createFreeDbJob({ planId?: number, databaseId?: number, subscriptionId?: number, + isRecommendedSettings?: boolean } onSuccessAction?: () => void, onFailAction?: () => void diff --git a/redisinsight/ui/src/slices/tests/instances/cloud.spec.ts b/redisinsight/ui/src/slices/tests/instances/cloud.spec.ts index 72b02cd830..3b162bddd7 100644 --- a/redisinsight/ui/src/slices/tests/instances/cloud.spec.ts +++ b/redisinsight/ui/src/slices/tests/instances/cloud.spec.ts @@ -20,6 +20,7 @@ import reducer, { loadAccountRedisCloudSuccess, loadAccountRedisCloud, setIsAutodiscoverySSO, + setIsRecommendedSettingsSSO, fetchSubscriptionsRedisCloud, fetchAccountRedisCloud, loadInstancesRedisCloud, @@ -584,6 +585,28 @@ describe('cloud slice', () => { }) }) + describe('setIsRecommendedSettingsSSO', () => { + it('should properly set state', () => { + // Arrange + const data = true + const state = { + ...initialState, + isRecommendedSettings: true, + } + + // Act + const nextState = reducer(initialState, setIsRecommendedSettingsSSO(data)) + + // Assert + const rootState = Object.assign(initialStateDefault, { + connections: { + cloud: nextState, + }, + }) + expect(cloudSelector(rootState)).toEqual(state) + }) + }) + describe('thunks', () => { describe('fetchSubscriptionsRedisCloud', () => { it('call fetchSubscriptionsRedisCloud, loadSubscriptionsRedisCloud, and loadSubscriptionsRedisCloudSuccess when fetch is successed', async () => { From 22b8cb956c082124a48aa64e2a6cf494bff3b886 Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Fri, 8 Dec 2023 14:30:37 +0400 Subject: [PATCH 07/11] #RI-5226 - resolve comments --- ...ate-free-subscription-and-database.cloud-job.ts | 4 +--- .../feature-flag/feature-flag.provider.spec.ts | 14 +++++++++----- .../feature-flag/feature-flag.provider.ts | 13 ++++++------- ...ns.flag.strategy.ts => common.flag.strategy.ts} | 2 +- .../strategies/feature.flag.strategy.spec.ts | 6 +++--- ...flag.strategy.ts => with-data.flag.strategy.ts} | 2 +- 6 files changed, 21 insertions(+), 20 deletions(-) rename redisinsight/api/src/modules/feature/providers/feature-flag/strategies/{insights-recommendations.flag.strategy.ts => common.flag.strategy.ts} (87%) rename redisinsight/api/src/modules/feature/providers/feature-flag/strategies/{simple.flag.strategy.ts => with-data.flag.strategy.ts} (90%) 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 e4f4f45221..7431802b98 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 @@ -50,13 +50,11 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { if (this.data?.isRecommendedSettings) { const plans = await this.dependencies.cloudSubscriptionApiService.getSubscriptionPlans(this.options.sessionMetadata); - planId = plans[0].id + planId = plans[0].id; } - const freeSubscription: CloudSubscription = await this.runChildJob( CreateFreeSubscriptionCloudJob, - // this.data, { planId }, this.options, ); 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 0e7db26255..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,16 +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 { CloudSsoRecommendedSettingsFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.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 { @@ -24,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, )); @@ -32,11 +31,11 @@ export class FeatureFlagProvider { this.featuresConfigService, this.settingsService, )); - this.strategies.set(KnownFeatures.CloudSsoRecommendedSettings, new CloudSsoRecommendedSettingsFlagStrategy( + this.strategies.set(KnownFeatures.CloudSsoRecommendedSettings, new CommonFlagStrategy( this.featuresConfigService, this.settingsService, )); - this.strategies.set(KnownFeatures.RedisModuleFilter, new SimpleFlagStrategy( + 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); From 36c07d215d73e5c99491649c28e2fa60ee975007 Mon Sep 17 00:00:00 2001 From: Roman Sergeenko Date: Fri, 8 Dec 2023 11:36:12 +0100 Subject: [PATCH 08/11] #RI-5227 - uncomment code --- redisinsight/ui/src/slices/app/features.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisinsight/ui/src/slices/app/features.ts b/redisinsight/ui/src/slices/app/features.ts index 820f5f2dde..e79aeb4ae8 100644 --- a/redisinsight/ui/src/slices/app/features.ts +++ b/redisinsight/ui/src/slices/app/features.ts @@ -101,7 +101,7 @@ const appFeaturesSlice = createSlice({ }, getFeatureFlagsSuccess: (state, { payload }) => { state.featureFlags.loading = false - // state.featureFlags.features = payload.features + state.featureFlags.features = payload.features }, getFeatureFlagsFailure: (state) => { state.featureFlags.loading = false From e0c77ed8687ed908e126edaf4bb0c7e226e6da55 Mon Sep 17 00:00:00 2001 From: Roman Sergeenko Date: Fri, 8 Dec 2023 11:49:32 +0100 Subject: [PATCH 09/11] #RI-5227 - fix tests --- .../ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx index 49b6c24467..ae2c788be9 100644 --- a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx @@ -95,7 +95,7 @@ describe('OAuthSocial', () => { expect(store.getActions()).toEqual(expectedActions) invokeMock.mockRestore(); - (sendEventTelemetry as jest.Mock).mockRegstore() + (sendEventTelemetry as jest.Mock).mockRestore() }) describe('Recommended Settings Enabled', () => { From 769cb1dd0c61ecf571c88e775893867c07ff88ec Mon Sep 17 00:00:00 2001 From: Roman Sergeenko Date: Fri, 8 Dec 2023 12:59:33 +0100 Subject: [PATCH 10/11] #RI-5227 - update text, telemetry --- .../oauth/oauth-social/OAuthSocial.spec.tsx | 2 ++ .../components/oauth/oauth-social/OAuthSocial.tsx | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx index ae2c788be9..b35629ccbb 100644 --- a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.spec.tsx @@ -58,6 +58,7 @@ describe('OAuthSocial', () => { eventData: { accountOption: 'Google', action: 'create', + recommendedSettings: null } }) @@ -84,6 +85,7 @@ describe('OAuthSocial', () => { eventData: { accountOption: 'GitHub', action: 'create', + recommendedSettings: null } }) diff --git a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx index 780692d518..0b79a7741f 100644 --- a/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx +++ b/redisinsight/ui/src/components/oauth/oauth-social/OAuthSocial.tsx @@ -45,9 +45,11 @@ const OAuthSocial = ({ type = OAuthSocialType.Modal, hideTitle = false }: Props) eventData: { accountOption, action: getAction(), - recommendedSettings: isAutodiscovery || !isRecommendedFeatureEnabled?.flag + recommendedSettings: isAutodiscovery ? undefined - : (isRecommended ? 'enabled' : 'disabled') + : (!isRecommendedFeatureEnabled?.flag + ? null + : (isRecommended ? 'enabled' : 'disabled')) } }) @@ -115,7 +117,13 @@ const OAuthSocial = ({ type = OAuthSocialType.Modal, hideTitle = false }: Props) data-testid="oauth-recommended-settings-checkbox" /> + The database will be automatically created using a pre-selected provider and region. +
+ You can change it by signing in to Redis Cloud. + + )} position="top" anchorClassName={styles.recommendedSettingsToolTip} > From a282871343ebe1afc46858ba749a4ddd64e78afd Mon Sep 17 00:00:00 2001 From: Amir Allayarov Date: Fri, 8 Dec 2023 17:59:06 +0400 Subject: [PATCH 11/11] #RI-5227 - sort plans --- ...te-free-subscription-and-database.cloud-job.ts | 9 ++++++++- ...cloud-sso-use-recommended-settings.strategy.ts | 15 --------------- 2 files changed, 8 insertions(+), 16 deletions(-) delete mode 100644 redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy.ts 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 7431802b98..4742164f73 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'; @@ -11,6 +12,7 @@ import { CloudDatabaseAnalytics } from 'src/modules/cloud/database/cloud-databas 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; @@ -50,7 +52,7 @@ export class CreateFreeSubscriptionAndDatabaseCloudJob extends CloudJob { if (this.data?.isRecommendedSettings) { const plans = await this.dependencies.cloudSubscriptionApiService.getSubscriptionPlans(this.options.sessionMetadata); - planId = plans[0].id; + planId = this.getRecommendedPlanId(plans); } const freeSubscription: CloudSubscription = await this.runChildJob( @@ -79,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/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy.ts b/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy.ts deleted file mode 100644 index 351748803f..0000000000 --- a/redisinsight/api/src/modules/feature/providers/feature-flag/strategies/cloud-sso-use-recommended-settings.strategy.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { FeatureFlagStrategy } from 'src/modules/feature/providers/feature-flag/strategies/feature.flag.strategy'; -import { Feature } from 'src/modules/feature/model/feature'; -import { IFeatureFlag } from 'src/modules/feature/constants'; - -export class CloudSsoRecommendedSettingsFlagStrategy extends FeatureFlagStrategy { - async calculate(knownFeature: IFeatureFlag, featureConfig: any): Promise { - const isInRange = await this.isInTargetRange(featureConfig?.perc); - - return { - name: knownFeature.name, - flag: isInRange - && await this.filter(featureConfig?.filters) ? !!featureConfig?.flag : !featureConfig?.flag, - }; - } -}