Skip to content

Commit 225d874

Browse files
committed
Add handling for indeterminate checkboxes
1 parent f1fc3c1 commit 225d874

3 files changed

Lines changed: 54 additions & 8 deletions

File tree

app/controllers/workflows/tabs_controller.rb

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,12 @@ def update # rubocop:disable Metrics/AbcSize
5757
success = false
5858
Workflow.transaction do
5959
success = true
60+
base_params = permitted_status_params
61+
indeterminate = permitted_indeterminate_params
6062
@roles.each do |role|
63+
role_params = indeterminate.empty? ? base_params : role_specific_params(base_params, indeterminate, role)
6164
result = Workflows::BulkUpdateService.new(role:, type: @type, tab: @tab)
62-
.call(permitted_status_params)
65+
.call(role_params)
6366
success = false unless result.success?
6467
end
6568
raise ActiveRecord::Rollback unless success
@@ -200,10 +203,40 @@ def workflows_for_form
200203
end
201204

202205
def permitted_status_params
203-
return {} if params["status"].blank?
206+
status_params("status")
207+
end
208+
209+
def permitted_indeterminate_params
210+
status_params("indeterminate_status")
211+
end
204212

205-
params["status"]
213+
def status_params(key)
214+
return {} if params[key].blank?
215+
216+
params[key]
206217
.to_unsafe_h
207-
.select { |key, value| /\A\d+\z/.match?(key) && value.keys.all? { /\A\d+\z/.match?(it) } }
218+
.select { |k, value| /\A\d+\z/.match?(k) && value.keys.all? { /\A\d+\z/.match?(it) } }
219+
end
220+
221+
def role_specific_params(base_params, indeterminate, role)
222+
params = base_params.deep_dup
223+
indeterminate.each do |old_id, new_ids|
224+
new_ids.each_key do |new_id|
225+
# Restore from DB so that it isn't overwritten by indeterminate state (unchecked)
226+
had_transition = Workflow.exists?(
227+
role_id: role.id,
228+
type_id: @type.id,
229+
old_status_id: old_id.to_i,
230+
new_status_id: new_id.to_i,
231+
author: @tab == "author",
232+
assignee: @tab == "assignee"
233+
)
234+
if had_transition
235+
params[old_id] ||= {}
236+
params[old_id][new_id] = "1"
237+
end
238+
end
239+
end
240+
params
208241
end
209242
end

app/views/workflows/_form.html.erb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ See COPYRIGHT and LICENSE files for more details.
191191
newly_added_status = @added_status_ids.include?(old_status.id) || @added_status_ids.include?(new_status.id)
192192
some_roles = !transition_role_ids.empty? && transition_role_ids.size < @roles.size && !newly_added_status %>
193193
<td>
194+
<%= hidden_field_tag "indeterminate_status[#{old_status.id}][#{new_status.id}]", "1" if some_roles %>
194195
<%=
195196
render(Primer::BaseComponent.new(tag: :div, display: :flex, align_items: :center, mx: 1)) do
196197
render(
@@ -199,7 +200,7 @@ See COPYRIGHT and LICENSE files for more details.
199200
name: "status[#{old_status.id}][#{new_status.id}]",
200201
id: "status_#{old_status.id}_#{new_status.id}", # See BUG https://github.com/primer/view_components/issues/3811
201202
value: name,
202-
checked: transition_role_ids.any? || newly_added_status,
203+
checked: !some_roles && (transition_role_ids.any? || newly_added_status),
203204
label: t(".matrix_checkbox_label", old_status: old_status.name, new_status: new_status.name),
204205
visually_hide_label: true,
205206
data: {

frontend/src/stimulus/controllers/dynamic/admin/workflow-checkbox-state.controller.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export default class WorkflowCheckboxStateController extends Controller<HTMLForm
8282
if (statusCheckboxes) {
8383
this.applyState(statusCheckboxes);
8484
// Recompute dirty flag: the restored state may differ from DB pristine.
85-
this.onCheckboxChange();
85+
this.recomputeDirtyFlag();
8686
} else {
8787
// Apply indeterminate checkboxes only on fresh server rendered content.
8888
this.initIndeterminateCheckboxes();
@@ -264,12 +264,24 @@ export default class WorkflowCheckboxStateController extends Controller<HTMLForm
264264
window.OpenProject.pageState = hasChanges ? 'edited' : 'pristine';
265265
}
266266

267-
private onCheckboxChange = () => {
267+
private onCheckboxChange = (event:Event) => {
268+
this.removeIndeterminateMarker(event.target as HTMLInputElement);
269+
this.recomputeDirtyFlag();
270+
};
271+
272+
private recomputeDirtyFlag() {
268273
const current = this.captureState();
269274
const hasChanges = Object.keys(current).some((key) => current[key] !== this.initialCheckboxState[key]);
270275

271276
this.hasCheckboxChangesValue = hasChanges;
272-
};
277+
}
278+
279+
private removeIndeterminateMarker(checkbox:HTMLInputElement):void {
280+
const { oldStatus, newStatus } = checkbox.dataset;
281+
this.element.querySelector<HTMLInputElement>(
282+
`input[name="indeterminate_status[${oldStatus}][${newStatus}]"]`,
283+
)?.remove();
284+
}
273285

274286
private captureState():Record<string, boolean> {
275287
const checkboxes:Record<string, boolean> = {};

0 commit comments

Comments
 (0)