discourse/spec/requests
Keegan George ad21ae98ff
FEATURE: Bulk select posts and delete drafts (#34972)
## 🔍 Overview

This PR adds bulk selection functionality to the PostList component and
implements optimized bulk deletion for the drafts page. Users can now
select multiple drafts and delete them all at once with a single network
request, significantly improving performance and user experience.

The implementation includes:
- A new reusable bulk selection system for PostList components
- Optimized bulk delete endpoint that reduces network requests by 90%
- Comprehensive bulk controls UI with select all/clear all functionality
- Shift+click range selection similar to topic lists
- Complete test coverage for all new functionality

##  More details

**Bulk Selection System**

The PostList component now supports optional bulk selection through
these new parameters:

- `@bulkSelectEnabled={{true}}` - Shows checkboxes next to each post
- `@bulkSelectHelper={{helper}}` - Manages selection state (use
`PostBulkSelectHelper`)
- `@bulkActions={{actions}}` - Array of bulk action objects for the
dropdown menu

**Usage Example:**
```gjs
import Component from "@glimmer/component";
import { action } from "@ember/object";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import PostBulkSelectHelper from "discourse/lib/post-bulk-select-helper";

export default class MyComponent extends Component {
  bulkSelectHelper = new PostBulkSelectHelper(this);

  constructor() {
    super(...arguments);
    // Initial updatePosts call
    this.updateBulkSelectPosts();
  }

  @action
  updateBulkSelectPosts() {
    if (this.shouldEnableBulkSelect && this.args.posts) {
      this.bulkSelectHelper.updatePosts(this.args.posts);
    }
  }

  get showBulkSelectHelper() {
    return this.shouldEnableBulkSelect ? this.bulkSelectHelper : null;
  }

  get bulkActions() {
    return [
      {
        label: "delete_selected",
        icon: "trash-can", 
        action: this.handleBulkDelete,
        class: "btn-danger"
      }
    ];
  }

  <template>
    <PostList 
      @posts={{@posts}}
      @bulkSelectEnabled={{this.shouldEnableBulkSelect}}
      @bulkSelectHelper={{this.showBulkSelectHelper}}
      @bulkActions={{this.bulkActions}}
      {{didUpdate this.updateBulkSelectPosts @posts}}
    />
  </template>
}
```

**Performance Optimization**

The drafts page now uses a new bulk delete endpoint (`DELETE
/drafts/bulk_destroy`) that:
- Processes multiple drafts in a single HTTP request instead of N
individual requests
- Uses database transactions for atomic operations (all-or-nothing)  
- Reduces database queries from 2N to 2 total queries
- Validates draft sequences upfront to fail fast on conflicts

**Technical Implementation**

- `PostBulkSelectHelper`: New helper class for managing selection state
with support for individual selection, range selection (shift+click),
and bulk operations with reactive posts tracking
- `PostListBulkControls`: New component providing selection count,
select all/clear all buttons, and bulk actions dropdown
- Enhanced PostList and PostListItem components with conditional bulk
selection UI
- Updated user-stream component to use optimized bulk deletion with
automatic selection cleanup
- Comprehensive styling with responsive design

**API Changes**

- New controller action: `DraftsController#bulk_destroy`
- New route: `DELETE /drafts/bulk_destroy`
- New JavaScript method: `Draft.bulkClear(drafts)`
- Enhanced `PostBulkSelectHelper` with `updatePosts()` method for
reactive data updates
- Fully backward compatible - existing single delete functionality
unchanged

**Testing**

- 9 new controller specs covering bulk deletion edge cases, validation,
and API access
- 11 integration tests for PostList bulk selection functionality  
- 10 system specs for end-to-end drafts page bulk selection workflows
- All existing tests continue to pass

## 📹 Screen Recording


https://github.com/user-attachments/assets/2d5a9b38-f1cb-43ee-88ac-285b71083612
2025-09-29 12:47:54 -07:00
..
admin DEV: Finish renaming secure_session to server_session 2025-09-23 10:35:02 +02:00
api DEV: Update api docs spec for invite enpoint (#34843) 2025-09-17 14:50:53 -05:00
examples SECURITY: Impose a upper bound on limit params in various controllers 2023-07-28 12:53:46 +01:00
about_controller_spec.rb FIX: crawler view with unicode usernames (#27051) 2024-05-16 17:11:24 +02:00
application_controller_spec.rb DEV: Add missing test case to ApplicationController (#35001) 2025-09-26 12:05:44 +08:00
associate_accounts_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
badges_controller_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
bookmarks_controller_spec.rb DEV: Call Discourse.redis.flushdb after the end of each test (#29117) 2024-10-09 07:19:31 +08:00
categories_controller_spec.rb DEV: Plugin api for saving category attribute (#34898) 2025-09-23 11:14:45 +08:00
clicks_controller_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
composer_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
composer_messages_controller_spec.rb DEV: remove educational tips and associated site settings (#33012) 2025-06-03 09:44:35 +04:00
crawler_hreflang_spec.rb FEATURE: Append locale to links in crawler view if present (#34466) 2025-08-22 08:46:33 +08:00
csp_reports_controller_spec.rb DEV: Don’t replace Rails logger in specs (#29721) 2024-11-13 08:47:39 +08:00
default_headers_spec.rb DEV: Disallow the use of Rails.logger= in RSpec tests (#31920) 2025-03-21 08:48:38 +08:00
directory_columns_controller_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
directory_items_controller_spec.rb FIX: Allow user directory searches to return more than 20 matching results (#31032) 2025-01-29 11:02:42 -04:00
discourse_id_controller_spec.rb DEV: Add validator for enable_discourse_id setting (#33710) 2025-07-21 08:18:36 -04:00
do_not_disturb_controller_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
drafts_controller_spec.rb FEATURE: Bulk select posts and delete drafts (#34972) 2025-09-29 12:47:54 -07:00
edit_directory_columns_controller_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
email_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
embed_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
emojis_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
exceptions_controller_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
export_csv_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
extra_locales_controller_spec.rb DEV: Switch to type="module" for translation files (#33126) 2025-06-09 13:13:42 +01:00
finish_installation_controller_spec.rb DEV: Improve error message when test fails (#25067) 2023-12-29 12:44:41 +08:00
form_templates_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
forums_controller_spec.rb FIX: improve "read only" modes (#33521) 2025-07-10 09:08:00 +02:00
groups_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
hashtags_controller_spec.rb DEV: add tag hashtag data source style type (#33289) 2025-06-20 18:08:47 +04:00
highlightjs_controller_spec.rb DEV: Simplify CORS logic for public asset routes (#33106) 2025-06-09 08:58:27 +01:00
home_page_controller_spec.rb DEV: Add site description to crawler homepage view (#32845) 2025-05-22 08:33:59 +10:00
inline_onebox_controller_spec.rb SECURITY: Limit /inline-onebox to 10 URLs at a time 2025-02-04 13:32:53 -03:00
invites_controller_spec.rb DEV: Finish renaming secure_session to server_session 2025-09-23 10:35:02 +02:00
list_controller_spec.rb DEV: Add configurable limit for page param in TopicQuery (#34728) 2025-09-04 12:20:44 -04:00
metadata_controller_spec.rb FEATURE: Add Discourse ID registration service with challenge endpoint (#34326) 2025-08-26 10:54:21 +02:00
net_http_header_spec.rb DEV: Add a user agent to all HTTP requests that Discourse makes. (#31555) 2025-03-03 16:32:25 +11:00
net_http_timeout_spec.rb FIX: Set sane default for Net::HTTP when processing a request (#28141) 2024-08-06 07:12:42 +08:00
noscript_escape_spec.rb SECURITY: Properly escape user content within <noscript> 2024-01-30 09:10:09 -07:00
notifications_controller_spec.rb FEATURE: Localize topic titles in notifications and bookmarks (#34059) 2025-08-05 12:12:22 +08:00
offline_controller_spec.rb Add RSpec 4 compatibility (#17652) 2022-07-28 10:27:38 +08:00
omniauth_callbacks_controller_spec.rb FIX: Don’t store return path in the session 2025-09-19 10:05:20 +02:00
onebox_controller_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
permalinks_controller_spec.rb FIX: Permalink.create didn't work as expected anymore (#29895) 2024-11-22 21:11:26 +01:00
post_action_users_controller_spec.rb DEV: Add post_action_users_list modifier for PostActionUsersController (#25740) 2024-02-20 09:48:09 +10:00
post_actions_controller_spec.rb DEV: Remove full group refreshes from tests (#25414) 2024-01-25 14:28:26 +08:00
post_localizations_controller_spec.rb DEV: Remove 'experimental' prefix from settings (#33233) 2025-06-19 12:23:42 +08:00
post_readers_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
posts_controller_spec.rb FIX: Allow reverting topic revisions with only tag changes (#34169) 2025-09-18 18:26:52 -07:00
presence_controller_spec.rb DEV: Call Discourse.redis.flushdb after the end of each test (#29117) 2024-10-09 07:19:31 +08:00
published_pages_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
push_notification_controller_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
qunit_controller_spec.rb DEV: Fix broken request/qunit_controller_spec.rb (#32376) 2025-04-21 13:46:25 +08:00
reviewable_claimed_topics_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
reviewable_notes_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
reviewables_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
robots_txt_controller_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
safe_mode_controller_spec.rb DEV: Compile 'common' CSS into own assets (#31416) 2025-05-01 10:44:49 +01:00
search_controller_spec.rb DEV: Prevent crawlers from loading search results. (#31553) 2025-08-27 15:59:08 +10:00
session_controller_spec.rb DEV: Finish renaming secure_session to server_session 2025-09-23 10:35:02 +02:00
sidebar_sections_controller_spec.rb FIX: Sidebar messages link updates (#33444) 2025-07-07 11:55:39 +08:00
similar_topics_controller_spec.rb DEV: Remove full group refreshes from tests (#25414) 2024-01-25 14:28:26 +08:00
site_controller_spec.rb DEV: Introduce stylelint (#29852) 2025-01-20 15:27:42 +00:00
sitemap_controller_spec.rb DEV: Remove unnecessary rails_helper requiring (#26364) 2024-03-26 11:32:01 +01:00
slugs_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
static_controller_spec.rb FEATURE: Preserve the query when redirecting to login/signup (#34363) 2025-08-20 10:59:47 -05:00
steps_controller_spec.rb FEATURE: Localize language names (#33790) 2025-07-29 11:48:45 +08:00
stylesheets_controller_spec.rb DEV: Fix test incorrectly removing stylesheet cache of other processes (#25103) 2024-01-03 13:15:35 +08:00
svg_sprite_controller_spec.rb DEV: enable raise_error in test envs for deprecated icons in svg_sprite.rb (#30980) 2025-02-04 21:21:20 +08:00
tag_groups_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
tags_controller_spec.rb FEATURE: Add new edit_tags_allowed_groups setting (#34000) 2025-08-01 15:19:20 +08:00
theme_javascripts_controller_spec.rb DEV: Use rollup for theme JS compilation (#33103) 2025-07-25 12:02:29 +01:00
topic_localizations_controller_spec.rb DEV: Remove 'experimental' prefix from settings (#33233) 2025-06-19 12:23:42 +08:00
topic_view_stats_controller_spec.rb FEATURE: topic_view_stats table with daily fidelity (#27197) 2024-05-27 15:25:32 +10:00
topics_controller_spec.rb FEATURE: Localize topic view in crawler view (#34253) 2025-08-13 16:38:07 +08:00
uploads_controller_multisite_spec.rb DEV: Upgrade Rails to version 7.1 2024-07-04 10:58:21 +02:00
uploads_controller_spec.rb FEATURE: managed auth can override avatars (#34123) 2025-09-02 12:49:43 -04:00
user_actions_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
user_api_key_clients_controller_spec.rb Add user api key client rate limit settings (#30402) 2024-12-30 11:10:48 -05:00
user_api_keys_controller_spec.rb Require permitted scopes when registering a client (#29718) 2024-11-19 15:28:04 -05:00
user_avatars_controller_spec.rb FEATURE: reduce avatar sizes to 6 from 20 (#21319) 2023-06-01 10:00:01 +10:00
user_badges_controller_spec.rb FIX: error when trying to un-favorite badge (#32369) 2025-04-22 15:36:48 +08:00
user_status_controller_spec.rb DEV: discourse-emojis gem (#31408) 2025-03-03 13:09:08 +01:00
users_controller_spec.rb DEV: Finish renaming secure_session to server_session 2025-09-23 10:35:02 +02:00
users_email_controller_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
webhooks_controller_spec.rb FIX: improve "read only" modes (#33521) 2025-07-10 09:08:00 +02:00
wizard_controller_spec.rb DEV: Fix various spec linting issues (#24672) 2023-12-04 13:45:19 +01:00