Commit 23f894fc authored by Dylan Griffith's avatar Dylan Griffith

Merge branch 'ss/add-scoped-labels-to-issuables-list' into 'master'

Add new label from gitlab-ui to issuables list labels

See merge request gitlab-org/gitlab!34334
parents 69b34501 e5b15216
......@@ -3,8 +3,10 @@
* This is tightly coupled to projects/issues/_issue.html.haml,
* any changes done to the haml need to be reflected here.
*/
// TODO: need to move this component to graphql - https://gitlab.com/gitlab-org/gitlab/-/issues/221246
import { escape, isNumber } from 'lodash';
import { GlLink, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import { GlLink, GlTooltipDirective as GlTooltip, GlLabel } from '@gitlab/ui';
import {
dateInWords,
formatDate,
......@@ -18,16 +20,21 @@ import initUserPopovers from '~/user_popovers';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import Icon from '~/vue_shared/components/icon.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { isScopedLabel } from '~/lib/utils/common_utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
isScopedLabel,
components: {
Icon,
IssueAssignees,
GlLink,
GlLabel,
},
directives: {
GlTooltip,
},
mixins: [glFeatureFlagsMixin()],
props: {
issuable: {
type: Object,
......@@ -57,8 +64,8 @@ export default {
return this.issuableLink({ milestone_title: title });
},
hasLabels() {
return Boolean(this.issuable.labels && this.issuable.labels.length);
scopedLabelsAvailable() {
return this.glFeatures.scopedLabels;
},
hasWeight() {
return isNumber(this.issuable.weight);
......@@ -163,15 +170,12 @@ export default {
initUserPopovers([this.$refs.openedAgoByContainer.querySelector('a')]);
},
methods: {
labelStyle(label) {
return {
backgroundColor: label.color,
color: label.text_color,
};
},
issuableLink(params) {
return mergeUrlParams(params, this.baseUrl);
},
isScoped({ name }) {
return isScopedLabel({ title: name }) && this.scopedLabelsAvailable;
},
labelHref({ name }) {
return this.issuableLink({ 'label_name[]': name });
},
......@@ -221,9 +225,9 @@ export default {
></i>
<gl-link :href="issuable.web_url">{{ issuable.title }}</gl-link>
</span>
<span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block">{{
issuable.task_status
}}</span>
<span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block">
{{ issuable.task_status }}
</span>
</div>
<div class="issuable-info">
......@@ -256,22 +260,19 @@ export default {
{{ dueDateWords }}
</span>
<span v-if="hasLabels" class="js-labels">
<gl-link
<gl-label
v-for="label in issuable.labels"
:key="label.id"
class="label-link mr-1"
:href="labelHref(label)"
:target="labelHref(label)"
:background-color="label.color"
:description="label.description"
:color="label.text_color"
:title="label.name"
:scoped="isScoped(label)"
size="sm"
class="mr-1"
>{{ label.name }}</gl-label
>
<span
v-gl-tooltip
class="badge color-label"
:style="labelStyle(label)"
:title="label.description"
>{{ label.name }}</span
>
</gl-link>
</span>
<span
v-if="hasWeight"
......
......@@ -9,6 +9,10 @@ module EE
alias_method :ee_authorize_admin_group!, :authorize_admin_group!
before_action :ee_authorize_admin_group!, only: [:restore]
before_action only: :issues do
push_frontend_feature_flag(:scoped_labels, @group)
end
end
override :render_show_html
......
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import { GlLabel } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import { trimText } from 'helpers/text_helper';
import initUserPopovers from '~/user_popovers';
......@@ -8,6 +8,7 @@ import { mergeUrlParams } from '~/lib/utils/url_utility';
import Issuable from '~/issuables_list/components/issuable.vue';
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import { simpleIssue, testAssignees, testLabels } from '../issuable_list_test_data';
import { isScopedLabel } from '~/lib/utils/common_utils';
jest.mock('~/user_popovers');
......@@ -37,13 +38,18 @@ describe('Issuable component', () => {
let DateOrig;
let wrapper;
const factory = (props = {}) => {
const factory = (props = {}, scopedLabels = false) => {
wrapper = shallowMount(Issuable, {
propsData: {
issuable: simpleIssue,
baseUrl: TEST_BASE_URL,
...props,
},
provide: {
glFeatures: {
scopedLabels,
},
},
});
};
......@@ -53,6 +59,7 @@ describe('Issuable component', () => {
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
beforeAll(() => {
......@@ -70,8 +77,7 @@ describe('Issuable component', () => {
const findMilestone = () => wrapper.find('.js-milestone');
const findMilestoneTooltip = () => findMilestone().attributes('title');
const findDueDate = () => wrapper.find('.js-due-date');
const findLabelContainer = () => wrapper.find('.js-labels');
const findLabelLinks = () => findLabelContainer().findAll(GlLink);
const findLabels = () => wrapper.findAll(GlLabel);
const findWeight = () => wrapper.find('.js-weight');
const findAssignees = () => wrapper.find(IssueAssignees);
const findMergeRequestsCount = () => wrapper.find('.js-merge-requests');
......@@ -79,6 +85,8 @@ describe('Issuable component', () => {
const findDownvotes = () => wrapper.find('.js-downvotes');
const findNotes = () => wrapper.find('.js-notes');
const findBulkCheckbox = () => wrapper.find('input.selected-issuable');
const findScopedLabels = () => findLabels().filter(w => isScopedLabel({ title: w.text() }));
const findUnscopedLabels = () => findLabels().filter(w => !isScopedLabel({ title: w.text() }));
describe('when mounted', () => {
it('initializes user popovers', () => {
......@@ -90,6 +98,54 @@ describe('Issuable component', () => {
});
});
describe('when scopedLabels feature is available', () => {
beforeEach(() => {
issuable.labels = [...testLabels];
factory({ issuable }, true);
});
describe('when label is scoped', () => {
it('returns label with correct props', () => {
const scopedLabel = findScopedLabels().at(0);
expect(scopedLabel.props('scoped')).toBe(true);
});
});
describe('when label is not scoped', () => {
it('returns label with correct props', () => {
const notScopedLabel = findUnscopedLabels().at(0);
expect(notScopedLabel.props('scoped')).toBe(false);
});
});
});
describe('when scopedLabels feature is not available', () => {
beforeEach(() => {
issuable.labels = [...testLabels];
factory({ issuable });
});
describe('when label is scoped', () => {
it('label scoped props is false', () => {
const scopedLabel = findScopedLabels().at(0);
expect(scopedLabel.props('scoped')).toBe(false);
});
});
describe('when label is not scoped', () => {
it('label scoped props is false', () => {
const notScopedLabel = findUnscopedLabels().at(0);
expect(notScopedLabel.props('scoped')).toBe(false);
});
});
});
describe('with simple issuable', () => {
beforeEach(() => {
Object.assign(issuable, {
......@@ -113,7 +169,7 @@ describe('Issuable component', () => {
${'task status'} | ${findTaskStatus}
${'milestone'} | ${findMilestone}
${'due date'} | ${findDueDate}
${'labels'} | ${findLabelContainer}
${'labels'} | ${findLabels}
${'weight'} | ${findWeight}
${'merge request count'} | ${findMergeRequestsCount}
${'upvotes'} | ${findUpvotes}
......@@ -239,10 +295,10 @@ describe('Issuable component', () => {
it('renders labels', () => {
factory({ issuable });
const labels = findLabelLinks().wrappers.map(label => ({
href: label.attributes('href'),
const labels = findLabels().wrappers.map(label => ({
href: label.props('target'),
text: label.text(),
tooltip: label.find('span').attributes('title'),
tooltip: label.attributes('description'),
}));
const expected = testLabels.map(label => ({
......
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