diff --git a/apps/spruce/cypress/integration/preferences/user_preferences.ts b/apps/spruce/cypress/integration/preferences/user_preferences.ts
index caf635a8f..f39159f4b 100644
--- a/apps/spruce/cypress/integration/preferences/user_preferences.ts
+++ b/apps/spruce/cypress/integration/preferences/user_preferences.ts
@@ -2,7 +2,7 @@ const baseRoute = "/preferences";
const tabNames = {
profile: "/profile",
cli: "/cli",
- newUI: "/newUI",
+ uiSettings: "/ui-settings",
};
describe("user preferences pages", () => {
it("visiting /preferences should redirect to the profile tab", () => {
@@ -26,7 +26,7 @@ describe("user preferences pages", () => {
describe("beta features", () => {
it("should be able to edit beta features", () => {
- cy.visit(`${baseRoute}${tabNames.newUI}`);
+ cy.visit(`${baseRoute}${tabNames.uiSettings}`);
cy.dataCy("save-beta-features-button").should(
"have.attr",
"aria-disabled",
diff --git a/apps/spruce/cypress/integration/waterfall/navigation.ts b/apps/spruce/cypress/integration/waterfall/navigation.ts
index 9079e8012..158397189 100644
--- a/apps/spruce/cypress/integration/waterfall/navigation.ts
+++ b/apps/spruce/cypress/integration/waterfall/navigation.ts
@@ -13,4 +13,34 @@ describe("navigation", () => {
cy.location("pathname").should("equal", "/project/spruce/waterfall");
cy.dataCy("waterfall-page").should("be.visible");
});
+
+ it("nav bar items change if opted into beta test", () => {
+ cy.visit("/preferences/ui-settings");
+
+ // The beta test is enabled but the user is not opted in.
+ cy.dataCy("project-health-link").should("be.visible");
+ cy.dataCy("waterfall-link").should("not.exist");
+ cy.dataCy("auxiliary-dropdown-link").click();
+ cy.dataCy("auxiliary-dropdown-waterfall").should("be.visible");
+ cy.dataCy("auxiliary-dropdown-project-health").should("not.exist");
+
+ // Opt the user in to the beta test.
+ cy.dataCy("spruce-waterfall-enabled").within(() => {
+ cy.get('[data-label="Enabled"]').click({ force: true });
+ });
+ 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.");
+
+ // Nav bar items should have changed.
+ cy.dataCy("project-health-link").should("not.exist");
+ cy.dataCy("waterfall-link").should("be.visible");
+ cy.dataCy("auxiliary-dropdown-link").click();
+ cy.dataCy("auxiliary-dropdown-waterfall").should("not.exist");
+ cy.dataCy("auxiliary-dropdown-project-health").should("be.visible");
+ });
});
diff --git a/apps/spruce/cypress/integration/waterfall/waterfall.ts b/apps/spruce/cypress/integration/waterfall/waterfall.ts
index e594454d9..308c9dc25 100644
--- a/apps/spruce/cypress/integration/waterfall/waterfall.ts
+++ b/apps/spruce/cypress/integration/waterfall/waterfall.ts
@@ -57,6 +57,9 @@ describe("waterfall page", () => {
describe("task stats tooltip", () => {
it("shows task stats when clicked", () => {
cy.dataCy("task-stats-tooltip").should("not.exist");
+ cy.dataCy("task-stats-tooltip-button")
+ .eq(3)
+ .should("have.attr", "aria-disabled", "false");
cy.dataCy("task-stats-tooltip-button").eq(3).click();
cy.dataCy("task-stats-tooltip").should("be.visible");
cy.dataCy("task-stats-tooltip").should("contain.text", "Failed");
diff --git a/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts b/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts
index e9c59f3e6..2504dfbe5 100644
--- a/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts
+++ b/apps/spruce/src/analytics/navbar/useNavbarAnalytics.ts
@@ -14,8 +14,9 @@ type Action =
| { name: "Clicked project settings link" }
| { name: "Clicked project patches link" }
| { name: "Clicked EVG wiki link" }
- | { name: "Clicked preferences link" }
+ | { name: "Clicked profile link" }
| { name: "Clicked notifications link" }
+ | { name: "Clicked UI settings link" }
| { name: "Clicked task queue link" };
export const useNavbarAnalytics = () =>
diff --git a/apps/spruce/src/components/Header/AuxiliaryDropdown.tsx b/apps/spruce/src/components/Header/AuxiliaryDropdown.tsx
index 3f030d4cf..9e69bc729 100644
--- a/apps/spruce/src/components/Header/AuxiliaryDropdown.tsx
+++ b/apps/spruce/src/components/Header/AuxiliaryDropdown.tsx
@@ -1,3 +1,4 @@
+import Badge, { Variant as BadgeVariant } from "@leafygreen-ui/badge";
import { useNavbarAnalytics } from "analytics";
import {
routes,
@@ -5,8 +6,14 @@ import {
getProjectPatchesRoute,
getProjectSettingsRoute,
getTaskQueueRoute,
+ getCommitsRoute,
+ getWaterfallRoute,
} from "constants/routes";
-import { useFirstDistro } from "hooks";
+import {
+ useAdminBetaFeatures,
+ useFirstDistro,
+ useMergedBetaFeatures,
+} from "hooks";
import { NavDropdown } from "./NavDropdown";
interface AuxiliaryDropdownProps {
@@ -19,6 +26,32 @@ export const AuxiliaryDropdown: React.FC = ({
const { sendEvent } = useNavbarAnalytics();
const { distro } = useFirstDistro();
+ const { adminBetaSettings } = useAdminBetaFeatures();
+
+ const { betaFeatures } = useMergedBetaFeatures();
+ const { spruceWaterfallEnabled } = betaFeatures ?? {};
+
+ const inverseLink = spruceWaterfallEnabled
+ ? {
+ "data-cy": "auxiliary-dropdown-project-health",
+ text: "Project Health",
+ to: getCommitsRoute(projectIdentifier),
+ onClick: () => sendEvent({ name: "Clicked project health link" }),
+ }
+ : {
+ "data-cy": "auxiliary-dropdown-waterfall",
+ text: (
+
+ Waterfall{" "}
+
+ Beta
+
+
+ ),
+ to: getWaterfallRoute(projectIdentifier),
+ onClick: () => sendEvent({ name: "Clicked waterfall link" }),
+ };
+
const menuItems = [
{
text: "All Hosts",
@@ -36,7 +69,6 @@ export const AuxiliaryDropdown: React.FC = ({
text: "Distro Settings",
onClick: () => sendEvent({ name: "Clicked distro settings link" }),
},
-
{
"data-cy": "auxiliary-dropdown-project-patches",
to: getProjectPatchesRoute(projectIdentifier),
@@ -49,6 +81,8 @@ export const AuxiliaryDropdown: React.FC = ({
to: getProjectSettingsRoute(projectIdentifier),
onClick: () => sendEvent({ name: "Clicked project settings link" }),
},
+ // Don't show inverse link if waterfall beta test is not active.
+ ...(adminBetaSettings?.spruceWaterfallEnabled ? [inverseLink] : []),
];
return (
diff --git a/apps/spruce/src/components/Header/NavDropdown/index.tsx b/apps/spruce/src/components/Header/NavDropdown/index.tsx
index 36e2c143d..f1507cd71 100644
--- a/apps/spruce/src/components/Header/NavDropdown/index.tsx
+++ b/apps/spruce/src/components/Header/NavDropdown/index.tsx
@@ -14,7 +14,7 @@ const NavDropdownMenuIcon: React.FC<{ open: boolean }> = ({ open }) => (
export interface MenuItemType {
"data-cy"?: string;
- text: string;
+ text: string | React.ReactNode;
href?: string;
to?: string;
onClick?: () => void;
diff --git a/apps/spruce/src/components/Header/Navbar.tsx b/apps/spruce/src/components/Header/Navbar.tsx
index d3c2373e4..837082c6a 100644
--- a/apps/spruce/src/components/Header/Navbar.tsx
+++ b/apps/spruce/src/components/Header/Navbar.tsx
@@ -16,13 +16,14 @@ import { wikiUrl } from "constants/externalResources";
import {
getCommitsRoute,
getUserPatchesRoute,
+ getWaterfallRoute,
routes,
slugs,
} from "constants/routes";
import { useAuthStateContext } from "context/Auth";
import { UserQuery, SpruceConfigQuery } from "gql/generated/types";
import { USER, SPRUCE_CONFIG } from "gql/queries";
-import { useLegacyUIURL } from "hooks";
+import { useLegacyUIURL, useMergedBetaFeatures } from "hooks";
import { validators } from "utils";
import { AuxiliaryDropdown } from "./AuxiliaryDropdown";
import { UserDropdown } from "./UserDropdown";
@@ -63,6 +64,9 @@ export const Navbar: React.FC = () => {
const projectIdentifier =
currProject || configData?.spruceConfig?.ui?.defaultProject;
+ const { betaFeatures } = useMergedBetaFeatures();
+ const { spruceWaterfallEnabled } = betaFeatures ?? {};
+
if (!isAuthenticated) {
return null;
}
@@ -75,13 +79,24 @@ export const Navbar: React.FC = () => {
>
- sendEvent({ name: "Clicked project health link" })}
- to={getCommitsRoute(projectIdentifier)}
- >
- Project Health
-
+ {spruceWaterfallEnabled ? (
+ sendEvent({ name: "Clicked waterfall link" })}
+ to={getWaterfallRoute(projectIdentifier)}
+ >
+ Waterfall
+
+ ) : (
+ sendEvent({ name: "Clicked project health link" })}
+ to={getCommitsRoute(projectIdentifier)}
+ >
+ Project Health
+
+ )}
+
sendEvent({ name: "Clicked my patches link" })}
// @ts-expect-error: FIXME. This comment was added by an automated script.
diff --git a/apps/spruce/src/components/Header/UserDropdown.tsx b/apps/spruce/src/components/Header/UserDropdown.tsx
index 51d7d0d52..f62da0367 100644
--- a/apps/spruce/src/components/Header/UserDropdown.tsx
+++ b/apps/spruce/src/components/Header/UserDropdown.tsx
@@ -17,15 +17,20 @@ export const UserDropdown = () => {
const menuItems: MenuItemType[] = [
{
- text: "Preferences",
+ text: "Profile",
to: getPreferencesRoute(PreferencesTabRoutes.Profile),
- onClick: () => sendEvent({ name: "Clicked preferences link" }),
+ onClick: () => sendEvent({ name: "Clicked profile link" }),
},
{
text: "Notifications",
to: getPreferencesRoute(PreferencesTabRoutes.Notifications),
onClick: () => sendEvent({ name: "Clicked notifications link" }),
},
+ {
+ text: "UI Settings",
+ to: getPreferencesRoute(PreferencesTabRoutes.UISettings),
+ onClick: () => sendEvent({ name: "Clicked UI settings link" }),
+ },
{
"data-cy": "log-out",
text: "Log out",
@@ -33,7 +38,7 @@ export const UserDropdown = () => {
},
];
if (permissions?.canEditAdminSettings) {
- menuItems.splice(2, 0, {
+ menuItems.splice(-1, 0, {
"data-cy": "admin-link",
text: "Admin",
href: adminSettingsURL,
diff --git a/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx b/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx
index 221211b46..36d2c603b 100644
--- a/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx
+++ b/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx
@@ -1,7 +1,20 @@
import { useParams, Navigate } from "react-router-dom";
-import { getCommitsRoute, slugs } from "constants/routes";
+import { getCommitsRoute, getWaterfallRoute, slugs } from "constants/routes";
+import { useMergedBetaFeatures } from "hooks";
export const WaterfallCommitsRedirect: React.FC = () => {
const { [slugs.projectIdentifier]: projectIdentifier } = useParams();
- return ;
+
+ const { betaFeatures } = useMergedBetaFeatures();
+ const { spruceWaterfallEnabled } = betaFeatures ?? {};
+
+ return (
+
+ );
};
diff --git a/apps/spruce/src/constants/routes.ts b/apps/spruce/src/constants/routes.ts
index bb4c58602..ebfdca275 100644
--- a/apps/spruce/src/constants/routes.ts
+++ b/apps/spruce/src/constants/routes.ts
@@ -20,7 +20,7 @@ export enum PreferencesTabRoutes {
Profile = "profile",
Notifications = "notifications",
CLI = "cli",
- NewUI = "newUI",
+ UISettings = "ui-settings",
PublicKeys = "publickeys",
}
diff --git a/apps/spruce/src/hooks/useBreadcrumbRoot/index.ts b/apps/spruce/src/hooks/useBreadcrumbRoot/index.ts
index d8b56262c..7eebe5704 100644
--- a/apps/spruce/src/hooks/useBreadcrumbRoot/index.ts
+++ b/apps/spruce/src/hooks/useBreadcrumbRoot/index.ts
@@ -1,6 +1,9 @@
import { useBreadcrumbAnalytics } from "analytics";
-import { getCommitsRoute } from "constants/routes";
-import { useGetUserPatchesPageTitleAndLink } from "hooks";
+import { getCommitsRoute, getWaterfallRoute } from "constants/routes";
+import {
+ useGetUserPatchesPageTitleAndLink,
+ useMergedBetaFeatures,
+} from "hooks";
export const useBreadcrumbRoot = (
isPatch: boolean,
@@ -9,6 +12,9 @@ export const useBreadcrumbRoot = (
) => {
const breadcrumbAnalytics = useBreadcrumbAnalytics();
+ const { betaFeatures } = useMergedBetaFeatures();
+ const { spruceWaterfallEnabled } = betaFeatures ?? {};
+
const { link: userPatchesPageLink, title: userPatchesPageTitle } =
useGetUserPatchesPageTitleAndLink(author, !isPatch) ?? {};
@@ -25,7 +31,9 @@ export const useBreadcrumbRoot = (
"data-cy": "bc-my-patches",
}
: {
- to: getCommitsRoute(projectIdentifier),
+ to: spruceWaterfallEnabled
+ ? getWaterfallRoute(projectIdentifier)
+ : getCommitsRoute(projectIdentifier),
text: projectIdentifier,
onClick: () => {
breadcrumbAnalytics.sendEvent({
diff --git a/apps/spruce/src/pages/preferences/PreferencesTabs.tsx b/apps/spruce/src/pages/preferences/PreferencesTabs.tsx
index 3565a46c1..e0c8c812f 100644
--- a/apps/spruce/src/pages/preferences/PreferencesTabs.tsx
+++ b/apps/spruce/src/pages/preferences/PreferencesTabs.tsx
@@ -8,10 +8,10 @@ import {
slugs,
} from "constants/routes";
import { CliTab } from "./preferencesTabs/CliTab";
-import { NewUITab } from "./preferencesTabs/NewUITab";
import { NotificationsTab } from "./preferencesTabs/NotificationsTab";
import { ProfileTab } from "./preferencesTabs/ProfileTab";
import { PublicKeysTab } from "./preferencesTabs/PublicKeysTab";
+import { UISettingsTab } from "./preferencesTabs/UISettingsTab";
export const PreferencesTabs: React.FC = () => {
// @ts-expect-error: FIXME. This comment was added by an automated script.
@@ -52,13 +52,23 @@ export const PreferencesTabs: React.FC = () => {
}
path={PreferencesTabRoutes.CLI}
/>
+ {/* Delete this redirect in DEVPROD-14564. */}
+
+ }
+ path="/newUI"
+ />
-
+
}
- path={PreferencesTabRoutes.NewUI}
+ path={PreferencesTabRoutes.UISettings}
/>
{
Public Keys
sendEvent({
name: "Changed tab",
- tab: PreferencesTabRoutes.NewUI,
+ tab: PreferencesTabRoutes.UISettings,
})
}
- to={getPreferencesRoute(PreferencesTabRoutes.NewUI)}
+ to={getPreferencesRoute(PreferencesTabRoutes.UISettings)}
>
- New UI
+ UI Settings
diff --git a/apps/spruce/src/pages/preferences/preferencesTabs/NewUITab.tsx b/apps/spruce/src/pages/preferences/preferencesTabs/UISettingsTab.tsx
similarity index 79%
rename from apps/spruce/src/pages/preferences/preferencesTabs/NewUITab.tsx
rename to apps/spruce/src/pages/preferences/preferencesTabs/UISettingsTab.tsx
index f64955154..090538d9a 100644
--- a/apps/spruce/src/pages/preferences/preferencesTabs/NewUITab.tsx
+++ b/apps/spruce/src/pages/preferences/preferencesTabs/UISettingsTab.tsx
@@ -1,10 +1,10 @@
import { ParagraphSkeleton } from "@leafygreen-ui/skeleton-loader";
import { SettingsCard } from "components/SettingsCard";
import { useAdminBetaFeatures, useUserBetaFeatures } from "hooks";
-import { BetaFeatureSettings } from "./newUITab/BetaFeatures";
-import { PreferenceToggles } from "./newUITab/PreferenceToggles";
+import { BetaFeatureSettings } from "./uiSettingsTab/BetaFeatures";
+import { PreferenceToggles } from "./uiSettingsTab/PreferenceToggles";
-export const NewUITab: React.FC = () => {
+export const UISettingsTab: React.FC = () => {
const { userBetaSettings } = useUserBetaFeatures();
const { adminBetaSettings } = useAdminBetaFeatures();
diff --git a/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx b/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx
index 04cda807a..9272cedf8 100644
--- a/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx
+++ b/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/UserSubscriptions.tsx
@@ -30,7 +30,7 @@ import {
Selector,
} from "gql/generated/types";
import { DELETE_SUBSCRIPTIONS } from "gql/mutations";
-import { useSpruceConfig } from "hooks";
+import { useMergedBetaFeatures, useSpruceConfig } from "hooks";
import {
NotificationMethods,
notificationMethodToCopy,
@@ -65,6 +65,9 @@ const SubscriptionsTable: React.FC<{
const spruceConfig = useSpruceConfig();
const jiraHost = spruceConfig?.jira?.host;
+ const { betaFeatures } = useMergedBetaFeatures();
+ const { spruceWaterfallEnabled } = betaFeatures ?? {};
+
const [deleteSubscriptions] = useMutation<
DeleteSubscriptionsMutation,
DeleteSubscriptionsMutationVariables
@@ -126,7 +129,11 @@ const SubscriptionsTable: React.FC<{
(s: Selector) => s.type !== "object" && s.type !== "requester",
);
const { data: selectorId } = resourceSelector ?? {};
- const route = getResourceRoute(resourceType, resourceSelector);
+ const route = getResourceRoute(
+ resourceType,
+ resourceSelector,
+ spruceWaterfallEnabled,
+ );
return route ? (
{selectorId}
@@ -178,7 +185,7 @@ const SubscriptionsTable: React.FC<{
},
},
],
- [jiraHost],
+ [jiraHost, spruceWaterfallEnabled],
);
const table = useLeafyGreenTable({
diff --git a/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/utils.ts b/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/utils.ts
index 1d0c5cdaf..169b4b831 100644
--- a/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/utils.ts
+++ b/apps/spruce/src/pages/preferences/preferencesTabs/notificationTab/utils.ts
@@ -3,6 +3,7 @@ import {
getPatchRoute,
getTaskRoute,
getVersionRoute,
+ getWaterfallRoute,
} from "constants/routes";
import { Selector } from "gql/generated/types";
import { ResourceType } from "types/triggers";
@@ -10,6 +11,7 @@ import { ResourceType } from "types/triggers";
export const getResourceRoute = (
resourceType: ResourceType,
selector: Selector,
+ spruceWaterfallEnabled: boolean = false,
) => {
const { data: id, type } = selector;
@@ -21,7 +23,9 @@ export const getResourceRoute = (
case ResourceType.Build:
case ResourceType.Version: {
if (type === "project") {
- return getCommitsRoute(id);
+ return spruceWaterfallEnabled
+ ? getWaterfallRoute(id)
+ : getCommitsRoute(id);
}
return getVersionRoute(id);
}
diff --git a/apps/spruce/src/pages/preferences/preferencesTabs/newUITab/BetaFeatures.tsx b/apps/spruce/src/pages/preferences/preferencesTabs/uiSettingsTab/BetaFeatures.tsx
similarity index 100%
rename from apps/spruce/src/pages/preferences/preferencesTabs/newUITab/BetaFeatures.tsx
rename to apps/spruce/src/pages/preferences/preferencesTabs/uiSettingsTab/BetaFeatures.tsx
diff --git a/apps/spruce/src/pages/preferences/preferencesTabs/newUITab/PreferenceToggles.tsx b/apps/spruce/src/pages/preferences/preferencesTabs/uiSettingsTab/PreferenceToggles.tsx
similarity index 100%
rename from apps/spruce/src/pages/preferences/preferencesTabs/newUITab/PreferenceToggles.tsx
rename to apps/spruce/src/pages/preferences/preferencesTabs/uiSettingsTab/PreferenceToggles.tsx
diff --git a/apps/spruce/src/pages/waterfall/TaskStatsTooltip.tsx b/apps/spruce/src/pages/waterfall/TaskStatsTooltip.tsx
index 21a96dbc8..2d0c64089 100644
--- a/apps/spruce/src/pages/waterfall/TaskStatsTooltip.tsx
+++ b/apps/spruce/src/pages/waterfall/TaskStatsTooltip.tsx
@@ -72,26 +72,28 @@ export const TaskStatsTooltip: React.FC<
>
- {data?.version?.taskStatusStats?.counts?.map(
- ({ count, status }) => (
-
- {count}
-
-
- |
- {taskStatusToCopy[status as TaskStatus]} |
-
- ),
- )}
-
-
-
- |
-
-
- {totalTaskCount}
- Total tasks |
-
+
+ {data?.version?.taskStatusStats?.counts?.map(
+ ({ count, status }) => (
+
+ {count}
+
+
+ |
+ {taskStatusToCopy[status as TaskStatus]} |
+
+ ),
+ )}
+
+
+
+ |
+
+
+ {totalTaskCount}
+ Total tasks |
+
+
@@ -105,6 +107,8 @@ const BtnContainer = styled.div`
const Table = styled.table``;
+const Tbody = styled.tbody``;
+
const Row = styled.tr``;
const Cell = styled.td`
diff --git a/apps/spruce/src/pages/waterfall/VersionLabel/__snapshots__/VersionLabel_SmallSize.storyshot b/apps/spruce/src/pages/waterfall/VersionLabel/__snapshots__/VersionLabel_SmallSize.storyshot
index 415920fb8..ab11d2c71 100644
--- a/apps/spruce/src/pages/waterfall/VersionLabel/__snapshots__/VersionLabel_SmallSize.storyshot
+++ b/apps/spruce/src/pages/waterfall/VersionLabel/__snapshots__/VersionLabel_SmallSize.storyshot
@@ -26,7 +26,7 @@
09/19/2024, 10:56