feat(tracker): field-prefixed search, inline filter chips and zero-hit empty state - #10998
feat(tracker): field-prefixed search, inline filter chips and zero-hit empty state#10998MichaelUray wants to merge 4 commits into
Conversation
…t empty state Adds SearchInputAdvanced (field:value prefixes routed to Elasticsearch query_string), match highlighting, inline filter chips with overflow popover, a reusable zero-hit empty state in view-resources, and search-scope/highlight view options for List and Kanban. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
| /** | ||
| * Allowed ES-native fields users may type directly. | ||
| * | ||
| * KEEP IN SYNC with elastic/src/adapter.ts KNOWN_FIELD_RE (and vice versa). |
There was a problem hiding this comment.
What do you think about refactoring it to ensure both sides use synced fields?
For example, add a small reusable package with common configs for server side and UI.
There was a problem hiding this comment.
Done — exported fullTextSearchFields from @hcengineering/core. Both @hcengineering/ui and @hcengineering/elastic already depend on core and it already hosts fulltext concepts, so no new package or dependency changes were needed. The encoder and the elastic adapter now derive their field lists from that single constant; per-field boosts stay in the adapter. A shared escapeRegExp (also in core) keeps the regex processing identical on both sides.
| // highlight toggles stay consistent across every viewlet that opts in. | ||
| const SEARCH_VIEW_OPTIONS: ViewOptionModel[] = [ | ||
| { | ||
| key: 'showQuickModeSelector', |
There was a problem hiding this comment.
Do I understand correctly that this isn't used in this PR, but will be used in Gantt charts?
There was a problem hiding this comment.
It is already used in this PR: SEARCH_VIEW_OPTIONS is included by issuesOptions(), which is consumed by both the List and Kanban viewlets. #10992 reuses it for Gantt as a third consumer.
| import { encodeSearch } from './SearchInputAdvanced.encoder' | ||
| import { propSyncValue } from './SearchInputAdvanced.sync' | ||
|
|
||
| describe('encodeSearch', () => { |
There was a problem hiding this comment.
I think it would be better to place tests in a separate __tests__ folder
There was a problem hiding this comment.
Moved all five new co-located test files into the existing package folders: packages/ui/src/__test__ and plugins/view-resources/src/__tests__.
| "SearchEmptyTitle": "No issues found for \"{query}\"", | ||
| "SearchEmptyActiveFilters": "Active filters: {filters}", | ||
| "SearchEmptyClearFilters": "Search without filters", | ||
| "SearchEmptyAllProjects": "Search in all projects" |
There was a problem hiding this comment.
I suggest to fix translations for this and other locales
There was a problem hiding this comment.
Translated the new keys in all supported locales, reusing each locale's existing terminology. The placeholder-parity test verifies {query}, {filters}, and {count} across all locale files.
| return '\\-' + tok.raw.slice(1).replace(/[+!(){}[\]^"~\\/:&|<>=]/g, '\\$&') | ||
| } | ||
| if (!PREFIX_VALUE_RESERVED_RE.test(tok.raw)) return tok.raw | ||
| return tok.raw.replace(/[+!(){}[\]^"~\\/:&|<>=]/g, '\\$&') |
There was a problem hiding this comment.
[minor] I suggest to move this regex to const
There was a problem hiding this comment.
Extracted — the character class was duplicated three times; it now derives from a single constant, with separate test and global-replace RegExp instances to avoid the shared-lastIndex pitfall.
| // set by the fast query alone, so a stale slow-query result could skew the | ||
| // count right after a search changed. | ||
| onDestroy(() => { | ||
| resultIssueCountStore.set(-1) |
There was a problem hiding this comment.
resultIssueCountStore is a single global mutated by multiple viewlets.
List and Kanban both write it; IssuesView resets it to -1 on query/filter change. The -1 sentinel + onDestroy reset largely covers teardown races, but during a viewlet swap (List→Kanban) both can be briefly mounted and write competing counts.
There was a problem hiding this comment.
Implemented an owner-token gate for the shared result count. Each primary result viewlet claims and releases ownership; writes and teardown resets from superseded owners are ignored. Reporting from the generic List component is opt-in and defaults to false; ListView opts in, while embedded lists remain outside the protocol. Unit tests cover owner supersession, stale writes, stale releases, query resets, and the embedded-list regression.
…rework Shared, non-storage-specific field list, test-folder conventions, locale coverage, single regex source and an owner-token gate for the result count. - core: add shared `fullTextSearchFields` constant next to FullTextSearchContext as the single source of truth for the full-text fields exposed to `field:value` targeting. The client encoder derives ES_NATIVE_FIELDS from it and the elastic adapter derives KNOWN_FIELD_RE from it, replacing the two "KEEP IN SYNC" copies. Per-field boost weights stay a local adapter detail. - tests: move the five co-located tests into each package's existing test folder convention (ui `__test__`, view-resources `__tests__`) and fix the relative imports. - i18n: translate the new tracker search/filter strings in the remaining locales (zh, ja, ko, cs, es, fr, it, pt, pt-br, tr), reusing each file's existing terminology; ICU placeholders left unchanged. - encoder: hoist the reserved-character class into one constant and build two RegExp instances from it (non-global for `.test()`, global for `.replace()`) to avoid the shared-lastIndex trap. - view-resources: guard `resultIssueCountStore` writes with an owner-token gate so a superseded viewlet can no longer clobber the active viewlet's count; List/KanbanView claim and release, IssuesView resets through the current owner. Assumes a single active IssuesView surface. - view-resources: make result-count reporting opt-in via a new `reportResultCount` prop on List (default true). Embedded, non-primary List instances (sub-issues / related issues in the issue edit panel, routed through SubIssueList) pass false and never claim the owner token, so opening and closing an issue can no longer strand the primary Issues viewlet with a dead token — the zero-hit SearchEmptyState card renders again afterwards. Add a regression test covering the opted-out embedded consumer. - elastic: escape every regex metacharacter (not just `.`) when building KNOWN_FIELD_RE from `fullTextSearchFields`, so a future field name carrying another metacharacter cannot silently corrupt the alternation. Behaviour for the current fields is unchanged. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
…ult-count store - Share escapeRegExp from @hcengineering/core so the client encoder and the server elastic adapter escape the fulltext field list identically, not just dots on the client. - Export resultIssueCountStore as a read-only Readable; the owner-token gate functions (setResultCount / resetResultCount / releaseResultCountOwner) are now the only write path. Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Summary
Split out of #10992 following review feedback. This supersedes the closed #10872 with a clean branch based on current
developand contains only the shared search/filter work; no Gantt code.Adds
SearchInputAdvanced, match highlighting, inline filter chips with an overflow popover, a reusable zero-hit empty state inview-resources, and search-scope/highlight view options wired into List and Kanban.Known field prefixes are routed through Elasticsearch
query_string; queries without a recognized prefix retain the existingsimple_query_stringpath. Workspace and space access filtering remain unchanged.The exact head passed all required fork-CI jobs, including
uitest,uitest-pg,uitest-workspaces,uitest-qms,build,svelte-check,test,formatting, anddocker-build.The Gantt feature remains in #10992 and builds on this PR.