project access_dropdown.vue - selection, save payload, preselect, and toggle label
## Context | | | |---|---| | **Phase** | 5 of 6 | | **Parallel with** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594887+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594889+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594890+ | | **Blocked by** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594885+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594886+ | | **Unblocks** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594891+ | ## Summary Update `access_dropdown.vue` (project level) to correctly build the save payload, handle preselection from server data, and update the toggle label for custom role selections. ## Background This issue covers the data-flow side of the custom roles dropdown integration. The display side (fetching and rendering the custom roles section) is covered in Issue 13. This issue handles: 1. **`setDataForSave`** — parsing server-returned access level data to populate initial selected state 2. **`selection` computed** — including `member_role_id` in the save payload emitted to the parent 3. **`setSelected`** — restoring selected state on initial load from preselected items 4. **`toggleLabel` computed** — showing "N custom roles" in the dropdown button label ## Relevant file `app/assets/javascripts/projects/settings/components/access_dropdown.vue` ## Changes required ### `setDataForSave(items)` method Add a branch for `member_role_id`: ```javascript setDataForSave(items) { this.selected = items.reduce( (selected, item) => { if (item.group_id) { selected[LEVEL_TYPES.GROUP].push({ id: item.group_id, ...item }); } else if (item.user_id) { selected[LEVEL_TYPES.USER].push({ id: item.user_id, ...item }); } else if (item.member_role_id) { selected[LEVEL_TYPES.MEMBER_ROLE].push({ id: item.member_role_id, ...item }); } else if (item.access_level) { const level = this.accessLevelsData.find(({ id }) => item.access_level === id); selected[LEVEL_TYPES.ROLE].push(level); } else if (item.deploy_key_id) { selected[LEVEL_TYPES.DEPLOY_KEY].push({ id: item.deploy_key_id, ...item }); } return selected; }, { /* ...empty initial state including MEMBER_ROLE */ } ); }, ``` Note: `member_role_id` must be checked before `access_level` because a `member_role`-type access level row also has an `access_level` integer set (to the role's `base_access_level`). Without this ordering, it would be misclassified as a plain role. ### `selection` computed property ```javascript selection() { return [ ...this.getDataForSave(LEVEL_TYPES.ROLE, 'access_level'), ...this.getDataForSave(LEVEL_TYPES.GROUP, 'group_id'), ...this.getDataForSave(LEVEL_TYPES.USER, 'user_id'), ...this.getDataForSave(LEVEL_TYPES.DEPLOY_KEY, 'deploy_key_id'), ...this.getDataForSave(LEVEL_TYPES.MEMBER_ROLE, 'member_role_id'), ]; }, ``` ### `setSelected({ initial })` method Add intersection logic for `MEMBER_ROLE`, similar to groups: ```javascript const selectedCustomRoles = intersectionWith( this.customRoles, this.preselectedItems, (role, selected) => { return selected.type === LEVEL_TYPES.MEMBER_ROLE && role.id === selected.member_role_id; }, ); this.selected[LEVEL_TYPES.MEMBER_ROLE] = selectedCustomRoles; ``` ### `toggleLabel` computed Add custom role count: ```javascript if (counts[LEVEL_TYPES.MEMBER_ROLE] > 0) { labelPieces.push(n__('1 custom role', '%d custom roles', counts[LEVEL_TYPES.MEMBER_ROLE])); } ``` And update `isOnlyRoleSelected` to also require `counts[LEVEL_TYPES.MEMBER_ROLE] === 0`. ### `clearSelection()` method Ensure `LEVEL_TYPES.MEMBER_ROLE` is included: ```javascript clearSelection() { Object.values(LEVEL_TYPES).forEach((level) => { this.selected[level] = []; }); }, ``` This already works if `LEVEL_TYPES.MEMBER_ROLE` is defined and `selected` has it as a key. ## Notes - The ordering in `setDataForSave` matters: check `member_role_id` before `access_level` since member role rows have both set - The save payload `{ member_role_id: N }` is what gets sent to the protected branches API endpoint (`allowed_to_merge`, `allowed_to_push`, `allowed_to_unprotect`) — this matches the format expected by Issue 6 ## Testing - Jest: `setDataForSave` correctly identifies `member_role_id` items - Jest: `setDataForSave` does NOT misclassify a `member_role_id` item as a `role` type when `access_level` is also present - Jest: `selection` computed includes `{ member_role_id: N }` entries for selected custom roles - Jest: `selection` includes `_destroy: true` for deselected custom roles - Jest: `setSelected` restores custom role selection from preselected items on initial load - Jest: `toggleLabel` shows "1 custom role" / "N custom roles" correctly - Jest: `toggleLabel` shows combined label when both role and custom role are selected ## Dependencies - Issue 12 (`MEMBER_ROLE` constant) - Issue 13 (custom roles data loading and rendering) — shares the same file; coordinate to avoid conflicts ## Labels
issue