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

Add filter on array and jsonb field types #7839

Merged
merged 9 commits into from
Oct 21, 2024
Merged

Add filter on array and jsonb field types #7839

merged 9 commits into from
Oct 21, 2024

Conversation

gitstart-app[bot]
Copy link
Contributor

@gitstart-app gitstart-app bot commented Oct 19, 2024

This PR was created by GitStart to address the requirements from this ticket: TWNTY-6784.
This ticket was imported from: TWNTY-6784


Description

  • Add filter on array and jsonb field types
  • We did not implement the contains any filter for arrays on the frontend because we would need to change the UI design since this should be an array of values, and now we have only one input

Demo

https://www.loom.com/share/0facf752b63f4120b5d4ea4ee9772d35?sid=d7bde469-e6a9-4298-a637-d81d40695a86

Fixes #6784

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 pull request adds filtering capabilities for array and jsonb field types, addressing the requirements outlined in issue #6784. Key changes include:

  • Added support for FieldMetadataType.Array and FieldMetadataType.RawJson in filter definitions
  • Introduced new ArrayFilter and RawJsonFilter types in GraphQL operations
  • Implemented isMatchingArrayFilter and isMatchingRawJsonFilter utility functions
  • Updated GraphqlQueryFilterFieldParser and computeWhereConditionParts to handle new array and jsonb operators
  • Modified UI components to support filtering on these new field types

The implementation appears consistent with existing patterns and integrates well with the current filtering system. However, there are a few points to consider:

  • The frontend implementation for "contains any" filter for arrays is not included due to UI design constraints
  • Some error handling and edge cases may need further review
  • Performance implications of new filtering operations, especially on large datasets, should be evaluated
  • Additional testing may be required to ensure robustness of the new filtering capabilities

13 file(s) reviewed, 26 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines 107 to 111
export type ArrayFilter = {
contains?: string[];
contains_any?: string[];
not_contains?: string[];
};
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 adding a type for the array elements instead of using string[] for all cases. This would provide better type safety for different array types.

};

export type RawJsonFilter = {
contains_filter?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

style: The contains_filter property uses a string type. Consider using a more specific type or adding a comment explaining the expected format of this filter.

Comment on lines 78 to 79
case 'ARRAY':
return [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain];
Copy link
Contributor

Choose a reason for hiding this comment

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

style: ARRAY type doesn't include empty operands. Consider adding them for consistency with other types

Copy link
Member

@Weiko Weiko Oct 21, 2024

Choose a reason for hiding this comment

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

Yes let's add isEmpty here as greptile suggested 👍

Comment on lines 80 to 82
default:
return [];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Default case returns an empty array. Consider throwing an error or logging a warning for unexpected filter types

Comment on lines 295 to 299
case 'ARRAY':
emptyRecordFilter = {
[correspondingField.name]: { not_contains: undefined } as ArrayFilter,
};
break;
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: The 'not_contains: undefined' filter for ARRAY might not work as expected. Consider using an empty array instead

Comment on lines +82 to +86
case 'contains':
return {
sql: `"${objectNameSingular}"."${key}" @> ARRAY[:...${key}${uuid}]`,
params: { [`${key}${uuid}`]: value },
};
Copy link
Contributor

Choose a reason for hiding this comment

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

style: The 'contains' operator for arrays uses the @> operator, which checks if the array column contains all elements of the input array. This is correct, but consider adding a comment explaining the behavior for clarity.

Comment on lines 88 to 92
case 'contains_any':
return {
sql: `"${objectNameSingular}"."${key}" && ARRAY[:...${key}${uuid}]`,
params: { [`${key}${uuid}`]: value },
};
Copy link
Contributor

Choose a reason for hiding this comment

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

style: The 'contains_any' operator uses the && operator, which checks for any overlap between arrays. This is correct, but a comment explaining the behavior would be helpful.

Comment on lines 99 to 103
case 'contains_filter':
return {
sql: `"${objectNameSingular}"."${key}"::text LIKE :${key}${uuid}`,
params: { [`${key}${uuid}`]: `%${value}%` },
};
Copy link
Contributor

Choose a reason for hiding this comment

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

style: The 'contains_filter' operator casts the column to text and uses LIKE. This works for jsonb, but might not be efficient for large objects. Consider using jsonb-specific operators for better performance.


import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type';

export const RawJsonFilterType = new GraphQLInputObjectType({
name: 'RawJsonFilter',
fields: {
is: { type: FilterIs },
contains_filter: { type: GraphQLString },
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Ensure that the 'contains_filter' implementation in the backend correctly handles JSON string matching.

Comment on lines 5 to 11
export const RawJsonFilterType = new GraphQLInputObjectType({
name: 'RawJsonFilter',
fields: {
is: { type: FilterIs },
contains_filter: { type: GraphQLString },
},
});
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 adding a description field to the GraphQLInputObjectType for better documentation.

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.

This was not mentioned in the initial issue but I would have added a featureFlag on the frontend. I left a few comments otherwise the rest LGTM

@@ -104,6 +104,17 @@ export type PhonesFilter = {
primaryPhoneCountryCode?: StringFilter;
};

export type ArrayFilter = {
contains?: string[];
contains_any?: string[];
Copy link
Member

Choose a reason for hiding this comment

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

I wouldn't put contains_any in this PR if not fully implemented.

Copy link
Member

Choose a reason for hiding this comment

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

We will actually re-implement it later when we will have advanced filter (using OR)

};

export type RawJsonFilter = {
contains_filter?: string;
Copy link
Member

Choose a reason for hiding this comment

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

filter is redundant here, let's use "contains" as it is the name for other filters

Comment on lines 78 to 79
case 'ARRAY':
return [ViewFilterOperand.Contains, ViewFilterOperand.DoesNotContain];
Copy link
Member

@Weiko Weiko Oct 21, 2024

Choose a reason for hiding this comment

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

Yes let's add isEmpty here as greptile suggested 👍

@@ -290,6 +292,20 @@ export const applyEmptyFilters = (
],
};
break;
case 'ARRAY':
emptyRecordFilter = {
[correspondingField.name]: { not_contains: undefined } as ArrayFilter,
Copy link
Member

Choose a reason for hiding this comment

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

applyEmptyFilter won't be called for array types, missing in turnObjectDropdownFilterIntoQueryFilter

@@ -290,6 +292,20 @@ export const applyEmptyFilters = (
],
};
break;
case 'ARRAY':
emptyRecordFilter = {
[correspondingField.name]: { not_contains: undefined } as ArrayFilter,
Copy link
Member

Choose a reason for hiding this comment

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

I don't think 'not_contains: undefined' will work as intended 🤔 Have you tested this use case?

if (operator === 'in') {
if (
operator === 'in' ||
operator === 'contains' ||
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 will have some issues in the future with this name. Contains is actually a bit "vague" and could be mistaken for "like" in StringsFilter 🤔 (the FE actually displays "Contains" in both cases)

Copy link
Member

Choose a reason for hiding this comment

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

cc @charlesBochet operators are not mapped to particular types and can be mistaken. We could add "array" in the operator here for example, just so you know.

case arrayFilter.not_contains !== undefined: {
return !arrayFilter.not_contains.every((item) => value.includes(item));
}

Copy link
Member

Choose a reason for hiding this comment

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

Let's add "IS" filter for array type to be able to filter with NULL, NOT_NULL in those columns.

value: string;
}) => {
switch (true) {
case rawJsonFilter.contains_filter !== undefined: {
Copy link
Member

Choose a reason for hiding this comment

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

To my point above, let's use the term "like" like the string type here since that's actually what happens behind the scene

sql: `NOT ("${objectNameSingular}"."${key}" && ARRAY[:...${key}${uuid}])`,
params: { [`${key}${uuid}`]: value },
};
case 'contains_filter':
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned above, let's not introduce a new operator and reuse "like" for JSON. We can imagine better operators later when we will actually leverage jsonb operators but right now we are simply doing a LIKE operation.

@Weiko Weiko merged commit 7b10bfa into main Oct 21, 2024
18 checks passed
@Weiko Weiko deleted the TWNTY-6784 branch October 21, 2024 16:11
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.

Add filter on array and jsonb field types
2 participants