Skip to content

MM-69625 MM-69633: Collapse-all and task filtering on playbook run checklists#9928

Open
asaadmahmood wants to merge 2 commits into
mainfrom
playbook-filters
Open

MM-69625 MM-69633: Collapse-all and task filtering on playbook run checklists#9928
asaadmahmood wants to merge 2 commits into
mainfrom
playbook-filters

Conversation

@asaadmahmood

@asaadmahmood asaadmahmood commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Brings two playbook-run capabilities from the webapp to mobile, surfaced as two icons in the Tasks header of the run screen:

  • Collapse/expand all checklists (MM-69625) — a toggle (arrow-collapse / arrow-expand) that collapses or expands every checklist at once. Individual per-checklist toggling still works; a monotonic epoch counter makes the run-level control re-sync each checklist even after it has been toggled on its own.
  • Task filter (MM-69633) — a filter button (filter-variant, tinted when active) opening a bottom sheet with All tasks, a Task state section (show checked / show skipped), and an Assignee section (Me / Unassigned / Others), matching the webapp. "All tasks" is a select-all/clear-all toggle.

Item index-safety fix (prerequisite for filtering)

The server mutates checklist items by their position in the checklist's canonical order (setChecklistItemState(runId, checklistNum, itemNum, ...), skipChecklistItem, setAssignee, …). checklist.tsx was passing itemNumber={renderIndex} from an already-filtered array (the enhancer dropped condition-hidden items), so omitting any item shifted the indices of the items after it — an action on one task could mutate a different task on the server. This was already reachable on main via condition-hidden items; user-facing filtering would make it trivial to trigger.

Fix: the enhancer now emits every item in canonical order (and observes assignee_id so the assignee filter reacts live), and checklist.tsx pairs each item with its true index before filtering — so itemNumber is always the server-side index regardless of what's hidden. Both the visible list and the off-screen height-calculator copy use the same filtered set so the collapse animation height stays correct.

Testing

  • Filtering logic extracted to a pure util (utils/task_filters.ts) with unit tests.
  • Component/screen tests cover both header controls and pin the index-safety guarantee (a filtered-out item must not shift the itemNumber of items after it).
  • Full playbooks suite: 1139 tests passing; TypeScript + ESLint clean.
  • Verified rendering on iOS simulator and Android emulator.

i18n

New strings added to en.json only, via npm run i18n-extract.

Note on the second commit

48daae1f6 is an unrelated build fix: @react-native/gradle-plugin (RN 0.83.9) pins foojay-resolver-convention 0.5.0, which references JvmVendorSpec.IBM_SEMERU — removed in Gradle 9 (this repo's wrapper). Without it, ./gradlew fails at settings evaluation on a clean checkout and Android can't build. Patched to foojay 1.0.0. Happy to split this into its own PR if preferred.

🤖 Generated with Claude Code

Images

CleanShot 2026-07-13 at 18 50 26@2x CleanShot 2026-07-13 at 18 50 23@2x CleanShot 2026-07-13 at 18 50 13@2x CleanShot 2026-07-13 at 18 50 11@2x

asaadmahmood and others added 2 commits July 13, 2026 18:47
The android/ gradle wrapper is on Gradle 9.0.0, but @react-native/gradle-plugin
(RN 0.83.9) pins foojay-resolver-convention 0.5.0. That plugin references
JvmVendorSpec.IBM_SEMERU, a field removed in Gradle 9, so it throws
NoSuchFieldError while its DistributionsKt class initializes. This happens
during settings evaluation, which means every ./gradlew invocation on a clean
checkout fails before any task runs (not even `gradlew help` works), and
`npm run android` can't build.

Patch bumps foojay-resolver-convention to 1.0.0, the first release compatible
with Gradle 9. (0.10.0 was tried first but still references the removed field.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ecklists

Bring two playbook-run capabilities that already exist in the webapp to mobile,
via two icons in the "Tasks" header of the run screen.

Collapse/expand all checklists
  A header toggle (arrow-collapse / arrow-expand) that collapses or expands
  every checklist at once. Per-checklist expand state already existed as local
  state inside Checklist; rather than replace it (individual toggling is still
  wanted), the run-level control is lifted into PlaybookRun and pushed down as
  a `collapseAll` flag plus a monotonic `collapseAllEpoch`. The epoch is what
  makes it work after a user has toggled a checklist on its own: without it,
  pressing "collapse all" again when `collapseAll` hadn't changed value would
  be a no-op, so each checklist re-syncs to `collapseAll` whenever the epoch
  advances (via useDidUpdate).

Task filter
  A filter button (filter-variant, tinted when a filter is active) opens a
  bottom sheet modelled on components/files/file_filter.tsx: "All tasks", then
  a Task state section (show checked / show skipped) and an Assignee section
  (Me / Unassigned / Others), matching the webapp. "All tasks" is a
  select-all/clear-all toggle. Filter state lives in PlaybookRun and is applied
  when rendering each checklist's items.

Item index-safety fix (prerequisite for filtering)
  The server mutates checklist items by their position in the checklist's
  canonical order (client.setChecklistItemState(runId, checklistNum, itemNum,
  ...), skipChecklistItem, setAssignee, ...). checklist.tsx was rendering items
  with itemNumber={renderIndex}, where renderIndex was the index into an
  already-filtered array (the enhancer dropped condition-hidden items before
  rendering). So whenever an item was omitted, every later item's itemNumber
  shifted, and an action on one task would target a different task on the
  server. This was already reachable on main via condition-hidden items;
  user-facing filtering would have made it trivial to hit.

  Fixed by moving all filtering to render time: the enhancer (checklist/index.ts)
  now emits every item in canonical order (and additionally observes
  assignee_id so the assignee filter reacts live), and checklist.tsx pairs each
  item with its true index before filtering, so itemNumber is always the
  server-side index regardless of what is hidden. Both the visible list and the
  off-screen height-calculator copy use the same filtered set so the collapse
  animation height stays correct.

Filtering logic is a pure util (utils/task_filters.ts: isItemVisible,
itemMatchesFilters, areDefaultTaskFilters) with its own unit tests; component
and screen tests cover both controls and pin the index-safety guarantee
(a filtered-out item must not shift the itemNumber of the items after it).
New i18n strings were added to en.json only, via npm run i18n-extract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mm-cloud-bot

Copy link
Copy Markdown

@asaadmahmood: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

I understand the commands that are listed here

@github-actions

Copy link
Copy Markdown
Documentation Impact Analysis — updates needed

Documentation Impact Analysis

Overall Assessment: Documentation Updates Recommended

Changes Summary

This PR adds two new user-facing controls to the Playbook Run screen in the Mattermost mobile app: a Collapse/Expand all checklists toggle and a Filter tasks bottom sheet. The filter sheet lets users narrow visible checklist items by task state (checked, skipped) and assignee (Me, Unassigned, Others). As a secondary internal change, filtering is moved from the data layer to the render layer to fix a server-index shifting bug, but this is not user-visible.

Documentation Impact Details

Change Type Files Changed Affected Personas Documentation Action Docs Location
New or changed UI component (new user interaction) app/products/playbooks/screens/playbook_run/task_filter/index.tsx, app/products/playbooks/screens/playbook_run/playbook_run.tsx End User Document new mobile collapse-all and filter-tasks controls in the existing mobile playbook runs section docs/source/end-user-guide/workflow-automation/work-with-runs.rst
New i18n string assets/base/i18n/en.json (keys: playbooks.playbook_run.collapse_all, playbooks.playbook_run.expand_all, playbooks.playbook_run.filter_tasks, playbooks.task_filter.*) End User Confirm the new strings correspond to the feature description above; no separate doc page needed docs/source/end-user-guide/workflow-automation/work-with-runs.rst

Recommended Actions

  • Update docs/source/end-user-guide/workflow-automation/work-with-runs.rst: expand the Playbook runs on mobile section (currently lines 26–29) to describe the two new header controls on the run view — the Collapse/Expand all checklists toggle and the Filter tasks button — and the filter options available in the bottom sheet (task state: checked/skipped; assignee: Me, Unassigned, Others). The existing desktop sentence ("the checklists can be collapsed and filtered based on their status") already describes the web/desktop behaviour; this update aligns the mobile section with parity now provided by this PR.

Confidence

Medium — The "Playbook runs on mobile" section in work-with-runs.rst is brief and intentionally describes what mobile users can do. Both new controls are genuinely new mobile capabilities (not previously available) with distinct i18n strings, and they mirror documented desktop functionality. The recommendation is to update the existing page rather than create a new one; whether the Playbook product team tracks mobile-specific parity notes in a changelog is unknown, but the end-user guide is the correct target.


@github-actions github-actions Bot added the Docs/Needed Requires documentation label Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Playbook task controls

Layer / File(s) Summary
Task filter contract and UI
app/products/playbooks/utils/task_filters.*, app/products/playbooks/screens/playbook_run/task_filter/*, assets/base/i18n/en.json
Adds task filter types, visibility predicates, filter controls, translations, and interaction tests.
Stable checklist item pipeline and rendering
app/products/playbooks/screens/playbook_run/checklist/*
Preserves canonical item ordering while applying hidden-item and task-filter visibility during rendering, including collapse synchronization.
Run-level controls and checklist propagation
app/products/playbooks/screens/playbook_run/playbook_run.*, app/products/playbooks/screens/playbook_run/checklist_list.*
Adds task-header controls and passes filter and collapse state through the checklist hierarchy with coverage for the resulting interactions.

Gradle toolchain patch

Layer / File(s) Summary
Gradle resolver version
patches/@react-native+gradle-plugin+0.83.9.patch
Updates the patched Gradle toolchain resolver plugin from 0.5.0 to 1.0.0.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PlaybookRun
  participant TaskFilter
  participant ChecklistList
  participant Checklist
  User->>PlaybookRun: Open task filters
  PlaybookRun->>TaskFilter: Open bottom sheet
  User->>TaskFilter: Change filter selection
  TaskFilter->>PlaybookRun: Return updated filters
  PlaybookRun->>ChecklistList: Pass filters and collapse state
  ChecklistList->>Checklist: Pass checklist props
  Checklist->>Checklist: Render visible items with server indexes
Loading

Suggested labels: Build Apps for PR, 3: QA Review, 2: UX Review, release-note

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: collapse-all and task filtering for playbook run checklists.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the checklist filtering and collapse-all work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch playbook-filters

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/products/playbooks/screens/playbook_run/task_filter/index.tsx (1)

88-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid side effects inside state updater functions.

onFiltersChanged is called inside the setSelected updater. React 18+ StrictMode double-invokes state updaters to detect impurities, so this callback fires twice per toggle. toggleAll (lines 97–101) already uses the correct pattern—computing next from selected directly and calling onFiltersChanged outside the updater. update should be consistent.

♻️ Proposed refactor for consistency with toggleAll
 const update = useCallback((changes: Partial<TaskFilters>) => {
-    setSelected((prev) => {
-        const next = {...prev, ...changes};
-        onFiltersChanged(next);
-        return next;
-    });
-}, [onFiltersChanged]);
+    const next = {...selected, ...changes};
+    setSelected(next);
+    onFiltersChanged(next);
+}, [onFiltersChanged, selected]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/products/playbooks/screens/playbook_run/task_filter/index.tsx` around
lines 88 - 94, Update the update callback to compute the next TaskFilters value
from the current selected state, call onFiltersChanged outside the setSelected
updater, and then apply the computed value with setSelected. Keep the existing
partial-change merge behavior and align the implementation with toggleAll.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/products/playbooks/screens/playbook_run/checklist/checklist.test.tsx`:
- Around line 503-537: Strengthen the four filter tests around Checklist by
asserting the complete rendered item ID array, not only that the filtered ID is
absent. Update the showChecked, showSkipped, showAssignedToMe, and
showAssignedToOthers cases to verify the expected array contents and length,
matching the adjacent tests’ full-result assertions.

In `@app/products/playbooks/screens/playbook_run/checklist/checklist.tsx`:
- Line 145: Initialize the checklist’s expanded state from the current
collapseAll prop instead of always defaulting to true. Update the useState
initialization near the checklist component and preserve useDidUpdate’s
synchronization behavior for subsequent collapseAll changes, so newly mounted
checklists immediately reflect the current state.

---

Nitpick comments:
In `@app/products/playbooks/screens/playbook_run/task_filter/index.tsx`:
- Around line 88-94: Update the update callback to compute the next TaskFilters
value from the current selected state, call onFiltersChanged outside the
setSelected updater, and then apply the computed value with setSelected. Keep
the existing partial-change merge behavior and align the implementation with
toggleAll.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9b595e28-9a00-425d-8e32-8e3265190a3c

📥 Commits

Reviewing files that changed from the base of the PR and between 4078234 and 7025ab4.

📒 Files selected for processing (14)
  • app/products/playbooks/screens/playbook_run/checklist/checklist.test.tsx
  • app/products/playbooks/screens/playbook_run/checklist/checklist.tsx
  • app/products/playbooks/screens/playbook_run/checklist/index.test.tsx
  • app/products/playbooks/screens/playbook_run/checklist/index.ts
  • app/products/playbooks/screens/playbook_run/checklist_list.test.tsx
  • app/products/playbooks/screens/playbook_run/checklist_list.tsx
  • app/products/playbooks/screens/playbook_run/playbook_run.test.tsx
  • app/products/playbooks/screens/playbook_run/playbook_run.tsx
  • app/products/playbooks/screens/playbook_run/task_filter/index.test.tsx
  • app/products/playbooks/screens/playbook_run/task_filter/index.tsx
  • app/products/playbooks/utils/task_filters.test.ts
  • app/products/playbooks/utils/task_filters.ts
  • assets/base/i18n/en.json
  • patches/@react-native+gradle-plugin+0.83.9.patch

Comment on lines +503 to +537
it('should hide checked items when showChecked is off', () => {
const props = getFilterProps();
props.filters = {...DEFAULT_TASK_FILTERS, showChecked: false};

const {getByTestId} = renderWithIntl(<Checklist {...props}/>);

expect(renderedItemIds(getByTestId)).not.toContain('checked');
});

it('should hide skipped items when showSkipped is off', () => {
const props = getFilterProps();
props.filters = {...DEFAULT_TASK_FILTERS, showSkipped: false};

const {getByTestId} = renderWithIntl(<Checklist {...props}/>);

expect(renderedItemIds(getByTestId)).not.toContain('skipped');
});

it('should hide items assigned to the current user when Me is off', () => {
const props = getFilterProps();
props.filters = {...DEFAULT_TASK_FILTERS, showAssignedToMe: false};

const {getByTestId} = renderWithIntl(<Checklist {...props}/>);

expect(renderedItemIds(getByTestId)).not.toContain('mine');
});

it('should hide items assigned to other users when Others is off', () => {
const props = getFilterProps();
props.filters = {...DEFAULT_TASK_FILTERS, showAssignedToOthers: false};

const {getByTestId} = renderWithIntl(<Checklist {...props}/>);

expect(renderedItemIds(getByTestId)).not.toContain('theirs');
});

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Filter tests only check absence, not full result set.

These four tests use .not.toContain(...) without checking length or the full expected array, unlike the adjacent tests at lines 500 and 546 that assert the complete array. As per coding guidelines, "In tests, check array lengths in addition to individual items to ensure no extra elements exist."

✅ Proposed fix: assert the full expected set
-            expect(renderedItemIds(getByTestId)).not.toContain('checked');
+            expect(renderedItemIds(getByTestId)).toEqual(['unchecked', 'skipped', 'mine', 'theirs']);

(similarly for the showSkipped, showAssignedToMe, and showAssignedToOthers tests)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/products/playbooks/screens/playbook_run/checklist/checklist.test.tsx`
around lines 503 - 537, Strengthen the four filter tests around Checklist by
asserting the complete rendered item ID array, not only that the filtered ID is
absent. Update the showChecked, showSkipped, showAssignedToMe, and
showAssignedToOthers cases to verify the expected array contents and length,
matching the adjacent tests’ full-result assertions.

Source: Coding guidelines

collapseAll,
collapseAllEpoch,
}: Props) => {
const [expanded, setExpanded] = useState(true);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Newly-mounted checklists ignore the current collapseAll state.

useDidUpdate (lines 153-156) skips the initial render, but expanded still defaults to true (line 145) regardless of collapseAll. A checklist that mounts for the first time while collapseAll is already true (e.g., a new checklist created mid-session) will render expanded until the next collapse-all toggle re-syncs it.

🔧 Proposed fix: seed initial state from the current prop
-    const [expanded, setExpanded] = useState(true);
+    const [expanded, setExpanded] = useState(() => !collapseAll);

Self-recovers on the next collapse/expand-all press, so impact is limited to a transient visual inconsistency.

Also applies to: 153-156

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/products/playbooks/screens/playbook_run/checklist/checklist.tsx` at line
145, Initialize the checklist’s expanded state from the current collapseAll prop
instead of always defaulting to true. Update the useState initialization near
the checklist component and preserve useDidUpdate’s synchronization behavior for
subsequent collapseAll changes, so newly mounted checklists immediately reflect
the current state.

@github-actions

Copy link
Copy Markdown

Coverage Comparison Report

Generated on July 13, 2026 at 14:01:26 UTC

+-----------------+------------+------------+-----------+
| Metric          | Main       | This PR    | Diff      |
+-----------------+------------+------------+-----------+
| Lines           |     87.75% |     87.79% |     0.04% |
| Statements      |     87.62% |     87.66% |     0.04% |
| Branches        |     76.58% |     76.62% |     0.04% |
| Functions       |     87.21% |     87.27% |     0.06% |
+-----------------+------------+------------+-----------+
| Total           |     84.79% |     84.83% |     0.04% |
+-----------------+------------+------------+-----------+

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.

2 participants