From a4589de0dd2de34be73ae09184dd31b8e45f4885 Mon Sep 17 00:00:00 2001 From: minnakt <47064971+minnakt@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:30:23 -0500 Subject: [PATCH] DEVPROD-10202: Allow users to opt in to beta features (#459) --- .../integration/preferences/notifications.ts | 2 +- .../preferences/user_preferences.ts | 33 +++ .../analytics/navbar/useNavbarAnalytics.ts | 1 + .../preferences/usePreferencesAnalytics.ts | 1 + apps/spruce/src/components/Header/Navbar.tsx | 2 +- .../SpruceForm/Widgets/LeafyGreenWidgets.tsx | 32 ++- .../components/SpruceForm/Widgets/types.ts | 1 + .../SpruceForm_Example2.storyshot | 14 +- .../src/components/SpruceForm/index.tsx | 2 + apps/spruce/src/gql/generated/types.ts | 45 ++++ apps/spruce/src/gql/mutations/index.ts | 2 + .../update-user-beta-features.graphql | 7 + .../gql/queries/admin-beta-features.graphql | 9 + apps/spruce/src/gql/queries/index.ts | 4 + .../gql/queries/user-beta-features.graphql | 8 + apps/spruce/src/hooks/index.ts | 5 + .../spruce/src/hooks/useBetaFeatures/index.ts | 70 ++++++ .../useBetaFeatures/useBetaFeatures.test.tsx | 119 ++++++++++ .../preferences/preferencesTabs/NewUITab.tsx | 122 ++-------- .../preferencesTabs/NotificationsTab.tsx | 2 +- .../preferencesTabs/ProfileTab.tsx | 210 +----------------- .../preferencesTabs/newUITab/BetaFeatures.tsx | 178 +++++++++++++++ .../newUITab/PreferenceToggles.tsx | 106 +++++++++ .../preferencesTabs/profileTab/Settings.tsx | 191 ++++++++++++++++ 24 files changed, 849 insertions(+), 317 deletions(-) create mode 100644 apps/spruce/src/gql/mutations/update-user-beta-features.graphql create mode 100644 apps/spruce/src/gql/queries/admin-beta-features.graphql create mode 100644 apps/spruce/src/gql/queries/user-beta-features.graphql create mode 100644 apps/spruce/src/hooks/useBetaFeatures/index.ts create mode 100644 apps/spruce/src/hooks/useBetaFeatures/useBetaFeatures.test.tsx create mode 100644 apps/spruce/src/pages/preferences/preferencesTabs/newUITab/BetaFeatures.tsx create mode 100644 apps/spruce/src/pages/preferences/preferencesTabs/newUITab/PreferenceToggles.tsx create mode 100644 apps/spruce/src/pages/preferences/preferencesTabs/profileTab/Settings.tsx diff --git a/apps/spruce/cypress/integration/preferences/notifications.ts b/apps/spruce/cypress/integration/preferences/notifications.ts index 37f024d31..c5464ba33 100644 --- a/apps/spruce/cypress/integration/preferences/notifications.ts +++ b/apps/spruce/cypress/integration/preferences/notifications.ts @@ -21,7 +21,7 @@ describe("preferences/notifications", () => { cy.dataCy("slack-username-field").clear(); cy.dataCy("slack-username-field").type("slack.user"); cy.dataCy("save-profile-changes-button").click(); - cy.validateToast("success", "Your changes have successfully been saved."); + cy.validateToast("success", "Your changes have been saved."); }); }); diff --git a/apps/spruce/cypress/integration/preferences/user_preferences.ts b/apps/spruce/cypress/integration/preferences/user_preferences.ts index 94d6db8f2..caf635a8f 100644 --- a/apps/spruce/cypress/integration/preferences/user_preferences.ts +++ b/apps/spruce/cypress/integration/preferences/user_preferences.ts @@ -2,6 +2,7 @@ const baseRoute = "/preferences"; const tabNames = { profile: "/profile", cli: "/cli", + newUI: "/newUI", }; describe("user preferences pages", () => { it("visiting /preferences should redirect to the profile tab", () => { @@ -22,4 +23,36 @@ describe("user preferences pages", () => { cy.contains("button", "Reset key").click(); cy.contains(defaultApiKey).should("not.exist"); }); + + describe("beta features", () => { + it("should be able to edit beta features", () => { + cy.visit(`${baseRoute}${tabNames.newUI}`); + cy.dataCy("save-beta-features-button").should( + "have.attr", + "aria-disabled", + "true", + ); + + cy.dataCy("spruce-waterfall-enabled").within(() => { + cy.get('[data-label="Disabled"]').should("be.checked"); + cy.get('[data-label="Enabled"]').click({ force: true }); + cy.get('[data-label="Disabled"]').should("not.be.checked"); + cy.get('[data-label="Enabled"]').should("be.checked"); + }); + + cy.dataCy("save-beta-features-button").should( + "have.attr", + "aria-disabled", + "false", + ); + cy.dataCy("save-beta-features-button").click(); + cy.validateToast("success", "Your changes have been saved."); + cy.reload(); + + cy.dataCy("spruce-waterfall-enabled").within(() => { + cy.get('[data-label="Disabled"]').should("not.be.checked"); + cy.get('[data-label="Enabled"]').should("be.checked"); + }); + }); + }); }); diff --git a/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts b/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts index ec6d973eb..e9c59f3e6 100644 --- a/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts +++ b/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts @@ -6,6 +6,7 @@ type Action = | { name: "Clicked legacy UI link" } | { name: "Clicked logo link" } | { name: "Clicked waterfall link" } + | { name: "Clicked project health link" } | { name: "Clicked my patches link" } | { name: "Clicked my hosts link" } | { name: "Clicked all hosts link" } diff --git a/apps/spruce/src/analytics/preferences/usePreferencesAnalytics.ts b/apps/spruce/src/analytics/preferences/usePreferencesAnalytics.ts index 5c2ebb1e3..94bae9963 100644 --- a/apps/spruce/src/analytics/preferences/usePreferencesAnalytics.ts +++ b/apps/spruce/src/analytics/preferences/usePreferencesAnalytics.ts @@ -4,6 +4,7 @@ import { AnalyticsIdentifier } from "analytics/types"; type Action = | { name: "Changed tab"; tab: string } | { name: "Saved profile info" } + | { name: "Saved beta feature settings" } | { name: "Saved notification preferences" } | { name: "Deleted subscriptions" } | { name: "Clicked CLI download link"; "download.name": string } diff --git a/apps/spruce/src/components/Header/Navbar.tsx b/apps/spruce/src/components/Header/Navbar.tsx index fb88953cf..1bb31de52 100644 --- a/apps/spruce/src/components/Header/Navbar.tsx +++ b/apps/spruce/src/components/Header/Navbar.tsx @@ -76,7 +76,7 @@ export const Navbar: React.FC = () => { sendEvent({ name: "Clicked waterfall link" })} + onClick={() => sendEvent({ name: "Clicked project health link" })} to={getCommitsRoute(projectIdentifier)} > Project Health diff --git a/apps/spruce/src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx b/apps/spruce/src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx index 93bd34503..26b28ae1c 100644 --- a/apps/spruce/src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx +++ b/apps/spruce/src/components/SpruceForm/Widgets/LeafyGreenWidgets.tsx @@ -1,4 +1,5 @@ import { useEffect, useRef } from "react"; +import { css } from "@emotion/react"; import styled from "@emotion/styled"; import Banner from "@leafygreen-ui/banner"; import Checkbox from "@leafygreen-ui/checkbox"; @@ -270,30 +271,48 @@ export const LeafyGreenRadio: React.FC = ({ elementWrapperCSS, enumDisabled, enumOptions, + inline, } = options; + + // RadioGroup components do not accept boolean props for value, so use the indices instead. + const valueMap = enumOptions.map(({ value: val }) => val); + return ( {label && ( - + )} onChange(e.target.value)} - value={value} + onChange={(e) => onChange(valueMap[Number(e.target.value)])} + value={valueMap.indexOf(value)} > {enumOptions.map((o) => { const optionDisabled = enumDisabled?.includes(o.value) ?? false; const { description } = o.schema ?? {}; return ( {o.label} @@ -351,8 +370,7 @@ export const LeafyGreenRadioBox: React.FC< data-cy={dataCy} id={id} name={label} - // @ts-expect-error: FIXME. This comment was added by an automated script. - onChange={(e) => onChange(valueMap[e.target.value])} + onChange={(e) => onChange(valueMap[Number(e.target.value)])} value={valueMap.indexOf(value)} > {enumOptions.map((o) => { diff --git a/apps/spruce/src/components/SpruceForm/Widgets/types.ts b/apps/spruce/src/components/SpruceForm/Widgets/types.ts index b275901b0..9726c1e74 100644 --- a/apps/spruce/src/components/SpruceForm/Widgets/types.ts +++ b/apps/spruce/src/components/SpruceForm/Widgets/types.ts @@ -17,6 +17,7 @@ export interface SpruceWidgetProps extends WidgetProps { emptyValue: string | null; errors: string[]; focusOnMount: boolean; + inline: boolean; inputType: TextInputType; rows: number; showLabel: boolean; diff --git a/apps/spruce/src/components/SpruceForm/__snapshots__/SpruceForm_Example2.storyshot b/apps/spruce/src/components/SpruceForm/__snapshots__/SpruceForm_Example2.storyshot index 9f96c39f3..770c1af71 100644 --- a/apps/spruce/src/components/SpruceForm/__snapshots__/SpruceForm_Example2.storyshot +++ b/apps/spruce/src/components/SpruceForm/__snapshots__/SpruceForm_Example2.storyshot @@ -271,7 +271,7 @@ >
@@ -279,19 +279,19 @@ class="leafygreen-ui-jpwlig" >