Skip to content
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

Merged
merged 33 commits into from
Nov 14, 2024
Merged

Aggregated queries #1 #8345

merged 33 commits into from
Nov 14, 2024

Conversation

ijreilly
Copy link
Collaborator

@ijreilly ijreilly commented Nov 5, 2024

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)

Capture d’écran 2024-11-06 à 15 48 46

@ijreilly ijreilly changed the title wip Aggregated queries - wip Nov 6, 2024
@@ -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" ]
Copy link
Collaborator Author

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

Copy link
Member

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

@ijreilly ijreilly marked this pull request as ready for review November 6, 2024 11:49
@ijreilly
Copy link
Collaborator Author

ijreilly commented Nov 6, 2024

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?
@Bonapara @FelixMalfait

@ijreilly ijreilly changed the title Aggregated queries - wip Aggregated queries #1 Nov 6, 2024
Copy link
Contributor

@greptile-apps greptile-apps bot left a 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" ]
Copy link
Contributor

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" ]
Copy link
Contributor

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" ]
Copy link
Contributor

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'

Copy link
Member

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" ]
Copy link
Contributor

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove also, no?

Comment on lines 41 to 43
if (hasEdges && fieldKey !== 'edges') {
continue;
}
Copy link
Contributor

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

Comment on lines 17 to 22
type AggregationValue = {
type: typeof GraphQLString;
description: string;
fromField: string;
aggregationOperation: AGGREGATION_OPERATIONS;
};
Copy link
Contributor

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

Comment on lines 10 to 11
const uuidRegex =
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
Copy link
Contributor

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 => {
Copy link
Contributor

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
Copy link
Contributor

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

@FelixMalfait
Copy link
Member

@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

@ijreilly
Copy link
Collaborator Author

ijreilly commented Nov 6, 2024

@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
Copy link
Member

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

Copy link
Member

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

Copy link
Collaborator Author

@ijreilly ijreilly Nov 6, 2024

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)!

Copy link
Member

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(
Copy link
Member

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?

@ijreilly
Copy link
Collaborator Author

ijreilly commented Nov 8, 2024

Follow-ups to this PR

  1. Refactor fields maps generated by generateObjectMetadataMap to differentiate between map with field names as keys and map with ids as keys (@FelixMalfait I know you would have wanted it to be integrated to this PR but im trying to close things before leaving on holidays today, @charlesBochet agreed it could wait - I did change the way the duplicates in fieldMetadataMap are handled though)
  2. Replace totalCount with countUniqueId and compute it in the same query as other fields (and aggregated fields)
  3. Implement aggregation fields for all other field types
  4. Use aggregated queries in the FE (they are only exposed in the api with this PR)

Other ideas @charlesBochet @FelixMalfait ?

@FelixMalfait
Copy link
Member

Great @ijreilly looking forward to having all this!

const fields: GraphQLFieldConfigMap<any, any> = {};
const fields: GraphQLFieldConfigMap<any, any> = Object.assign(
{},
...getAvailableAggregationsFromObjectFields(objectMetadata.fields).map(
Copy link
Member

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;
Copy link
Member

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?

@charlesBochet
Copy link
Member

charlesBochet commented Nov 11, 2024

Todo:

  • check hasEdges
  • Use feature flag
  • Take relations into consideration
  • Handle composite amount field
  • Test integration

Copy link
Member

@Weiko Weiko left a 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.

@Weiko Weiko merged commit a799370 into main Nov 14, 2024
16 checks passed
@Weiko Weiko deleted the aggregate-queries-1 branch November 14, 2024 17:05
khuddite pushed a commit to khuddite/twenty that referenced this pull request Nov 18, 2024
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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants