feat(data-fabric): add query method with joins, aggregates, and filters#334
feat(data-fabric): add query method with joins, aggregates, and filters#334keerthikiranvalathoru wants to merge 5 commits intomainfrom
Conversation
Add `query()` method to EntityService supporting cross-entity joins,
aggregates, filters, grouping, and sorting via POST /api/EntityService/{EntityName}/query.
Changes:
- Add QUERY endpoint to DATA_FABRIC_ENDPOINTS
- Add query types: QueryAggregate, QueryJoin, QueryFilter, QueryFilterGroup, QuerySortOption, EntityQueryOptions, EntityQueryResponse
- Add query enums: QueryAggregateFunction, QueryJoinType, QueryLogicalOperator
- Add query() to EntityServiceModel interface and EntityService implementation
- Add bound query() to EntityMethods and createEntityMethods factory
- Add unit tests for service and model layers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add @experimental JSDoc tag and preview warning to all new query types, enums, and methods following the pattern from PR #328. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The query endpoint uses /api/EntityService/entity/{entityId}/query
matching the pattern of all other Data Fabric endpoints, not
/api/EntityService/{EntityName}/query.
Renames entityName -> entityId across endpoint, service, models, and tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verified query API against live Data Fabric at cloud.uipath.com.
The API returns { totalRecordCount, value }, not { totalCount, records }.
Also improved JSDoc: selectedFields is required when using aggregates
and must include groupBy fields.
Tested against keerthiuipclitest org:
- Filtered query on Customers (1000 records): OK
- Aggregates with groupBy on ClaimHeaders (5000 records): OK
- INNER join between ClaimHeaders and Policies: OK
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ting String operators must be lowercase: "contains", "startswith", "endswith". Verified via 44 live SDK tests against cloud.uipath.com. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| @@ -590,6 +590,40 @@ describe('Entity Models', () => { | |||
| ).rejects.toThrow(ENTITY_TEST_CONSTANTS.ERROR_MESSAGE_ENTITY_ID_UNDEFINED); | |||
| }); | |||
There was a problem hiding this comment.
Can you add integration test
|
Please update |
ee8f133 to
b55f4e1
Compare
|
|
@claude review this PR |
|
Claude finished @Sarath1018's task in 1m 58s —— View job PR Review
Overall this is a solid addition — the service implementation, method binding, and endpoint constant all follow existing patterns correctly. There are a few issues to address before merging. Bugs / Correctness1. Test mock data doesn't match The mock API response uses data: {
Records: [...],
TotalCount: 2
}After 2. No transform validation in tests (per conventions) For methods with a transform pipeline, tests must verify both:
None of the Missing Requirements3.
4. No integration test (as noted by @Sarath1018)
Type Safety5. The operators are a fixed, known set: export type QueryFilterOperator = '=' | '!=' | '>' | '<' | '>=' | '<=' | 'contains' | 'startswith' | 'endswith';6. export enum QueryLogicalOperator {
AND = 0,
OR = 1,
}All other enums in this file ( JSDoc Issues7. Examples use raw string literals instead of enums ( // ❌ Current
joins: [{ type: 'INNER', entity: 'Customer', on: { left: 'customerId', right: 'Id' } }]
aggregates: [{ function: 'COUNT', field: 'Id', alias: 'totalOrders' }]
// ✅ Should be
joins: [{ type: QueryJoinType.INNER, entity: 'Customer', on: { left: 'customerId', right: 'Id' } }]
aggregates: [{ function: QueryAggregateFunction.COUNT, field: 'Id', alias: 'totalOrders' }]Same issue in 8. Should be 9. Unusual /**
* @experimental
*
* /// warning
Preview: This type is experimental...
///This looks like Rust doc comment syntax and won't render correctly in TypeDoc. Use standard JSDoc: /**
* @experimental
* @remarks **Preview**: This type is experimental and may change or be removed in future releases.Minor10. Summary: The core implementation is correct. The main blockers are:
Items #6–#9 are documentation quality issues that should also be fixed before merging. |
| * // Simple query with filters | ||
| * const result = await entities.query('<entityId>', { | ||
| * selectedFields: ['name', 'email'], | ||
| * filterGroup: { | ||
| * logicalOperator: 0, | ||
| * queryFilters: [{ fieldName: 'status', operator: '=', value: 'active' }] | ||
| * }, | ||
| * limit: 50 | ||
| * }); | ||
| * | ||
| * // Cross-entity join query | ||
| * const joinResult = await entities.query('<entityId>', { | ||
| * selectedFields: ['Orders.orderId', 'Customer.name'], | ||
| * joins: [{ | ||
| * type: 'INNER', | ||
| * entity: 'Customer', | ||
| * on: { left: 'customerId', right: 'Id' } | ||
| * }], | ||
| * sortOptions: [{ fieldName: 'Orders.createdDate', isDescending: true }], | ||
| * limit: 100 | ||
| * }); | ||
| * | ||
| * // Aggregate query | ||
| * const stats = await entities.query('<entityId>', { | ||
| * aggregates: [ | ||
| * { function: 'COUNT', field: 'Id', alias: 'totalOrders' }, | ||
| * { function: 'SUM', field: 'amount', alias: 'totalAmount' } | ||
| * ], | ||
| * groupBy: ['status'] | ||
| * }); |
There was a problem hiding this comment.
Since we have defined enums for these values, why not use them in the examples as well?
line 491 logicalOperator: 0, -> QueryLogicalOperator.AND
line 501 type: 'INNER', -> QueryJoinType.INNER
and the aggregate function on line 512 and 513
| /** Comparison operator: "=", "!=", ">", "<", ">=", "<=", "contains", "startswith", "endswith" (lowercase for string operators) */ | ||
| operator: string; |
There was a problem hiding this comment.
Could we have this as an enum?
| /** Field to aggregate on */ | ||
| field: string; | ||
| /** Alias for the aggregated result */ | ||
| alias: string; |
There was a problem hiding this comment.
question : Is alias always required by the API?
| /** Starting offset for pagination (default: 0) */ | ||
| start?: number; | ||
| /** Maximum number of records to return (default: 100) */ | ||
| limit?: number; | ||
| } |
There was a problem hiding this comment.
The SDK already supports pagination. Can't we use that instead?
| const camelResponse = pascalToCamelCaseKeys(response.data); | ||
| return camelResponse; |
There was a problem hiding this comment.
@Sarath1018 we had plans of removing this conversion from records. Please check its status, we'll need to update here accordingly.
| const body: Record<string, unknown> = {}; | ||
|
|
||
| if (options.selectedFields) body.selectedFields = options.selectedFields; | ||
| if (options.aggregates) body.aggregates = options.aggregates; | ||
| if (options.joins) body.joins = options.joins; | ||
| if (options.filterGroup) body.filterGroup = options.filterGroup; | ||
| if (options.groupBy) body.groupBy = options.groupBy; | ||
| if (options.sortOptions) body.sortOptions = options.sortOptions; | ||
| if (options.start !== undefined) body.start = options.start; | ||
| if (options.limit !== undefined) body.limit = options.limit; | ||
|
|
||
| const response = await this.post<EntityQueryResponse>( | ||
| DATA_FABRIC_ENDPOINTS.ENTITY.QUERY(entityId), | ||
| body | ||
| ); |
There was a problem hiding this comment.
Is this necessary? Can we not pass the options object directly?
| * const stats = await entities.query('<entityId>', { | ||
| * aggregates: [ | ||
| * { function: 'COUNT', field: 'Id', alias: 'totalOrders' }, | ||
| * { function: 'SUM', field: 'amount', alias: 'totalAmount' } | ||
| * ], | ||
| * groupBy: ['status'] |
There was a problem hiding this comment.
This example uses aggregates without selectedFields. Wouldn't this error out?



Add
query()method to EntityService supporting cross-entity joins, aggregates, filters, grouping, and sorting via POST /api/EntityService/{EntityName}/query.Changes: