Skip to content

feat(desktop): group, drag, and bulk-edit automations - #9210

Merged
Kitenite merged 5 commits into
mainfrom
automation-tags-grouping-31d77ed3
Oct 31, 2026
Merged

Kitenite merged 5 commits into
mainfrom
automation-tags-grouping-31d77ed3

Conversation

@Kitenite

@Kitenite Kitenite commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Stack (merge in order)

  1. feat(db): add automations.group_name #9208: migration
  2. feat(api): group automations (setGroup, CLI --group, MCP group) #9209: API, CLI, MCP
  3. this PR: desktop UI and docs

Film and walkthrough (78s, real input in the dev app): https://app.superset.sh/page/automation-groups-mlki3d

Summary

This PR bundles four related changes:

  1. Grouped table: collapsible section rows (A–Z, "No group" last). Headers sit close to the edge and are shorter than rows, so rows read as children. Hover a header for + to create an automation already in that group.
  2. Move to group: a submenu in the row's right-click and ⋯ menus and the detail page's ⋯ menu. A filter field sits on top; typing a name that matches nothing shows "Create group", like Linear's "Move to project". The detail breadcrumb shows the group.
  3. Multiselect and bulk bar: ⌘-click starts a selection, and checkboxes appear only then. A bar offers Move to group, Pause, Resume, and Delete.
  4. Drag between groups: drag a row, or the whole selection, onto another section. Dropping on "No group" ungroups.

Added during review:

  • New group… at the end of every Move to group menu. It keeps the menu open, the field becomes "New group name…", and Enter creates the group.
  • Right-click a group header to choose Delete group…. It confirms first, then moves the group's automations to No group; the automations are kept. Mine tab only, and not on "No group".
  • Review fixes:
    • IME composition no longer triggers a move.
    • Bulk pause and resume refresh the detail page.
    • Hidden rows leave the selection.
    • Deleting one row keeps an unrelated selection.
    • ⌘X no longer toggles a row.
    • The optimistic move cancels any in-flight list fetch first.
    • The i18n catalog-integrity check now handles a nested placeholder at the end of a plural branch.

Keyboard and mouse

Input Action
⌘ / Ctrl click a row Start a selection, or toggle the row (does not open it)
⇧ click Select the on-screen range, across groups
X Toggle the focused row
⌘ click a group header Select or clear the group (a plain click collapses it)
⌘A / Esc Select everything on screen / clear the selection
Drag a row Move it, or the selection it belongs to, to the section under the pointer

How it works

  • Sorting applies within each group; the groups themselves stay A–Z. Search filters within groups, and an empty group disappears.
  • Moves are optimistic: utils.automation.list.setData, then invalidate. Bulk actions call setGroup, setEnabled, and delete per row through Promise.allSettled, so no batch endpoint is needed.
  • Drag uses @dnd-kit/core, like the Linear board. The mouse sensor starts after 8px so clicks still open rows. A one-shot capture listener swallows the click that follows a drop. Auto-scroll starts only in the outer 10% of the list.
  • Selection and drag are on the Mine tab only, because every mutation is owner-scoped on the server.

Design decisions

  • A group, not run tags. automations.tags file run workspaces into sidebar folders and default to ["automation"]. Grouping by them would put every row in one section and tie list organization to the sidebar.
  • One group per automation. Each row appears in exactly one section.
  • No hover checkboxes. They appear only while selecting, at the request of the feature owner.

Manual QA (dev app over CDP, real mouse and keyboard input)

  • Create a group from a row by typing a new name and pressing Enter; partial filter ("gro" picks Growth); keyboard only (→, ↓, Enter)
  • Typing while the pointer still rests on "Move to group" goes into the filter
  • ⌘-click, ⇧-click range across sections, X, ⌘A, Esc, ⌘-click a group header; no stray text selection
  • Bulk move into a new group, bulk pause then resume, bulk delete ("Delete 2 automations?"); Esc with the dialog open keeps the selection
  • Drag one row onto a header, a 2-row selection onto a row, onto "No group", onto a collapsed header, and within the same group (no API call)
  • A plain click still opens the row after drag support was added; the drop never opens the row under it
  • Header + creates in the group and opens with the group in the breadcrumb
  • Team tab: flat-padded where ungrouped, no checkboxes, no drag, no "Move to group" on teammates' rows
  • Checkboxes are centered (the shared TableCell nudges checkboxes 2px; overridden); names do not shift when checkboxes appear
  • No console errors across the runs

Testing

  • bun run typecheck, ./scripts/lint.sh, bun run check:i18n (strict compile; new strings translated in all 17 locales with plural forms)
  • bun test apps/desktop/src/renderer/routes/_authenticated/_dashboard: 1,797 tests, 0 failures, including new groupAutomations and selectRange tests

Known limitations

  • Collapsed sections and the selection reset when you leave the page. Keeping them needs a registered persisted key.
  • No rename-group action, and no manual ordering within a group (sort applies).
  • Bulk actions are one request per row: fine for tens of rows; a batch endpoint would matter only at hundreds.

Deployment

Merge after #9209 is live in production; this PR calls automation.setGroup and reads group.


Summary by cubic

Groups automations into collapsible A–Z sections on the Mine tab, with "No group" last, and adds "Move to group" (filter with inline create), drag-and-drop between sections, and multiselect (⌘‑click, ⇧‑range, X, ⌘A, Esc) with a bulk bar for moving, pausing, resuming, and deleting. The detail breadcrumb shows the group, and a hover + on a section header creates an automation in that group. The "Move to group" menu always ends with "New group…": picking it turns the field into a name field, and Enter creates the group (or joins an exact match). Right-clicking a section header offers "Delete group…", which confirms first and then moves that group's automations to "No group".

Groups are now also settable through the API (automation.setGroup, create/update), the CLI (--group, --clear-group, list --group), and the MCP create/update tools. Names are trimmed and capped at 64 characters and matched case-insensitively to existing groups, so CLI/MCP spelling joins the current spelling.

Fixes in this update

  • The group filter ignores Enter and arrows while an IME composes text.
  • Bulk pause and resume now refresh each automation's detail query too.
  • Rows that leave the screen (filtered out or collapsed) leave the selection, so they don't return selected.
  • Deleting one row no longer clears an unrelated selection; deleted rows leave the selection on their own.
  • X toggles the focused row only without ⌘/Ctrl/Alt, so ⌘X still cuts.
  • The optimistic move cancels any in-flight list refetch first.
  • automation.update no longer drops extra schedules when only non-schedule fields (name, group) change.
  • The translation catalog check now reads nested plural branches, so valid strings ending in a placeholder aren't flagged as missing.

Moves are optimistic and applied per row through existing endpoints, so no batch API is needed. Docs and all 17 locales are included.

Written for commit ef86f91. Summary will update on new commits.

View guided diff Turn on auto-fix

@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@greptile-apps

greptile-apps Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect how owners organize and manage automations in the desktop list.

The PR appears safe to merge with non-blocking fixes for group-name input, stale detail status, and returning selections.

Findings

  1. P2 Confirming text moves an automation ▶
  2. P2 Details show the old status ▶
  3. P2 Old selections return ▶
Fix with agent prompt
### Issue 1
apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/components/AutomationGroupPicker/AutomationGroupPicker.tsx:63-70
The group filter treats Enter used to confirm Chinese, Japanese, or Korean text as a move command. `onInputKeyDown` clicks the first menu item, which can move the automation and close the menu before the user finishes typing. Ignore Enter and arrow keys while `event.nativeEvent.isComposing` is true.

### Issue 2
apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx:281
Bulk Pause and Resume invalidate only `automation.list`, unlike the single-row action, which also invalidates `automation.get`. Open an automation, return to the list, bulk-pause it, then reopen it within the detail query's 30-second `staleTime`: the detail page still shows it as enabled. Invalidate `automation.get` for every affected ID when the batch settles, including partial failures.

### Issue 3
apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/hooks/useRowSelection/useRowSelection.ts:16-19
`selected` hides IDs that leave `order`, but those IDs remain in `picked`. Select a row, filter it out, select another row, then clear the search: the first row becomes selected again and joins later bulk actions. This contradicts the hook's promise that filtered or collapsed rows drop out of the selection. Prune `picked` when rows leave `order`, and cover filtering and collapsing in hook tests.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds grouped automation lists, searchable move menus, group breadcrumbs, row selection, bulk actions, and drag-to-group support.

  • The automation list groups rows into named sections and keeps ungrouped rows last.
  • Owners can move an automation from its menus or detail page.
  • Owners can select several automations and act on them together.
  • Owners can drag a row or selection onto another group.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  List[Automation list] --> Sections[Sorted group sections]
  Sections --> Selection[Select visible rows]
  Sections --> Drag[Drag rows to a section]
  Selection --> Bulk[Move, pause, resume, or delete]
  Drag --> Move[setGroup]
  Bulk --> API[Per-row API requests]
  Move --> API
  API --> Refresh[Refresh query data]
Loading

Reviews (1) · Last reviewed commit: "feat(desktop): group, drag, and bulk-edi..." · Reviewed by Greptile

Comment on lines +63 to +70
const onInputKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
event.preventDefault();
items()[0]?.click();
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
event.preventDefault();
const list = items();
(event.key === "ArrowDown" ? list[0] : list.at(-1))?.focus();

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.

P2 Confirming text moves an automation

The group filter treats Enter used to confirm Chinese, Japanese, or Korean text as a move command. onInputKeyDown clicks the first menu item, which can move the automation and close the menu before the user finishes typing. Ignore Enter and arrow keys while event.nativeEvent.isComposing is true.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/components/AutomationGroupPicker/AutomationGroupPicker.tsx
Line: 63-70

Comment:
**Confirming text moves an automation**

The group filter treats Enter used to confirm Chinese, Japanese, or Korean text as a move command. `onInputKeyDown` clicks the first menu item, which can move the automation and close the menu before the user finishes typing. Ignore Enter and arrow keys while `event.nativeEvent.isComposing` is true.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

toast.error(
errorMessage(error, t({ message: "Failed to update automation" })),
),
onSettled: () => void utils.automation.list.invalidate(),

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.

P2 Details show the old status

Bulk Pause and Resume invalidate only automation.list, unlike the single-row action, which also invalidates automation.get. Open an automation, return to the list, bulk-pause it, then reopen it within the detail query's 30-second staleTime: the detail page still shows it as enabled. Invalidate automation.get for every affected ID when the batch settles, including partial failures.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx
Line: 281

Comment:
**Details show the old status**

Bulk Pause and Resume invalidate only `automation.list`, unlike the single-row action, which also invalidates `automation.get`. Open an automation, return to the list, bulk-pause it, then reopen it within the detail query's 30-second `staleTime`: the detail page still shows it as enabled. Invalidate `automation.get` for every affected ID when the batch settles, including partial failures.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +16 to +19
const selected = useMemo(() => {
const shown = new Set(order);
return new Set([...picked].filter((id) => shown.has(id)));
}, [picked, order]);

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.

P2 Old selections return

selected hides IDs that leave order, but those IDs remain in picked. Select a row, filter it out, select another row, then clear the search: the first row becomes selected again and joins later bulk actions. This contradicts the hook's promise that filtered or collapsed rows drop out of the selection. Prune picked when rows leave order, and cover filtering and collapsing in hook tests.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/hooks/useRowSelection/useRowSelection.ts
Line: 16-19

Comment:
**Old selections return**

`selected` hides IDs that leave `order`, but those IDs remain in `picked`. Select a row, filter it out, select another row, then clear the search: the first row becomes selected again and joins later bulk actions. This contradicts the hook's promise that filtered or collapsed rows drop out of the selection. Prune `picked` when rows leave `order`, and cover filtering and collapsing in hook tests.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) ✅ View Branch
Vercel API (Vercel) ✅ Open Preview
Vercel Web (Vercel) ✅ Open Preview
Vercel Marketing (Vercel) ✅ Open Preview
Vercel Docs (Vercel) ✅ Open Preview

Preview updates automatically with new commits

Copy link
Copy Markdown
Contributor

Verdict: minor issues. The UI works end to end in the dev app (grouping, Move to group with create, drag between sections, ⌘/⇧ selection, and bulk pause/resume/delete). However, CI Test is red because of this PR's new plural string.

  • packages/i18n/test/catalog-integrity.test.ts:40: de/ja/ko/tr fail with "missing {group}" on Moved # automation to {group} (useSetAutomationGroup.ts:56). The translations are fine. stripIcu's lazy regex stops at the first }} and strips {group}. A brace-depth stripIcu makes all 17 locales pass (tested locally).
  • useRowSelection.ts:16: hidden rows stay in picked and come back when you expand a group or clear the search. Reproduced 4 → 2 → 4 selected by collapsing and then expanding Ops.
  • page.tsx:294: deleting one row from its menu clears an unrelated multi-selection (found by reading the code).
  • page.tsx:122-125: UNGROUPED_KEY now sits under the "Create with AI" prompt's comment.
  • AutomationRow.tsx:224: X toggles selection even with ⌘/Ctrl held (⌘X).
  • Nit, useSetAutomationGroup.ts:31: cancel the in-flight automation.list query before the optimistic setData.

Full review with screenshots: https://app.superset.sh/page/pr-9210-review-group-drag-bulk-edit-automations-5c8n8p

- collapsible group sections (A-Z, No group last) with a hover + to create in a group
- "Move to group" submenu with a filter field and inline "Create group"
- multiselect started by cmd-click (shift range, X, cmd-A, Esc) with a bulk bar:
  move to group, pause, resume, delete
- drag rows or a selection between group sections
- group crumb on the detail page breadcrumb
…t returns

- the group filter ignores Enter and arrows while an IME composes text
- bulk pause and resume invalidate each automation's detail query too
- rows that leave the screen (filtered, collapsed) leave the selection, so
  they don't come back selected; regression test in useRowSelection.test.ts
- catalog-integrity: stripIcu counts braces, so a branch that ends in a
  nested placeholder ("Moved # automation to {group}") no longer cuts the
  plural block short and flags correct translations as missing {group}
- deleting one row no longer clears an unrelated selection; deleted rows
  leave the selection on their own
- X toggles the row only without cmd/ctrl/alt (cmd-X is cut)
- cancel an in-flight list refetch before the optimistic group move
- UNGROUPED_KEY moved out from under the "Create with AI" comment
Creating a group needed you to know that typing an unknown name offers
"Create group". The menu now always ends with "New group…": it keeps the
menu open, turns the field into "New group name…", and Enter creates the
group (or joins one with that exact name).
"Delete group…" asks first, then moves the group's automations to No group;
the automations themselves are kept. Mine tab only (owners regroup), and
not on "No group". Component test covers the menu and its absence on
No group.
@Kitenite
Kitenite force-pushed the automation-tags-grouping-31d77ed3 branch from 21132c7 to ef86f91 Compare October 31, 2026 22:52
@Kitenite
Kitenite changed the base branch from kiet/automation-groups-api to main October 31, 2026 22:53
@Kitenite
Kitenite merged commit 955c865 into main Oct 31, 2026
11 checks passed
@Kitenite
Kitenite deleted the automation-tags-grouping-31d77ed3 branch October 31, 2026 22:53

This branch was previously deployed

1 inactive deployment
preview — ef86f910 Deployed Oct 31, 2026 by Kitenite via Deploy API #13471
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants