Skip to content

FE-840: Add Property Filter To Entities Visualizer#8786

Open
yusufkinatas wants to merge 5 commits into
mainfrom
yk/follow-up-entities-visualizer-property-filter
Open

FE-840: Add Property Filter To Entities Visualizer#8786
yusufkinatas wants to merge 5 commits into
mainfrom
yk/follow-up-entities-visualizer-property-filter

Conversation

@yusufkinatas

@yusufkinatas yusufkinatas commented May 29, 2026

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Follow-up for #8783 based on the task
Adds per-property value filtering to the Entities Visualizer. Previously the ribbon only supported filtering by web, type, and archived status; you could narrow the table by which entities were shown but not by the values of their properties.

This lets a user filter the table on the properties they can see — e.g. Age > 13, Name contains "Acme", Active is true, or Email has any value — by adding property-filter "pills" to the existing filter ribbon. Each pill picks a property, an operator appropriate to the property's value kind, and (where relevant) a value; the resulting clauses are ANDed with the existing web / type / archived filters and pushed down into the graph query.

🔍 What does this change?

Filter model & query building

  • Extends EntitiesFilterState with a propertyFilters array (data/types.ts).
  • Adds data/property-filters/types.ts defining PropertyFilter, the PropertyFilterOperator set, the FilterValueKind (number / string / boolean), and the FilterableProperty shape (filterable, or disabled-with-reason).
  • buildPropertyFilterClause (data/property-filters/build-property-filter-clause.ts) translates a single filter into a graph Filter clause as a pure, unit-testable function. It owns parameter typing (numbers coerced to JS numbers so comparisons are numeric, strings kept untrimmed for case-sensitive matching) and the exists / not exists composition. An incomplete or invalid filter yields null and contributes no clause, so an unfinished pill is inert rather than matching nothing. buildEntitiesFilter now folds these clauses into the query.
  • deriveFilterableProperties (data/property-filters/derive-filterable-properties.ts) derives the pickable properties from the same closed-entity-type / definitions data that builds the visible columns ("filter on the columns you see"). Properties that can't be filtered in v1 (lists, nested objects, multiple data types, unsupported kinds) are either omitted or surfaced disabled with a reason. The filterable interpretation wins when a base URL appears in multiple shapes across types.
  • get-operators-for-kind.ts catalogs the operators available per value kind, their dropdown labels, pill connectors, and whether they require a value, with a sensible default operator per kind.

UI

  • AddFiltersMenu gains a "Property…" entry that opens an in-menu PropertyFilterPicker (searchable list of filterable properties, disabled rows with explanatory tooltips).
  • PropertyFilterPill renders each property filter in the ribbon with a kind icon, operator/value editor popover (operator changes apply immediately; value input is debounced before hitting shared state to avoid refetching on every keystroke), an active/incomplete visual state, and removal.
  • FilterRibbon wires up add/change/remove handlers, auto-opens the editor for a just-added pill, wraps to multiple lines, and includes property filters in the "filters are default" / clear-all logic.
  • incompletePillSx added for the muted placeholder look of pills that don't yet contribute a clause.

Data-type pool robustness (supporting change)

  • EntitiesTableData now bundles the dataTypeDefinitions pool alongside the rows it was generated from, unioned across paginated pages. This guarantees a row's value can always resolve its data type and fixes formatValue throwing when a refetch narrowed the result set (e.g. a property filter matching nothing) before rows were regenerated. EntitiesTable no longer takes definitions directly and reads the pool from the table data.
  • getReferencedDataTypeIds (format-value.ts) is exposed so the table can cheaply check that every data type a value depends on is present before rendering, falling back to a "Not Found" cell instead of crashing the grid.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

  • v1 only supports filtering number, string, and boolean properties. List, nested, and multiple-data-type properties are listed in the picker but disabled with an explanatory tooltip.
  • String matching (equals / contains / startsWith / endsWith) is case-sensitive.

🐾 Next steps

  • Broaden support to currently-unfilterable kinds (lists, nested properties, multiple data types).
  • Consider case-insensitive string matching as an option.

🛡 What tests cover this?

  • none

❓ How to test this?

  1. Checkout the branch / view the deployment.
  2. Open the Entities Visualizer for a set of entities with properties of varying kinds.
  3. In the filter ribbon, click the add-filter control and choose Property….
  4. Search for and select a property; confirm the operator list matches its value kind and a pill appears with its editor open.
  5. Choose an operator and enter a value (where required); confirm the table refetches and narrows accordingly, and that an incomplete pill is shown muted and applies no filter.
  6. Add multiple property filters and confirm they are ANDed together; remove them and confirm "Clear filters" resets everything.
  7. Confirm a filter matching nothing shows an empty table without crashing the grid.

📹 Demo

Screenshare.-.2026-05-29.2_10_00.PM.mp4

@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview, Comment Jun 12, 2026 11:48am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Jun 12, 2026 11:48am
petrinaut Skipped Skipped Jun 12, 2026 11:48am

@github-actions github-actions Bot added area/apps > hash* Affects HASH (a `hash-*` app) type/eng > frontend Owned by the @frontend team area/apps labels May 29, 2026
@yusufkinatas yusufkinatas changed the title Implement entities visualizer property filter Add property filter to entities visualizer May 29, 2026
@yusufkinatas yusufkinatas changed the title Add property filter to entities visualizer Add Property Filter To Entities Visualizer May 29, 2026
@vercel vercel Bot temporarily deployed to Preview – petrinaut May 29, 2026 12:21 Inactive
@yusufkinatas yusufkinatas marked this pull request as ready for review May 29, 2026 12:40
@yusufkinatas yusufkinatas requested a review from CiaranMn May 29, 2026 12:40
@augmentcode

augmentcode Bot commented May 29, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: This PR adds per-property value filtering to the Entities Visualizer so users can narrow the table by property values (e.g. numeric comparisons, string matching, boolean/existence checks).

Changes:

  • Extends EntitiesFilterState with a propertyFilters array and incorporates those clauses into buildEntitiesFilter.
  • Adds a property-filter model (PropertyFilter, operators, value kinds) plus operator catalogs/defaults.
  • Introduces buildPropertyFilterClause to translate a single pill into a Graph API Filter clause, treating incomplete/invalid filters as inert.
  • Adds deriveFilterableProperties to compute which visible-column properties are filterable (and why others are disabled).
  • Updates the filter ribbon UI: an “Add filter” menu entry for “Property…”, a searchable picker, and editable/removable filter “pills”.
  • Makes table rendering more robust by bundling/unioning dataTypeDefinitions into EntitiesTableData across pages.
  • Exposes getReferencedDataTypeIds and adds a defensive check to avoid formatValue crashes when a referenced data type is missing.

Technical Notes: Property filter clauses are ANDed with existing web/type/archived filters, and operator/value input is debounced to reduce refetch churn while typing.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review completed. No suggestions at this time.

Comment augment review to trigger a new review at any time.

Base automatically changed from yk/update-entities-visualizer-final to main June 10, 2026 16:35
@github-actions github-actions Bot added area/tests New or updated tests area/tests > playwright New or updated Playwright tests labels Jun 10, 2026
@CiaranMn CiaranMn force-pushed the yk/follow-up-entities-visualizer-property-filter branch from 09f3d64 to 477f2ea Compare June 12, 2026 09:03
@vercel vercel Bot temporarily deployed to Preview – petrinaut June 12, 2026 09:03 Inactive
@cursor

cursor Bot commented Jun 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New graph filter clauses from user input affect entity queries and table refetch behavior; changes are frontend-only with defensive rendering guards.

Overview
Adds per-property value filters to the Entities Visualizer filter ribbon (e.g. numeric comparisons, string contains, boolean/existence checks). Filter state gains a propertyFilters list; each committed pill is turned into a graph Filter via buildPropertyFilterClause and ANDed in buildEntitiesFilter. Pickable properties come from deriveFilterableProperties (aligned with visible columns); unsupported shapes stay in the picker disabled with tooltips.

Ribbon UI adds a searchable Property… flow (PropertyFilterPicker, PropertyFilterPill) and wraps filters on multiple lines; the global Clear control is removed. Property filters apply only after Add/Save in the pill popover (not on every keystroke).

Table stability: EntitiesTableData now carries dataTypeDefinitions with its rows (unioned across pages), and cells use getReferencedDataTypeIds to show a fallback instead of crashing when types are missing after a narrow refetch.

Cleanup: visualizer modules move under shared/ and entities-table-data; sidebar refetch uses generateSidebarEntitiesQueryVariables and drops use-entity-type-entities.tsx.

Reviewed by Cursor Bugbot for commit 178deff. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions github-actions Bot removed area/tests New or updated tests area/tests > playwright New or updated Playwright tests labels Jun 12, 2026
@vercel vercel Bot temporarily deployed to Preview – petrinaut June 12, 2026 10:23 Inactive
@CiaranMn CiaranMn changed the title Add Property Filter To Entities Visualizer FE-840: Add Property Filter To Entities Visualizer Jun 12, 2026
@vercel vercel Bot temporarily deployed to Preview – petrinaut June 12, 2026 11:40 Inactive
@github-actions github-actions Bot added area/tests New or updated tests area/tests > playwright New or updated Playwright tests labels Jun 12, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 178deff. Configure here.

{operatorDescriptor.label}
</MenuItem>
))}
</Select>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Select closes popover cancels draft

High Severity

The property-filter popover’s onClose handler treats every close as cancel in add mode and calls onRemove, which drops the draft pill. The operator Select uses the default portaled menu, so choosing an operator is often treated as an outside click and closes the popover before the user can enter a value or click Add—especially for string and number filters where the default operator is not enough on its own.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 178deff. Configure here.

kind: property.kind,
operator: getDefaultOperatorForKind(property.kind),
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

New property replaces existing draft

Low Severity

Starting a second property filter from the add menu calls setDraftPropertyFilter with a new draft and overwrites any existing draft. There is no guard on the Property menu item or merge step, so an in-progress pill (including a partially filled value) is dropped silently when another property is chosen.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 178deff. Configure here.

@TimDiekmann TimDiekmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Given how many edge cases we have here (and I really don't like the UX, from a filter I expect it to behave differently): I would not filter the types based on the currently shown entities.

Comment on lines +56 to +62
const setPropertyFilters = (
updater: (prev: PropertyFilter[]) => PropertyFilter[],
) =>
setFilterState((prev) => ({
...prev,
propertyFilters: updater(prev.propertyFilters),
}));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a word of warning: Without a web-filter and without a type filter, this will slow down the frontend like crazy for big graphs. Also for web filters with webs with very many entities. Properties are not indexed (due to the nature of JSONb in Postgres). The single archived property before already implied this effect as Postgres requires to do a full scan. We need to be careful with adding filters to random things.

* **filterable** interpretation wins, so the user can still filter on the column
* they see. Among unfilterable interpretations the first encountered wins.
*
* Pure function – authored so it can be unit-tested in isolation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be good to also add a few unit tests in this case

Comment on lines +104 to +105
default:
return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we add a new operator, we very likely miss to add It here. It would be good if the switch statement would be exhaustive (and add satisfies never)

if (typeIds.length === 0) {
return {
clause: {
equal: [{ path: ["type", "versionedUrl"] }, { parameter: "" }],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not a valid filter. An always-true filter is all: [] ("every") and an always-false filter is any: [] ("at least one").

Comment on lines 35 to 41
const filterStateWithoutType = useMemo<EntitiesFilterState>(
() => ({
...filterState,
type: { selectedTypeIds: null },
}),
[filterState],
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This only nulls the type, but it retains the property filters. This means other types cannot be selected anymore. It also implies the quite expensive call with includeTypeIds. I would not restrict the type IDs at all. If you search for a type, e.g. Pdf, but don't have any in your web, the result set is empty. That's expected UX.

mode: "add" | "edit";
/** Whether to open the editor automatically (just-added draft). */
autoOpen: boolean;
onAutoOpenHandled: () => void;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We never use this, right?

filter: PropertyFilter;
mode: "add" | "edit";
/** Whether to open the editor automatically (just-added draft). */
autoOpen: boolean;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we ever flip this?

baseUrl: BaseUrl;
title: string;
kind: FilterValueKind;
}) => void;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is FilterableProperty

Comment on lines +64 to +68
const handleAddPropertyFilter = (property: {
baseUrl: BaseUrl;
title: string;
kind: FilterValueKind;
}) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is FilterableProperty

Comment on lines +124 to +146
{filterState.propertyFilters.map((propertyFilter) => (
<PropertyFilterPill
key={propertyFilter.id}
filter={propertyFilter}
mode="edit"
autoOpen={false}
onAutoOpenHandled={() => {}}
onCommit={(committed) =>
handleCommitPropertyFilter(propertyFilter.id, committed)
}
onRemove={() => handleRemovePropertyFilter(propertyFilter.id)}
/>
))}
{draftPropertyFilter && (
<PropertyFilterPill
key={draftPropertyFilter.id}
filter={draftPropertyFilter}
mode="add"
autoOpen
onAutoOpenHandled={() => {}}
onCommit={handleCommitDraftPropertyFilter}
onRemove={() => setDraftPropertyFilter(null)}
/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's not clear, what filters apply how

Image

The property filter could mean they both needs to match or any needs to match

I appreciate, that we don't have a way to create other filters (such as (X and Y) or (A and B) but it should be clear from the UI what is filtered.

Also, it's not possible to only see archived entities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash* Affects HASH (a `hash-*` app) area/apps area/tests > playwright New or updated Playwright tests area/tests New or updated tests type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

4 participants