Commit 3f2e13f4 authored by Phil Hughes's avatar Phil Hughes

Merge branch '219260-show-confidential-icon-in-epics' into 'master'

Add confidentiality icon and context in Epics

Closes #219260

See merge request gitlab-org/gitlab!34443
parents 8d4aa787 b85cc6b7
...@@ -17,7 +17,7 @@ import { ...@@ -17,7 +17,7 @@ import {
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue'; import loadingButton from '../../vue_shared/components/loading_button.vue';
...@@ -28,8 +28,7 @@ import issuableStateMixin from '../mixins/issuable_state'; ...@@ -28,8 +28,7 @@ import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'CommentForm', name: 'CommentForm',
components: { components: {
issueWarning, NoteableWarning,
epicWarning: () => import('ee_component/vue_shared/components/epic/epic_warning.vue'),
noteSignedOutWidget, noteSignedOutWidget,
discussionLockedWidget, discussionLockedWidget,
markdownField, markdownField,
...@@ -350,14 +349,15 @@ export default { ...@@ -350,14 +349,15 @@ export default {
<form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form"> <form ref="commentForm" class="new-note common-note-form gfm-form js-main-target-form">
<div class="error-alert"></div> <div class="error-alert"></div>
<issue-warning <noteable-warning
v-if="hasWarning(getNoteableData) && isIssueType" v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)" :is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)" :is-confidential="isConfidential(getNoteableData)"
:locked-issue-docs-path="lockedIssueDocsPath" :noteable-type="noteableType"
:confidential-issue-docs-path="confidentialIssueDocsPath" :locked-noteable-docs-path="lockedIssueDocsPath"
:confidential-noteable-docs-path="confidentialIssueDocsPath"
/> />
<epic-warning :is-confidential="isConfidential(getNoteableData)" />
<markdown-field <markdown-field
ref="markdownField" ref="markdownField"
:is-submitting="isSubmitting" :is-submitting="isSubmitting"
...@@ -374,16 +374,14 @@ export default { ...@@ -374,16 +374,14 @@ export default {
dir="auto" dir="auto"
:disabled="isSubmitting" :disabled="isSubmitting"
name="note[note]" name="note[note]"
class="note-textarea js-vue-comment-form js-note-text class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true" data-supports-quick-actions="true"
:aria-label="__('Description')" :aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')" :placeholder="__('Write a comment or drag your files here…')"
@keydown.up="editCurrentUserLastNote()" @keydown.up="editCurrentUserLastNote()"
@keydown.meta.enter="handleSave()" @keydown.meta.enter="handleSave()"
@keydown.ctrl.enter="handleSave()" @keydown.ctrl.enter="handleSave()"
> ></textarea>
</textarea>
</markdown-field> </markdown-field>
<gl-alert <gl-alert
v-if="isToggleBlockedIssueWarning" v-if="isToggleBlockedIssueWarning"
...@@ -417,13 +415,11 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input" ...@@ -417,13 +415,11 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
</gl-alert> </gl-alert>
<div class="note-form-actions"> <div class="note-form-actions">
<div <div
class="btn-group class="btn-group append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
> >
<button <button
:disabled="isSubmitButtonDisabled" :disabled="isSubmitButtonDisabled"
class="btn btn-success js-comment-button js-comment-submit-button class="btn btn-success js-comment-button js-comment-submit-button qa-comment-button"
qa-comment-button"
type="submit" type="submit"
:data-track-label="trackingLabel" :data-track-label="trackingLabel"
data-track-event="click_button" data-track-event="click_button"
...@@ -440,7 +436,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" ...@@ -440,7 +436,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
data-toggle="dropdown" data-toggle="dropdown"
:aria-label="__('Open comment type dropdown')" :aria-label="__('Open comment type dropdown')"
> >
<i aria-hidden="true" class="fa fa-caret-down toggle-icon"> </i> <i aria-hidden="true" class="fa fa-caret-down toggle-icon"></i>
</button> </button>
<ul class="note-type-dropdown dropdown-open-top dropdown-menu"> <ul class="note-type-dropdown dropdown-open-top dropdown-menu">
...@@ -450,7 +446,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" ...@@ -450,7 +446,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-transparent" class="btn btn-transparent"
@click.prevent="setNoteType('comment')" @click.prevent="setNoteType('comment')"
> >
<i aria-hidden="true" class="fa fa-check icon"> </i> <i aria-hidden="true" class="fa fa-check icon"></i>
<div class="description"> <div class="description">
<strong>{{ __('Comment') }}</strong> <strong>{{ __('Comment') }}</strong>
<p> <p>
...@@ -470,7 +466,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" ...@@ -470,7 +466,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-transparent qa-discussion-option" class="btn btn-transparent qa-discussion-option"
@click.prevent="setNoteType('discussion')" @click.prevent="setNoteType('discussion')"
> >
<i aria-hidden="true" class="fa fa-check icon"> </i> <i aria-hidden="true" class="fa fa-check icon"></i>
<div class="description"> <div class="description">
<strong>{{ __('Start thread') }}</strong> <strong>{{ __('Start thread') }}</strong>
<p>{{ startDiscussionDescription }}</p> <p>{{ startDiscussionDescription }}</p>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { mapGetters, mapActions, mapState } from 'vuex'; import { mapGetters, mapActions, mapState } from 'vuex';
import { mergeUrlParams } from '~/lib/utils/url_utility'; import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state'; import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
...@@ -12,7 +12,7 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave'; ...@@ -12,7 +12,7 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave';
export default { export default {
name: 'NoteForm', name: 'NoteForm',
components: { components: {
issueWarning, NoteableWarning,
markdownField, markdownField,
}, },
mixins: [issuableStateMixin, resolvable], mixins: [issuableStateMixin, resolvable],
...@@ -303,12 +303,12 @@ export default { ...@@ -303,12 +303,12 @@ export default {
></div> ></div>
<div class="flash-container timeline-content"></div> <div class="flash-container timeline-content"></div>
<form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form"> <form :data-line-code="lineCode" class="edit-note common-note-form js-quick-submit gfm-form">
<issue-warning <noteable-warning
v-if="hasWarning(getNoteableData)" v-if="hasWarning(getNoteableData)"
:is-locked="isLocked(getNoteableData)" :is-locked="isLocked(getNoteableData)"
:is-confidential="isConfidential(getNoteableData)" :is-confidential="isConfidential(getNoteableData)"
:locked-issue-docs-path="lockedIssueDocsPath" :locked-noteable-docs-path="lockedIssueDocsPath"
:confidential-issue-docs-path="confidentialIssueDocsPath" :confidential-noteable-docs-path="confidentialIssueDocsPath"
/> />
<markdown-field <markdown-field
......
...@@ -8,6 +8,18 @@ function buildDocsLinkStart(path) { ...@@ -8,6 +8,18 @@ function buildDocsLinkStart(path) {
return `<a href="${escape(path)}" target="_blank" rel="noopener noreferrer">`; return `<a href="${escape(path)}" target="_blank" rel="noopener noreferrer">`;
} }
const NoteableType = {
Issue: 'issue',
Epic: 'epic',
MergeRequest: 'merge_request',
};
const NoteableTypeText = {
[NoteableType.Issue]: __('issue'),
[NoteableType.Epic]: __('epic'),
[NoteableType.MergeRequest]: __('merge request'),
};
export default { export default {
components: { components: {
icon, icon,
...@@ -24,12 +36,17 @@ export default { ...@@ -24,12 +36,17 @@ export default {
default: false, default: false,
required: false, required: false,
}, },
lockedIssueDocsPath: { noteableType: {
type: String,
required: false,
default: NoteableType.Issue,
},
lockedNoteableDocsPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
confidentialIssueDocsPath: { confidentialNoteableDocsPath: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
...@@ -45,19 +62,33 @@ export default { ...@@ -45,19 +62,33 @@ export default {
isLockedAndConfidential() { isLockedAndConfidential() {
return this.isConfidential && this.isLocked; return this.isConfidential && this.isLocked;
}, },
noteableTypeText() {
return NoteableTypeText[this.noteableType];
},
confidentialAndLockedDiscussionText() { confidentialAndLockedDiscussionText() {
return sprintf( return sprintf(
__( __(
'This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.', 'This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}.',
), ),
{ {
confidentialLinkStart: buildDocsLinkStart(this.confidentialIssueDocsPath), noteableTypeText: this.noteableTypeText,
lockedLinkStart: buildDocsLinkStart(this.lockedIssueDocsPath), confidentialLinkStart: buildDocsLinkStart(this.confidentialNoteableDocsPath),
lockedLinkStart: buildDocsLinkStart(this.lockedNoteableDocsPath),
linkEnd: '</a>', linkEnd: '</a>',
}, },
false, false,
); );
}, },
confidentialContextText() {
return sprintf(__('This is a confidential %{noteableTypeText}.'), {
noteableTypeText: this.noteableTypeText,
});
},
lockedContextText() {
return sprintf(__('This %{noteableTypeText} is locked.'), {
noteableTypeText: this.noteableTypeText,
});
},
}, },
}; };
</script> </script>
...@@ -73,19 +104,15 @@ export default { ...@@ -73,19 +104,15 @@ export default {
</span> </span>
<span v-else-if="isConfidential" ref="confidential"> <span v-else-if="isConfidential" ref="confidential">
{{ __('This is a confidential issue.') }} {{ confidentialContextText }}
{{ __('People without permission will never get a notification.') }} {{ __('People without permission will never get a notification.') }}
<gl-link :href="confidentialIssueDocsPath" target="_blank"> <gl-link :href="confidentialNoteableDocsPath" target="_blank">{{ __('Learn more') }}</gl-link>
{{ __('Learn more') }}
</gl-link>
</span> </span>
<span v-else-if="isLocked" ref="locked"> <span v-else-if="isLocked" ref="locked">
{{ __('This issue is locked.') }} {{ lockedContextText }}
{{ __('Only project members can comment.') }} {{ __('Only project members can comment.') }}
<gl-link :href="lockedIssueDocsPath" target="_blank"> <gl-link :href="lockedNoteableDocsPath" target="_blank">{{ __('Learn more') }}</gl-link>
{{ __('Learn more') }}
</gl-link>
</span> </span>
</div> </div>
</template> </template>
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { GlDeprecatedButton } from '@gitlab/ui'; import { GlDeprecatedButton, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
...@@ -17,7 +16,7 @@ export default { ...@@ -17,7 +16,7 @@ export default {
tooltip, tooltip,
}, },
components: { components: {
Icon, GlIcon,
GlDeprecatedButton, GlDeprecatedButton,
UserAvatarLink, UserAvatarLink,
TimeagoTooltip, TimeagoTooltip,
...@@ -32,6 +31,7 @@ export default { ...@@ -32,6 +31,7 @@ export default {
'author', 'author',
'created', 'created',
'canUpdate', 'canUpdate',
'confidential',
]), ]),
...mapGetters(['isEpicOpen']), ...mapGetters(['isEpicOpen']),
statusIcon() { statusIcon() {
...@@ -82,10 +82,13 @@ export default { ...@@ -82,10 +82,13 @@ export default {
:class="{ 'status-box-open': isEpicOpen, 'status-box-issue-closed': !isEpicOpen }" :class="{ 'status-box-open': isEpicOpen, 'status-box-issue-closed': !isEpicOpen }"
class="issuable-status-box status-box" class="issuable-status-box status-box"
> >
<icon :name="statusIcon" class="d-block d-sm-none" /> <gl-icon :name="statusIcon" class="d-block d-sm-none" />
<span class="d-none d-sm-block">{{ statusText }}</span> <span class="d-none d-sm-block">{{ statusText }}</span>
</div> </div>
<div class="issuable-meta"> <div class="issuable-meta">
<div v-if="confidential" class="issuable-warning-icon inline">
<gl-icon name="eye-slash" class="icon" />
</div>
{{ __('Opened') }} {{ __('Opened') }}
<timeago-tooltip :time="created" /> <timeago-tooltip :time="created" />
{{ __('by') }} {{ __('by') }}
...@@ -110,15 +113,13 @@ export default { ...@@ -110,15 +113,13 @@ export default {
:loading="epicStatusChangeInProgress" :loading="epicStatusChangeInProgress"
:class="actionButtonClass" :class="actionButtonClass"
@click="toggleEpicStatus(isEpicOpen)" @click="toggleEpicStatus(isEpicOpen)"
>{{ actionButtonText }}</gl-deprecated-button
> >
{{ actionButtonText }}
</gl-deprecated-button>
</div> </div>
<gl-deprecated-button <gl-deprecated-button
:aria-label="__('Toggle sidebar')" :aria-label="__('Toggle sidebar')"
variant="secondary" variant="secondary"
class="float-right d-block d-sm-none class="float-right d-block d-sm-none gutter-toggle issuable-gutter-toggle js-sidebar-toggle"
gutter-toggle issuable-gutter-toggle js-sidebar-toggle"
type="button" type="button"
@click="toggleSidebar({ sidebarCollapsed })" @click="toggleSidebar({ sidebarCollapsed })"
> >
......
...@@ -56,6 +56,7 @@ export default () => ({ ...@@ -56,6 +56,7 @@ export default () => ({
ancestors: [], ancestors: [],
participants: [], participants: [],
subscribed: false, subscribed: false,
confidential: false,
// Create Epic Props // Create Epic Props
newEpicTitle: '', newEpicTitle: '',
......
<script>
import { mapGetters } from 'vuex';
import { GlIcon, GlLink } from '@gitlab/ui';
import * as constants from '~/notes/constants';
export default {
components: {
GlIcon,
GlLink,
},
props: {
isConfidential: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
...mapGetters(['getNoteableDataByProp']),
isNoteableTypeEpic() {
return this.getNoteableDataByProp('noteableType') === constants.EPIC_NOTEABLE_TYPE;
},
confidentialEpicDocsPath() {
return this.getNoteableDataByProp('confidential_epics_docs_path');
},
},
};
</script>
<template>
<div v-if="isNoteableTypeEpic && isConfidential" ref="epicWarning" class="issuable-note-warning">
<gl-icon name="eye-slash" :size="16" class="icon" />
<span ref="confidential">
{{ __('This is a confidential epic.') }}
{{ __('People without permission will never get a notification.') }}
<gl-link :href="confidentialEpicDocsPath" target="_blank">
{{ __('Learn more') }}
</gl-link>
</span>
</div>
</template>
...@@ -23,6 +23,7 @@ module EE ...@@ -23,6 +23,7 @@ module EE
data[:epicLinksEndpoint] = group_epic_links_path(parent, issuable) data[:epicLinksEndpoint] = group_epic_links_path(parent, issuable)
data[:fullPath] = parent.full_path data[:fullPath] = parent.full_path
data[:projectsEndpoint] = expose_path(api_v4_groups_projects_path(id: parent.id)) data[:projectsEndpoint] = expose_path(api_v4_groups_projects_path(id: parent.id))
data[:confidential] = issuable.confidential
end end
data data
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
.issuable-main-info .issuable-main-info
.issue-title.title .issue-title.title
%span.issue-title-text{ data: { qa_selector: 'epic_title_text' } } %span.issue-title-text{ data: { qa_selector: 'epic_title_text' } }
- if epic.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(epic)
= link_to epic.title, epic_path(epic) = link_to epic.title, epic_path(epic)
.issuable-info .issuable-info
%span.issuable-reference %span.issuable-reference
......
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import EpicHeader from 'ee/epic/components/epic_header.vue'; import EpicHeader from 'ee/epic/components/epic_header.vue';
import createStore from 'ee/epic/store'; import createStore from 'ee/epic/store';
import { statusType } from 'ee/epic/constants'; import { statusType } from 'ee/epic/constants';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { mockEpicMeta, mockEpicData } from '../mock_data'; import { mockEpicMeta, mockEpicData } from '../mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('EpicHeaderComponent', () => { describe('EpicHeaderComponent', () => {
let wrapper;
let vm; let vm;
let store; let store;
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(EpicHeader);
store = createStore(); store = createStore();
store.dispatch('setEpicMeta', mockEpicMeta); store.dispatch('setEpicMeta', mockEpicMeta);
store.dispatch('setEpicData', mockEpicData); store.dispatch('setEpicData', mockEpicData);
vm = mountComponentWithStore(Component, { wrapper = shallowMount(EpicHeader, {
localVue,
store, store,
}); });
vm = wrapper.vm;
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
}); });
describe('computed', () => { describe('computed', () => {
...@@ -96,21 +105,29 @@ describe('EpicHeaderComponent', () => { ...@@ -96,21 +105,29 @@ describe('EpicHeaderComponent', () => {
}); });
it('renders epic status icon and text elements', () => { it('renders epic status icon and text elements', () => {
const statusEl = vm.$el.querySelector('.issuable-status-box'); const statusEl = wrapper.find('.issuable-status-box');
expect(statusEl).not.toBeNull(); expect(statusEl.exists()).toBe(true);
expect( expect(statusEl.find(GlIcon).props('name')).toBe('issue-open-m');
statusEl.querySelector('svg.ic-issue-open-m use').getAttribute('xlink:href'), expect(statusEl.find('span').text()).toBe('Open');
).toContain('issue-open-m'); });
it('renders confidential icon when `confidential` prop is true', () => {
vm.$store.state.confidential = true;
expect(statusEl.querySelector('span').innerText.trim()).toBe('Open'); return Vue.nextTick(() => {
const iconEl = wrapper.find('.issuable-warning-icon').find(GlIcon);
expect(iconEl.exists()).toBe(true);
expect(iconEl.props('name')).toBe('eye-slash');
});
}); });
it('renders epic author details element', () => { it('renders epic author details element', () => {
const metaEl = vm.$el.querySelector('.issuable-meta'); const metaEl = wrapper.find('.issuable-meta');
expect(metaEl).not.toBeNull(); expect(metaEl.exists()).toBe(true);
expect(metaEl.querySelector('strong a.user-avatar-link')).not.toBeNull(); expect(metaEl.find(TimeagoTooltip).exists()).toBe(true);
expect(metaEl.find(UserAvatarLink).exists()).toBe(true);
}); });
it('renders action buttons element', () => { it('renders action buttons element', () => {
...@@ -122,13 +139,13 @@ describe('EpicHeaderComponent', () => { ...@@ -122,13 +139,13 @@ describe('EpicHeaderComponent', () => {
}); });
it('renders toggle sidebar button element', () => { it('renders toggle sidebar button element', () => {
const toggleButtonEl = vm.$el.querySelector('button.js-sidebar-toggle'); const toggleButtonEl = wrapper.find('.js-sidebar-toggle');
expect(toggleButtonEl).not.toBeNull(); expect(toggleButtonEl.exists()).toBe(true);
expect(toggleButtonEl.getAttribute('aria-label')).toBe('Toggle sidebar'); expect(toggleButtonEl.attributes('aria-label')).toBe('Toggle sidebar');
expect(toggleButtonEl.classList.contains('d-block')).toBe(true); expect(toggleButtonEl.classes()).toEqual(
expect(toggleButtonEl.classList.contains('d-sm-none')).toBe(true); expect.arrayContaining([('d-block', 'd-sm-none', 'gutter-toggle')]),
expect(toggleButtonEl.classList.contains('gutter-toggle')).toBe(true); );
}); });
it('renders GitLab team member badge when `author.isGitlabEmployee` is `true`', () => { it('renders GitLab team member badge when `author.isGitlabEmployee` is `true`', () => {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Epic Warning Component when noteable type is epic epic is confidential renders information about confidential epic 1`] = `
<span>
This is a confidential epic.
People without permission will never get a notification.
<gl-link-stub
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
import { shallowMount } from '@vue/test-utils';
import EpicWarning from 'ee/vue_shared/components/epic/epic_warning.vue';
import { store } from '~/notes/stores';
import { GlIcon } from '@gitlab/ui';
describe('Epic Warning Component', () => {
let wrapper;
const findIcon = () => wrapper.find(GlIcon);
const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' });
const findEpicWarning = () => wrapper.find({ ref: 'epicWarning' });
const createComponent = (props, isNoteableEpic = true) => {
wrapper = shallowMount(EpicWarning, {
store,
propsData: {
...props,
},
computed: {
isNoteableTypeEpic() {
return isNoteableEpic;
},
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when noteable type is epic', () => {
describe('epic is not confidential', () => {
beforeEach(() => {
createComponent({ isConfidential: false });
});
it('does not render warning icon', () => {
expect(findIcon().exists()).toBe(false);
});
it('does not render information about epic issue', () => {
expect(findConfidentialBlock().exists()).toBe(false);
});
});
describe('epic is confidential', () => {
beforeEach(() => {
createComponent({ isConfidential: true });
});
it('renders information about confidential epic', () => {
expect(findConfidentialBlock().exists()).toBe(true);
expect(findConfidentialBlock().element).toMatchSnapshot();
});
it('renders warning icon', () => {
expect(findIcon().exists()).toBe(true);
});
});
});
describe('when noteable type is not epic', () => {
beforeEach(() => {
createComponent({ isConfidential: true }, false);
});
it('does not render itself', () => {
expect(findEpicWarning().exists()).toBe(false);
});
});
});
...@@ -13,8 +13,13 @@ RSpec.describe IssuablesHelper do ...@@ -13,8 +13,13 @@ RSpec.describe IssuablesHelper do
end end
context 'for an epic' do context 'for an epic' do
let_it_be(:epic) { create(:epic, author: user, description: 'epic text', confidential: true) }
before do
stub_feature_flags(confidential_epics: true)
end
it 'returns the correct data' do it 'returns the correct data' do
epic = create(:epic, author: user, description: 'epic text')
@group = epic.group @group = epic.group
expected_data = { expected_data = {
...@@ -35,10 +40,11 @@ RSpec.describe IssuablesHelper do ...@@ -35,10 +40,11 @@ RSpec.describe IssuablesHelper do
groupPath: @group.path, groupPath: @group.path,
initialTitleHtml: epic.title, initialTitleHtml: epic.title,
initialTitleText: epic.title, initialTitleText: epic.title,
initialDescriptionHtml: '<p dir="auto">epic text</p>', initialDescriptionHtml: '<p data-sourcepos="1:1-1:9" dir="auto">epic text</p>',
initialDescriptionText: 'epic text', initialDescriptionText: 'epic text',
initialTaskStatus: '0 of 0 tasks completed', initialTaskStatus: '0 of 0 tasks completed',
projectsEndpoint: "/api/v4/groups/#{@group.id}/projects" projectsEndpoint: "/api/v4/groups/#{@group.id}/projects",
confidential: epic.confidential
} }
expect(helper.issuable_initial_data(epic)).to eq(expected_data) expect(helper.issuable_initial_data(epic)).to eq(expected_data)
end end
......
...@@ -22837,6 +22837,12 @@ msgstr "" ...@@ -22837,6 +22837,12 @@ msgstr ""
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment." msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
msgstr "" msgstr ""
msgid "This %{noteableTypeText} is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
msgstr ""
msgid "This %{noteableTypeText} is locked."
msgstr ""
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead." msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
msgstr "" msgstr ""
...@@ -22984,10 +22990,7 @@ msgstr "" ...@@ -22984,10 +22990,7 @@ msgstr ""
msgid "This is a Work in Progress" msgid "This is a Work in Progress"
msgstr "" msgstr ""
msgid "This is a confidential epic." msgid "This is a confidential %{noteableTypeText}."
msgstr ""
msgid "This is a confidential issue."
msgstr "" msgstr ""
msgid "This is a delayed job to run in %{remainingTime}" msgid "This is a delayed job to run in %{remainingTime}"
...@@ -23014,9 +23017,6 @@ msgstr "" ...@@ -23014,9 +23017,6 @@ msgstr ""
msgid "This is your current session" msgid "This is your current session"
msgstr "" msgstr ""
msgid "This issue is %{confidentialLinkStart}confidential%{linkEnd} and %{lockedLinkStart}locked%{linkEnd}."
msgstr ""
msgid "This issue is confidential" msgid "This issue is confidential"
msgstr "" msgstr ""
...@@ -23026,9 +23026,6 @@ msgstr "" ...@@ -23026,9 +23026,6 @@ msgstr ""
msgid "This issue is in a child epic of the filtered epic" msgid "This issue is in a child epic of the filtered epic"
msgstr "" msgstr ""
msgid "This issue is locked."
msgstr ""
msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}" msgid "This job depends on other jobs with expired/erased artifacts: %{invalid_dependencies}"
msgstr "" msgstr ""
...@@ -26852,6 +26849,9 @@ msgstr "" ...@@ -26852,6 +26849,9 @@ msgstr ""
msgid "entries cannot contain HTML tags" msgid "entries cannot contain HTML tags"
msgstr "" msgstr ""
msgid "epic"
msgstr ""
msgid "error" msgid "error"
msgstr "" msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Issue Warning Component when issue is confidential but not locked renders information about confidential issue 1`] = ` exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
<span>
This issue is locked.
Only project members can comment.
<gl-link-stub
href="locked-path"
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
exports[`Issue Warning Component when noteable is confidential but not locked renders information about confidential issue 1`] = `
<span> <span>
This is a confidential issue. This is a confidential issue.
...@@ -10,14 +25,12 @@ exports[`Issue Warning Component when issue is confidential but not locked rende ...@@ -10,14 +25,12 @@ exports[`Issue Warning Component when issue is confidential but not locked rende
href="confidential-path" href="confidential-path"
target="_blank" target="_blank"
> >
Learn more
Learn more
</gl-link-stub> </gl-link-stub>
</span> </span>
`; `;
exports[`Issue Warning Component when issue is locked and confidential renders information about locked and confidential issue 1`] = ` exports[`Issue Warning Component when noteable is locked and confidential renders information about locked and confidential noteable 1`] = `
<span> <span>
<span> <span>
This issue is This issue is
...@@ -43,20 +56,3 @@ exports[`Issue Warning Component when issue is locked and confidential renders i ...@@ -43,20 +56,3 @@ exports[`Issue Warning Component when issue is locked and confidential renders i
</span> </span>
`; `;
exports[`Issue Warning Component when issue is locked but not confidential renders information about locked issue 1`] = `
<span>
This issue is locked.
Only project members can comment.
<gl-link-stub
href="locked-path"
target="_blank"
>
Learn more
</gl-link-stub>
</span>
`;
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import IssueWarning from '~/vue_shared/components/issue/issue_warning.vue'; import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
describe('Issue Warning Component', () => { describe('Issue Warning Component', () => {
let wrapper; let wrapper;
const findIcon = () => wrapper.find(Icon); const findIcon = (w = wrapper) => w.find(Icon);
const findLockedBlock = () => wrapper.find({ ref: 'locked' }); const findLockedBlock = (w = wrapper) => w.find({ ref: 'locked' });
const findConfidentialBlock = () => wrapper.find({ ref: 'confidential' }); const findConfidentialBlock = (w = wrapper) => w.find({ ref: 'confidential' });
const findLockedAndConfidentialBlock = () => wrapper.find({ ref: 'lockedAndConfidential' }); const findLockedAndConfidentialBlock = (w = wrapper) => w.find({ ref: 'lockedAndConfidential' });
const createComponent = props => { const createComponent = props =>
wrapper = shallowMount(IssueWarning, { shallowMount(NoteableWarning, {
propsData: { propsData: {
...props, ...props,
}, },
}); });
};
afterEach(() => { afterEach(() => {
wrapper.destroy(); if (wrapper) {
wrapper = null; wrapper.destroy();
wrapper = null;
}
}); });
describe('when issue is locked but not confidential', () => { describe('when issue is locked but not confidential', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ wrapper = createComponent({
isLocked: true, isLocked: true,
lockedIssueDocsPath: 'locked-path', lockedNoteableDocsPath: 'locked-path',
isConfidential: false, isConfidential: false,
}); });
}); });
...@@ -50,12 +51,12 @@ describe('Issue Warning Component', () => { ...@@ -50,12 +51,12 @@ describe('Issue Warning Component', () => {
}); });
}); });
describe('when issue is confidential but not locked', () => { describe('when noteable is confidential but not locked', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ wrapper = createComponent({
isLocked: false, isLocked: false,
isConfidential: true, isConfidential: true,
confidentialIssueDocsPath: 'confidential-path', confidentialNoteableDocsPath: 'confidential-path',
}); });
}); });
...@@ -68,24 +69,24 @@ describe('Issue Warning Component', () => { ...@@ -68,24 +69,24 @@ describe('Issue Warning Component', () => {
expect(wrapper.find(Icon).exists()).toBe(true); expect(wrapper.find(Icon).exists()).toBe(true);
}); });
it('does not render information about locked issue', () => { it('does not render information about locked noteable', () => {
expect(findLockedBlock().exists()).toBe(false); expect(findLockedBlock().exists()).toBe(false);
}); });
it('does not render information about locked and confidential issue', () => { it('does not render information about locked and confidential noteable', () => {
expect(findLockedAndConfidentialBlock().exists()).toBe(false); expect(findLockedAndConfidentialBlock().exists()).toBe(false);
}); });
}); });
describe('when issue is locked and confidential', () => { describe('when noteable is locked and confidential', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ wrapper = createComponent({
isLocked: true, isLocked: true,
isConfidential: true, isConfidential: true,
}); });
}); });
it('renders information about locked and confidential issue', () => { it('renders information about locked and confidential noteable', () => {
expect(findLockedAndConfidentialBlock().exists()).toBe(true); expect(findLockedAndConfidentialBlock().exists()).toBe(true);
expect(findLockedAndConfidentialBlock().element).toMatchSnapshot(); expect(findLockedAndConfidentialBlock().element).toMatchSnapshot();
}); });
...@@ -94,12 +95,99 @@ describe('Issue Warning Component', () => { ...@@ -94,12 +95,99 @@ describe('Issue Warning Component', () => {
expect(wrapper.find(Icon).exists()).toBe(false); expect(wrapper.find(Icon).exists()).toBe(false);
}); });
it('does not render information about locked issue', () => { it('does not render information about locked noteable', () => {
expect(findLockedBlock().exists()).toBe(false); expect(findLockedBlock().exists()).toBe(false);
}); });
it('does not render information about confidential issue', () => { it('does not render information about confidential noteable', () => {
expect(findConfidentialBlock().exists()).toBe(false); expect(findConfidentialBlock().exists()).toBe(false);
}); });
}); });
describe('when noteableType prop is defined', () => {
let wrapperLocked;
let wrapperConfidential;
let wrapperLockedAndConfidential;
beforeEach(() => {
wrapperLocked = createComponent({
isLocked: true,
isConfidential: false,
});
wrapperConfidential = createComponent({
isLocked: false,
isConfidential: true,
});
wrapperLockedAndConfidential = createComponent({
isLocked: true,
isConfidential: true,
});
});
afterEach(() => {
wrapperLocked.destroy();
wrapperConfidential.destroy();
wrapperLockedAndConfidential.destroy();
});
it('renders confidential & locked messages with noteable "issue"', () => {
expect(findLockedBlock(wrapperLocked).text()).toContain('This issue is locked.');
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential issue.',
);
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This issue is confidential and locked.',
);
});
it('renders confidential & locked messages with noteable "epic"', async () => {
wrapperLocked.setProps({
noteableType: 'epic',
});
wrapperConfidential.setProps({
noteableType: 'epic',
});
wrapperLockedAndConfidential.setProps({
noteableType: 'epic',
});
await wrapperLocked.vm.$nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This epic is locked.');
await wrapperConfidential.vm.$nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential epic.',
);
await wrapperLockedAndConfidential.vm.$nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This epic is confidential and locked.',
);
});
it('renders confidential & locked messages with noteable "merge request"', async () => {
wrapperLocked.setProps({
noteableType: 'merge_request',
});
wrapperConfidential.setProps({
noteableType: 'merge_request',
});
wrapperLockedAndConfidential.setProps({
noteableType: 'merge_request',
});
await wrapperLocked.vm.$nextTick();
expect(findLockedBlock(wrapperLocked).text()).toContain('This merge request is locked.');
await wrapperConfidential.vm.$nextTick();
expect(findConfidentialBlock(wrapperConfidential).text()).toContain(
'This is a confidential merge request.',
);
await wrapperLockedAndConfidential.vm.$nextTick();
expect(findLockedAndConfidentialBlock(wrapperLockedAndConfidential).text()).toContain(
'This merge request is confidential and locked.',
);
});
});
}); });
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