Commit a999d8d4 authored by Tom Quirk's avatar Tom Quirk Committed by Kushal Pandya

Create UserAccessRoleBadge component

Adds component that wraps GlBadge, but applies
some custom styles.

We then use this component whereever the
.user-access-role class is used in Vue components.
parent aa8384f1
<script> <script>
/* eslint-disable vue/no-v-html */ import {
import { GlLoadingIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui'; GlLoadingIcon,
import { visitUrl } from '../../lib/utils/url_utility'; GlBadge,
import identicon from '../../vue_shared/components/identicon.vue'; GlIcon,
GlTooltipDirective,
GlSafeHtmlDirective,
} from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import identicon from '~/vue_shared/components/identicon.vue';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants'; import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import itemActions from './item_actions.vue'; import itemActions from './item_actions.vue';
import itemCaret from './item_caret.vue'; import itemCaret from './item_caret.vue';
import itemStats from './item_stats.vue'; import itemStats from './item_stats.vue';
import itemStatsValue from './item_stats_value.vue';
import itemTypeIcon from './item_type_icon.vue'; import itemTypeIcon from './item_type_icon.vue';
export default { export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
SafeHtml: GlSafeHtmlDirective,
}, },
components: { components: {
GlBadge, GlBadge,
GlLoadingIcon, GlLoadingIcon,
GlIcon,
UserAccessRoleBadge,
identicon, identicon,
itemCaret, itemCaret,
itemTypeIcon, itemTypeIcon,
itemStats, itemStats,
itemStatsValue,
itemActions, itemActions,
}, },
props: { props: {
...@@ -91,6 +98,7 @@ export default { ...@@ -91,6 +98,7 @@ export default {
} }
}, },
}, },
safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] },
}; };
</script> </script>
...@@ -140,28 +148,31 @@ export default { ...@@ -140,28 +148,31 @@ export default {
data-testid="group-name" data-testid="group-name"
:href="group.relativePath" :href="group.relativePath"
:title="group.fullName" :title="group.fullName"
class="no-expand gl-mt-3 gl-mr-3 gl-text-gray-900!" class="no-expand gl-mr-3 gl-mt-3 gl-text-gray-900!"
:itemprop="microdata.nameItemprop" :itemprop="microdata.nameItemprop"
>{{ >
{{
// ending bracket must be by closing tag to prevent // ending bracket must be by closing tag to prevent
// link hover text-decoration from over-extending // link hover text-decoration from over-extending
group.name group.name
}}</a }}
> </a>
<item-stats-value <gl-icon
:icon-name="visibilityIcon" v-gl-tooltip.hover.bottom
class="gl-display-inline-flex gl-align-items-center gl-mr-3 gl-mt-3 gl-text-gray-500"
:name="visibilityIcon"
:title="visibilityTooltip" :title="visibilityTooltip"
css-class="item-visibility d-inline-flex align-items-center gl-mt-3 gl-mr-2 text-secondary" data-testid="group-visibility-icon"
/> />
<span v-if="group.permission" class="user-access-role gl-mt-3"> <user-access-role-badge v-if="group.permission" class="gl-mt-3">
{{ group.permission }} {{ group.permission }}
</span> </user-access-role-badge>
</div> </div>
<div v-if="group.description" class="description"> <div v-if="group.description" class="description">
<span <span
v-safe-html:[$options.safeHtmlConfig]="group.description"
:itemprop="microdata.descriptionItemprop" :itemprop="microdata.descriptionItemprop"
data-testid="group-description" data-testid="group-description"
v-html="group.description"
> >
</span> </span>
</div> </div>
......
...@@ -7,6 +7,7 @@ import { deprecatedCreateFlash as flash } from '~/flash'; ...@@ -7,6 +7,7 @@ import { deprecatedCreateFlash as flash } from '~/flash';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import eventHub from '~/sidebar/event_hub'; import eventHub from '~/sidebar/event_hub';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { splitCamelCase } from '../../lib/utils/text_utility'; import { splitCamelCase } from '../../lib/utils/text_utility';
import ReplyButton from './note_actions/reply_button.vue'; import ReplyButton from './note_actions/reply_button.vue';
...@@ -17,6 +18,7 @@ export default { ...@@ -17,6 +18,7 @@ export default {
ReplyButton, ReplyButton,
GlButton, GlButton,
GlDropdownItem, GlDropdownItem,
UserAccessRoleBadge,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -226,24 +228,30 @@ export default { ...@@ -226,24 +228,30 @@ export default {
<template> <template>
<div class="note-actions"> <div class="note-actions">
<span <user-access-role-badge
v-if="isAuthor" v-if="isAuthor"
class="note-role user-access-role has-tooltip d-none d-md-inline-block" v-gl-tooltip
class="gl-mx-3 d-none d-md-inline-block"
:title="displayAuthorBadgeText" :title="displayAuthorBadgeText"
>{{ __('Author') }}</span
> >
<span {{ __('Author') }}
</user-access-role-badge>
<user-access-role-badge
v-if="accessLevel" v-if="accessLevel"
class="note-role user-access-role has-tooltip" v-gl-tooltip
class="gl-mx-3"
:title="displayMemberBadgeText" :title="displayMemberBadgeText"
>{{ accessLevel }}</span
> >
<span {{ accessLevel }}
</user-access-role-badge>
<user-access-role-badge
v-else-if="isContributor" v-else-if="isContributor"
class="note-role user-access-role has-tooltip" v-gl-tooltip
class="gl-mx-3"
:title="displayContributorBadgeText" :title="displayContributorBadgeText"
>{{ __('Contributor') }}</span
> >
{{ __('Contributor') }}
</user-access-role-badge>
<gl-button <gl-button
v-if="canResolve" v-if="canResolve"
ref="resolveButton" ref="resolveButton"
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants'; import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
export default { export default {
components: { components: {
...@@ -20,6 +21,7 @@ export default { ...@@ -20,6 +21,7 @@ export default {
GlButton, GlButton,
GlTooltip, GlTooltip,
GlLink, GlLink,
UserAccessRoleBadge,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -72,7 +74,9 @@ export default { ...@@ -72,7 +74,9 @@ export default {
<template> <template>
<li :class="rowClass" class="group-row"> <li :class="rowClass" class="group-row">
<div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5"> <div class="group-row-contents gl-display-flex gl-align-items-center gl-py-3 gl-pr-5">
<div class="folder-toggle-wrap gl-mr-2 gl-display-flex gl-align-items-center"> <div
class="folder-toggle-wrap gl-mr-3 gl-display-flex gl-align-items-center gl-text-gray-500"
>
<gl-icon name="folder-o" /> <gl-icon name="folder-o" />
</div> </div>
<gl-link <gl-link
...@@ -84,12 +88,12 @@ export default { ...@@ -84,12 +88,12 @@ export default {
<div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center"> <div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
<div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1"> <div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
<div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3"> <div class="title gl-display-flex gl-align-items-center gl-flex-wrap gl-mr-3">
<gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">{{ <gl-link :href="group.relative_path" class="gl-mt-3 gl-mr-3 gl-text-gray-900!">
group.full_name {{ group.full_name }}
}}</gl-link> </gl-link>
<gl-icon <gl-icon
v-gl-tooltip.hover.bottom v-gl-tooltip.hover.bottom
class="gl-mr-0 gl-inline-flex gl-mt-3 text-secondary" class="gl-display-inline-flex gl-mt-3 gl-mr-3 gl-text-gray-500"
:name="visibilityIcon" :name="visibilityIcon"
:title="visibilityTooltip" :title="visibilityTooltip"
/> />
...@@ -99,11 +103,11 @@ export default { ...@@ -99,11 +103,11 @@ export default {
class="gl-display-none gl-sm-display-flex gl-mt-3 gl-mr-1" class="gl-display-none gl-sm-display-flex gl-mt-3 gl-mr-1"
>{{ __('pending removal') }}</gl-badge >{{ __('pending removal') }}</gl-badge
> >
<span v-if="group.permission" class="user-access-role gl-mt-3"> <user-access-role-badge v-if="group.permission" class="gl-mt-3">
{{ group.permission }} {{ group.permission }}
</span> </user-access-role-badge>
</div> </div>
<div v-if="group.description" class="description"> <div v-if="group.description" class="description gl-line-height-20">
<span v-safe-html="group.markdown_description"> </span> <span v-safe-html="group.markdown_description"> </span>
</div> </div>
</div> </div>
......
<script>
/**
* This component applies particular styling to GlBadge that isn't
* available in the current GlBadge variants.
* Where possible, prefer one of the supported GlBadge variants.
* Discussion issue: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1247
*/
import { GlBadge } from '@gitlab/ui';
export default {
name: 'UserAccessRoleBadge',
components: {
GlBadge,
},
};
</script>
<template>
<gl-badge class="gl-bg-transparent! gl-inset-border-1-gray-100!">
<slot></slot>
</gl-badge>
</template>
...@@ -519,10 +519,6 @@ table.pipeline-project-metrics tr td { ...@@ -519,10 +519,6 @@ table.pipeline-project-metrics tr td {
margin-left: 25px; margin-left: 25px;
} }
.item-visibility {
margin-right: 0;
}
.last-updated { .last-updated {
position: relative; position: relative;
min-width: 250px; min-width: 250px;
......
---
title: Improve styling of user access role badges
merge_request: 56061
author:
type: changed
...@@ -188,7 +188,7 @@ describe('GroupItemComponent', () => { ...@@ -188,7 +188,7 @@ describe('GroupItemComponent', () => {
}); });
it('should render component template correctly', () => { it('should render component template correctly', () => {
const visibilityIconEl = vm.$el.querySelector('.item-visibility'); const visibilityIconEl = vm.$el.querySelector('[data-testid="group-visibility-icon"]');
expect(vm.$el.getAttribute('id')).toBe('group-55'); expect(vm.$el.getAttribute('id')).toBe('group-55');
expect(vm.$el.classList.contains('group-row')).toBeTruthy(); expect(vm.$el.classList.contains('group-row')).toBeTruthy();
...@@ -209,8 +209,7 @@ describe('GroupItemComponent', () => { ...@@ -209,8 +209,7 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined(); expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();
expect(visibilityIconEl).not.toBe(null); expect(visibilityIconEl).not.toBe(null);
expect(visibilityIconEl.title).toBe(vm.visibilityTooltip); expect(visibilityIconEl.getAttribute('title')).toBe(vm.visibilityTooltip);
expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
expect(vm.$el.querySelector('.access-type')).toBeDefined(); expect(vm.$el.querySelector('.access-type')).toBeDefined();
expect(vm.$el.querySelector('.description')).toBeDefined(); expect(vm.$el.querySelector('.description')).toBeDefined();
......
...@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -6,6 +6,7 @@ import axios from '~/lib/utils/axios_utils';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import noteActions from '~/notes/components/note_actions.vue'; import noteActions from '~/notes/components/note_actions.vue';
import createStore from '~/notes/stores'; import createStore from '~/notes/stores';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
import { userDataMock } from '../mock_data'; import { userDataMock } from '../mock_data';
describe('noteActions', () => { describe('noteActions', () => {
...@@ -15,6 +16,9 @@ describe('noteActions', () => { ...@@ -15,6 +16,9 @@ describe('noteActions', () => {
let actions; let actions;
let axiosMock; let axiosMock;
const findUserAccessRoleBadge = (idx) => wrapper.findAll(UserAccessRoleBadge).at(idx);
const findUserAccessRoleBadgeText = (idx) => findUserAccessRoleBadge(idx).text().trim();
const mountNoteActions = (propsData, computed) => { const mountNoteActions = (propsData, computed) => {
const localVue = createLocalVue(); const localVue = createLocalVue();
return mount(localVue.extend(noteActions), { return mount(localVue.extend(noteActions), {
...@@ -66,11 +70,11 @@ describe('noteActions', () => { ...@@ -66,11 +70,11 @@ describe('noteActions', () => {
}); });
it('should render noteable author badge', () => { it('should render noteable author badge', () => {
expect(wrapper.findAll('.note-role').at(0).text().trim()).toEqual('Author'); expect(findUserAccessRoleBadgeText(0)).toBe('Author');
}); });
it('should render access level badge', () => { it('should render access level badge', () => {
expect(wrapper.findAll('.note-role').at(1).text().trim()).toEqual(props.accessLevel); expect(findUserAccessRoleBadgeText(1)).toBe(props.accessLevel);
}); });
it('should render contributor badge', () => { it('should render contributor badge', () => {
...@@ -80,7 +84,7 @@ describe('noteActions', () => { ...@@ -80,7 +84,7 @@ describe('noteActions', () => {
}); });
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll('.note-role').at(1).text().trim()).toBe('Contributor'); expect(findUserAccessRoleBadgeText(1)).toBe('Contributor');
}); });
}); });
......
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import UserAccessRoleBadge from '~/vue_shared/components/user_access_role_badge.vue';
describe('UserAccessRoleBadge', () => {
let wrapper;
const createComponent = ({ slots } = {}) => {
wrapper = shallowMount(UserAccessRoleBadge, {
slots,
});
};
it('renders slot content inside GlBadge', () => {
createComponent({
slots: {
default: 'test slot content',
},
});
const badge = wrapper.find(GlBadge);
expect(badge.exists()).toBe(true);
expect(badge.html()).toContain('test slot content');
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment