-
Notifications
You must be signed in to change notification settings - Fork 121
feat: Get search attributes API #1044
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Assem-Uber
merged 8 commits into
cadence-workflow:master
from
Assem-Uber:feature/15450/search-attributes-api
Oct 13, 2025
+257
−0
Merged
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7064ffe
get search attributes API
Assem-Uber bf51873
add getSearchAttributes to client
Assem-Uber b5fbcd3
add type to the mock
Assem-Uber b60e3a6
update test cases
Assem-Uber 6341434
update query params and add type for response
Assem-Uber a66d05b
remove spread searchAttributesResponse since we have the type now
Assem-Uber 217ab14
Merge branch 'master' into feature/15450/search-attributes-api
Assem-Uber d26a44a
remove test case for extra fields
Assem-Uber File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { type NextRequest } from 'next/server'; | ||
|
||
import getSearchAttributes from '@/route-handlers/get-search-attributes/get-search-attributes'; | ||
import { type RouteParams } from '@/route-handlers/get-search-attributes/get-search-attributes.types'; | ||
import { routeHandlerWithMiddlewares } from '@/utils/route-handlers-middleware'; | ||
import routeHandlersDefaultMiddlewares from '@/utils/route-handlers-middleware/config/route-handlers-default-middlewares.config'; | ||
|
||
export async function GET( | ||
request: NextRequest, | ||
options: { params: RouteParams } | ||
) { | ||
return routeHandlerWithMiddlewares( | ||
getSearchAttributes, | ||
request, | ||
options, | ||
routeHandlersDefaultMiddlewares | ||
); | ||
} |
140 changes: 140 additions & 0 deletions
140
src/route-handlers/get-search-attributes/__tests__/get-search-attributes.node.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { NextRequest } from 'next/server'; | ||
|
||
import { IndexedValueType } from '@/__generated__/proto-ts/uber/cadence/api/v1/IndexedValueType'; | ||
import { mockGrpcClusterMethods } from '@/utils/route-handlers-middleware/middlewares/__mocks__/grpc-cluster-methods'; | ||
|
||
import getSearchAttributes from '../get-search-attributes'; | ||
import { | ||
type Context, | ||
type RequestParams, | ||
} from '../get-search-attributes.types'; | ||
|
||
jest.mock('../get-search-attributes.constants', () => ({ | ||
SYSTEM_SEARCH_ATTRIBUTES: new Set(['WorkflowType', 'CloseTime', 'IsCron']), | ||
})); | ||
|
||
const mockSearchAttributesResponse = { | ||
keys: { | ||
WorkflowType: IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD, | ||
CloseTime: IndexedValueType.INDEXED_VALUE_TYPE_DATETIME, | ||
IsCron: IndexedValueType.INDEXED_VALUE_TYPE_BOOL, | ||
CustomAttribute: IndexedValueType.INDEXED_VALUE_TYPE_STRING, | ||
AnotherCustom: IndexedValueType.INDEXED_VALUE_TYPE_INT, | ||
}, | ||
}; | ||
|
||
const baseUrl = | ||
'http://localhost:3000/api/clusters/test-cluster/search-attributes'; | ||
|
||
describe('getSearchAttributes', () => { | ||
it('should return response with all attributes by default', async () => { | ||
const { mockGetSearchAttributes, response, data } = await setup({ | ||
mockResponse: mockSearchAttributesResponse, | ||
}); | ||
|
||
expect(response.status).toBe(200); | ||
expect(data).toEqual(mockSearchAttributesResponse); | ||
expect(mockGetSearchAttributes).toHaveBeenCalledWith({}); | ||
}); | ||
|
||
it('should handle RPC errors gracefully', async () => { | ||
const { mockGetSearchAttributes, response, data } = await setup({ | ||
mockError: new Error('RPC connection failed'), | ||
}); | ||
|
||
expect(response.status).toBe(500); | ||
expect(data).toHaveProperty('message'); | ||
expect(data.message).toContain('Failed to fetch search attributes'); | ||
expect(mockGetSearchAttributes).toHaveBeenCalledWith({}); | ||
}); | ||
|
||
it('should handle empty search attributes response', async () => { | ||
const { response, data } = await setup({ | ||
mockResponse: { keys: {} }, | ||
}); | ||
|
||
expect(response.status).toBe(200); | ||
expect(data.keys).toEqual({}); | ||
}); | ||
|
||
it('should return other fields from RPC response along with the keys', async () => { | ||
const mockResponse = { | ||
keys: mockSearchAttributesResponse.keys, | ||
metadata: { version: '1.0' }, | ||
}; | ||
|
||
const { response, data } = await setup({ | ||
mockResponse, | ||
}); | ||
|
||
expect(response.status).toBe(200); | ||
expect(data).toEqual(mockResponse); | ||
}); | ||
|
||
it('should filter system attributes when category=system', async () => { | ||
const expectedSystemKeys = { | ||
WorkflowType: IndexedValueType.INDEXED_VALUE_TYPE_KEYWORD, | ||
CloseTime: IndexedValueType.INDEXED_VALUE_TYPE_DATETIME, | ||
IsCron: IndexedValueType.INDEXED_VALUE_TYPE_BOOL, | ||
}; | ||
|
||
const { response, data } = await setup({ | ||
mockResponse: mockSearchAttributesResponse, | ||
url: `${baseUrl}?category=system`, | ||
}); | ||
|
||
expect(response.status).toBe(200); | ||
expect(data.keys).toEqual(expectedSystemKeys); | ||
}); | ||
|
||
it('should filter custom attributes when category=custom', async () => { | ||
const expectedCustomKeys = { | ||
CustomAttribute: IndexedValueType.INDEXED_VALUE_TYPE_STRING, | ||
AnotherCustom: IndexedValueType.INDEXED_VALUE_TYPE_INT, | ||
}; | ||
|
||
const { response, data } = await setup({ | ||
mockResponse: mockSearchAttributesResponse, | ||
url: `${baseUrl}?category=custom`, | ||
}); | ||
|
||
expect(response.status).toBe(200); | ||
expect(data.keys).toEqual(expectedCustomKeys); | ||
}); | ||
}); | ||
|
||
async function setup(options?: { | ||
mockResponse?: any; | ||
mockError?: Error; | ||
url?: string; | ||
}) { | ||
const mockGetSearchAttributes = jest | ||
.spyOn(mockGrpcClusterMethods, 'getSearchAttributes') | ||
.mockImplementationOnce(async () => { | ||
if (options?.mockError) { | ||
throw options.mockError; | ||
} | ||
return options?.mockResponse || { keys: {} }; | ||
}); | ||
|
||
const context: Context = { | ||
grpcClusterMethods: mockGrpcClusterMethods, | ||
}; | ||
|
||
const request = new NextRequest(options?.url || baseUrl); | ||
const requestParams: RequestParams = { | ||
params: { cluster: 'test-cluster' }, | ||
}; | ||
|
||
const response = await getSearchAttributes(request, requestParams, context); | ||
const data = await response.json(); | ||
|
||
return { | ||
mockGetSearchAttributes, | ||
context, | ||
request, | ||
requestParams, | ||
response, | ||
data, | ||
}; | ||
} |
20 changes: 20 additions & 0 deletions
20
src/route-handlers/get-search-attributes/get-search-attributes.constants.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
/** | ||
* System attributes as defined by Cadence. | ||
* These are built-in attributes that are automatically indexed by the system. | ||
* https://github.com/cadence-workflow/cadence/blob/b9e01ea9b881daff690434419b253d1d36fc486a/common/definition/indexedKeys.go#L92 | ||
*/ | ||
export const SYSTEM_SEARCH_ATTRIBUTES: Set<string> = new Set([ | ||
'DomainID', | ||
'WorkflowID', | ||
'RunID', | ||
'WorkflowType', | ||
'StartTime', | ||
'ExecutionTime', | ||
'CloseTime', | ||
'CloseStatus', | ||
'HistoryLength', | ||
'TaskList', | ||
'IsCron', | ||
'NumClusters', | ||
'UpdateTime', | ||
]); |
63 changes: 63 additions & 0 deletions
63
src/route-handlers/get-search-attributes/get-search-attributes.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { type NextRequest, NextResponse } from 'next/server'; | ||
|
||
import decodeUrlParams from '@/utils/decode-url-params'; | ||
import { getHTTPStatusCode, GRPCError } from '@/utils/grpc/grpc-error'; | ||
import logger, { type RouteHandlerErrorPayload } from '@/utils/logger'; | ||
|
||
import { SYSTEM_SEARCH_ATTRIBUTES } from './get-search-attributes.constants'; | ||
import { | ||
type GetSearchAttributesResponse, | ||
type Context, | ||
type RequestParams, | ||
type RouteParams, | ||
} from './get-search-attributes.types'; | ||
|
||
export default async function getSearchAttributes( | ||
request: NextRequest, | ||
requestParams: RequestParams, | ||
ctx: Context | ||
): Promise<Response> { | ||
const decodedParams = decodeUrlParams(requestParams.params) as RouteParams; | ||
const category = request.nextUrl.searchParams.get('category'); | ||
try { | ||
const searchAttributesResponse = | ||
await ctx.grpcClusterMethods.getSearchAttributes({}); | ||
|
||
let filteredKeys = searchAttributesResponse.keys || {}; | ||
|
||
if (category === 'system') { | ||
filteredKeys = Object.fromEntries( | ||
Object.entries(filteredKeys).filter(([key]) => | ||
SYSTEM_SEARCH_ATTRIBUTES.has(key) | ||
) | ||
); | ||
} else if (category === 'custom') { | ||
filteredKeys = Object.fromEntries( | ||
Object.entries(filteredKeys).filter( | ||
([key]) => !SYSTEM_SEARCH_ATTRIBUTES.has(key) | ||
) | ||
); | ||
} | ||
|
||
return NextResponse.json({ | ||
Assem-Uber marked this conversation as resolved.
Show resolved
Hide resolved
|
||
...searchAttributesResponse, | ||
adhityamamallan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
keys: filteredKeys, | ||
} satisfies GetSearchAttributesResponse); | ||
} catch (e) { | ||
logger.error<RouteHandlerErrorPayload>( | ||
{ requestParams: decodedParams, error: e }, | ||
'Failed to fetch search attributes from Cadence service' | ||
); | ||
|
||
return NextResponse.json( | ||
{ | ||
message: | ||
e instanceof GRPCError | ||
? e.message | ||
: 'Failed to fetch search attributes', | ||
cause: e, | ||
}, | ||
{ status: getHTTPStatusCode(e) } | ||
); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/route-handlers/get-search-attributes/get-search-attributes.types.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { type GRPCClusterMethods } from '@/utils/grpc/grpc-client'; | ||
|
||
export { type GetSearchAttributesResponse } from '@/__generated__/proto-ts/uber/cadence/api/v1/GetSearchAttributesResponse'; | ||
|
||
export type SearchAttributesCategory = 'all' | 'system' | 'custom'; | ||
|
||
export type Context = { | ||
grpcClusterMethods: GRPCClusterMethods; | ||
}; | ||
|
||
export type RouteParams = { | ||
cluster: string; | ||
}; | ||
|
||
export type RequestParams = { | ||
params: RouteParams; | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.