Skip to content

Commit 53d3a91

Browse files
committed
Switch basic and advanced workflows base on a flag (#818)
* Switch basic and advanced workflows base on a flag * lint fix * add spaces between sections * fix typos and remove unimportant type * fix lint
1 parent baa76dc commit 53d3a91

File tree

8 files changed

+441
-3
lines changed

8 files changed

+441
-3
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { type NextRequest } from 'next/server';
2+
3+
import { describeCluster } from '@/route-handlers/describe-cluster/describe-cluster';
4+
import type { RouteParams } from '@/route-handlers/describe-domain/describe-domain.types';
5+
import { routeHandlerWithMiddlewares } from '@/utils/route-handlers-middleware';
6+
import routeHandlersDefaultMiddlewares from '@/utils/route-handlers-middleware/config/route-handlers-default-middlewares.config';
7+
8+
export async function GET(
9+
request: NextRequest,
10+
options: { params: RouteParams }
11+
) {
12+
return routeHandlerWithMiddlewares(
13+
describeCluster,
14+
request,
15+
options,
16+
routeHandlersDefaultMiddlewares
17+
);
18+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { NextRequest } from 'next/server';
2+
3+
import type { DescribeClusterResponse as OriginalDescribeClusterResponse } from '@/__generated__/proto-ts/uber/cadence/admin/v1/DescribeClusterResponse';
4+
import { GRPCError } from '@/utils/grpc/grpc-error';
5+
import { mockGrpcClusterMethods } from '@/utils/route-handlers-middleware/middlewares/__mocks__/grpc-cluster-methods';
6+
7+
import { describeCluster } from '../describe-cluster';
8+
import {
9+
type DescribeClusterResponse,
10+
type Context,
11+
} from '../describe-cluster.types';
12+
13+
describe('describeCluster', () => {
14+
it('calls describeCluster and returns valid response without membershipInfo', async () => {
15+
const { res, mockDescribeCluster, mockSuccessResponse } = await setup({});
16+
17+
expect(mockDescribeCluster).toHaveBeenCalledWith({
18+
name: 'mock-cluster',
19+
});
20+
const { membershipInfo, ...rest } = mockSuccessResponse;
21+
const routHandleRes: DescribeClusterResponse = rest;
22+
const responseJson = await res.json();
23+
expect(responseJson).toEqual(routHandleRes);
24+
});
25+
26+
it('returns an error when describeCluster errors out', async () => {
27+
const { res, mockDescribeCluster } = await setup({
28+
error: true,
29+
});
30+
31+
expect(mockDescribeCluster).toHaveBeenCalled();
32+
33+
expect(res.status).toEqual(500);
34+
const responseJson = await res.json();
35+
expect(responseJson).toEqual(
36+
expect.objectContaining({
37+
message: 'Failed to fetch cluster info',
38+
})
39+
);
40+
});
41+
42+
it('returns static response with advancedVisibilityEnabled true when CADENCE_ADVANCED_VISIBILITY env value is true', async () => {
43+
const originalEnvObj = globalThis.process.env;
44+
globalThis.process.env = {
45+
...process.env,
46+
CADENCE_ADVANCED_VISIBILITY: 'true',
47+
};
48+
49+
const { res, mockDescribeCluster } = await setup({});
50+
51+
expect(mockDescribeCluster).not.toHaveBeenCalled();
52+
53+
const responseJson = await res.json();
54+
expect(
55+
responseJson.persistenceInfo.visibilityStore.features[0].enabled
56+
).toBe(true);
57+
globalThis.process.env = originalEnvObj;
58+
});
59+
60+
it('returns static response with advancedVisibilityEnabled false when CADENCE_ADVANCED_VISIBILITY env value is not true', async () => {
61+
const originalEnvObj = globalThis.process.env;
62+
globalThis.process.env = {
63+
...process.env,
64+
CADENCE_ADVANCED_VISIBILITY: 'not true',
65+
};
66+
67+
const { res, mockDescribeCluster } = await setup({});
68+
69+
expect(mockDescribeCluster).not.toHaveBeenCalled();
70+
71+
const responseJson = await res.json();
72+
expect(
73+
responseJson.persistenceInfo.visibilityStore.features[0].enabled
74+
).toBe(false);
75+
globalThis.process.env = originalEnvObj;
76+
});
77+
});
78+
79+
async function setup({ error }: { error?: true }) {
80+
const mockSuccessResponse: OriginalDescribeClusterResponse = {
81+
persistenceInfo: {},
82+
membershipInfo: null,
83+
supportedClientVersions: null,
84+
};
85+
86+
const mockDescribeCluster = jest
87+
.spyOn(mockGrpcClusterMethods, 'describeCluster')
88+
.mockImplementationOnce(async () => {
89+
if (error) {
90+
throw new GRPCError('Failed to fetch cluster info');
91+
}
92+
return mockSuccessResponse;
93+
});
94+
95+
const res = await describeCluster(
96+
new NextRequest('http://localhost/api/clusters/:cluster', {
97+
method: 'Get',
98+
}),
99+
{
100+
params: {
101+
cluster: 'mock-cluster',
102+
},
103+
},
104+
{
105+
grpcClusterMethods: mockGrpcClusterMethods,
106+
} as Context
107+
);
108+
109+
return { res, mockDescribeCluster, mockSuccessResponse };
110+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import omit from 'lodash/omit';
2+
import { type NextRequest, NextResponse } from 'next/server';
3+
4+
import decodeUrlParams from '@/utils/decode-url-params';
5+
import { getHTTPStatusCode, GRPCError } from '@/utils/grpc/grpc-error';
6+
import logger, { type RouteHandlerErrorPayload } from '@/utils/logger';
7+
8+
import {
9+
type DescribeClusterResponse,
10+
type Context,
11+
type RequestParams,
12+
type RouteParams,
13+
} from './describe-cluster.types';
14+
15+
export async function describeCluster(
16+
_: NextRequest,
17+
requestParams: RequestParams,
18+
ctx: Context
19+
) {
20+
const decodedParams = decodeUrlParams(requestParams.params) as RouteParams;
21+
22+
// temporary solution to disable invoking describeCluster
23+
if (process.env.CADENCE_ADVANCED_VISIBILITY) {
24+
const res = {
25+
persistenceInfo: {
26+
visibilityStore: {
27+
features: [
28+
{
29+
key: 'advancedVisibilityEnabled',
30+
enabled: process.env.CADENCE_ADVANCED_VISIBILITY === 'true',
31+
},
32+
],
33+
},
34+
},
35+
supportedClientVersions: null,
36+
};
37+
return NextResponse.json(res);
38+
}
39+
40+
try {
41+
const res = await ctx.grpcClusterMethods.describeCluster({
42+
name: decodedParams.cluster,
43+
});
44+
45+
const sanitizedRes: DescribeClusterResponse = omit(res, 'membershipInfo'); // No need to return host information to client
46+
47+
return NextResponse.json(sanitizedRes);
48+
} catch (e) {
49+
logger.error<RouteHandlerErrorPayload>(
50+
{ requestParams: decodedParams, cause: e },
51+
'Error fetching cluster info'
52+
);
53+
54+
return NextResponse.json(
55+
{
56+
message:
57+
e instanceof GRPCError ? e.message : 'Error fetching cluster info',
58+
cause: e,
59+
},
60+
{ status: getHTTPStatusCode(e) }
61+
);
62+
}
63+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { type DescribeClusterResponse as OriginalDescribeClusterResponse } from '@/__generated__/proto-ts/uber/cadence/admin/v1/DescribeClusterResponse';
2+
import { type DefaultMiddlewaresContext } from '@/utils/route-handlers-middleware';
3+
4+
export type RouteParams = {
5+
cluster: string;
6+
};
7+
8+
export type RequestParams = {
9+
params: RouteParams;
10+
};
11+
12+
export type Context = DefaultMiddlewaresContext;
13+
export type DescribeClusterResponse = Omit<
14+
OriginalDescribeClusterResponse,
15+
'membershipInfo'
16+
>;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Suspense } from 'react';
2+
3+
import { HttpResponse } from 'msw';
4+
import { act } from 'react-dom/test-utils';
5+
6+
import { render, screen } from '@/test-utils/rtl';
7+
8+
import { type DescribeClusterResponse } from '@/route-handlers/describe-cluster/describe-cluster.types';
9+
import { type DomainPageTabContentProps } from '@/views/domain-page/domain-page-content/domain-page-content.types';
10+
11+
import DomainWorkflows from '../domain-workflows';
12+
13+
jest.mock('@/views/domain-workflows-basic/domain-workflows-basic', () =>
14+
jest.fn(() => <div>Basic Workflows</div>)
15+
);
16+
jest.mock('../domain-workflows-header/domain-workflows-header', () =>
17+
jest.fn(() => <div>Workflows Header</div>)
18+
);
19+
jest.mock('../domain-workflows-table/domain-workflows-table', () =>
20+
jest.fn(() => <div>Workflows Table</div>)
21+
);
22+
23+
describe('DomainWorkflows', () => {
24+
it('should render basic workflows when advanced visibility is disabled', async () => {
25+
await setup({ isAdvancedVisibility: false });
26+
27+
expect(await screen.findByText('Basic Workflows')).toBeInTheDocument();
28+
});
29+
30+
it('should render workflows header and table when advanced visibility is enabled', async () => {
31+
await setup({ isAdvancedVisibility: true });
32+
33+
expect(await screen.findByText('Workflows Header')).toBeInTheDocument();
34+
expect(await screen.findByText('Workflows Table')).toBeInTheDocument();
35+
});
36+
37+
it('should render workflows header and table when advanced visibility is enabled', async () => {
38+
let renderErrorMessage;
39+
try {
40+
await act(async () => {
41+
await setup({ error: true });
42+
});
43+
} catch (error) {
44+
if (error instanceof Error) {
45+
renderErrorMessage = error.message;
46+
}
47+
}
48+
49+
expect(renderErrorMessage).toEqual('Failed to fetch cluster info');
50+
});
51+
});
52+
53+
async function setup({
54+
isAdvancedVisibility = false,
55+
error,
56+
}: {
57+
error?: boolean;
58+
isAdvancedVisibility?: boolean;
59+
}) {
60+
const props: DomainPageTabContentProps = {
61+
domain: 'test-domain',
62+
cluster: 'test-cluster',
63+
};
64+
65+
render(
66+
<Suspense>
67+
<DomainWorkflows {...props} />
68+
</Suspense>,
69+
{
70+
endpointsMocks: [
71+
{
72+
path: '/api/clusters/test-cluster',
73+
httpMethod: 'GET',
74+
mockOnce: false,
75+
...(error
76+
? {
77+
httpResolver: () => {
78+
return HttpResponse.json(
79+
{ message: 'Failed to fetch cluster info' },
80+
{ status: 500 }
81+
);
82+
},
83+
}
84+
: {
85+
jsonResponse: {
86+
persistenceInfo: {
87+
visibilityStore: {
88+
features: [
89+
{
90+
key: 'advancedVisibilityEnabled',
91+
enabled: isAdvancedVisibility,
92+
},
93+
],
94+
backend: '',
95+
settings: [],
96+
},
97+
},
98+
supportedClientVersions: null,
99+
} satisfies DescribeClusterResponse,
100+
}),
101+
},
102+
],
103+
}
104+
);
105+
}

src/views/domain-workflows/domain-workflows.tsx

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,43 @@
1-
import React from 'react';
1+
import React, { useMemo } from 'react';
22

3+
import { useSuspenseQuery } from '@tanstack/react-query';
4+
import dynamic from 'next/dynamic';
5+
6+
import { type DescribeClusterResponse } from '@/route-handlers/describe-cluster/describe-cluster.types';
7+
import request from '@/utils/request';
38
import { type DomainPageTabContentProps } from '@/views/domain-page/domain-page-content/domain-page-content.types';
49

5-
import DomainWorkflowsHeader from './domain-workflows-header/domain-workflows-header';
6-
import DomainWorkflowsTable from './domain-workflows-table/domain-workflows-table';
10+
import isClusterAdvancedVisibilityEnabled from './helpers/is-cluster-advanced-visibility-enabled';
11+
12+
const DomainWorkflowsBasic = dynamic(
13+
() => import('@/views/domain-workflows-basic/domain-workflows-basic')
14+
);
15+
16+
const DomainWorkflowsHeader = dynamic(
17+
() => import('./domain-workflows-header/domain-workflows-header')
18+
);
19+
20+
const DomainWorkflowsTable = dynamic(
21+
() => import('./domain-workflows-table/domain-workflows-table')
22+
);
723

824
export default function DomainWorkflows(props: DomainPageTabContentProps) {
25+
const { data } = useSuspenseQuery<DescribeClusterResponse>({
26+
queryKey: ['describeCluster', props],
27+
queryFn: () =>
28+
request(`/api/clusters/${props.cluster}`).then((res) => res.json()),
29+
});
30+
31+
const isAdvancedVisibilityEnabled = useMemo(() => {
32+
return isClusterAdvancedVisibilityEnabled(data);
33+
}, [data]);
34+
35+
if (!isAdvancedVisibilityEnabled) {
36+
return (
37+
<DomainWorkflowsBasic domain={props.domain} cluster={props.cluster} />
38+
);
39+
}
40+
941
return (
1042
<>
1143
<DomainWorkflowsHeader domain={props.domain} cluster={props.cluster} />

0 commit comments

Comments
 (0)