project access_dropdown.vue - fetch and display custom roles section
## Context
| | |
|---|---|
| **Phase** | 5 of 6 |
| **Parallel with** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594888+ <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/594886+ |
| **Unblocks** | https://gitlab.com/gitlab-org/gitlab/-/work_items/594891+ |
## Summary
Update the project-level `access_levels_drawer.vue` component to fetch custom roles from the API and display them as a new "Custom Roles" section in the dropdown, between the Roles and Groups sections.
## Background
`app/assets/javascripts/projects/settings/components/access_dropdown.vue` is the **legacy** shared dropdown for configuring who can push, merge, and unprotect protected branches. It currently supports four sections: Roles, Groups, Users, and Deploy Keys.
`app/assets/javascripts/projects/settings/branch_rules/components/access_levels_drawer.vue` is the corresponding component in the branch rules details view.
This is an EE-only feature. The component changes should be added in an EE override or via a slot/prop mechanism. ~~However, given the complexity of the component and the fact that the `MEMBER_ROLE` constant addition in Issue 12 is already in the CE file, the cleanest approach is to add the custom roles data and section within the CE component guarded by `hasLicense` (which is already the gate for users/groups).~~
> [!NOTE]
> @psjakubowska: Branch rules components have been recently refactored to use named slots for EE extensions: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/227127/diffs. Let's follow this pattern here as well.
The ~~`getMemberRoles` API function (Issue 11) and~~ `MEMBER_ROLE` constant (Issue 12) must be available before starting this issue.
> [!NOTE]
> We don't need the `getMemberRoles` API function for the project level. Let's move `ee/app/assets/javascripts/invite_members/graphql/queries/project_member_roles.query.graphql` to `graphql_shared` directory and reuse it directly for this.
## Relevant file
~~`app/assets/javascripts/projects/settings/components/access_dropdown.vue`~~
`app/assets/javascripts/projects/settings/branch_rules/components/access_levels_drawer.vue`
## Changes required
### Props
Add a new optional prop to pass the `projectPath` for fetching custom roles:
```javascript
projectPath: {
type: String,
required: false,
default: null,
},
```
This is injected in `app/assets/javascripts/projects/settings/branch_rules/components/index.vue`
### Data
Fetch custom roles with Apollo upon `isOpen` change to `true`. Reuse `ee/app/assets/javascripts/invite_members/graphql/queries/project_member_roles.query.graphql`.
### `getRuleEditData()` method
Include in `getRuleEditData()`
```javascript
const ruleEditRoles = [
// ...existing roles
...this.formatItemsData(this.updatedCustomRoles, 'id', 'CustomRoles'),
];
let ruleEditAccessLevels = [];
// ...existing logic
if (this.isCustomRolesSelected) {
ruleEditAccessLevels.push({ accessLevel: ACCESS_LEVEL_CUSTOM_ROLES_INTEGER });
}
return [...ruleEditRoles, ...ruleEditAccessLevels];
```
### i18n
Use `accessLevelConfig` from `app/assets/javascripts/projects/settings/branch_rules/components/constants.js` with previously defined integer from https://gitlab.com/gitlab-org/gitlab/-/work_items/594886+
### Template
Add a new section between Roles and Groups:
```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 || showDeployKeys" />
</template>
```
> [!NOTE]
> The component is getting rather long. Let's extract the checkboxes to a presentational component, while leaving the logic in side `access_levels_drawer.vue`
## Notes
- Custom roles should only display when `hasLicense` is true (EE-only feature)
- If the namespace has no custom roles, the section is hidden (already handled by `v-if="customRoles.length"`)
- Search/filter: custom roles should not be filtered by the search query (they are a finite list, not paginated like users). Include all roles regardless of query, similar to how groups work
## Testing
- Jest: custom roles section renders when `customRoles` has items
- Jest: custom roles section hidden when `customRoles` is empty
- Jest: `getProjectMemberRoles` is fetched when drawer is open
- Jest: `getProjectMemberRoles` does not fetch on initial mount
- Jest: clicking a custom role item calls `handleAccessLevelSelected` with the correct item
- Jest: custom role items show as checked when selected
## Dependencies
- Issue 12 (`MEMBER_ROLE` constant)
issue