Skip to content

Conversation

@myabc
Copy link
Contributor

@myabc myabc commented Jan 7, 2026

Ticket

https://community.openproject.org/wp/70292

What are you trying to accomplish?

Screenshots

What approach did you choose and why?

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)


if (cb.checked) {
const multi = this.multiSelectTargets[0] as HTMLInputElement|undefined;
const multi = undefined; // FIXME this.multiSelectTargets[0] as HTMLInputElement|undefined;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable multi.

Copilot Autofix

AI 21 days ago

In general, unused variables should be removed when they are not read anywhere, especially if any logic depending on them is already commented out. If future logic is planned, that intent can be captured in comments or TODO/FIXME notes without introducing dead code.

Here, the multi variable on line 153 is never used. The logic that would have referenced it is commented out. To fix this without changing behavior, delete the const multi = ... line, but keep the FIXME comment so the future plan remains documented. No imports or additional methods are needed. Only the uncheckOtherDefaults method in frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts needs editing, removing that single declaration line.

Suggested changeset 1
frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts
--- a/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts
+++ b/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts
@@ -150,8 +150,7 @@
     const cb = event.target as HTMLInputElement;
 
     if (cb.checked) {
-      const multi = undefined; // FIXME this.multiSelectTargets[0] as HTMLInputElement|undefined;
-
+      // FIXME use this.multiSelectTargets[0] as HTMLInputElement|undefined
       // if (multi?.checked === false) {
       //   this.customOptionDefaultsTargets.forEach((el) => (el.checked = false));
       //   cb.checked = true;
EOF
@@ -150,8 +150,7 @@
const cb = event.target as HTMLInputElement;

if (cb.checked) {
const multi = undefined; // FIXME this.multiSelectTargets[0] as HTMLInputElement|undefined;

// FIXME use this.multiSelectTargets[0] as HTMLInputElement|undefined
// if (multi?.checked === false) {
// this.customOptionDefaultsTargets.forEach((el) => (el.checked = false));
// cb.checked = true;
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the custom field forms to use Primer Design System components (ViewComponent-based) instead of the legacy form helpers. The changes replace the old ERB-based forms with modular form components organized under CustomFields::Details::* and introduce new component-based rendering for custom options tables.

Changes:

  • Replaced legacy form helpers with Primer Forms components for custom field configuration
  • Refactored custom options table into reusable ViewComponent-based components
  • Removed CustomFieldFormatDependent#attributes method in favor of simpler visible? method
  • Updated Stimulus controller to use template-based row cloning instead of manual DOM manipulation

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
spec/lib/open_project/custom_field_format_dependent_spec.rb Removed obsolete spec testing the removed attributes and stimulus_config methods
spec/components/custom_fields/custom_options_component_spec.rb Added component test for the new CustomOptionsComponent
spec/components/custom_fields/custom_options/table_component_spec.rb Added component test for the custom options table
spec/components/custom_fields/custom_options/row_component_spec.rb Added component test for custom option rows
lib/open_project/custom_field_format_dependent.rb Simplified to remove format-dependent targets config and expose only visible? method
frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts Updated to use template-based row addition and removed format-dependent target management
app/views/custom_fields/new.html.erb Converted from labelled_tabular_form_for to settings_primer_form_with
app/views/custom_fields/edit.html.erb Converted from labelled_tabular_form_for to settings_primer_form_with
app/views/custom_fields/_form.html.erb Replaced inline form fields with modular Primer Form components
app/views/custom_fields/_custom_options.html.erb Removed legacy partial, replaced by component
app/views/admin/settings/project_custom_fields/new.html.erb Converted to settings_primer_form_with
app/views/admin/settings/project_custom_fields/edit.html.erb Converted to settings_primer_form_with
app/forms/custom_fields/save.rb New form component for submit button
app/forms/custom_fields/details/*.rb New modular form components for each custom field detail section
app/components/table_component.rb Enhanced to accept table_arguments for passing attributes to table element
app/components/table_component.html.erb Updated to use Primer::BaseComponent for table rendering
app/components/custom_fields/custom_options_component.rb New component orchestrating custom options display
app/components/custom_fields/custom_options_component.html.erb Template for custom options with table and hidden template row
app/components/custom_fields/custom_options/table_component.rb Table component for custom options
app/components/custom_fields/custom_options/row_component.rb Row component for individual custom option
app/components/custom_fields/custom_options/base_row_component.rb Base class for custom option rows with common functionality

f.check_box(
name: :editable,
label: attribute_name(:editable),
caption: instructions_for(:admin_only)
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The caption for the editable checkbox is incorrectly using :admin_only instead of :editable. This will display the wrong help text. Change to instructions_for(:editable).

Suggested change
caption: instructions_for(:admin_only)
caption: instructions_for(:editable)

Copilot uses AI. Check for mistakes.
f.check_box(
name: :searchable,
label: attribute_name(:searchable),
caption: for_project ? instructions_for(:searchable_for_project) : instructions_for(:searchable)
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The translation key :searchable_for_project does not exist in the translations. Based on the en.yml file, the correct keys are :searchable with nested keys all and project. Change to instructions_for(for_project ? :searchable_for_project : :searchable) and add the missing translation key, or use the existing structure like I18n.t(for_project ? 'custom_fields.instructions.searchable.project' : 'custom_fields.instructions.searchable.all').

Suggested change
caption: for_project ? instructions_for(:searchable_for_project) : instructions_for(:searchable)
caption: I18n.t(
for_project ? 'custom_fields.instructions.searchable.project' : 'custom_fields.instructions.searchable.all'
)

Copilot uses AI. Check for mistakes.
f.check_box(
name: :is_required,
label: attribute_name(:is_required),
caption: for_project ? instructions_for(:is_required_for_project) : instructions_for(:is_required)
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The translation key :is_required_for_project does not exist. Based on en.yml, the correct structure uses nested keys under is_required with all and project sub-keys. Change to use the existing translation structure like I18n.t(for_project ? 'custom_fields.instructions.is_required.project' : 'custom_fields.instructions.is_required.all').

Suggested change
caption: for_project ? instructions_for(:is_required_for_project) : instructions_for(:is_required)
caption: I18n.t(for_project ? 'custom_fields.instructions.is_required.project' : 'custom_fields.instructions.is_required.all')

Copilot uses AI. Check for mistakes.
Comment on lines 80 to +89
<% when "ProjectCustomField" %>
<div class="form--field">
<%= f.check_box :is_required %>
<div class="form--field-instructions">
<p><%= t("custom_fields.instructions.is_required.project") %></p>
</div>
</div>
<div class="form--field">
<%= f.check_box :is_for_all %>
<div class="form--field-instructions">
<p><%= t("custom_fields.instructions.is_for_all.project") %></p>
</div>
</div>
<div class="form--field">
<%= f.check_box :admin_only %>
<div class="form--field-instructions">
<p><%= t("custom_fields.instructions.admin_only.project") %></p>
</div>
</div>
<div class="form--field" <%= format_dependent.attributes(:searchable) %>>
<%= f.check_box :searchable %>
<div class="form--field-instructions">
<p><%= t("custom_fields.instructions.searchable.project") %></p>
</div>
</div>
<% when "TimeEntryCustomField" %>
<div class="form--field">
<%= f.check_box :is_required %>
<div class="form--field-instructions">
<p><%= t("custom_fields.instructions.is_required.all") %></p>
</div>
</div>
<%=
render(
Primer::Forms::FormList.new(
CustomFields::Details::RequiredForm.new(f, for_project: true),
CustomFields::Details::AdminOnlyForm.new(f),
CustomFields::Details::SearchableForm.new(f, for_project: true)
)
)
%>
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The is_for_all field is missing for ProjectCustomField. According to the removed old form code, ProjectCustomField should have the following fields: is_required, is_for_all, admin_only, and searchable. The is_for_all field needs to be added. Consider adding a CustomFields::Details::IsForAllForm.new(f) component between RequiredForm and AdminOnlyForm, or including it in the AdminOnlyForm or a ProjectCustomFieldForm component.

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +158
const multi = undefined; // FIXME this.multiSelectTargets[0] as HTMLInputElement|undefined;

if (multi?.checked === false) {
this.customOptionDefaultsTargets.forEach((el) => (el.checked = false));
cb.checked = true;
}
// if (multi?.checked === false) {
// this.customOptionDefaultsTargets.forEach((el) => (el.checked = false));
// cb.checked = true;
// }
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The uncheckOtherDefaults functionality is commented out with a FIXME note. This breaks the behavior where checking a default value should uncheck others when multi-select is disabled. The multi-select target needs to be properly identified in the new component structure, or this logic needs to be reimplemented to work with the new form components.

Copilot uses AI. Check for mistakes.
if (idx < this.customOptionRowTargets.length - 1) {
this.customOptionRowTargets[idx + 1].after(row);
const idx = this.customOptionRows.indexOf(row);

Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

Trailing whitespace on an otherwise empty line should be removed for code cleanliness.

Copilot uses AI. Check for mistakes.
<section
class="form--section"
id="custom_field_form">
++#%>
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The ERB closing tag has unusual formatting with +++#%> instead of the standard ++#%>. While this might work, it's inconsistent with the opening tag format and could be confusing.

Suggested change
++#%>
%>

Copilot uses AI. Check for mistakes.
See COPYRIGHT and LICENSE files for more details.
++#%>
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

The ERB closing tag has unusual formatting with +++#%> instead of the standard ++#%>. Ensure consistency with the opening copyright comment format.

Suggested change
++#%>
--%>

Copilot uses AI. Check for mistakes.

event.preventDefault();
event.stopImmediatePropagation();

Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

Trailing whitespace should be removed from this empty line.

Copilot uses AI. Check for mistakes.

if (cb.checked) {
const multi = this.multiSelectTargets[0] as HTMLInputElement|undefined;
const multi = undefined; // FIXME this.multiSelectTargets[0] as HTMLInputElement|undefined;
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

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

Unused variable multi.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants