-
Notifications
You must be signed in to change notification settings - Fork 535
feat(onboarding): connection terminal and flags table #7856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
talissoncosta
wants to merge
21
commits into
main
Choose a base branch
from
feat/onboarding-terminal-flags-table-7766
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+869
−19
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
6d88a66
feat(onboarding): add the verify terminal (#7766)
talissoncosta d192575
feat(onboarding): add the flags table (#7766)
talissoncosta cdd1cb7
feat(onboarding): render terminal + flags table with a real Dev toggl…
talissoncosta 35b5022
style(onboarding): radius tokens + restore the connected card's purpl…
talissoncosta eb2725e
refactor(onboarding): route connection status through useOnboardingCo…
talissoncosta 202b3c5
feat(onboarding): show the flag's real tags in the table (#7766)
talissoncosta 82e4046
feat(onboarding): attach an Onboarding tag to the demo flag in bootst…
talissoncosta dbd2433
feat(onboarding): tick the verify checklist as the user copies the sn…
talissoncosta fe9913b
style(onboarding): align the flags toggle under the ENABLED header (#…
talissoncosta 96e125e
refactor(onboarding): use a tag-palette colour for the Onboarding tag…
talissoncosta d2474f9
fix(forms): disable browser autofill on GhostInput
talissoncosta 63459ee
fix(onboarding): toast on flag toggle failure
talissoncosta 815647d
docs(onboarding): note the verify checklist is session-only
talissoncosta 880188a
refactor(onboarding): use Bootstrap/token utilities for layout and su…
talissoncosta 36c1a9e
feat(onboarding): accessible names for the copy buttons and flag switch
talissoncosta 79ae58f
fix(onboarding): preserve tags when renaming the flag
talissoncosta bb88e01
test(e2e): cover the single-page onboarding flow
talissoncosta fd28698
fix(onboarding): send new users to the onboarding flow, not /create
talissoncosta 04a7b84
test(e2e): wait for the post-signup redirect instead of navigating
talissoncosta bfa5cd6
fix(onboarding): make the flag toggle optimistic
talissoncosta b57ee74
fix(onboarding): lock the flag toggle until the app connects
talissoncosta File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
49 changes: 49 additions & 0 deletions
49
frontend/documentation/pages/onboarding/OnboardingFlagsTable.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import type { Meta, StoryObj } from 'storybook' | ||
|
|
||
| import OnboardingFlagsTable, { | ||
| OnboardingFlagRow, | ||
| } from 'components/pages/onboarding/OnboardingFlagsTable' | ||
|
|
||
| const demoFlag: OnboardingFlagRow = { | ||
| description: 'Controls the demo button shown to your users', | ||
| enabled: true, | ||
| name: 'show_demo_button', | ||
| } | ||
|
|
||
| const meta: Meta<typeof OnboardingFlagsTable> = { | ||
| args: { | ||
| flags: [demoFlag], | ||
| onToggle: () => {}, | ||
| status: 'connected', | ||
| }, | ||
| component: OnboardingFlagsTable, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| component: | ||
| 'The "Your flags" card from the onboarding flow, reusing the product FeatureName / Tag / Switch. Prop-driven: the page owns the flag data and the persisted Dev toggle. `connected` lifts the card with the accent border and glow; `waiting` dims it until the first evaluation arrives.', | ||
| }, | ||
| }, | ||
| layout: 'padded', | ||
| }, | ||
| title: 'Pages/Onboarding/OnboardingFlagsTable', | ||
| } | ||
| export default meta | ||
|
|
||
| type Story = StoryObj<typeof OnboardingFlagsTable> | ||
|
|
||
| export const Connected: Story = {} | ||
|
|
||
| export const Waiting: Story = { | ||
| args: { status: 'waiting' }, | ||
| } | ||
|
|
||
| export const Off: Story = { | ||
| args: { flags: [{ ...demoFlag, enabled: false }] }, | ||
| } | ||
|
|
||
| export const WithTag: Story = { | ||
| args: { | ||
| flags: [{ ...demoFlag, tags: [{ color: '#6837FC', label: 'demo' }] }], | ||
| }, | ||
| } |
40 changes: 40 additions & 0 deletions
40
frontend/documentation/pages/onboarding/OnboardingTerminal.stories.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import type { Meta, StoryObj } from 'storybook' | ||
|
|
||
| import OnboardingTerminal from 'components/pages/onboarding/OnboardingTerminal' | ||
|
|
||
| const meta: Meta<typeof OnboardingTerminal> = { | ||
| args: { | ||
| connected: false, | ||
| featureName: 'show_demo_button', | ||
| installCopied: false, | ||
| snippetCopied: false, | ||
| }, | ||
| component: OnboardingTerminal, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| component: | ||
| 'The onboarding verify console. The checklist ticks as the user acts (copy install, copy snippet), and the first evaluation flips the badge to LIVE and prints the connection receipt. Always dark, since a terminal reads the same in light and dark mode.', | ||
| }, | ||
| }, | ||
| layout: 'padded', | ||
| }, | ||
| title: 'Pages/Onboarding/OnboardingTerminal', | ||
| } | ||
| export default meta | ||
|
|
||
| type Story = StoryObj<typeof OnboardingTerminal> | ||
|
|
||
| export const Listening: Story = {} | ||
|
|
||
| export const InstallCopied: Story = { | ||
| args: { installCopied: true }, | ||
| } | ||
|
|
||
| export const SnippetsCopied: Story = { | ||
| args: { installCopied: true, snippetCopied: true }, | ||
| } | ||
|
|
||
| export const Connected: Story = { | ||
| args: { connected: true, installCopied: true, snippetCopied: true }, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import { test, expect } from '../test-setup'; | ||
| import { byId, createHelpers, getFlagsmith, log, visualSnapshot } from '../helpers'; | ||
| import { E2E_SIGN_UP_USER, PASSWORD } from '../config'; | ||
|
|
||
| // The single-page onboarding flow (onboarding_quickstart_flow) a new user lands | ||
| // on at /getting-started. Mirror of the legacy signup test's guard: this runs | ||
| // only when the flag is on, the legacy signup test runs only when it's off. | ||
| // | ||
| // Selectors are accessibility-first (roles / labels / text), not data-test ids: | ||
| // the header inputs expose aria-labels, the copy buttons and the flag switch | ||
| // carry accessible names, and the flags table is a labelled region. | ||
| test.describe('Onboarding', () => { | ||
| test('New user connects via the single-page onboarding flow @oss', async ({ | ||
| page, | ||
| }, testInfo) => { | ||
| const { addErrorLogging, click, setText, waitForElementVisible } = | ||
| createHelpers(page); | ||
| const flagsmith = await getFlagsmith(); | ||
|
|
||
| test.skip( | ||
| !flagsmith.hasFeature('onboarding_quickstart_flow'), | ||
| 'Onboarding flow is behind onboarding_quickstart_flow', | ||
| ); | ||
|
|
||
| await addErrorLogging(); | ||
|
|
||
| // The flow renders once bootstrap settles (it shows a loader, then an error | ||
| // heading on failure - so the welcome heading means "ready"). | ||
| const flowReady = async () => | ||
| page | ||
| .getByRole('heading', { name: /Welcome/ }) | ||
| .waitFor({ state: 'visible', timeout: 30000 }); | ||
|
|
||
| // Sign up a fresh user; with the flag on, a getting-started user is routed | ||
| // to /getting-started where the flow bootstraps the org / project / flag. | ||
| log('Sign up'); | ||
| await page.goto('/'); | ||
| await click(byId('jsSignup')); | ||
| await waitForElementVisible(byId('firstName')); | ||
| await setText(byId('firstName'), 'Bullet'); | ||
| await setText(byId('lastName'), 'Train'); | ||
| await setText(byId('email'), E2E_SIGN_UP_USER); | ||
| await setText(byId('password'), PASSWORD); | ||
| await click(byId('signup-btn')); | ||
|
|
||
| // Don't navigate manually - a goto here races the post-signup auth and gets | ||
| // bounced to /?redirect=. The app redirects a getting-started user to the | ||
| // flow itself once authenticated, so just wait for it to land there. | ||
| log('Land on the onboarding flow'); | ||
| await page.waitForURL((url) => url.pathname === '/getting-started', { | ||
| timeout: 30000, | ||
| }); | ||
| await flowReady(); | ||
| await visualSnapshot(page, 'onboarding-flow', testInfo); | ||
|
|
||
| // The verify terminal starts pre-connection: LISTENING, nothing ticked. | ||
| await expect(page.getByText('LISTENING')).toBeVisible(); | ||
| await expect(page.getByText('Copy install command')).not.toContainText('✓'); | ||
|
|
||
| // Copying the install + wire snippets ticks the checklist (the visible | ||
| // [✓] prefix is the done state). | ||
| log('Copy snippets, checklist ticks'); | ||
| await page.getByRole('button', { name: 'Copy install command' }).click(); | ||
| await expect(page.getByText('Copy install command')).toContainText('✓'); | ||
| await page.getByRole('button', { name: 'Copy code snippet' }).click(); | ||
| await expect(page.getByText('Copy code snippet')).toContainText('✓'); | ||
|
|
||
| // The flags table is locked until the app connects, and there's no real | ||
| // first evaluation in a test - so force the connected state via ?connected | ||
| // (the stub seam for #7767). The badge flips to LIVE and the toggle unlocks. | ||
| log('Force the connected state'); | ||
| await page.goto('/getting-started?connected'); | ||
| await flowReady(); | ||
| await expect(page.getByText('LIVE', { exact: true })).toBeVisible(); | ||
|
|
||
| // Now the Development toggle is enabled. Two switches on the page (theme + | ||
| // flag), so scope to the flags region. | ||
| log('Toggle the flag'); | ||
| const flagsTable = page.getByRole('region', { name: 'Your flags' }); | ||
| const flagSwitch = flagsTable.getByRole('switch'); | ||
| await flagSwitch.waitFor({ state: 'visible' }); | ||
| const wasChecked = (await flagSwitch.getAttribute('class'))?.includes( | ||
| 'switch-checked', | ||
| ); | ||
| await flagSwitch.click(); | ||
| await expect(flagSwitch).toHaveClass( | ||
| wasChecked ? /switch-unchecked/ : /switch-checked/, | ||
| ); | ||
|
|
||
| // The Onboarding badge (attached in bootstrap) shows in the flags table. | ||
| // Exact match: the header crumb also contains the word "Onboarding". | ||
| await expect(flagsTable.getByText('Onboarding', { exact: true })).toBeVisible(); | ||
|
|
||
| // Rename the flag. Names are immutable, so this delete + recreates; the | ||
| // Onboarding tag must survive (the recreate carries the old flag's tags). | ||
| log('Rename the flag'); | ||
| const flagInput = page.getByLabel('Flag name'); | ||
| await flagInput.fill('renamed_demo_flag'); | ||
| await flagInput.press('Enter'); | ||
|
|
||
| // Reload to prove the rename persisted server-side and the tag came with it | ||
| // (bootstrap is idempotent and reuses the renamed flag on revisit). | ||
| await page.reload(); | ||
| await flowReady(); | ||
| await expect(page.getByLabel('Flag name')).toHaveValue('renamed_demo_flag'); | ||
| await expect( | ||
| page | ||
| .getByRole('region', { name: 'Your flags' }) | ||
| .getByText('Onboarding', { exact: true }), | ||
| ).toBeVisible(); | ||
| await visualSnapshot(page, 'onboarding-renamed', testInfo); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,8 +136,17 @@ const App = class extends Component { | |
| } | ||
|
|
||
| if (!AccountStore.getOrganisation() && !invite) { | ||
| // If user has no organisation redirect to /create | ||
| this.props.history.replace(`/create${query}`) | ||
| // New users with no organisation go through the single-page onboarding | ||
| // flow when it's enabled - it creates the organisation itself, so it | ||
| // replaces the legacy /create page. Everyone else still gets /create. | ||
| if ( | ||
| AccountStore.getUser()?.isGettingStarted && | ||
| Utils.getFlagsmithHasFeature('onboarding_quickstart_flow') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also gate L190 with the flag ? |
||
| ) { | ||
| this.props.history.replace('/getting-started') | ||
| } else { | ||
| this.props.history.replace(`/create${query}`) | ||
| } | ||
| return | ||
| } | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this has been added to the
Usertype