group access_dropdown.vue - add custom roles section
## 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/594888+ <br> https://gitlab.com/gitlab-org/gitlab/-/work_items/594889+ |
| **Blocked by** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594882+ <br> 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
Add custom role selection support to the group-level settings access dropdown (`app/assets/javascripts/groups/settings/components/access_dropdown.vue`), enabling custom roles to be selected when configuring group-level protected branches.
## Background
Group settings has its own access dropdown component, separate from the project settings one. It currently supports Roles, Groups, and Users — but no Deploy Keys or Custom Roles. The component is used in the group protected branches settings page.
This component fetches sub-groups (not project groups) and optionally users. Custom roles for the group must be fetched from the same `GET /groups/:id/member_roles` endpoint used by the project dropdown.
The groups dropdown uses `app/assets/javascripts/groups/settings/constants.js` for `LEVEL_TYPES` (updated in Issue 12).
## Relevant files
- `app/assets/javascripts/groups/settings/components/access_dropdown.vue` (352 lines)
- `app/assets/javascripts/groups/settings/constants.js` — `LEVEL_TYPES` (updated in Issue 12)
- `app/assets/javascripts/groups/settings/api/access_dropdown_api.js` — has `getSubGroups` and `getUsers`
## Changes required
### New EE API utility (if not reusing Issue 11's file)
Add `getMemberRoles` to `ee/app/assets/javascripts/groups/settings/api/access_dropdown_api.js` (create if not exists):
```javascript
import axios from '~/lib/utils/axios_utils';
const BASE_PATH = '/api/v4/groups';
export const getMemberRoles = (groupId) => {
return axios.get(`${gon.relative_url_root || ''}${BASE_PATH}/${groupId}/member_roles`);
};
```
### Data
```javascript
data() {
return {
// ...existing
customRoles: [],
selected: {
[LEVEL_TYPES.ROLE]: [],
[LEVEL_TYPES.GROUP]: [],
[LEVEL_TYPES.USER]: [],
[LEVEL_TYPES.MEMBER_ROLE]: [], // add
},
};
},
```
### `getData()` method
When `hasLicense`, add `getMemberRoles(groupId)` to the `Promise.all` call. The group ID can be obtained from a prop or `gon.current_group_id`.
### `consolidateData()` method
Populate `this.customRoles` from the response, tagging with `type: LEVEL_TYPES.MEMBER_ROLE`.
### `setDataForSave()`, `selection`, `setSelected()`, `toggleLabel`
Apply identical changes to those in Issues 13 and 14 for the project dropdown:
- `setDataForSave`: detect `member_role_id`, check before `access_level`
- `selection`: include `getDataForSave(LEVEL_TYPES.MEMBER_ROLE, 'member_role_id')`
- `setSelected`: restore custom role selection from preselected items
- `toggleLabel`: add "N custom roles" count
### Template
Add "Custom Roles" section between Roles and Groups, mirroring the project dropdown template changes in Issue 13.
```html
<template v-if="customRoles.length">
<gl-dropdown-section-header>{{ $options.i18n.customRolesSectionHeader }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="role in customRoles"
:key="`member-role-${role.id}`"
data-testid="member-role-dropdown-item"
is-check-item
:is-checked="isSelected(role)"
@click.capture.native.stop="onItemClick(role)"
>
{{ role.name }}
</gl-dropdown-item>
<gl-dropdown-divider v-if="groups.length || users.length" />
</template>
```
## Notes
- The group dropdown does not support Deploy Keys — the `canAdminContainer` guard used in the project dropdown does not apply here in the same way. Check what condition gates the custom roles fetch (likely just `hasLicense`)
- Group ID: The group dropdown does not currently have a `gon.current_group_id` equivalent wired up universally — check how the component is instantiated in the group settings page and pass the group ID via prop if necessary
- Coordinate with Issue 8 (group API) to ensure the save payload `{ member_role_id: N }` is accepted by the group protected branches API
## Testing
- Jest: custom roles section renders when `customRoles` has items
- Jest: `getMemberRoles` called when `hasLicense` is true
- Jest: `setDataForSave` correctly identifies `member_role_id` items (checks before `access_level`)
- Jest: `selection` includes `{ member_role_id: N }` for selected custom roles
- Jest: `setSelected` restores custom role selection from preselected items
- Jest: `toggleLabel` shows "1 custom role" / "N custom roles"
## Dependencies
- Issue 8 (group API) — backend must accept `member_role_id` for group protected branches
- Issue 12 (`MEMBER_ROLE` constant in groups constants)
- Issues 13 & 14 patterns (use same approach, different component file)
## Labels
issue