satellite/{web, console}: feature flag for new access grant flow

Added a feature flag for new create access grant flow.
Also added some initial setup.

Change-Id: I7f738181c8a83f5a724f9e562427445cae146b6f
This commit is contained in:
Vitalii 2023-02-07 16:52:20 +02:00 committed by Storj Robot
parent 38275bd710
commit 65e3cfb9c6
11 changed files with 124 additions and 1 deletions

View File

@ -92,6 +92,7 @@ type Config struct {
PathwayOverviewEnabled bool `help:"indicates if the overview onboarding step should render with pathways" default:"true"`
NewProjectDashboard bool `help:"indicates if new project dashboard should be used" default:"true"`
NewBillingScreen bool `help:"indicates if new billing screens should be used" default:"true"`
NewAccessGrantFlow bool `help:"indicates if new access grant flow should be used" default:"false"`
GeneratedAPIEnabled bool `help:"indicates if generated console api should be used" default:"false"`
OptionalSignupSuccessURL string `help:"optional url to external registration success page" default:""`
HomepageURL string `help:"url link to storj.io homepage" default:"https://www.storj.io"`
@ -457,6 +458,7 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
DefaultPaidStorageLimit memory.Size
DefaultPaidBandwidthLimit memory.Size
NewBillingScreen bool
NewAccessGrantFlow bool
InactivityTimerEnabled bool
InactivityTimerDuration int
InactivityTimerViewerEnabled bool
@ -507,6 +509,7 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
data.PasswordMinimumLength = console.PasswordMinimumLength
data.PasswordMaximumLength = console.PasswordMaximumLength
data.ABTestingEnabled = server.config.ABTesting.Enabled
data.NewAccessGrantFlow = server.config.NewAccessGrantFlow
templates, err := server.loadTemplates()
if err != nil || templates.index == nil {

View File

@ -244,6 +244,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
# indicates if storj native token payments system is enabled
# console.native-token-payments-enabled: false
# indicates if new access grant flow should be used
# console.new-access-grant-flow: false
# indicates if new billing screens should be used
# console.new-billing-screen: true

View File

@ -32,6 +32,7 @@
<meta name="default-paid-storage-limit" content="{{ .DefaultPaidStorageLimit }}">
<meta name="default-paid-bandwidth-limit" content="{{ .DefaultPaidBandwidthLimit }}">
<meta name="new-billing-screen" content="{{ .NewBillingScreen }}">
<meta name="new-access-grant-flow" content="{{ .NewAccessGrantFlow }}">
<meta name="inactivity-timer-enabled" content="{{ .InactivityTimerEnabled }}">
<meta name="inactivity-timer-duration" content="{{ .InactivityTimerDuration }}">
<meta name="inactivity-timer-viewer-enabled" content="{{ .InactivityTimerViewerEnabled }}">

View File

@ -14,6 +14,7 @@ import { Component, Vue } from 'vue-property-decorator';
import { PartneredSatellite } from '@/types/common';
import { APP_STATE_ACTIONS } from '@/utils/constants/actionNames';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { MetaUtils } from '@/utils/meta';
import NotificationArea from '@/components/notifications/NotificationArea.vue';
@ -40,6 +41,7 @@ export default class App extends Vue {
const couponCodeBillingUIEnabled = MetaUtils.getMetaContent('coupon-code-billing-ui-enabled');
const couponCodeSignupUIEnabled = MetaUtils.getMetaContent('coupon-code-signup-ui-enabled');
const isNewProjectDashboard = MetaUtils.getMetaContent('new-project-dashboard');
const isNewAccessGrantFlow = MetaUtils.getMetaContent('new-access-grant-flow');
if (satelliteName) {
this.$store.dispatch(APP_STATE_ACTIONS.SET_SATELLITE_NAME, satelliteName);
@ -72,6 +74,10 @@ export default class App extends Vue {
this.$store.dispatch(APP_STATE_ACTIONS.SET_PROJECT_DASHBOARD_STATUS, isNewProjectDashboard === 'true');
}
if (isNewAccessGrantFlow) {
this.$store.commit(APP_STATE_MUTATIONS.SET_ACCESS_GRANT_FLOW_STATUS, isNewAccessGrantFlow === 'true');
}
this.fixViewportHeight();
}

View File

@ -168,6 +168,7 @@ import { ACCESS_GRANTS_ACTIONS } from '@/store/modules/accessGrants';
import { AccessGrant } from '@/types/accessGrants';
import { AnalyticsHttpApi } from '@/api/analytics';
import { AnalyticsErrorEventSource, AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { AccessType } from '@/types/createAccessGrant';
import AccessGrantsItem from '@/components/accessGrants/AccessGrantsItem.vue';
import ConfirmDeletePopup from '@/components/accessGrants/ConfirmDeletePopup.vue';
@ -342,6 +343,16 @@ export default class AccessGrants extends Vue {
*/
public accessGrantClick(): void {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_ACCESS_GRANT_CLICKED);
if (this.isNewAccessGrantFlow) {
this.trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.AccessGrant },
});
return;
}
this.trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
@ -354,6 +365,16 @@ export default class AccessGrants extends Vue {
*/
public s3Click(): void {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_S3_CREDENTIALS_CLICKED);
if (this.isNewAccessGrantFlow) {
this.trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.S3 },
});
return;
}
this.trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
@ -366,6 +387,16 @@ export default class AccessGrants extends Vue {
*/
public cliClick(): void {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_KEYS_FOR_CLI_CLICKED);
if (this.isNewAccessGrantFlow) {
this.trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.APIKey },
});
return;
}
this.trackPageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
@ -379,6 +410,13 @@ export default class AccessGrants extends Vue {
public trackPageVisit(link: string): void {
this.analytics.pageVisit(link);
}
/**
* Indicates if new access grant flow should be used.
*/
private get isNewAccessGrantFlow(): boolean {
return this.$store.state.appStateModule.isNewAccessGrantFlow;
}
}
</script>
<style scoped lang="scss">

View File

@ -0,0 +1,24 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<VModal :on-close="closeModal">
<template #content />
</VModal>
</template>
<script setup lang="ts">
import { useRouter } from '@/utils/hooks';
import { RouteConfig } from '@/router';
import VModal from '@/components/common/VModal.vue';
const router = useRouter();
/**
* Closes create access grant flow.
*/
function closeModal(): void {
router.push(RouteConfig.AccessGrants.path);
}
</script>

View File

@ -49,6 +49,7 @@ import { AnalyticsEvent } from '@/utils/constants/analyticsEventNames';
import { RouteConfig } from '@/router';
import { APP_STATE_MUTATIONS } from '@/store/mutationConstants';
import { User } from '@/types/users';
import { AccessType } from '@/types/createAccessGrant';
import NewProjectIcon from '@/../static/images/navigation/newProject.svg';
import CreateAGIcon from '@/../static/images/navigation/createAccessGrant.svg';
@ -78,6 +79,16 @@ export default class QuickStartLinks extends Vue {
public navigateToCreateAG(): void {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_AN_ACCESS_GRANT_CLICKED);
this.closeDropdowns();
if (this.isNewAccessGrantFlow) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.AccessGrant },
}).catch(() => {return;});
return;
}
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
@ -91,6 +102,16 @@ export default class QuickStartLinks extends Vue {
public navigateToAccessGrantS3(): void {
this.analytics.eventTriggered(AnalyticsEvent.CREATE_S3_CREDENTIALS_CLICKED);
this.closeDropdowns();
if (this.isNewAccessGrantFlow) {
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.NewCreateAccessModal).path);
this.$router.push({
name: RouteConfig.NewCreateAccessModal.name,
params: { accessType: AccessType.S3 },
}).catch(() => {return;});
return;
}
this.analytics.pageVisit(RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).path);
this.$router.push({
name: RouteConfig.AccessGrants.with(RouteConfig.CreateAccessModal).name,
@ -139,6 +160,13 @@ export default class QuickStartLinks extends Vue {
this.closeDropdowns();
}
/**
* Indicates if new access grant flow should be used.
*/
private get isNewAccessGrantFlow(): boolean {
return this.$store.state.appStateModule.isNewAccessGrantFlow;
}
}
</script>

View File

@ -9,6 +9,7 @@ import { MetaUtils } from '@/utils/meta';
import AccessGrants from '@/components/accessGrants/AccessGrants.vue';
import CreateAccessModal from '@/components/accessGrants/CreateAccessModal.vue';
import CreateAccessGrantFlow from '@/components/accessGrants/newCreateFlow/CreateAccessGrantFlow.vue';
import AccountArea from '@/components/account/AccountArea.vue';
import AccountBilling from '@/components/account/billing/BillingArea.vue';
import BillingOverview from '@/components/account/billing/billingTabs/Overview.vue';
@ -92,6 +93,7 @@ export abstract class RouteConfig {
// access grant child paths
public static CreateAccessModal = new NavigationLink('create-access-modal', 'Create Access Modal');
public static NewCreateAccessModal = new NavigationLink('new-create-access-modal', 'New Create Access Modal');
// onboarding tour child paths
public static OverviewStep = new NavigationLink('overview', 'Onboarding Overview');
@ -352,6 +354,11 @@ export const router = new Router({
name: RouteConfig.CreateAccessModal.name,
component: CreateAccessModal,
},
{
path: RouteConfig.NewCreateAccessModal.path,
name: RouteConfig.NewCreateAccessModal.name,
component: CreateAccessGrantFlow,
},
],
},
{

View File

@ -70,6 +70,7 @@ class State {
public couponCodeBillingUIEnabled = false,
public couponCodeSignupUIEnabled = false,
public isNewProjectDashboard = false,
public isNewAccessGrantFlow = false,
){}
}
@ -271,6 +272,9 @@ export const appStateModule = {
[APP_STATE_MUTATIONS.SET_PROJECT_DASHBOARD_STATUS](state: State, isNewProjectDashboard: boolean): void {
state.isNewProjectDashboard = isNewProjectDashboard;
},
[APP_STATE_MUTATIONS.SET_ACCESS_GRANT_FLOW_STATUS](state: State, isNewAccessGrantFlow: boolean): void {
state.isNewAccessGrantFlow = isNewAccessGrantFlow;
},
[APP_STATE_MUTATIONS.SET_ONB_AG_NAME_STEP_BACK_ROUTE](state: State, backRoute: string): void {
state.appState.onbAGStepBackRoute = backRoute;
},

View File

@ -62,6 +62,7 @@ export const APP_STATE_MUTATIONS = {
SET_COUPON_CODE_BILLING_UI_STATUS: 'SET_COUPON_CODE_BILLING_UI_STATUS',
SET_COUPON_CODE_SIGNUP_UI_STATUS: 'SET_COUPON_CODE_SIGNUP_UI_STATUS',
SET_PROJECT_DASHBOARD_STATUS: 'SET_PROJECT_DASHBOARD_STATUS',
SET_ACCESS_GRANT_FLOW_STATUS: 'SET_ACCESS_GRANT_FLOW_STATUS',
SET_ONB_AG_NAME_STEP_BACK_ROUTE: 'SET_ONB_AG_NAME_STEP_BACK_ROUTE',
SET_ONB_API_KEY_STEP_BACK_ROUTE: 'SET_ONB_API_KEY_STEP_BACK_ROUTE',
SET_ONB_API_KEY: 'SET_ONB_API_KEY',

View File

@ -0,0 +1,8 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.
export enum AccessType {
APIKey = 'apikey',
S3 = 's3',
AccessGrant = 'accessGrant',
}