-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Aggregated queries #1 #8345
Aggregated queries #1 #8345
Conversation
...graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts
Outdated
Show resolved
Hide resolved
@@ -18,5 +18,5 @@ | |||
"src/**/*.d.ts", | |||
"src/**/*.ts", | |||
"src/**/*.tsx" | |||
] | |||
, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commited this although I suppose it should not be this way. I suppose something is wrong with my test file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this line seems strange, why would you add this? Sorry I don't understand the goal
...runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts
Outdated
Show resolved
Hide resolved
...graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts
Show resolved
Hide resolved
Question - do we want the operations to be computed on all of the records (as we do for total counts), or on the records matching the potential filters in the query? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR Summary
This PR adds initial support for aggregate queries in the GraphQL API, implementing min/max operations for datetime fields and min/max/avg/sum for number fields.
- Added
getAvailableAggregationsFromObjectFields
utility to dynamically generate aggregation fields based on field types - Modified
GraphqlQueryFindManyResolverService
to handle aggregated field queries using SQL OVER() clause - Added UUID validation utility to properly filter UUID fields during aggregation processing
- Updated
ConnectionTypeDefinitionFactory
to include aggregation fields in GraphQL schema - Potential issue: AggregationValue type incorrectly uses GraphQLString instead of union type for different field types
10 file(s) reviewed, 15 comment(s)
Edit PR Review Bot Settings | Greptile
@@ -18,5 +18,5 @@ | |||
"src/**/*.d.ts", | |||
"src/**/*.ts", | |||
"src/**/*.tsx" | |||
] | |||
, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Server-side files should not be included in front-end build config. Remove these files from the include array.
@@ -18,5 +18,5 @@ | |||
"src/**/*.d.ts", | |||
"src/**/*.ts", | |||
"src/**/*.tsx" | |||
] | |||
, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Test files (*.spec.ts) should be excluded as per line 8, but are being included here
@@ -9,5 +9,5 @@ | |||
"src/**/*.d.ts", | |||
"src/**/*.ts", | |||
"src/**/*.tsx" | |||
] | |||
, "../twenty-server/src/utils/is-uuid.ts", "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Server files should not be included in frontend tsconfig. Remove '../twenty-server/src/utils/is-uuid.ts' and '../twenty-server/src/utils/is-uuid.utils.spec.ts'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove also, no?
@@ -15,5 +15,5 @@ | |||
"tsup.config.ts", | |||
"tsup.ui.index.tsx", | |||
"vite.config.ts" | |||
] | |||
, "../twenty-server/src/utils/is-uuid.utils.spec.ts" ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Remove this line - server test files should not be included in front-end test config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove also, no?
if (hasEdges && fieldKey !== 'edges') { | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: skipping non-edges fields when edges exist could prevent aggregation fields from being processed at the root level
type AggregationValue = { | ||
type: typeof GraphQLString; | ||
description: string; | ||
fromField: string; | ||
aggregationOperation: AGGREGATION_OPERATIONS; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: AggregationValue.type is incorrectly typed as GraphQLString when it needs to handle both GraphQLISODateTime and GraphQLFloat
...graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util.ts
Outdated
Show resolved
Hide resolved
const uuidRegex = | ||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Consider caching this regex pattern outside the function since it's constant and regex compilation is expensive
@@ -0,0 +1,14 @@ | |||
export const isUuid = (value: unknown): boolean => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Function lacks JSDoc documentation explaining its purpose and expected input/output
|
||
it('should return false for similar looking strings', () => { | ||
expect(isUuid('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')).toBe(false); | ||
expect(isUuid('00000000-0000-0000-0000-000000000000')).toBe(true); // valid but all zeros |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: test case for all-zeros UUID returns true, but this may be a security risk if used for validation in sensitive contexts
@ijreilly I'm surprised that totalCount returns the count without applying the filters. How do we display the totalCount of a view for example? My intuition was that filter should be applied everywhere |
@FelixMalfait my bad - both totalCount and aggregated queries take the filter into account. all good ! |
objectFields: Object.values( | ||
Object.fromEntries( | ||
Object.entries(objectMetadataMapItem.fields).filter( | ||
([key, _value]) => !isUuid(key), // remove objectMetadataMapItem fields duplicates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels a bit ugly, did you consider solving the root cause in generateObjectMetadataMap
directly, introducing 2 maps instead of 1? I know it might be a largr refactor with many downstream consequences but it's probably better than this uuid hack which we'll have to pay back one day?
Note: We'll need to make sure to clear all the metadata cache when we deploy if we go with the "clean" way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I say 2 objects it's 2 different map for object level but also have 2 properties instead of one (for the field map by id + field map by name) within the ObjectMetadataMapItem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure how this map with ids as key are used today, I just know/think its for perf improvement reasons. @charlesBochet has more context and probably knows better if a refactor is desirable i think !
I think you understood that from the review but to give context, I introduced the filter here not to duplicate the aggregated fields computed (otherwise we would find "minCreatedAt" twice for instance), which we could also do differently not to allow a duplicated value in the selectedAggregatedFields array, which I had done initially and is maybe less ugly (but still a bit hacky)!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I understand! Thank you. But we should introduce two maps on byName and one byId. It doesn't make sense to mix both and lose information, to then re-create it later
@@ -83,9 +89,7 @@ export class GraphqlQuerySelectedFieldsParser { | |||
} | |||
|
|||
private shouldNotParseField(fieldKey: string): boolean { | |||
return ['__typename', 'totalCount', 'pageInfo', 'cursor'].includes( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we replace totalCount everywhere in the app by countUniqueId? We'll need to give a heads up for deprecation but we can start replacing it in our frontend at least. We plan to email people about API Key deprecation so we can let them know about this at the same time.
If I'm not mistaken we're doing a separate query for totalCount now while this could fit in the same query with all other aggregated fields?
Follow-ups to this PR
Other ideas @charlesBochet @FelixMalfait ? |
Great @ijreilly looking forward to having all this! |
const fields: GraphQLFieldConfigMap<any, any> = {}; | ||
const fields: GraphQLFieldConfigMap<any, any> = Object.assign( | ||
{}, | ||
...getAvailableAggregationsFromObjectFields(objectMetadata.fields).map( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should follow the existing factory pattern here:
this.connectionTypeFactory.create(...) see lines below
} | ||
|
||
type AggregationValue = { | ||
type: typeof GraphQLString; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a big fan of typeOf! why not directly putting the right one?
df11294
to
22d0fb9
Compare
Todo:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, well done @ijreilly !!
I agree with Charles comments and will try to address them.
...runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser.ts
Outdated
Show resolved
Hide resolved
First step of twentyhq#6868 Adds min.., max.. queries for DATETIME fields adds min.., max.., avg.., sum.. queries for NUMBER fields (count distinct operation and composite fields such as CURRENCY handling will be dealt with in a future PR) <img width="1422" alt="Capture d’écran 2024-11-06 à 15 48 46" src="https://github.com/user-attachments/assets/4bcdece0-ad3e-4536-9720-fe4044a36719"> --------- Co-authored-by: Charles Bochet <[email protected]> Co-authored-by: Weiko <[email protected]>
First step of #6868
Adds min.., max.. queries for DATETIME fields
adds min.., max.., avg.., sum.. queries for NUMBER fields
(count distinct operation and composite fields such as CURRENCY handling will be dealt with in a future PR)