Skip to content

Commit 75f4c0f

Browse files
authored
feat: multiAccount- adding switch button and supporting UI (#3509)
* Added new Account settings page, and adjusted the billing page too * Billing Page: tabbed in with the account settings page if the flag is on * if the flag is off, no changes at all on the page * added e2e tests, adjusted existing tests * update openApi SHA
1 parent b24743d commit 75f4c0f

File tree

13 files changed

+612
-28
lines changed

13 files changed

+612
-28
lines changed

cypress/e2e/cloud/billing.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,20 @@ describe('Billing Page PAYG Users', () => {
5959
cy.flush().then(() =>
6060
cy.signin().then(() => {
6161
cy.get('@org').then(({id}: Organization) => {
62-
cy.setFeatureFlags({uiUnificationFlag: true}).then(() => {
62+
cy.setFeatureFlags({
63+
uiUnificationFlag: true,
64+
multiAccount: true,
65+
}).then(() => {
6366
cy.quartzProvision({
6467
accountType: 'pay_as_you_go',
6568
}).then(() => {
6669
cy.visit(`/orgs/${id}/billing`)
67-
cy.getByTestID('billing-page--header').should('be.visible')
70+
cy.getByTestID('accounts-billing-tab').should('be.visible')
71+
72+
cy.getByTestID('accounts-billing-tab').should(
73+
'have.class',
74+
'cf-tabs--tab__active'
75+
)
6876
})
6977
})
7078
})
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {Organization} from '../../../src/types'
2+
3+
const doSetup = (cy, numAccounts: number) => {
4+
cy.signin().then(() => {
5+
cy.get('@org').then(({id}: Organization) => {
6+
cy.setFeatureFlags({
7+
uiUnificationFlag: true,
8+
multiAccount: true,
9+
}).then(() => {
10+
cy.quartzProvision({
11+
accountType: 'free',
12+
numAccounts,
13+
}).then(() => {
14+
cy.visit(`/orgs/${id}/accounts/settings`)
15+
})
16+
})
17+
})
18+
})
19+
}
20+
21+
describe('Account Page; user with 3 accounts', () => {
22+
beforeEach(() => doSetup(cy, 3))
23+
24+
it('can get to the page and get the accounts, and the switch button is showing', () => {
25+
cy.getByTestID('account-settings--header').should('be.visible')
26+
cy.getByTestID('user-account-switch-btn').should('be.visible')
27+
cy.getByTestID('account-active-name--block').contains('Influx')
28+
29+
cy.getByTestID('user-account-switch-btn').click()
30+
31+
cy.getByTestID('switch-account--dialog').within(() => {
32+
const prefix = 'accountSwitch-toggle-choice'
33+
34+
cy.getByTestID(`${prefix}-0-ID`).should('be.visible')
35+
cy.getByTestID(`${prefix}-0-ID`).contains('Influx')
36+
cy.getByTestID(`${prefix}-0-ID--input`).should('be.checked')
37+
38+
cy.getByTestID(`${prefix}-1-ID`).should('be.visible')
39+
cy.getByTestID(`${prefix}-1-ID`).contains('Veganomicon (default)')
40+
41+
cy.getByTestID(`${prefix}-2-ID`).should('be.visible')
42+
cy.getByTestID(`${prefix}-2-ID`).contains('Stradivarius')
43+
44+
// at first; the switch button should be disabled:
45+
cy.getByTestID('actually-switch-account--btn').should('be.disabled')
46+
47+
// now: select another option:
48+
cy.getByTestID(`${prefix}-2-ID`).click()
49+
50+
// check that it is selected before checking the button enabled state:
51+
cy.getByTestID(`${prefix}-2-ID--input`).should('be.checked')
52+
53+
// now; the button should be enabled:
54+
cy.getByTestID('actually-switch-account--btn').should('be.enabled')
55+
})
56+
})
57+
})
58+
59+
describe('Account Page; user with one account', () => {
60+
beforeEach(() => cy.flush().then(() => doSetup(cy, 1)))
61+
62+
it('can get to the page and get the accounts, and the switch button is NOT showing', () => {
63+
cy.getByTestID('account-settings--header').should('be.visible')
64+
cy.getByTestID('user-account-switch-btn').should('not.exist')
65+
66+
cy.getByTestID('account-active-name--block').contains('Veganomicon')
67+
})
68+
})

cypress/support/commands.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,13 +617,23 @@ export const flush = (): Cypress.Chainable<Cypress.Response<any>> => {
617617
}
618618
}
619619

620+
/**
621+
* numAccounts: this is the number of accounts that a particular user has.
622+
* (if there is more than one account, then show the switch button,
623+
* if only one account don't show the switch button)
624+
*
625+
* if this number is not supplied, then quartz-mock has a default value of 1 (1 account)
626+
*
627+
* this is for the new multi-user/multi-account capability (adding to the UI in early 2022)
628+
* */
620629
export type ProvisionData = {
621630
accountType?: AccountType
622631
hasData?: boolean
623632
hasUsers?: boolean
624633
isOperator?: boolean
625634
operatorRole?: string
626635
isRegionBeta?: boolean
636+
numAccounts?: number
627637
}
628638

629639
/*

src/accounts/AccountHeader.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React, {FC} from 'react'
2+
3+
// Components
4+
import {Page} from '@influxdata/clockface'
5+
import RateLimitAlert from 'src/cloud/components/RateLimitAlert'
6+
import LimitChecker from 'src/cloud/components/LimitChecker'
7+
8+
type Props = {
9+
testID?: string
10+
}
11+
12+
const AccountHeader: FC<Props> = ({testID = 'member-page--header'}) => (
13+
<Page.Header fullWidth={false} testID={testID}>
14+
<Page.Title title="Account" />
15+
<LimitChecker>
16+
<RateLimitAlert />
17+
</LimitChecker>
18+
</Page.Header>
19+
)
20+
21+
export default AccountHeader

src/accounts/AccountPage.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Libraries
2+
import React, {FC, useContext, useState} from 'react'
3+
import {Button, IconFont, Overlay, Page} from '@influxdata/clockface'
4+
5+
// Utils
6+
import {pageTitleSuffixer} from 'src/shared/utils/pageTitles'
7+
import {UserAccountProvider} from 'src/accounts/context/userAccount'
8+
import AccountTabContainer from 'src/accounts/AccountTabContainer'
9+
10+
import {UserAccountContext} from 'src/accounts/context/userAccount'
11+
12+
import {SwitchAccountOverlay} from 'src/accounts/SwitchAccountOverlay'
13+
14+
const AccountAboutPage: FC = () => {
15+
const {userAccounts} = useContext(UserAccountContext)
16+
const [isSwitchAccountVisible, setSwitchAccountVisible] = useState(false)
17+
18+
/**
19+
* confirmed with @Grace and @distortia that there is guaranteed
20+
* to be at least one account (since the user has to be logged in
21+
* to get to this call); and each account is guaranteed to have a name.
22+
*
23+
* and one of the accounts has to be active (the one that the user currently
24+
* is logged in as)
25+
*/
26+
27+
const activeAcctName =
28+
userAccounts && userAccounts.filter(acct => acct.isActive)[0].name
29+
30+
const showSwitchAccountDialog = () => {
31+
setSwitchAccountVisible(true)
32+
}
33+
34+
const closeSwitchAccountDialog = () => {
35+
setSwitchAccountVisible(false)
36+
}
37+
38+
return (
39+
<Page titleTag={pageTitleSuffixer(['About', 'Account'])}>
40+
<AccountTabContainer activeTab="about">
41+
<>
42+
{userAccounts && userAccounts.length >= 2 && (
43+
<Button
44+
text="Switch Account"
45+
icon={IconFont.Switch_New}
46+
onClick={showSwitchAccountDialog}
47+
testID="user-account-switch-btn"
48+
/>
49+
)}
50+
<hr />
51+
<h2 data-testid="account-settings--header"> Account Details </h2>
52+
<div data-testid="account-active-name--block">
53+
Currently logged in Active Account: {activeAcctName}
54+
</div>
55+
56+
<Overlay visible={isSwitchAccountVisible}>
57+
<SwitchAccountOverlay onDismissOverlay={closeSwitchAccountDialog} />
58+
</Overlay>
59+
</>
60+
</AccountTabContainer>
61+
</Page>
62+
)
63+
}
64+
65+
const AccountPage: FC = () => {
66+
return (
67+
<Page titleTag={pageTitleSuffixer(['Account Settings Page'])}>
68+
<UserAccountProvider>
69+
<AccountAboutPage />
70+
</UserAccountProvider>
71+
</Page>
72+
)
73+
}
74+
75+
export default AccountPage
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Libraries
2+
import React, {PureComponent} from 'react'
3+
4+
// Components
5+
import AccountTabs from 'src/accounts/AccountTabs'
6+
import AccountHeader from 'src/accounts/AccountHeader'
7+
8+
import {Tabs, Orientation, Page} from '@influxdata/clockface'
9+
10+
// Decorators
11+
import {ErrorHandling} from 'src/shared/decorators/errors'
12+
13+
interface Props {
14+
activeTab: string
15+
}
16+
17+
@ErrorHandling
18+
class AccountTabContainer extends PureComponent<Props> {
19+
public render() {
20+
const {activeTab, children} = this.props
21+
22+
return (
23+
<Page.Contents fullWidth={false} scrollable={true}>
24+
<Tabs.Container orientation={Orientation.Horizontal}>
25+
<AccountHeader testID="account-page--header" />
26+
<AccountTabs activeTab={activeTab} />
27+
<Tabs.TabContents>{children}</Tabs.TabContents>
28+
</Tabs.Container>
29+
</Page.Contents>
30+
)
31+
}
32+
}
33+
34+
export default AccountTabContainer

src/accounts/AccountTabs.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Libraries
2+
import React, {FC} from 'react'
3+
import {useSelector} from 'react-redux'
4+
import {Link} from 'react-router-dom'
5+
6+
// Components
7+
import {Tabs, Orientation, ComponentSize} from '@influxdata/clockface'
8+
9+
// Utils
10+
import {getOrg} from 'src/organizations/selectors'
11+
12+
interface Props {
13+
activeTab: string
14+
}
15+
16+
interface AccountPageTab {
17+
text: string
18+
id: string
19+
link: string
20+
testID: string
21+
}
22+
23+
enum Tab {
24+
Billing = 'billing',
25+
About = 'about',
26+
}
27+
28+
const AccountTabs: FC<Props> = ({activeTab}) => {
29+
const orgID = useSelector(getOrg)?.id
30+
31+
const tabs: AccountPageTab[] = [
32+
{
33+
text: 'Billing',
34+
id: Tab.Billing,
35+
testID: 'accounts-billing-tab',
36+
link: `/orgs/${orgID}/billing`,
37+
},
38+
{
39+
text: 'Settings',
40+
id: Tab.About,
41+
testID: 'accounts-setting-tab',
42+
link: `/orgs/${orgID}/accounts/settings`,
43+
},
44+
]
45+
46+
return (
47+
<Tabs orientation={Orientation.Horizontal} size={ComponentSize.Large}>
48+
{tabs.map(tabInfo => {
49+
const isActive = tabInfo.id === activeTab
50+
51+
return (
52+
<Tabs.Tab
53+
key={tabInfo.id}
54+
text={tabInfo.text}
55+
id={tabInfo.id}
56+
linkElement={className => (
57+
<Link to={tabInfo.link} className={className} />
58+
)}
59+
testID={tabInfo.testID}
60+
active={isActive}
61+
/>
62+
)
63+
})}
64+
</Tabs>
65+
)
66+
}
67+
68+
export default AccountTabs

0 commit comments

Comments
 (0)