From 0e35252a54c58beb8c4a9b5de2bb168afda973be Mon Sep 17 00:00:00 2001
From: Simon Knox <simon@gitlab.com>
Date: Tue, 11 Mar 2025 04:34:43 +1100
Subject: [PATCH] Fix archived view of custom field settings

---
 .../settings/work_items/custom_field_form.vue | 15 ++++++++++
 .../work_items/custom_fields_list.vue         | 22 +++++++++++----
 .../work_items/custom_field_form_spec.js      | 28 +++++++++++++++++++
 .../work_items/custom_fields_list_spec.js     | 14 ++++++++++
 4 files changed, 73 insertions(+), 6 deletions(-)

diff --git a/ee/app/assets/javascripts/groups/settings/work_items/custom_field_form.vue b/ee/app/assets/javascripts/groups/settings/work_items/custom_field_form.vue
index 029ae1b44de87..c8bf70b427b1e 100644
--- a/ee/app/assets/javascripts/groups/settings/work_items/custom_field_form.vue
+++ b/ee/app/assets/javascripts/groups/settings/work_items/custom_field_form.vue
@@ -170,6 +170,19 @@ export default {
         option.value.trim(),
       );
     },
+    resetForm() {
+      this.formData = {
+        fieldType: FIELD_TYPE_OPTIONS[0].value,
+        fieldName: '',
+        workItemTypes: [],
+        selectOptions: [{ value: '' }],
+      };
+      this.formState = {
+        fieldName: null,
+        selectOptions: null,
+      };
+      this.mutationError = '';
+    },
     async saveCustomField() {
       if (!this.validateForm()) {
         return;
@@ -211,6 +224,7 @@ export default {
 
         this.$emit(this.isEditing ? 'updated' : 'created');
         this.visible = false;
+        this.resetForm();
       } catch (error) {
         Sentry.captureException(error);
         this.mutationError =
@@ -311,6 +325,7 @@ export default {
             v-model="formData.fieldName"
             width="md"
             :state="formState.fieldName"
+            autocomplete="off"
             @input="validateFieldName"
           />
         </gl-form-group>
diff --git a/ee/app/assets/javascripts/groups/settings/work_items/custom_fields_list.vue b/ee/app/assets/javascripts/groups/settings/work_items/custom_fields_list.vue
index 45a6244dbb3ef..f73e8b0ed47d8 100644
--- a/ee/app/assets/javascripts/groups/settings/work_items/custom_fields_list.vue
+++ b/ee/app/assets/javascripts/groups/settings/work_items/custom_fields_list.vue
@@ -54,6 +54,9 @@ export default {
         ? s__('WorkItem|Active custom fields')
         : s__('WorkItem|Archived custom fields');
     },
+    archiveButtonIcon() {
+      return this.showActive ? 'archive' : 'redo';
+    },
   },
   apollo: {
     customFields: {
@@ -104,7 +107,7 @@ export default {
       return null;
     },
     archiveButtonText(item) {
-      return item.active
+      return this.showActive
         ? sprintf(s__('WorkItem|Archive %{itemName}'), { itemName: item.name })
         : sprintf(s__('WorkItem|Unarchive %{itemName}'), { itemName: item.name });
     },
@@ -272,10 +275,15 @@ export default {
       data-testid="table-title"
     >
       {{ titleText }}
-      <gl-badge v-if="!isLoading" class="gl-mx-4">
-        <!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings -->
-        {{ customFields.count }}/50
-      </gl-badge>
+      <template v-if="!isLoading">
+        <gl-badge v-if="showActive" class="gl-mx-4">
+          <!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings -->
+          {{ customFields.count }}/50
+        </gl-badge>
+        <gl-badge v-else class="gl-mx-4">
+          {{ customFields.count }}
+        </gl-badge>
+      </template>
 
       <custom-field-form class="gl-ml-auto" @created="$apollo.queries.customFields.refetch()" />
     </div>
@@ -324,14 +332,16 @@ export default {
       <template #cell(actions)="{ item }">
         <div class="gl-align-items-center gl-end gl-flex gl-justify-end gl-gap-1">
           <custom-field-form
+            v-if="showActive"
             :custom-field-id="item.id"
             :custom-field-name="item.name"
+            data-testid="editButton"
             @updated="$apollo.queries.customFields.refetch()"
           />
           <gl-button
             v-gl-tooltip="archiveButtonText(item)"
             :aria-label="archiveButtonText(item)"
-            icon="archive"
+            :icon="archiveButtonIcon"
             category="tertiary"
             data-testid="archiveButton"
             @click="archiveCustomField(item.id)"
diff --git a/ee/spec/frontend/groups/settings/work_items/custom_field_form_spec.js b/ee/spec/frontend/groups/settings/work_items/custom_field_form_spec.js
index 782c431c1d652..ecd707afcfdd4 100644
--- a/ee/spec/frontend/groups/settings/work_items/custom_field_form_spec.js
+++ b/ee/spec/frontend/groups/settings/work_items/custom_field_form_spec.js
@@ -158,6 +158,10 @@ describe('CustomFieldForm', () => {
       findToggleModalButton().vm.$emit('click');
     });
 
+    it('has autocomplete disabled on the name field', () => {
+      expect(findFieldNameInput().attributes('autocomplete')).toBe('off');
+    });
+
     it.each(['SINGLE_SELECT', 'MULTI_SELECT'])(
       `shows select options section when field type is %s`,
       async (type) => {
@@ -334,6 +338,30 @@ describe('CustomFieldForm', () => {
 
       expect(Sentry.captureException).toHaveBeenCalled();
     });
+
+    it('resets form after successful creation', async () => {
+      const createFieldHandler = jest.fn().mockResolvedValue(mockCreateFieldResponse);
+      createComponent({ createFieldHandler });
+
+      await findToggleModalButton().vm.$emit('click');
+
+      findFieldTypeSelect().vm.$emit('input', 'TEXT');
+      findFieldNameInput().vm.$emit('input', 'Test Field');
+      findWorkItemTypeListbox().vm.$emit('select', [mockWorkItemTypes[2].id]);
+
+      await nextTick();
+
+      findSaveCustomFieldButton().vm.$emit('click');
+      await waitForPromises();
+
+      await findToggleModalButton().vm.$emit('click');
+      await waitForPromises();
+
+      expect(findFieldTypeSelect().attributes('value')).toBe('SINGLE_SELECT');
+      expect(findFieldNameInput().props('value')).toBe('');
+      expect(findWorkItemTypeListbox().props('selected')).toEqual([]);
+      expect(findWorkItemTypeListbox().props('toggleText')).toBe('Select types');
+    });
   });
 
   describe('edit mode', () => {
diff --git a/ee/spec/frontend/groups/settings/work_items/custom_fields_list_spec.js b/ee/spec/frontend/groups/settings/work_items/custom_fields_list_spec.js
index 578616f5b5b05..bb6c5ce9b47f7 100644
--- a/ee/spec/frontend/groups/settings/work_items/custom_fields_list_spec.js
+++ b/ee/spec/frontend/groups/settings/work_items/custom_fields_list_spec.js
@@ -63,6 +63,7 @@ describe('CustomFieldsTable', () => {
   });
 
   const findTableItems = () => wrapper.findAll('tbody tr');
+  const findEditButton = () => wrapper.find('[data-testid="editButton"]');
   const findArchiveButton = () => wrapper.find('[data-testid="archiveButton"]');
   const findDetailsButton = () => wrapper.find('[data-testid="toggleDetailsButton"');
   const findAlert = () => wrapper.find('[data-testid="alert"]');
@@ -178,6 +179,19 @@ describe('CustomFieldsTable', () => {
       expect(timeagoComponents.exists()).toBe(true);
       expect(timeagoComponents.at(0).props('time')).toBe(selectField.updatedAt);
     });
+
+    it('shows archive icon and edit button for active fields', () => {
+      expect(findArchiveButton().props('icon')).toBe('archive');
+      expect(findEditButton().exists()).toBe(true);
+    });
+
+    it('shows redo icon and no edit button for archived fields', async () => {
+      await findArchivedFilterButton().vm.$emit('click');
+      await waitForPromises();
+
+      expect(findArchiveButton().props('icon')).toBe('redo');
+      expect(findEditButton().exists()).toBe(false);
+    });
   });
 
   it('refetches list after create-custom-field emits created', async () => {
-- 
GitLab