From 84d303f54e84cefcb62e43ee2f7c6ddb3c5683ab Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 7 Jan 2026 12:31:40 +0100 Subject: [PATCH 1/8] Modify flag for history v2 Signed-off-by: Adhitya Mamallan --- src/config/dynamic/dynamic.config.ts | 3 +- .../history-page-v2-enabled-values.config.ts | 8 +++ .../resolvers/history-page-v2-enabled.ts | 20 ++++++-- .../history-page-v2-enabled.types.ts | 4 ++ .../resolvers/schemas/resolver-schemas.ts | 3 +- .../__fixtures__/resolved-config-values.ts | 2 +- .../use-is-workflow-history-v2-enabled.ts | 9 ++++ .../workflow-history-wrapper.test.tsx | 50 ++++++------------- .../workflow-history-wrapper.tsx | 8 ++- 9 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts create mode 100644 src/config/dynamic/resolvers/history-page-v2-enabled.types.ts create mode 100644 src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts diff --git a/src/config/dynamic/dynamic.config.ts b/src/config/dynamic/dynamic.config.ts index 1c1134cdf..df914d7fd 100644 --- a/src/config/dynamic/dynamic.config.ts +++ b/src/config/dynamic/dynamic.config.ts @@ -17,6 +17,7 @@ import extendedDomainInfoEnabled from './resolvers/extended-domain-info-enabled' import { type ExtendedDomainInfoEnabledConfig } from './resolvers/extended-domain-info-enabled.types'; import failoverHistoryEnabled from './resolvers/failover-history-enabled'; import historyPageV2Enabled from './resolvers/history-page-v2-enabled'; +import { type HistoryPageV2EnabledConfigValue } from './resolvers/history-page-v2-enabled.types'; import workflowActionsEnabled from './resolvers/workflow-actions-enabled'; import { type WorkflowActionsEnabledResolverParams, @@ -76,7 +77,7 @@ const dynamicConfigs: { >; HISTORY_PAGE_V2_ENABLED: ConfigAsyncResolverDefinition< undefined, - boolean, + HistoryPageV2EnabledConfigValue, 'request', true >; diff --git a/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts b/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts new file mode 100644 index 000000000..762993dd1 --- /dev/null +++ b/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts @@ -0,0 +1,8 @@ +const HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG = [ + 'DISABLED', + 'OPT-IN', + 'OPT-OUT', + 'ENABLED', +] as const; + +export default HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG; diff --git a/src/config/dynamic/resolvers/history-page-v2-enabled.ts b/src/config/dynamic/resolvers/history-page-v2-enabled.ts index 174e28c07..ec8876cf4 100644 --- a/src/config/dynamic/resolvers/history-page-v2-enabled.ts +++ b/src/config/dynamic/resolvers/history-page-v2-enabled.ts @@ -1,11 +1,21 @@ +import HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG from './history-page-v2-enabled-values.config'; +import { type HistoryPageV2EnabledConfigValue } from './history-page-v2-enabled.types'; + /** - * WIP: Returns whether the new Workflow History (V2) page is enabled + * WIP: Returns the configuration value for the new Workflow History (V2) page * - * To enable the new Workflow History (V2) page, set the CADENCE_HISTORY_PAGE_V2_ENABLED env variable to true. + * To configure the new Workflow History (V2) page, set the CADENCE_HISTORY_PAGE_V2_ENABLED env variable + * to one of: 'DISABLED', 'OPT-OUT', 'OPT-IN', or 'ENABLED'. * For further customization, override the implementation of this resolver. * - * @returns {Promise} Whether Workflow History (V2) page is enabled. + * @returns {Promise} The configuration value for Workflow History (V2) page. */ -export default async function historyPageV2Enabled(): Promise { - return process.env.CADENCE_HISTORY_PAGE_V2_ENABLED === 'true'; +export default async function historyPageV2Enabled(): Promise { + const envValue = process.env.CADENCE_HISTORY_PAGE_V2_ENABLED; + + if (HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG.find((v) => v === envValue)) { + return envValue as HistoryPageV2EnabledConfigValue; + } + + return 'DISABLED'; } diff --git a/src/config/dynamic/resolvers/history-page-v2-enabled.types.ts b/src/config/dynamic/resolvers/history-page-v2-enabled.types.ts new file mode 100644 index 000000000..0f4944a4d --- /dev/null +++ b/src/config/dynamic/resolvers/history-page-v2-enabled.types.ts @@ -0,0 +1,4 @@ +import type HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG from './history-page-v2-enabled-values.config'; + +export type HistoryPageV2EnabledConfigValue = + (typeof HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG)[number]; diff --git a/src/config/dynamic/resolvers/schemas/resolver-schemas.ts b/src/config/dynamic/resolvers/schemas/resolver-schemas.ts index dec2449fb..82339ee29 100644 --- a/src/config/dynamic/resolvers/schemas/resolver-schemas.ts +++ b/src/config/dynamic/resolvers/schemas/resolver-schemas.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; import { type ResolverSchemas } from '../../../../utils/config/config.types'; +import HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG from '../history-page-v2-enabled-values.config'; import WORKFLOW_ACTIONS_DISABLED_VALUES_CONFIG from '../workflow-actions-disabled-values.config'; const workflowActionsEnabledValueSchema = z.enum([ @@ -72,7 +73,7 @@ const resolverSchemas: ResolverSchemas = { }, HISTORY_PAGE_V2_ENABLED: { args: z.undefined(), - returnType: z.boolean(), + returnType: z.enum(HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG), }, }; diff --git a/src/utils/config/__fixtures__/resolved-config-values.ts b/src/utils/config/__fixtures__/resolved-config-values.ts index f002ad4b0..6fc74cd62 100644 --- a/src/utils/config/__fixtures__/resolved-config-values.ts +++ b/src/utils/config/__fixtures__/resolved-config-values.ts @@ -43,6 +43,6 @@ const mockResolvedConfigValues: LoadedConfigResolvedValues = { WORKFLOW_DIAGNOSTICS_ENABLED: false, ARCHIVAL_DEFAULT_SEARCH_ENABLED: false, FAILOVER_HISTORY_ENABLED: false, - HISTORY_PAGE_V2_ENABLED: false, + HISTORY_PAGE_V2_ENABLED: 'DISABLED', }; export default mockResolvedConfigValues; diff --git a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts new file mode 100644 index 000000000..2a99033d8 --- /dev/null +++ b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts @@ -0,0 +1,9 @@ +import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; + +export default function useIsWorkflowHistoryV2Enabled(): boolean { + const { data: historyPageV2Config } = useSuspenseConfigValue( + 'HISTORY_PAGE_V2_ENABLED' + ); + + return historyPageV2Config === 'ENABLED' || historyPageV2Config === 'OPT-OUT'; +} diff --git a/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx b/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx index b67936f75..4e4b55de2 100644 --- a/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx +++ b/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx @@ -1,14 +1,15 @@ -import React, { Suspense } from 'react'; - -import { HttpResponse } from 'msw'; - -import { render, screen, waitForElementToBeRemoved } from '@/test-utils/rtl'; - -import { type GetConfigResponse } from '@/route-handlers/get-config/get-config.types'; +import { render, screen } from '@/test-utils/rtl'; import { type Props } from '../../workflow-history.types'; import WorkflowHistoryWrapper from '../workflow-history-wrapper'; +const mockUseIsWorkflowHistoryV2Enabled = jest.fn(); + +jest.mock( + '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled', + () => jest.fn(() => mockUseIsWorkflowHistoryV2Enabled()) +); + jest.mock('../../../workflow-history-v2/workflow-history-v2', () => jest.fn(() => (
Workflow History V2
@@ -20,23 +21,24 @@ jest.mock('../../workflow-history', () => ); describe(WorkflowHistoryWrapper.name, () => { - it('should render WorkflowHistoryV2 when HISTORY_PAGE_V2_ENABLED is true', async () => { - await setup({ isHistoryPageV2Enabled: true }); + it('should render WorkflowHistoryV2 when useIsWorkflowHistoryV2Enabled returns true', () => { + mockUseIsWorkflowHistoryV2Enabled.mockReturnValue(true); + setup(); expect(screen.getByTestId('workflow-history-v2')).toBeInTheDocument(); expect(screen.queryByTestId('workflow-history')).not.toBeInTheDocument(); }); - it('should render WorkflowHistory when HISTORY_PAGE_V2_ENABLED is false', async () => { - await setup({ isHistoryPageV2Enabled: false }); + it('should render WorkflowHistory when useIsWorkflowHistoryV2Enabled returns false', () => { + mockUseIsWorkflowHistoryV2Enabled.mockReturnValue(false); + setup(); expect(screen.getByTestId('workflow-history')).toBeInTheDocument(); expect(screen.queryByTestId('workflow-history-v2')).not.toBeInTheDocument(); }); }); -async function setup({ - isHistoryPageV2Enabled = false, +function setup({ props = { params: { cluster: 'test-cluster', @@ -47,27 +49,7 @@ async function setup({ }, }, }: { - isHistoryPageV2Enabled?: boolean; props?: Props; } = {}) { - render( - Loading...}> - - , - { - endpointsMocks: [ - { - path: '/api/config', - httpMethod: 'GET', - mockOnce: false, - httpResolver: async () => - HttpResponse.json( - isHistoryPageV2Enabled satisfies GetConfigResponse<'HISTORY_PAGE_V2_ENABLED'> - ), - }, - ], - } - ); - - await waitForElementToBeRemoved(() => screen.queryAllByText('Loading...')); + render(); } diff --git a/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx b/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx index 780439b53..d62fade64 100644 --- a/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx +++ b/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx @@ -1,4 +1,4 @@ -import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; +import useIsWorkflowHistoryV2Enabled from '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled'; import WorkflowHistoryV2 from '@/views/workflow-history-v2/workflow-history-v2'; import WorkflowHistory from '../workflow-history'; @@ -6,13 +6,11 @@ import WorkflowHistoryContextProvider from '../workflow-history-context-provider import { type Props } from '../workflow-history.types'; export default function WorkflowHistoryWrapper(props: Props) { - const { data: isHistoryPageV2Enabled } = useSuspenseConfigValue( - 'HISTORY_PAGE_V2_ENABLED' - ); + const isWorkflowHistoryV2Enabled = useIsWorkflowHistoryV2Enabled(); return ( - {isHistoryPageV2Enabled ? ( + {isWorkflowHistoryV2Enabled ? ( ) : ( From 44c3069454bf83983764e415179f7608dfa05400 Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 7 Jan 2026 12:52:58 +0100 Subject: [PATCH 2/8] changes to config value Signed-off-by: Adhitya Mamallan --- .../history-page-v2-enabled-values.config.ts | 4 +- .../resolvers/history-page-v2-enabled.ts | 13 +++- ...se-is-workflow-history-v2-enabled.test.tsx | 72 +++++++++++++++++++ ...uspense-is-workflow-history-v2-enabled.ts} | 5 +- .../workflow-history-wrapper.test.tsx | 14 ++-- .../workflow-history-wrapper.tsx | 4 +- 6 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx rename src/views/workflow-history-v2/hooks/{use-is-workflow-history-v2-enabled.ts => use-suspense-is-workflow-history-v2-enabled.ts} (57%) diff --git a/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts b/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts index 762993dd1..13efc1b70 100644 --- a/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts +++ b/src/config/dynamic/resolvers/history-page-v2-enabled-values.config.ts @@ -1,7 +1,7 @@ const HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG = [ 'DISABLED', - 'OPT-IN', - 'OPT-OUT', + 'OPT_IN', + 'OPT_OUT', 'ENABLED', ] as const; diff --git a/src/config/dynamic/resolvers/history-page-v2-enabled.ts b/src/config/dynamic/resolvers/history-page-v2-enabled.ts index ec8876cf4..1ccc66cc7 100644 --- a/src/config/dynamic/resolvers/history-page-v2-enabled.ts +++ b/src/config/dynamic/resolvers/history-page-v2-enabled.ts @@ -4,8 +4,12 @@ import { type HistoryPageV2EnabledConfigValue } from './history-page-v2-enabled. /** * WIP: Returns the configuration value for the new Workflow History (V2) page * - * To configure the new Workflow History (V2) page, set the CADENCE_HISTORY_PAGE_V2_ENABLED env variable - * to one of: 'DISABLED', 'OPT-OUT', 'OPT-IN', or 'ENABLED'. + * To configure the new Workflow History (V2) page, set the CADENCE_HISTORY_PAGE_V2_ENABLED env variable to one of the following: + * - `DISABLED` - disable the feature entirely (default if env var is not set or invalid) + * - `OPT_IN` - allow users to view the new page using a button on the old page + * - `OPT_OUT` - default to the new page with an option to fall back to the old page + * - `ENABLED` - completely enable the new page with no option to fall back + * * For further customization, override the implementation of this resolver. * * @returns {Promise} The configuration value for Workflow History (V2) page. @@ -13,7 +17,10 @@ import { type HistoryPageV2EnabledConfigValue } from './history-page-v2-enabled. export default async function historyPageV2Enabled(): Promise { const envValue = process.env.CADENCE_HISTORY_PAGE_V2_ENABLED; - if (HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG.find((v) => v === envValue)) { + if ( + HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG.find((v) => v === envValue) !== + undefined + ) { return envValue as HistoryPageV2EnabledConfigValue; } diff --git a/src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx b/src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx new file mode 100644 index 000000000..34b3851ee --- /dev/null +++ b/src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx @@ -0,0 +1,72 @@ +import React, { Suspense } from 'react'; + +import { renderHook, waitFor } from '@/test-utils/rtl'; + +import { type UseSuspenseConfigValueResult } from '@/hooks/use-config-value/use-config-value.types'; +import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; + +import useSuspenseIsWorkflowHistoryV2Enabled from '../use-suspense-is-workflow-history-v2-enabled'; + +jest.mock('@/hooks/use-config-value/use-suspense-config-value'); + +const mockUseSuspenseConfigValue = + useSuspenseConfigValue as jest.MockedFunction; + +describe(useSuspenseIsWorkflowHistoryV2Enabled.name, () => { + it('should return true when config value is ENABLED', async () => { + const { result } = setup({ configValue: 'ENABLED' }); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it('should return true when config value is OPT_OUT', async () => { + const { result } = setup({ configValue: 'OPT_OUT' }); + + await waitFor(() => { + expect(result.current).toBe(true); + }); + }); + + it('should return false when config value is DISABLED', async () => { + const { result } = setup({ configValue: 'DISABLED' }); + + await waitFor(() => { + expect(result.current).toBe(false); + }); + }); + + it('should return false when config value is OPT_IN', async () => { + const { result } = setup({ configValue: 'OPT_IN' }); + + await waitFor(() => { + expect(result.current).toBe(false); + }); + }); +}); + +function setup({ + configValue, +}: { + configValue: 'DISABLED' | 'OPT_IN' | 'OPT_OUT' | 'ENABLED'; +}) { + mockUseSuspenseConfigValue.mockReturnValue({ + data: configValue, + } satisfies Pick< + UseSuspenseConfigValueResult<'HISTORY_PAGE_V2_ENABLED'>, + 'data' + >); + + const { result } = renderHook( + () => useSuspenseIsWorkflowHistoryV2Enabled(), + undefined, + { + wrapper: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + } + ); + + return { result }; +} diff --git a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts similarity index 57% rename from src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts rename to src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts index 2a99033d8..74d03b255 100644 --- a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts +++ b/src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts @@ -1,9 +1,10 @@ import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; -export default function useIsWorkflowHistoryV2Enabled(): boolean { +export default function useSuspenseIsWorkflowHistoryV2Enabled(): boolean { const { data: historyPageV2Config } = useSuspenseConfigValue( 'HISTORY_PAGE_V2_ENABLED' ); - return historyPageV2Config === 'ENABLED' || historyPageV2Config === 'OPT-OUT'; + // TODO @adhitya.mamallan - implement local-storage based behaviour for remembering opt-ins + return historyPageV2Config === 'ENABLED' || historyPageV2Config === 'OPT_OUT'; } diff --git a/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx b/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx index 4e4b55de2..a235e231b 100644 --- a/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx +++ b/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx @@ -3,11 +3,11 @@ import { render, screen } from '@/test-utils/rtl'; import { type Props } from '../../workflow-history.types'; import WorkflowHistoryWrapper from '../workflow-history-wrapper'; -const mockUseIsWorkflowHistoryV2Enabled = jest.fn(); +const mockUseSuspenseIsWorkflowHistoryV2Enabled = jest.fn(); jest.mock( - '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled', - () => jest.fn(() => mockUseIsWorkflowHistoryV2Enabled()) + '@/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled', + () => jest.fn(() => mockUseSuspenseIsWorkflowHistoryV2Enabled()) ); jest.mock('../../../workflow-history-v2/workflow-history-v2', () => @@ -21,16 +21,16 @@ jest.mock('../../workflow-history', () => ); describe(WorkflowHistoryWrapper.name, () => { - it('should render WorkflowHistoryV2 when useIsWorkflowHistoryV2Enabled returns true', () => { - mockUseIsWorkflowHistoryV2Enabled.mockReturnValue(true); + it('should render WorkflowHistoryV2 when useSuspenseIsWorkflowHistoryV2Enabled returns true', () => { + mockUseSuspenseIsWorkflowHistoryV2Enabled.mockReturnValue(true); setup(); expect(screen.getByTestId('workflow-history-v2')).toBeInTheDocument(); expect(screen.queryByTestId('workflow-history')).not.toBeInTheDocument(); }); - it('should render WorkflowHistory when useIsWorkflowHistoryV2Enabled returns false', () => { - mockUseIsWorkflowHistoryV2Enabled.mockReturnValue(false); + it('should render WorkflowHistory when useSuspenseIsWorkflowHistoryV2Enabled returns false', () => { + mockUseSuspenseIsWorkflowHistoryV2Enabled.mockReturnValue(false); setup(); expect(screen.getByTestId('workflow-history')).toBeInTheDocument(); diff --git a/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx b/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx index d62fade64..1388cb0da 100644 --- a/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx +++ b/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx @@ -1,4 +1,4 @@ -import useIsWorkflowHistoryV2Enabled from '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled'; +import useSuspenseIsWorkflowHistoryV2Enabled from '@/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled'; import WorkflowHistoryV2 from '@/views/workflow-history-v2/workflow-history-v2'; import WorkflowHistory from '../workflow-history'; @@ -6,7 +6,7 @@ import WorkflowHistoryContextProvider from '../workflow-history-context-provider import { type Props } from '../workflow-history.types'; export default function WorkflowHistoryWrapper(props: Props) { - const isWorkflowHistoryV2Enabled = useIsWorkflowHistoryV2Enabled(); + const isWorkflowHistoryV2Enabled = useSuspenseIsWorkflowHistoryV2Enabled(); return ( From c32111a108838309601b465c89aa1b131b3ae02b Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 7 Jan 2026 14:51:59 +0100 Subject: [PATCH 3/8] Implement using local storage and restructure code Signed-off-by: Adhitya Mamallan --- ...se-is-workflow-history-v2-enabled.test.tsx | 210 ++++++++++++++++++ ...se-is-workflow-history-v2-enabled.test.tsx | 72 ------ .../use-is-workflow-history-v2-enabled.ts | 56 +++++ ...suspense-is-workflow-history-v2-enabled.ts | 10 - ...orkflow-history-user-preferences.config.ts | 7 + .../workflow-history-component.test.tsx | 60 +++++ .../workflow-history-component.tsx | 17 ++ ...workflow-history-context-provider.test.tsx | 8 + .../workflow-history-context-provider.tsx | 6 + ...workflow-history-context-provider.types.ts | 3 + .../workflow-history-wrapper.test.tsx | 55 ----- .../workflow-history-wrapper.tsx | 13 +- 12 files changed, 369 insertions(+), 148 deletions(-) create mode 100644 src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-enabled.test.tsx delete mode 100644 src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx create mode 100644 src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts delete mode 100644 src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts create mode 100644 src/views/workflow-history/workflow-history-component/__tests__/workflow-history-component.test.tsx create mode 100644 src/views/workflow-history/workflow-history-component/workflow-history-component.tsx delete mode 100644 src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx diff --git a/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-enabled.test.tsx b/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-enabled.test.tsx new file mode 100644 index 000000000..4713b339e --- /dev/null +++ b/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-enabled.test.tsx @@ -0,0 +1,210 @@ +import React, { Suspense } from 'react'; + +import { renderHook, waitFor, act } from '@/test-utils/rtl'; + +import { type UseSuspenseConfigValueResult } from '@/hooks/use-config-value/use-config-value.types'; +import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; +import * as localStorageModule from '@/utils/local-storage'; +import workflowHistoryUserPreferencesConfig from '@/views/workflow-history/config/workflow-history-user-preferences.config'; + +import useIsWorkflowHistoryV2Enabled from '../use-is-workflow-history-v2-enabled'; + +jest.mock('@/hooks/use-config-value/use-suspense-config-value'); +jest.mock('@/utils/local-storage', () => ({ + getLocalStorageValue: jest.fn(), + setLocalStorageValue: jest.fn(), + clearLocalStorageValue: jest.fn(), +})); + +const mockUseSuspenseConfigValue = + useSuspenseConfigValue as jest.MockedFunction; + +describe(useIsWorkflowHistoryV2Enabled.name, () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should return true when config value is ENABLED', async () => { + const { result } = setup({ configValue: 'ENABLED' }); + + await waitFor(() => { + expect(result.current[0]).toBe(true); + }); + }); + + it('should return true when config value is OPT_OUT', async () => { + const { result } = setup({ configValue: 'OPT_OUT' }); + + await waitFor(() => { + expect(result.current[0]).toBe(true); + }); + }); + + it('should return false when config value is DISABLED', async () => { + const { result } = setup({ configValue: 'DISABLED' }); + + await waitFor(() => { + expect(result.current[0]).toBe(false); + }); + }); + + it('should return false when config value is OPT_IN and localStorage has no value', async () => { + const { result } = setup({ configValue: 'OPT_IN' }); + + await waitFor(() => { + expect(result.current[0]).toBe(false); + }); + }); + + it('should return true when config value is OPT_IN and localStorage has true', async () => { + const { result } = setup({ + configValue: 'OPT_IN', + localStorageValue: true, + }); + + await waitFor(() => { + expect(result.current[0]).toBe(true); + }); + }); + + it('should return false when config value is OPT_IN and localStorage has false', async () => { + const { result } = setup({ + configValue: 'OPT_IN', + localStorageValue: false, + }); + + await waitFor(() => { + expect(result.current[0]).toBe(false); + }); + }); + + it('should not allow setting value when config is DISABLED', async () => { + const { result, mockSetLocalStorageValue } = setup({ + configValue: 'DISABLED', + }); + + await waitFor(() => { + expect(result.current[0]).toBe(false); + }); + + act(() => { + result.current[1](true); + }); + + expect(result.current[0]).toBe(false); + expect(mockSetLocalStorageValue).not.toHaveBeenCalled(); + }); + + it('should not allow setting value when config is ENABLED', async () => { + const { result, mockSetLocalStorageValue } = setup({ + configValue: 'ENABLED', + }); + + await waitFor(() => { + expect(result.current[0]).toBe(true); + }); + + act(() => { + result.current[1](false); + }); + + expect(result.current[0]).toBe(true); + expect(mockSetLocalStorageValue).not.toHaveBeenCalled(); + }); + + it('should allow setting value when config is OPT_OUT', async () => { + const { result, mockSetLocalStorageValue } = setup({ + configValue: 'OPT_OUT', + }); + + await waitFor(() => { + expect(result.current[0]).toBe(true); + }); + + act(() => { + result.current[1](false); + }); + + expect(result.current[0]).toBe(false); + expect(mockSetLocalStorageValue).not.toHaveBeenCalled(); + }); + + it('should allow setting value and update localStorage when config is OPT_IN', async () => { + const { result, mockSetLocalStorageValue } = setup({ + configValue: 'OPT_IN', + localStorageValue: false, + }); + + await waitFor(() => { + expect(result.current[0]).toBe(false); + }); + + act(() => { + result.current[1](true); + }); + + expect(result.current[0]).toBe(true); + expect(mockSetLocalStorageValue).toHaveBeenCalledWith( + workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.key, + 'true' + ); + }); + + it('should update localStorage when setting value to false in OPT_IN mode', async () => { + const { result, mockSetLocalStorageValue } = setup({ + configValue: 'OPT_IN', + localStorageValue: true, + }); + + await waitFor(() => { + expect(result.current[0]).toBe(true); + }); + + act(() => { + result.current[1](false); + }); + + expect(result.current[0]).toBe(false); + expect(mockSetLocalStorageValue).toHaveBeenCalledWith( + workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.key, + 'false' + ); + }); +}); + +function setup({ + configValue, + localStorageValue, +}: { + configValue: 'DISABLED' | 'OPT_IN' | 'OPT_OUT' | 'ENABLED'; + localStorageValue?: boolean | null; +}) { + mockUseSuspenseConfigValue.mockReturnValue({ + data: configValue, + } satisfies Pick< + UseSuspenseConfigValueResult<'HISTORY_PAGE_V2_ENABLED'>, + 'data' + >); + + const mockGetLocalStorageValue = jest.fn(() => localStorageValue ?? null); + const mockSetLocalStorageValue = jest.fn(); + + jest + .spyOn(localStorageModule, 'getLocalStorageValue') + .mockImplementation(mockGetLocalStorageValue); + jest + .spyOn(localStorageModule, 'setLocalStorageValue') + .mockImplementation(mockSetLocalStorageValue); + + const { result } = renderHook( + () => useIsWorkflowHistoryV2Enabled(), + undefined, + { + wrapper: ({ children }: { children: React.ReactNode }) => ( + {children} + ), + } + ); + + return { result, mockGetLocalStorageValue, mockSetLocalStorageValue }; +} diff --git a/src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx b/src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx deleted file mode 100644 index 34b3851ee..000000000 --- a/src/views/workflow-history-v2/hooks/__tests__/use-suspense-is-workflow-history-v2-enabled.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { Suspense } from 'react'; - -import { renderHook, waitFor } from '@/test-utils/rtl'; - -import { type UseSuspenseConfigValueResult } from '@/hooks/use-config-value/use-config-value.types'; -import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; - -import useSuspenseIsWorkflowHistoryV2Enabled from '../use-suspense-is-workflow-history-v2-enabled'; - -jest.mock('@/hooks/use-config-value/use-suspense-config-value'); - -const mockUseSuspenseConfigValue = - useSuspenseConfigValue as jest.MockedFunction; - -describe(useSuspenseIsWorkflowHistoryV2Enabled.name, () => { - it('should return true when config value is ENABLED', async () => { - const { result } = setup({ configValue: 'ENABLED' }); - - await waitFor(() => { - expect(result.current).toBe(true); - }); - }); - - it('should return true when config value is OPT_OUT', async () => { - const { result } = setup({ configValue: 'OPT_OUT' }); - - await waitFor(() => { - expect(result.current).toBe(true); - }); - }); - - it('should return false when config value is DISABLED', async () => { - const { result } = setup({ configValue: 'DISABLED' }); - - await waitFor(() => { - expect(result.current).toBe(false); - }); - }); - - it('should return false when config value is OPT_IN', async () => { - const { result } = setup({ configValue: 'OPT_IN' }); - - await waitFor(() => { - expect(result.current).toBe(false); - }); - }); -}); - -function setup({ - configValue, -}: { - configValue: 'DISABLED' | 'OPT_IN' | 'OPT_OUT' | 'ENABLED'; -}) { - mockUseSuspenseConfigValue.mockReturnValue({ - data: configValue, - } satisfies Pick< - UseSuspenseConfigValueResult<'HISTORY_PAGE_V2_ENABLED'>, - 'data' - >); - - const { result } = renderHook( - () => useSuspenseIsWorkflowHistoryV2Enabled(), - undefined, - { - wrapper: ({ children }: { children: React.ReactNode }) => ( - {children} - ), - } - ); - - return { result }; -} diff --git a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts new file mode 100644 index 000000000..4ad81d3dc --- /dev/null +++ b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts @@ -0,0 +1,56 @@ +import { useCallback, useState } from 'react'; + +import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; +import { + getLocalStorageValue, + setLocalStorageValue, +} from '@/utils/local-storage'; +import workflowHistoryUserPreferencesConfig from '@/views/workflow-history/config/workflow-history-user-preferences.config'; + +export default function useIsWorkflowHistoryV2Enabled(): [ + boolean, + (v: boolean) => void, +] { + const { data: historyPageV2Config } = useSuspenseConfigValue( + 'HISTORY_PAGE_V2_ENABLED' + ); + + const [isEnabled, setIsEnabled] = useState(() => { + switch (historyPageV2Config) { + case 'DISABLED': + return false; + case 'ENABLED': + case 'OPT_OUT': + return true; + case 'OPT_IN': + const userPreference = getLocalStorageValue( + workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.key, + workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.schema + ); + return userPreference ?? false; + } + }); + + const setIsWorkflowHistoryV2Enabled = useCallback( + (v: boolean) => { + if ( + historyPageV2Config === 'DISABLED' || + historyPageV2Config === 'ENABLED' + ) { + return; + } + + setIsEnabled(v); + + if (historyPageV2Config === 'OPT_IN') { + setLocalStorageValue( + workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.key, + String(v) + ); + } + }, + [historyPageV2Config] + ); + + return [isEnabled, setIsWorkflowHistoryV2Enabled]; +} diff --git a/src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts deleted file mode 100644 index 74d03b255..000000000 --- a/src/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled.ts +++ /dev/null @@ -1,10 +0,0 @@ -import useSuspenseConfigValue from '@/hooks/use-config-value/use-suspense-config-value'; - -export default function useSuspenseIsWorkflowHistoryV2Enabled(): boolean { - const { data: historyPageV2Config } = useSuspenseConfigValue( - 'HISTORY_PAGE_V2_ENABLED' - ); - - // TODO @adhitya.mamallan - implement local-storage based behaviour for remembering opt-ins - return historyPageV2Config === 'ENABLED' || historyPageV2Config === 'OPT_OUT'; -} diff --git a/src/views/workflow-history/config/workflow-history-user-preferences.config.ts b/src/views/workflow-history/config/workflow-history-user-preferences.config.ts index f24cf6095..b4f6ed0ea 100644 --- a/src/views/workflow-history/config/workflow-history-user-preferences.config.ts +++ b/src/views/workflow-history/config/workflow-history-user-preferences.config.ts @@ -10,6 +10,13 @@ const workflowHistoryUserPreferencesConfig = { .refine((val) => val === 'true' || val === 'false') .transform((val) => val === 'true'), }, + historyV2ViewEnabled: { + key: 'history-v2-view-enabled', + schema: z + .string() + .refine((val) => val === 'true' || val === 'false') + .transform((val) => val === 'true'), + }, } as const satisfies Record>; export default workflowHistoryUserPreferencesConfig; diff --git a/src/views/workflow-history/workflow-history-component/__tests__/workflow-history-component.test.tsx b/src/views/workflow-history/workflow-history-component/__tests__/workflow-history-component.test.tsx new file mode 100644 index 000000000..58f6311d6 --- /dev/null +++ b/src/views/workflow-history/workflow-history-component/__tests__/workflow-history-component.test.tsx @@ -0,0 +1,60 @@ +import { render, screen } from '@/test-utils/rtl'; + +import { WorkflowHistoryContext } from '../../workflow-history-context-provider/workflow-history-context-provider'; +import { type Props } from '../../workflow-history.types'; +import WorkflowHistoryComponent from '../workflow-history-component'; + +jest.mock('@/views/workflow-history-v2/workflow-history-v2', () => + jest.fn(() => ( +
Workflow History V2
+ )) +); + +jest.mock('../../workflow-history', () => + jest.fn(() =>
Workflow History V1
) +); + +describe(WorkflowHistoryComponent.name, () => { + it('should render WorkflowHistoryV2 when isWorkflowHistoryV2Enabled is true', () => { + setup({ isWorkflowHistoryV2Enabled: true }); + + expect(screen.getByTestId('workflow-history-v2')).toBeInTheDocument(); + expect(screen.queryByTestId('workflow-history')).not.toBeInTheDocument(); + }); + + it('should render WorkflowHistory when isWorkflowHistoryV2Enabled is false', () => { + setup({ isWorkflowHistoryV2Enabled: false }); + + expect(screen.getByTestId('workflow-history')).toBeInTheDocument(); + expect(screen.queryByTestId('workflow-history-v2')).not.toBeInTheDocument(); + }); +}); + +function setup({ + isWorkflowHistoryV2Enabled = false, + props = { + params: { + cluster: 'test-cluster', + domain: 'test-domain', + workflowId: 'test-workflow-id', + runId: 'test-run-id', + workflowTab: 'history' as const, + }, + }, +}: { + isWorkflowHistoryV2Enabled?: boolean; + props?: Props; +} = {}) { + render( + + + + ); +} diff --git a/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx b/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx new file mode 100644 index 000000000..dc5a6880d --- /dev/null +++ b/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx @@ -0,0 +1,17 @@ +import { useContext } from 'react'; + +import WorkflowHistoryV2 from '@/views/workflow-history-v2/workflow-history-v2'; + +import WorkflowHistory from '../workflow-history'; +import { WorkflowHistoryContext } from '../workflow-history-context-provider/workflow-history-context-provider'; +import { type Props } from '../workflow-history.types'; + +export default function WorkflowHistoryComponent(props: Props) { + const { isWorkflowHistoryV2Enabled } = useContext(WorkflowHistoryContext); + + return isWorkflowHistoryV2Enabled ? ( + + ) : ( + + ); +} diff --git a/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx b/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx index d37e3fdbe..56eb1af36 100644 --- a/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx +++ b/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx @@ -34,6 +34,14 @@ jest.mock('@/utils/local-storage', () => ({ clearLocalStorageValue: jest.fn(), })); +jest.mock( + '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled', + () => ({ + __esModule: true, + default: jest.fn(() => [false, jest.fn()]), + }) +); + describe(WorkflowHistoryContextProvider.name, () => { afterEach(() => { jest.restoreAllMocks(); diff --git a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx index 24f52ce31..56f52ae3f 100644 --- a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx +++ b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx @@ -5,6 +5,7 @@ import { getLocalStorageValue, setLocalStorageValue, } from '@/utils/local-storage'; +import useIsWorkflowHistoryV2Enabled from '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled'; import workflowHistoryUserPreferencesConfig from '../config/workflow-history-user-preferences.config'; @@ -37,11 +38,16 @@ export default function WorkflowHistoryContextProvider({ [] ); + const [isWorkflowHistoryV2Enabled, setIsWorkflowHistoryV2Enabled] = + useIsWorkflowHistoryV2Enabled(); + return ( {children} diff --git a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts index 6fd588094..677a472be 100644 --- a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts +++ b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts @@ -1,4 +1,7 @@ export type WorkflowHistoryContextType = { ungroupedViewUserPreference: boolean | null; setUngroupedViewUserPreference: (v: boolean) => void; + // Workflow History V2 Enabled: clean up once the feature has been fully rolled out + isWorkflowHistoryV2Enabled: boolean; + setIsWorkflowHistoryV2Enabled: (v: boolean) => void; }; diff --git a/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx b/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx deleted file mode 100644 index a235e231b..000000000 --- a/src/views/workflow-history/workflow-history-wrapper/__tests__/workflow-history-wrapper.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { render, screen } from '@/test-utils/rtl'; - -import { type Props } from '../../workflow-history.types'; -import WorkflowHistoryWrapper from '../workflow-history-wrapper'; - -const mockUseSuspenseIsWorkflowHistoryV2Enabled = jest.fn(); - -jest.mock( - '@/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled', - () => jest.fn(() => mockUseSuspenseIsWorkflowHistoryV2Enabled()) -); - -jest.mock('../../../workflow-history-v2/workflow-history-v2', () => - jest.fn(() => ( -
Workflow History V2
- )) -); - -jest.mock('../../workflow-history', () => - jest.fn(() =>
Workflow History V1
) -); - -describe(WorkflowHistoryWrapper.name, () => { - it('should render WorkflowHistoryV2 when useSuspenseIsWorkflowHistoryV2Enabled returns true', () => { - mockUseSuspenseIsWorkflowHistoryV2Enabled.mockReturnValue(true); - setup(); - - expect(screen.getByTestId('workflow-history-v2')).toBeInTheDocument(); - expect(screen.queryByTestId('workflow-history')).not.toBeInTheDocument(); - }); - - it('should render WorkflowHistory when useSuspenseIsWorkflowHistoryV2Enabled returns false', () => { - mockUseSuspenseIsWorkflowHistoryV2Enabled.mockReturnValue(false); - setup(); - - expect(screen.getByTestId('workflow-history')).toBeInTheDocument(); - expect(screen.queryByTestId('workflow-history-v2')).not.toBeInTheDocument(); - }); -}); - -function setup({ - props = { - params: { - cluster: 'test-cluster', - domain: 'test-domain', - workflowId: 'test-workflow-id', - runId: 'test-run-id', - workflowTab: 'history' as const, - }, - }, -}: { - props?: Props; -} = {}) { - render(); -} diff --git a/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx b/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx index 1388cb0da..1709268ea 100644 --- a/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx +++ b/src/views/workflow-history/workflow-history-wrapper/workflow-history-wrapper.tsx @@ -1,20 +1,11 @@ -import useSuspenseIsWorkflowHistoryV2Enabled from '@/views/workflow-history-v2/hooks/use-suspense-is-workflow-history-v2-enabled'; -import WorkflowHistoryV2 from '@/views/workflow-history-v2/workflow-history-v2'; - -import WorkflowHistory from '../workflow-history'; +import WorkflowHistoryComponent from '../workflow-history-component/workflow-history-component'; import WorkflowHistoryContextProvider from '../workflow-history-context-provider/workflow-history-context-provider'; import { type Props } from '../workflow-history.types'; export default function WorkflowHistoryWrapper(props: Props) { - const isWorkflowHistoryV2Enabled = useSuspenseIsWorkflowHistoryV2Enabled(); - return ( - {isWorkflowHistoryV2Enabled ? ( - - ) : ( - - )} + ); } From 2c5894e0b0c8cf627384a36f2e454df49d3aea5d Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 7 Jan 2026 16:36:55 +0100 Subject: [PATCH 4/8] Fix tests Signed-off-by: Adhitya Mamallan --- .../workflow-history-v2/__tests__/workflow-history-v2.test.tsx | 2 ++ src/views/workflow-history/__tests__/workflow-history.test.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx b/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx index 0b1ef82f2..cd7f4d503 100644 --- a/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx +++ b/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx @@ -452,6 +452,8 @@ async function setup({ value={{ ungroupedViewUserPreference: ungroupedViewPreference ?? null, setUngroupedViewUserPreference: mockSetUngroupedViewUserPreference, + isWorkflowHistoryV2Enabled: false, + setIsWorkflowHistoryV2Enabled: jest.fn(), }} > Date: Thu, 8 Jan 2026 15:32:00 +0100 Subject: [PATCH 5/8] Add docstring for hook Signed-off-by: Adhitya Mamallan --- .../dynamic/resolvers/history-page-v2-enabled.ts | 5 +---- .../hooks/use-is-workflow-history-v2-enabled.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/config/dynamic/resolvers/history-page-v2-enabled.ts b/src/config/dynamic/resolvers/history-page-v2-enabled.ts index 1ccc66cc7..8a3bcd5f8 100644 --- a/src/config/dynamic/resolvers/history-page-v2-enabled.ts +++ b/src/config/dynamic/resolvers/history-page-v2-enabled.ts @@ -17,10 +17,7 @@ import { type HistoryPageV2EnabledConfigValue } from './history-page-v2-enabled. export default async function historyPageV2Enabled(): Promise { const envValue = process.env.CADENCE_HISTORY_PAGE_V2_ENABLED; - if ( - HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG.find((v) => v === envValue) !== - undefined - ) { + if (HISTORY_PAGE_V2_ENABLED_VALUES_CONFIG.includes(envValue as any)) { return envValue as HistoryPageV2EnabledConfigValue; } diff --git a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts index 4ad81d3dc..b11ed4985 100644 --- a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts +++ b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts @@ -7,6 +7,19 @@ import { } from '@/utils/local-storage'; import workflowHistoryUserPreferencesConfig from '@/views/workflow-history/config/workflow-history-user-preferences.config'; +/** + * Manages Workflow History V2 enabled state based on config and localStorage. + * + * @returns A tuple containing: + * - `isWorkflowHistoryV2Enabled`: boolean indicating whether Workflow History V2 is enabled + * - `setIsWorkflowHistoryV2Enabled`: function to update the enabled state + * + * Behavior by config mode: + * - `DISABLED`: Always returns `false`. Setter has no effect. + * - `ENABLED`: Always returns `true`. Setter has no effect. + * - `OPT_OUT`: Always starts with `true`. Setter updates state but does not persist to localStorage. + * - `OPT_IN`: Reads initial state from localStorage (defaults to `false`). Setter updates both state and localStorage. + */ export default function useIsWorkflowHistoryV2Enabled(): [ boolean, (v: boolean) => void, From d0cc5cd04067acc5f8c5dc013c1251877046dee5 Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 14 Jan 2026 22:10:20 +0530 Subject: [PATCH 6/8] add comment Signed-off-by: Adhitya Mamallan --- .../hooks/use-is-workflow-history-v2-enabled.ts | 2 ++ .../workflow-history-context-provider.types.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts index b11ed4985..89044a483 100644 --- a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts +++ b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts @@ -55,6 +55,8 @@ export default function useIsWorkflowHistoryV2Enabled(): [ setIsEnabled(v); + // We persist user preference only for opt-ins and not opt-outs, to allow + // us to collect feedback from users who prefer the older history view if (historyPageV2Config === 'OPT_IN') { setLocalStorageValue( workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.key, diff --git a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts index 677a472be..f6dce117f 100644 --- a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts +++ b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts @@ -1,7 +1,7 @@ export type WorkflowHistoryContextType = { ungroupedViewUserPreference: boolean | null; setUngroupedViewUserPreference: (v: boolean) => void; - // Workflow History V2 Enabled: clean up once the feature has been fully rolled out + // TODO @adhitya.mamallan: clean up once Workflow History V2 has been fully rolled out isWorkflowHistoryV2Enabled: boolean; setIsWorkflowHistoryV2Enabled: (v: boolean) => void; }; From 04c83a0d12e0276d2f4f05392dc11557a4a7637e Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 14 Jan 2026 22:19:53 +0530 Subject: [PATCH 7/8] Rename enabled hook to selected Signed-off-by: Adhitya Mamallan --- .../__tests__/workflow-history-v2.test.tsx | 4 ++-- ...-is-workflow-history-v2-selected.test.tsx} | 7 ++++--- ...=> use-is-workflow-history-v2-selected.ts} | 21 ++++++++++--------- .../__tests__/workflow-history.test.tsx | 4 ++-- .../workflow-history-component.test.tsx | 16 +++++++------- .../workflow-history-component.tsx | 4 ++-- ...workflow-history-context-provider.test.tsx | 2 +- .../workflow-history-context-provider.tsx | 10 ++++----- ...workflow-history-context-provider.types.ts | 4 ++-- 9 files changed, 37 insertions(+), 35 deletions(-) rename src/views/workflow-history-v2/hooks/__tests__/{use-is-workflow-history-v2-enabled.test.tsx => use-is-workflow-history-v2-selected.test.tsx} (96%) rename src/views/workflow-history-v2/hooks/{use-is-workflow-history-v2-enabled.ts => use-is-workflow-history-v2-selected.ts} (68%) diff --git a/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx b/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx index cd7f4d503..9292b2188 100644 --- a/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx +++ b/src/views/workflow-history-v2/__tests__/workflow-history-v2.test.tsx @@ -452,8 +452,8 @@ async function setup({ value={{ ungroupedViewUserPreference: ungroupedViewPreference ?? null, setUngroupedViewUserPreference: mockSetUngroupedViewUserPreference, - isWorkflowHistoryV2Enabled: false, - setIsWorkflowHistoryV2Enabled: jest.fn(), + isWorkflowHistoryV2Selected: false, + setIsWorkflowHistoryV2Selected: jest.fn(), }} > ({ @@ -19,7 +19,7 @@ jest.mock('@/utils/local-storage', () => ({ const mockUseSuspenseConfigValue = useSuspenseConfigValue as jest.MockedFunction; -describe(useIsWorkflowHistoryV2Enabled.name, () => { +describe(useIsWorkflowHistoryV2Selected.name, () => { afterEach(() => { jest.restoreAllMocks(); }); @@ -197,7 +197,7 @@ function setup({ .mockImplementation(mockSetLocalStorageValue); const { result } = renderHook( - () => useIsWorkflowHistoryV2Enabled(), + () => useIsWorkflowHistoryV2Selected(), undefined, { wrapper: ({ children }: { children: React.ReactNode }) => ( @@ -208,3 +208,4 @@ function setup({ return { result, mockGetLocalStorageValue, mockSetLocalStorageValue }; } + diff --git a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-selected.ts similarity index 68% rename from src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts rename to src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-selected.ts index 89044a483..a6bfca86d 100644 --- a/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled.ts +++ b/src/views/workflow-history-v2/hooks/use-is-workflow-history-v2-selected.ts @@ -8,11 +8,11 @@ import { import workflowHistoryUserPreferencesConfig from '@/views/workflow-history/config/workflow-history-user-preferences.config'; /** - * Manages Workflow History V2 enabled state based on config and localStorage. + * Manages Workflow History V2 selected state based on config and localStorage. * * @returns A tuple containing: - * - `isWorkflowHistoryV2Enabled`: boolean indicating whether Workflow History V2 is enabled - * - `setIsWorkflowHistoryV2Enabled`: function to update the enabled state + * - `isWorkflowHistoryV2Selected`: boolean indicating whether Workflow History V2 is selected + * - `setIsWorkflowHistoryV2Selected`: function to update the selected state * * Behavior by config mode: * - `DISABLED`: Always returns `false`. Setter has no effect. @@ -20,7 +20,7 @@ import workflowHistoryUserPreferencesConfig from '@/views/workflow-history/confi * - `OPT_OUT`: Always starts with `true`. Setter updates state but does not persist to localStorage. * - `OPT_IN`: Reads initial state from localStorage (defaults to `false`). Setter updates both state and localStorage. */ -export default function useIsWorkflowHistoryV2Enabled(): [ +export default function useIsWorkflowHistoryV2Selected(): [ boolean, (v: boolean) => void, ] { @@ -28,7 +28,7 @@ export default function useIsWorkflowHistoryV2Enabled(): [ 'HISTORY_PAGE_V2_ENABLED' ); - const [isEnabled, setIsEnabled] = useState(() => { + const [isSelected, setIsSelected] = useState(() => { switch (historyPageV2Config) { case 'DISABLED': return false; @@ -44,7 +44,7 @@ export default function useIsWorkflowHistoryV2Enabled(): [ } }); - const setIsWorkflowHistoryV2Enabled = useCallback( + const setIsWorkflowHistoryV2Selected = useCallback( (v: boolean) => { if ( historyPageV2Config === 'DISABLED' || @@ -53,10 +53,11 @@ export default function useIsWorkflowHistoryV2Enabled(): [ return; } - setIsEnabled(v); + setIsSelected(v); - // We persist user preference only for opt-ins and not opt-outs, to allow - // us to collect feedback from users who prefer the older history view + // We only save user preferences to localStorage for OPT_IN mode, not for OPT_OUT, + // so that we can learn when users explicitly choose to stick with the old workflow history view. + // This helps us gather feedback from those who opt to not try the new experience. if (historyPageV2Config === 'OPT_IN') { setLocalStorageValue( workflowHistoryUserPreferencesConfig.historyV2ViewEnabled.key, @@ -67,5 +68,5 @@ export default function useIsWorkflowHistoryV2Enabled(): [ [historyPageV2Config] ); - return [isEnabled, setIsWorkflowHistoryV2Enabled]; + return [isSelected, setIsWorkflowHistoryV2Selected]; } diff --git a/src/views/workflow-history/__tests__/workflow-history.test.tsx b/src/views/workflow-history/__tests__/workflow-history.test.tsx index e2e5c615f..b51414d62 100644 --- a/src/views/workflow-history/__tests__/workflow-history.test.tsx +++ b/src/views/workflow-history/__tests__/workflow-history.test.tsx @@ -298,8 +298,8 @@ async function setup({ value={{ ungroupedViewUserPreference: ungroupedViewPreference ?? null, setUngroupedViewUserPreference: mockSetUngroupedViewUserPreference, - isWorkflowHistoryV2Enabled: false, - setIsWorkflowHistoryV2Enabled: jest.fn(), + isWorkflowHistoryV2Selected: false, + setIsWorkflowHistoryV2Selected: jest.fn(), }} > ); describe(WorkflowHistoryComponent.name, () => { - it('should render WorkflowHistoryV2 when isWorkflowHistoryV2Enabled is true', () => { - setup({ isWorkflowHistoryV2Enabled: true }); + it('should render WorkflowHistoryV2 when isWorkflowHistoryV2Selected is true', () => { + setup({ isWorkflowHistoryV2Selected: true }); expect(screen.getByTestId('workflow-history-v2')).toBeInTheDocument(); expect(screen.queryByTestId('workflow-history')).not.toBeInTheDocument(); }); - it('should render WorkflowHistory when isWorkflowHistoryV2Enabled is false', () => { - setup({ isWorkflowHistoryV2Enabled: false }); + it('should render WorkflowHistory when isWorkflowHistoryV2Selected is false', () => { + setup({ isWorkflowHistoryV2Selected: false }); expect(screen.getByTestId('workflow-history')).toBeInTheDocument(); expect(screen.queryByTestId('workflow-history-v2')).not.toBeInTheDocument(); @@ -31,7 +31,7 @@ describe(WorkflowHistoryComponent.name, () => { }); function setup({ - isWorkflowHistoryV2Enabled = false, + isWorkflowHistoryV2Selected = false, props = { params: { cluster: 'test-cluster', @@ -42,7 +42,7 @@ function setup({ }, }, }: { - isWorkflowHistoryV2Enabled?: boolean; + isWorkflowHistoryV2Selected?: boolean; props?: Props; } = {}) { render( @@ -50,8 +50,8 @@ function setup({ value={{ ungroupedViewUserPreference: null, setUngroupedViewUserPreference: jest.fn(), - isWorkflowHistoryV2Enabled, - setIsWorkflowHistoryV2Enabled: jest.fn(), + isWorkflowHistoryV2Selected, + setIsWorkflowHistoryV2Selected: jest.fn(), }} > diff --git a/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx b/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx index dc5a6880d..f585ece39 100644 --- a/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx +++ b/src/views/workflow-history/workflow-history-component/workflow-history-component.tsx @@ -7,9 +7,9 @@ import { WorkflowHistoryContext } from '../workflow-history-context-provider/wor import { type Props } from '../workflow-history.types'; export default function WorkflowHistoryComponent(props: Props) { - const { isWorkflowHistoryV2Enabled } = useContext(WorkflowHistoryContext); + const { isWorkflowHistoryV2Selected } = useContext(WorkflowHistoryContext); - return isWorkflowHistoryV2Enabled ? ( + return isWorkflowHistoryV2Selected ? ( ) : ( diff --git a/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx b/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx index 56eb1af36..4f308249b 100644 --- a/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx +++ b/src/views/workflow-history/workflow-history-context-provider/__tests__/workflow-history-context-provider.test.tsx @@ -35,7 +35,7 @@ jest.mock('@/utils/local-storage', () => ({ })); jest.mock( - '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled', + '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-selected', () => ({ __esModule: true, default: jest.fn(() => [false, jest.fn()]), diff --git a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx index 56f52ae3f..7797e8184 100644 --- a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx +++ b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.tsx @@ -5,7 +5,7 @@ import { getLocalStorageValue, setLocalStorageValue, } from '@/utils/local-storage'; -import useIsWorkflowHistoryV2Enabled from '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-enabled'; +import useIsWorkflowHistoryV2Selected from '@/views/workflow-history-v2/hooks/use-is-workflow-history-v2-selected'; import workflowHistoryUserPreferencesConfig from '../config/workflow-history-user-preferences.config'; @@ -38,16 +38,16 @@ export default function WorkflowHistoryContextProvider({ [] ); - const [isWorkflowHistoryV2Enabled, setIsWorkflowHistoryV2Enabled] = - useIsWorkflowHistoryV2Enabled(); + const [isWorkflowHistoryV2Selected, setIsWorkflowHistoryV2Selected] = + useIsWorkflowHistoryV2Selected(); return ( {children} diff --git a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts index f6dce117f..09f5d63e2 100644 --- a/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts +++ b/src/views/workflow-history/workflow-history-context-provider/workflow-history-context-provider.types.ts @@ -2,6 +2,6 @@ export type WorkflowHistoryContextType = { ungroupedViewUserPreference: boolean | null; setUngroupedViewUserPreference: (v: boolean) => void; // TODO @adhitya.mamallan: clean up once Workflow History V2 has been fully rolled out - isWorkflowHistoryV2Enabled: boolean; - setIsWorkflowHistoryV2Enabled: (v: boolean) => void; + isWorkflowHistoryV2Selected: boolean; + setIsWorkflowHistoryV2Selected: (v: boolean) => void; }; From 698bdf98a3fbaae97040a58b418557687ad0f07d Mon Sep 17 00:00:00 2001 From: Adhitya Mamallan Date: Wed, 14 Jan 2026 22:22:23 +0530 Subject: [PATCH 8/8] fix lint error Signed-off-by: Adhitya Mamallan --- .../hooks/__tests__/use-is-workflow-history-v2-selected.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-selected.test.tsx b/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-selected.test.tsx index 9b87e1d77..c7765d028 100644 --- a/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-selected.test.tsx +++ b/src/views/workflow-history-v2/hooks/__tests__/use-is-workflow-history-v2-selected.test.tsx @@ -208,4 +208,3 @@ function setup({ return { result, mockGetLocalStorageValue, mockSetLocalStorageValue }; } -