Skip to content

Activity Search Phase 1#1875

Open
joswig wants to merge 20 commits into
developfrom
dev/activity_search
Open

Activity Search Phase 1#1875
joswig wants to merge 20 commits into
developfrom
dev/activity_search

Conversation

@joswig
Copy link
Copy Markdown
Collaborator

@joswig joswig commented Feb 16, 2026

Summary

New /search page for finding activity directives across all plans, plus shared-component improvements that came out of building it.

image

Search page

Filters (all bookmarkable via URL query params):

  • Mission Model
  • Activity Type (multi-select; narrowed by selected model)
  • Activity Name
  • Argument Name (typeahead from the selected model's parameter schemas; narrows further with a selected Type)
  • Argument Value (tooltip notes mission-model defaults are not applied. Also – typing 5 matches XYZ: 5 and XYZ: [1, 2, 5]; arrays/structs accepted as JSON)
  • Tag
  • Preset (model-filtered)
  • Created By
  • Last Modified By
  • Created After / Created Before
  • Last Modified Before / After
  • Start Offset min/max
  • Plan Name
  • Plan Owner
  • Plan Tag
  • Scheduling Goal
  • Scheduler-created only

Results:

  • Server-side pagination, 50/page, with aggregate count
  • Server-side sort translated from AG Grid column state
  • Sticky-footer Search/Clear
  • 500ms-delayed spinner overlay during search
  • Inline banner for subscription errors
  • Per-dropdown loading state

Columns (default-visible unless noted):

  • Open in plan (pinned-left icon, full-cell click)
  • Activity Name, Activity Type, Activity ID (hidden)
  • Plan Name, Plan Owner, Plan ID (hidden), Model, Model ID
  • Tags, Applied Preset
  • Start Offset, Absolute Start Time, Arguments (hidden)
  • Created By, Created At (hidden), Modified By, Last Modified
  • Scheduling Goal, Sched. Goal ID (hidden)
  • Column picker with show/hide all + reset (action column is structural, exempt)
  • Visibility, order, width, sort, pinning persisted to localStorage

Selection & cross-plan copy:

  • Click / cmd-click / shift-click selects rows
  • Right-click menu: "Copy N Activities" puts the selection on the clipboard
  • "Open in plan" — per-row icon button (always visible, pinned left) and a right-click menu item when exactly one row is selected
  • Paste in the target plan via the existing in-plan paste context menu. Cross-plan timing re-grounds correctly (see fixes below but needs review/discussion)

Shared changes

DataGrid:

  • New persistColumnStateKey + transformColumnState props centralize column-state localStorage save/clear
  • Forwarded through BulkActionDataGrid and SingleActionDataGrid
  • WSP and SearchResults consume it
  • WSP keeps its name-column processing via transformColumnState

SearchableDropdown:

  • Menu width uses canvas measureText with font read off a hidden DOM ref (replaces the maxOptionChars * 8 heuristic that overshot for proportional fonts)
  • Items flow through RowVirtualizerFixed as a slot prop so menus refresh while open
  • New loading prop
  • Internal toggle button got type="button" so Enter inside a sibling input no longer toggles the menu

RowVirtualizerFixed:

  • items prop replaces count; slot exposes item
  • TimelineItemList + mock updated

Bug fixes & polish

  • Cross-plan paste now respects the user's right-click paste-at-time even when source activities' absolute times fall outside the target plan window (previously snapped to plan start). Needs review/discussion. Additionally, copy/paste was modified to keep track of directive origin plan in order to avoid clobbering directives from different plans with the same IDs.
  • DataGrid: switched the selected-row class toggle to querySelectorAll so pinned-left and center containers stay in sync (fixes two-tone selected row with pinned columns — affects every table with pinned columns, not just search)

Test plan

  • Filter combinations produce expected results
  • Pagination traverses all pages
  • Each sortable column sorts via server
  • URL state round-trips across refresh
  • Column-picker state persists across reload
  • Error banner appears on subscription failure

TODO

  • e2e tests
  • Tooltip for argument name width bugfix
  • Multi-select activity type filter
  • Multi-select + cross-plan copy flow
  • Model / Model ID / Absolute Start Time / Scheduling Goal columns
  • Activity ID sort bug fix
  • Cross-plan paste-at-time bug fix
  • New filters: Last Modified By, Created date range, Plan Tag, Scheduling Goal

Future Work

Future directions for this work could involve:

  • Search on activity instances (spans), requires sorting out which simulation dataset to query for each plan.
  • Making use of the activity filter builder paradigm to enable more complex search expressions
  • Searching for plans that contain various activities
  • Searching for activities similar to the one currently selected in a plan
  • Command palette menu integration (once WIP Command Palette 🎨 #1824 is completed) for cross-app activity search without needing to start on /search page
  • Potentially adding a db table/view that would actually give us queryable directive arguments. This has likely been considered in the past but not sure what technical barriers exist.
  • Instantiate cross-plan activity search within a plan panel
  • Constraint violation search - find activities involved in constraint violations
  • Temporal search with absolute times via new computed Hasura field (or two phase query) to resolve absolute times
  • Aggregate views to offer summaries of activity type distribution across plans, parameter value distribution for a selected type/argument, etc

@AaronPlave
Copy link
Copy Markdown
Contributor

AaronPlave commented Feb 18, 2026

This feels very useful, so much potential here

@AaronPlave AaronPlave force-pushed the dev/activity_search branch from 9e5e9c4 to 1849440 Compare April 21, 2026 15:52
@AaronPlave AaronPlave force-pushed the dev/activity_search branch from b91a810 to 34a44a1 Compare April 27, 2026 17:38
@AaronPlave AaronPlave self-assigned this Apr 28, 2026
@AaronPlave AaronPlave changed the title activity search form Activity Search Phase 1 Apr 28, 2026
@AaronPlave AaronPlave marked this pull request as ready for review April 28, 2026 16:20
@AaronPlave AaronPlave requested a review from a team as a code owner April 28, 2026 16:20
@dandelany dandelany force-pushed the dev/activity_search branch from 425dfe5 to 032a64a Compare May 26, 2026 23:53
Copy link
Copy Markdown
Collaborator

@dandelany dandelany left a comment

Choose a reason for hiding this comment

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

Still working on testing, but I noticed one potential issue with IDs I wanted to call out early - see review comments.

<BulkActionDataGrid
bind:dataGrid
bind:selectedItemIds
idKey="id"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If I'm following correctly, this is the id field of an activity_directive row used as primary ID key for the data grid. This may cause problems here because (confusingly) directive IDs are not unique on their own, they're only unique within a given plan - eg. a branch/duplicate plan will have activities with the same IDs as the original. See the postgres schema here, which uses a compound primary key (id, plan_id):

https://github.com/NASA-AMMOS/plandev/blob/3c89ed24fb4ad28792effa02ef8592b617430e41/deployment/postgres-init-db/sql/tables/merlin/activity_directive/activity_directive.sql#L20-L21

We have gotten away with using this ID in other places in UI because they are single-plan only, but since activity search is explicitly cross-plan this will likely cause subtle problems when results contain duplicate IDs from several plans - we should copy the backend and key by a compound (id, plan_id) string here if possible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah yes, good catch, can make a compound key here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Done

return [];
}
const idSet = new Set(selectedItemIds);
return activities.filter(a => idSet.has(a.id));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See comment below re: non-unique activity directive IDs, I believe it applies here too

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Done

Comment thread src/utilities/activities.ts Outdated

export function copyActivityDirectivesToClipboard(sourcePlan: Plan, activities: ActivityDirective[]) {
export function copyActivityDirectivesToClipboard(sourcePlan: Plan | null, activities: ActivityDirective[]) {
const copiedActivityIds = new Set(activities.map(a => a.id));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same comment from above re: activity directive IDs may apply in this function too, it seems to be assuming unique directive IDs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yep this does not support cross-plan activity copying. Will take a look.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Resolved in latest commit, take a look.

joswig and others added 19 commits May 27, 2026 15:21
- Server-side pagination (offset + aggregate count) with first/prev/next/last controls
- Expanded filters: plan name/owner, created_by, last-modified date range, scheduling-goal origin
- Argument name dropdown driven by the selected model's activity type parameter schemas;
  narrows further when an activity type is selected
- Server-side sort: AG Grid column state translated to nested Hasura order_by
- All filter state encoded in URL query params (bookmarkable / shareable searches),
  initialized on load with deferred model-id resolution while subscription primes
- Result table: tag chips, JSON arguments column, formatted timestamps via
  getShortISOForDate, plus toggleable Created At / Plan ID / Activity ID columns
- Column picker (ActivityTableMenu) with show/hide all + reset; column state
  persisted via localStorage. Forward columnVisible / columnsReset events through
  SingleActionDataGrid so the picker can react
- Search button always enabled; auto-search-on-load still gated on a non-empty filter
Adds persistColumnStateKey + transformColumnState props on DataGrid (forwarded
through BulkAction/SingleActionDataGrid). WorkspaceFileBrowser drops its
duplicated save/clear logic and rides its name-column processing through
transformColumnState.
Replace maxOptionChars*8 width estimate with canvas measureText, reading the
font off a hidden DOM ref so it tracks CSS. Pass items through
RowVirtualizerFixed as a slot prop (fixes stale content when array changes
in place). Add loading prop. TimelineItemList + mock updated for new
virtualizer API.
Mission Model uses SearchableDropdown. Collapse pending-model state into a
single selectedModelId (selectedModel derived). Inline subscription-error
banner. Per-dropdown loading. Start Offset min/max filters. Argument Value
defaults tooltip. Search always enabled, above Clear, sticky footer.
500ms-delayed spinner overlay over results; no-op local sort comparators
avoid the flash before server-sorted data lands. SearchResults adopts the
new persistColumnStateKey for column-state persistence.
UI / UX
- Multi-select Activity Type filter (`_in` clause); arg-name dropdown handles multi
- New filters: Last Modified By, Created After/Before, Plan Tag, Scheduling Goal
- New columns: Model, Model ID, Absolute Start Time, Scheduling Goal
- Column order regrouped: action → identity → plan/model → tagging → timing → audit → provenance
- Open-in-plan: Svelte component cell, full-cell click, pinned-left icon column
- Right-click context menu: "Copy N Activit(ies)" and "Open in plan" (single-select)
- Click-drag column resize (no shift-key required)
- Open-in-plan exempt from column picker and the "hide all" sweep

Behavior fixes
- Cross-plan paste respects the right-click paste-at-time even when the source
  activities' absolute times fall outside the target plan window
- Arg value matching: JSON.parse + singleton-array sugar so typing `5` finds
  `XYZ: 5` and `XYZ: [1, 2, 5]`; numeric/bool branch covers all four shapes
- Drop the `directive_id: id` gql alias — fixes Hasura sort error on Activity ID
- DataGrid: querySelectorAll so the selected-row class stays in sync between
  center and pinned-left containers (fixes the two-tone selected row bug)

Types / internals
- ActivityDirectiveSearchResult now extends ActivityDirectiveDB instead of
  redefining shared fields; corrects applied_preset shape along the way
- SEARCH_ACTIVITIES extends plan with mission_model, adds source_scheduling_goal,
  anchor_id, anchored_to_start, metadata for the clipboard mapping
- Use ColumnPinnedType / SortDirection imports for the two literal ColDef fields
Activity directive ids are unique per-plan but not globally; cross-plan
search results can collide on the bare id. Key the search results grid
and the clipboard/paste anchor remap by (plan_id, id) so row identity,
selection, and anchor preservation stay scoped to the source plan.
@dandelany
Copy link
Copy Markdown
Collaborator

@AaronPlave is working on testing this at larger scale (~100k-1M directives), will hold off on final approval until done

@AaronPlave
Copy link
Copy Markdown
Contributor

@AaronPlave is working on testing this at larger scale (~100k-1M directives), will hold off on final approval until done

@dandelany did some performance testing and found that with 500k activities search performance was usually under a few seconds with the exception of search with a sort on directives by plan name (requiring a join) with no other significant filters to reduce result set - in that case search was slower but maybe 5-10 seconds on my machine, which feels like acceptable performance.

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

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants