Skip to content

Filter for DependenciesList and DependentsList#5267

Open
thomasdax98 wants to merge 11 commits intomainfrom
thomasdax98/deps-list-filter
Open

Filter for DependenciesList and DependentsList#5267
thomasdax98 wants to merge 11 commits intomainfrom
thomasdax98/deps-list-filter

Conversation

@thomasdax98
Copy link
Member

@thomasdax98 thomasdax98 commented Mar 2, 2026

Add filter to DependenciesList and DependentsList:

filter.mp4

Jira: https://vivid-planet.atlassian.net/browse/PHSB2C-6541

@thomasdax98 thomasdax98 added this to the v9 milestone Mar 2, 2026
return qb;
}

private applyFilter(qb: Knex.QueryBuilder, filter?: DependencyFilter | DependentFilter): void {
Copy link
Member Author

Choose a reason for hiding this comment

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

I could not use the helpers from https://github.com/vivid-planet/comet/blob/99950fa9d1a1b62e48722808f89483006f824046/packages/api/cms-api/src/common/filter/mikro-orm.ts because block_index_dependencies isn't an actual entity and we need to join it on EntityInfo. Thus, the filter and sort logic is basically duplicated

Copy link
Member

Choose a reason for hiding this comment

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

if you create a entity class for block_index_dependencies (similar to EntityInfoObject) and query that instead of using query builder, we could use the helpers.

I just noticed here we join EntityInfo (on block_index_dependencies), but block_index_dependencies (the view) does already join EntityInfo for entityVisible: we could add name & secondaryInformation to block_index_dependencies and would not have to join again. Or is there a reason, like it updates faster as it is not materialized?

Comment on lines +290 to +291
private getBaseQueryBuilder(
internalFilter: { targetEntityName?: string; rootEntityName?: string; targetId?: string; rootId?: string },
Copy link
Member Author

@thomasdax98 thomasdax98 Mar 17, 2026

Choose a reason for hiding this comment

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

I'm not 100% certain if this split into internalFilter and other filters (in L316) is a good idea. We could maybe combine this. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO getBaseQueryBuilder, applyFilter, and applySort could be implemented in a single getQueryBuilderWithFilters (or getQueryBuilderWithFiltersAndSort) as before.

Comment on lines +84 to +91
function getDisplayNameString(displayName: ReactNode, fallback: string): string {
if (typeof displayName === "string") return displayName;
if (isValidElement(displayName)) {
const { defaultMessage } = displayName.props as { defaultMessage?: string };
if (typeof defaultMessage === "string") return defaultMessage;
}
return fallback;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a bit of a workaround because displayName is a ReactNode. I thought about changing it to a string, but the displayName field is shared with DocumentInterface so I didn't want to touch it. Also this is defined in Page.tsx outside a react component, so intl.formatMessage() isn't a suitable alternative either.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The implementation isn't correct because it would always use the default message, not the translated one. Instead I'd implement it like we do here:

if (typeof block.displayName === "string") {
blockName = block.displayName;
} else if (isFormattedMessage(block.displayName)) {
blockName = intl.formatMessage(block.displayName.props);
} else {
throw new TypeError("Block displayName must be either a string or a FormattedMessage");
}

displayName will always be either string or <FormattedMessage>.


Users can now filter dependencies/dependents by name, type, secondary information, and visibility, and sort by all columns. A default filter shows only visible items. The `GqlFilter` type is now exported from `@comet/admin`.

**Breaking changes:**
Copy link
Member Author

Choose a reason for hiding this comment

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

I deliberately didn't add these changes to the migration guide since I'm 99% sure these apis aren't used in any real project so it would just unnecessarily prolong the guide. But maybe it doesn't matter since the primary target are AI agents.
What do you think, should I add it?

@thomasdax98 thomasdax98 force-pushed the thomasdax98/deps-list-filter branch from d03d93d to e747a7c Compare March 18, 2026 00:03
@thomasdax98 thomasdax98 marked this pull request as ready for review March 18, 2026 00:03
@auto-assign auto-assign bot requested a review from johnnyomair March 18, 2026 00:03
@thomasdax98 thomasdax98 requested review from Copilot, johnnyomair and nsams and removed request for johnnyomair March 18, 2026 00:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds server-side filtering and sorting support to the Dependencies/Dependents lists end-to-end (GraphQL schema + API query handling + admin UI grids), including updated docs and demo queries.

Changes:

  • Extend the CMS API GraphQL inputs/args to support filter (with nested StringFilter/BooleanFilter) and sort (DependencySort).
  • Implement filter/sort application in DependenciesService and pass sort through the resolvers.
  • Update admin DependenciesList/DependentsList to enable DataGrid filtering/sorting and forward $filter/$sort variables; update docs/demo queries accordingly.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/api/cms-api/src/dependencies/dto/dependency-sort.ts Introduces GraphQL input + enum for dependency sorting.
packages/api/cms-api/src/dependencies/dto/dependencies.filter.ts Expands dependency/dependent filters to nested string/boolean filters plus and/or.
packages/api/cms-api/src/dependencies/dto/dependencies.args.ts Adds sort argument to dependencies/dependents GraphQL args.
packages/api/cms-api/src/dependencies/dependents.resolver.factory.ts Passes sort through to service for dependents.
packages/api/cms-api/src/dependencies/dependencies.service.ts Implements Knex-based filter/sort application and ensures count query clears ordering.
packages/api/cms-api/src/dependencies/dependencies.resolver.factory.ts Passes sort through to service for dependencies.
packages/api/cms-api/schema.gql Updates public schema for new filter/sort inputs and args; rehomes shared filter/sort types.
packages/admin/cms-admin/src/dependencies/DependentsList.tsx Enables grid filter/sort UI and forwards filter/sort vars to GraphQL query.
packages/admin/cms-admin/src/dependencies/DependenciesList.tsx Enables grid filter/sort UI and forwards filter/sort vars to GraphQL query.
packages/admin/cms-admin/src/dam/FileForm/EditFile.gql.ts Updates dependents query to accept/forward $filter/$sort and request visible.
packages/admin/admin/src/index.ts Exports GqlFilter type from muiGridFilterToGql.
docs/docs/2-core-concepts/7-dependencies/index.md Updates documentation example queries for new filter/sort variables and visible.
demo/api/schema.gql Updates demo schema to include new filter/sort capabilities.
demo/admin/src/documents/pages/EditPage.tsx Updates demo page queries to accept/forward $filter/$sort and request visible.
.changeset/deps-list-filter-sort.md Declares version bumps and documents breaking changes + required query updates.

this.applyStringFilterToKnex(qb, '"EntityInfo"."secondaryInformation"', filter.secondaryInformation);
}
if (filter.visible) {
if (filter.visible.equal !== undefined) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +366 to +384
if (filter.contains !== undefined) {
qb.andWhere(knex.raw(`${column} ILIKE ?`, [`%${filter.contains}%`]));
}
if (filter.notContains !== undefined) {
qb.andWhere(knex.raw(`${column} NOT ILIKE ?`, [`%${filter.notContains}%`]));
}
if (filter.startsWith !== undefined) {
qb.andWhere(knex.raw(`${column} ILIKE ?`, [`${filter.startsWith}%`]));
}
if (filter.endsWith !== undefined) {
qb.andWhere(knex.raw(`${column} ILIKE ?`, [`%${filter.endsWith}`]));
}
if (filter.equal !== undefined) {
qb.andWhere(knex.raw(`${column} = ?`, [filter.equal]));
}
if (filter.notEqual !== undefined) {
qb.andWhere(knex.raw(`${column} != ?`, [filter.notEqual]));
}
if (filter.isAnyOf !== undefined && filter.isAnyOf.length > 0) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines +345 to +361
if (filter.and) {
for (const subFilter of filter.and) {
qb.andWhere((sub) => {
this.applyFilter(sub, subFilter);
});
}
}
if (filter.or) {
const orFilters = filter.or;
qb.andWhere((outer) => {
for (const subFilter of orFilters) {
outer.orWhere((sub) => {
this.applyFilter(sub, subFilter);
});
}
});
}
Copy link
Member Author

Choose a reason for hiding this comment

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

@thomasdax98 thomasdax98 changed the title Draft: Filter for DependenciesList and DependentsList Filter for DependenciesList and DependentsList Mar 18, 2026
@thomasdax98 thomasdax98 changed the base branch from next to main March 18, 2026 11:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds server- and client-side filtering/sorting for the Dependencies/Dependents lists, updating the CMS GraphQL API inputs and the admin UI data grids accordingly.

Changes:

  • Extend GraphQL API for dependencies / dependents with filter (nested StringFilter / BooleanFilter + and/or) and sort (DependencySort).
  • Update DependenciesService to apply new filter + sort options against the block_index_dependencies view and EntityInfo join.
  • Update admin DependenciesList / DependentsList to use DataGrid filter/sort UI and pass $filter / $sort variables; update docs/demo queries and schemas.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/api/cms-api/src/dependencies/dto/dependency-sort.ts Introduces DependencySort + DependencySortField GraphQL input/enum.
packages/api/cms-api/src/dependencies/dto/dependencies.filter.ts Expands filter inputs to nested StringFilter/BooleanFilter and and/or.
packages/api/cms-api/src/dependencies/dto/dependencies.args.ts Adds sort argument to dependencies/dependents field args.
packages/api/cms-api/src/dependencies/dependents.resolver.factory.ts Wires sort through to service.
packages/api/cms-api/src/dependencies/dependencies.resolver.factory.ts Wires sort through to service.
packages/api/cms-api/src/dependencies/dependencies.service.ts Implements Knex-side filter/sort application for dependencies/dependents queries.
packages/api/cms-api/schema.gql Updates generated schema to expose new filter/sort inputs and visible field usage.
packages/admin/cms-admin/src/dependencies/DependentsList.tsx Adds DataGrid filter/sort UI and forwards GQL filter/sort variables.
packages/admin/cms-admin/src/dependencies/DependenciesList.tsx Adds DataGrid filter/sort UI and forwards GQL filter/sort variables.
packages/admin/cms-admin/src/dam/FileForm/EditFile.gql.ts Updates dependents query to accept and forward $filter/$sort; includes visible.
packages/admin/admin/src/index.ts Re-exports GqlFilter type from @comet/admin.
docs/docs/2-core-concepts/7-dependencies/index.md Updates documentation query example to include $filter/$sort and visible.
demo/api/schema.gql Updates demo schema for new filter/sort inputs.
demo/admin/src/documents/pages/EditPage.tsx Updates demo queries to include $filter/$sort and request visible.
.changeset/deps-list-filter-sort.md Declares version bumps and documents breaking API/query changes.

Comment on lines +84 to +91
function getDisplayNameString(displayName: ReactNode, fallback: string): string {
if (typeof displayName === "string") return displayName;
if (isValidElement(displayName)) {
const { defaultMessage } = displayName.props as { defaultMessage?: string };
if (typeof defaultMessage === "string") return defaultMessage;
}
return fallback;
}
Comment on lines +346 to +360
for (const subFilter of filter.and) {
qb.andWhere((sub) => {
this.applyFilter(sub, subFilter);
});
}
}
if (filter.or?.length) {
const orFilters = filter.or;
qb.andWhere((outer) => {
for (const subFilter of orFilters) {
outer.orWhere((sub) => {
this.applyFilter(sub, subFilter);
});
}
});
Comment on lines +363 to 377
private applyStringFilterToKnex(qb: Knex.QueryBuilder, column: string, filter: StringFilter): void {
const knex = this.entityManager.getKnex("read");

if (filter?.rootColumnName) {
qb.andWhere({ rootColumnName: filter.rootColumnName });
if (filter.contains !== undefined && filter.contains !== null) {
qb.andWhere(knex.raw(`${column} ILIKE ?`, [`%${filter.contains}%`]));
}
if (filter.notContains !== undefined && filter.notContains !== null) {
qb.andWhere(knex.raw(`${column} NOT ILIKE ?`, [`%${filter.notContains}%`]));
}
if (filter.startsWith !== undefined && filter.startsWith !== null) {
qb.andWhere(knex.raw(`${column} ILIKE ?`, [`${filter.startsWith}%`]));
}
if (filter.endsWith !== undefined && filter.endsWith !== null) {
qb.andWhere(knex.raw(`${column} ILIKE ?`, [`%${filter.endsWith}`]));
}
sortBy: "visible",
toGqlFilter: (filterItem) => {
if (filterItem.value === undefined || filterItem.value === "") return {};
return { visible: { equal: filterItem.value === "true" || filterItem.value === true } } as unknown as GqlFilter;
sortBy: "visible",
toGqlFilter: (filterItem) => {
if (filterItem.value === undefined || filterItem.value === "") return {};
return { visible: { equal: filterItem.value === "true" || filterItem.value === true } } as unknown as GqlFilter;
Comment on lines +84 to +91
function getDisplayNameString(displayName: ReactNode, fallback: string): string {
if (typeof displayName === "string") return displayName;
if (isValidElement(displayName)) {
const { defaultMessage } = displayName.props as { defaultMessage?: string };
if (typeof defaultMessage === "string") return defaultMessage;
}
return fallback;
}
};

return (
<div style={{ display: "flex" }}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of inline styles, could we use MUI's Box?

@Field(() => [DependencySort], { nullable: true })
@ValidateNested({ each: true })
@Type(() => DependencySort)
@IsOptional()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we use IsUndefinable (and maybe IsNullable)?

},
paginationArgs?: { offset: number; limit: number },
options?: { forceRefresh: boolean },
options?: { forceRefresh: boolean; sort?: DependencySort[] },
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's weird that filter and pagination are passed as root args, while sort is passed via options.

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