Commit 5ca49bfb authored by Eulyeon Ko's avatar Eulyeon Ko Committed by Kushal Pandya

Make issuable_header_warnings shared component

Also added a js class in haml as a render target

Apply suggested patch

Update relevant specs

Add js-lock-issue-data and update mock noteable data
Test issuable_header_warnings for both mr and issue

Move to vue_shared

Update issuable header warning component

Also update the spec

Apply the suggestions from the second review round

Re-organize the spec
Remove not needed mock data
parent cfa204bd
<script>
import { mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
},
computed: {
...mapState({
confidential: ({ noteableData }) => noteableData.confidential,
dicussionLocked: ({ noteableData }) => noteableData.discussion_locked,
}),
},
};
</script>
<template>
<div class="gl-display-inline-block">
<div v-if="confidential" class="issuable-warning-icon inline">
<icon class="icon" name="eye-slash" data-testid="confidential" />
</div>
<div v-if="dicussionLocked" class="issuable-warning-icon inline">
<icon class="icon" name="lock" data-testid="locked" />
</div>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import issuableApp from './components/app.vue'; import issuableApp from './components/app.vue';
import IssuableHeaderWarnings from './components/issuable_header_warnings.vue';
import { parseIssuableData } from './utils/parse_data'; import { parseIssuableData } from './utils/parse_data';
import { store } from '~/notes/stores';
export default function initIssueableApp() { export default function initIssueableApp() {
return new Vue({ return new Vue({
...@@ -17,13 +15,3 @@ export default function initIssueableApp() { ...@@ -17,13 +15,3 @@ export default function initIssueableApp() {
}, },
}); });
} }
export function issuableHeaderWarnings() {
return new Vue({
el: document.getElementById('js-issuable-header-warnings'),
store,
render(createElement) {
return createElement(IssuableHeaderWarnings);
},
});
}
...@@ -5,6 +5,7 @@ import store from '~/mr_notes/stores'; ...@@ -5,6 +5,7 @@ import store from '~/mr_notes/stores';
import notesApp from '../notes/components/notes_app.vue'; import notesApp from '../notes/components/notes_app.vue';
import discussionKeyboardNavigator from '../notes/components/discussion_keyboard_navigator.vue'; import discussionKeyboardNavigator from '../notes/components/discussion_keyboard_navigator.vue';
import initWidget from '../vue_merge_request_widget'; import initWidget from '../vue_merge_request_widget';
import { parseBoolean } from '~/lib/utils/common_utils';
export default () => { export default () => {
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -20,6 +21,7 @@ export default () => { ...@@ -20,6 +21,7 @@ export default () => {
const noteableData = JSON.parse(notesDataset.noteableData); const noteableData = JSON.parse(notesDataset.noteableData);
noteableData.noteableType = notesDataset.noteableType; noteableData.noteableType = notesDataset.noteableType;
noteableData.targetType = notesDataset.targetType; noteableData.targetType = notesDataset.targetType;
noteableData.discussion_locked = parseBoolean(notesDataset.isLocked);
return { return {
noteableData, noteableData,
......
// Note: this getter is important because
// `noteableData` is namespaced under `notes` for `~/mr_notes/stores`
// while `noteableData` is directly available as `state.noteableData` for `~/notes/stores`
export const getNoteableData = state => state.notes.noteableData;
export default { export default {
isLoggedIn(state, getters) { isLoggedIn(state, getters) {
return Boolean(getters.getUserData.id); return Boolean(getters.getUserData.id);
......
...@@ -3,16 +3,18 @@ import Issue from '~/issue'; ...@@ -3,16 +3,18 @@ import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
import '~/notes/index'; import '~/notes/index';
import initIssueableApp, { issuableHeaderWarnings } from '~/issue_show'; import { store } from '~/notes/stores';
import initIssueableApp from '~/issue_show';
import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_issuable_header_warning';
import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace'; import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace';
import initRelatedMergeRequestsApp from '~/related_merge_requests'; import initRelatedMergeRequestsApp from '~/related_merge_requests';
import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle'; import initVueIssuableSidebarApp from '~/issuable_sidebar/sidebar_bundle';
export default function() { export default function() {
initIssueableApp(); initIssueableApp();
initIssuableHeaderWarning(store);
initSentryErrorStackTraceApp(); initSentryErrorStackTraceApp();
initRelatedMergeRequestsApp(); initRelatedMergeRequestsApp();
issuableHeaderWarnings();
import(/* webpackChunkName: 'design_management' */ '~/design_management') import(/* webpackChunkName: 'design_management' */ '~/design_management')
.then(module => module.default()) .then(module => module.default())
......
...@@ -2,6 +2,8 @@ import initMrNotes from '~/mr_notes'; ...@@ -2,6 +2,8 @@ import initMrNotes from '~/mr_notes';
import { initReviewBar } from '~/batch_comments'; import { initReviewBar } from '~/batch_comments';
import initSidebarBundle from '~/sidebar/sidebar_bundle'; import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initShow from '../init_merge_request_show'; import initShow from '../init_merge_request_show';
import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_issuable_header_warning';
import store from '~/mr_notes/stores';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initShow(); initShow();
...@@ -10,4 +12,5 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -10,4 +12,5 @@ document.addEventListener('DOMContentLoaded', () => {
} }
initMrNotes(); initMrNotes();
initReviewBar(); initReviewBar();
initIssuableHeaderWarning(store);
}); });
import Vue from 'vue';
import IssuableHeaderWarnings from './issuable_header_warnings.vue';
export default function issuableHeaderWarnings(store) {
return new Vue({
el: document.getElementById('js-issuable-header-warnings'),
store,
render(createElement) {
return createElement(IssuableHeaderWarnings);
},
});
}
<script>
import { mapGetters } from 'vuex';
import { GlIcon } from '@gitlab/ui';
export default {
components: {
GlIcon,
},
computed: {
...mapGetters(['getNoteableData']),
isLocked() {
return this.getNoteableData.discussion_locked;
},
isConfidential() {
return this.getNoteableData.confidential;
},
warningIconsMeta() {
return [
{
iconName: 'lock',
visible: this.isLocked,
dataTestId: 'locked',
},
{
iconName: 'eye-slash',
visible: this.isConfidential,
dataTestId: 'confidential',
},
];
},
},
};
</script>
<template>
<div class="gl-display-inline-block">
<template v-for="meta in warningIconsMeta">
<div v-if="meta.visible" :key="meta.iconName" class="issuable-warning-icon inline">
<gl-icon :name="meta.iconName" :data-testid="meta.dataTestId" class="icon" />
</div>
</template>
</div>
</template>
...@@ -15,8 +15,7 @@ ...@@ -15,8 +15,7 @@
= state_human_name = state_human_name
.issuable-meta .issuable-meta
- if @merge_request.discussion_locked? #js-issuable-header-warnings
.issuable-warning-icon.inline= sprite_icon('lock', size: 16, css_class: 'icon')
= issuable_meta(@merge_request, @project, "Merge request") = issuable_meta(@merge_request, @project, "Merge request")
%a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } %a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" }
......
...@@ -70,7 +70,8 @@ ...@@ -70,7 +70,8 @@
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
target_type: 'merge_request', target_type: 'merge_request',
help_page_path: suggest_changes_help_path, help_page_path: suggest_changes_help_path,
current_user_data: @current_user_data} } current_user_data: @current_user_data,
is_locked: @merge_request.discussion_locked.to_s } }
= render "projects/merge_requests/tabs/pane", name: "commits", id: "commits", class: "commits" do = render "projects/merge_requests/tabs/pane", name: "commits", id: "commits", class: "commits" do
-# This tab is always loaded via AJAX -# This tab is always loaded via AJAX
......
---
title: Consolidate issuable_header_warning for both MR and issue
merge_request: 37043
author:
type: other
import initSidebarBundle from 'ee/sidebar/sidebar_bundle'; import initSidebarBundle from 'ee/sidebar/sidebar_bundle';
import { initReviewBar } from '~/batch_comments'; import { initReviewBar } from '~/batch_comments';
import initMrNotes from '~/mr_notes'; import initMrNotes from '~/mr_notes';
import store from '~/mr_notes/stores';
import initIssuableHeaderWarning from '~/vue_shared/components/issuable/init_issuable_header_warning';
import initShow from '~/pages/projects/merge_requests/init_merge_request_show'; import initShow from '~/pages/projects/merge_requests/init_merge_request_show';
import trackShowInviteMemberLink from 'ee/projects/track_invite_members'; import trackShowInviteMemberLink from 'ee/projects/track_invite_members';
...@@ -11,6 +13,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -11,6 +13,7 @@ document.addEventListener('DOMContentLoaded', () => {
} }
initMrNotes(); initMrNotes();
initReviewBar(); initReviewBar();
initIssuableHeaderWarning(store);
const assigneeDropdown = document.querySelector('.js-sidebar-assignee-dropdown'); const assigneeDropdown = document.querySelector('.js-sidebar-assignee-dropdown');
......
...@@ -22,6 +22,7 @@ export default function initVueMRPage() { ...@@ -22,6 +22,7 @@ export default function initVueMRPage() {
mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock)); mrDiscussionsEl.setAttribute('data-noteable-data', JSON.stringify(noteableDataMock));
mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock)); mrDiscussionsEl.setAttribute('data-notes-data', JSON.stringify(notesDataMock));
mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request'); mrDiscussionsEl.setAttribute('data-noteable-type', 'merge-request');
mrDiscussionsEl.setAttribute('data-is-locked', 'false');
mrTestEl.appendChild(mrDiscussionsEl); mrTestEl.appendChild(mrDiscussionsEl);
const discussionCounterEl = document.createElement('div'); const discussionCounterEl = document.createElement('div');
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import IssuableHeaderWarnings from '~/issue_show/components/issuable_header_warnings.vue';
import createStore from '~/notes/stores';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('IssuableHeaderWarnings', () => {
let wrapper;
let store;
const findConfidential = () => wrapper.find('[data-testid="confidential"]');
const findLocked = () => wrapper.find('[data-testid="locked"]');
const confidentialIconName = () => findConfidential().attributes('name');
const lockedIconName = () => findLocked().attributes('name');
const createComponent = () => {
wrapper = shallowMount(IssuableHeaderWarnings, { store, localVue });
};
beforeEach(() => {
store = createStore();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
store = null;
});
describe('when confidential is on', () => {
beforeEach(() => {
store.state.noteableData.confidential = true;
createComponent();
});
it('renders the confidential icon', () => {
expect(confidentialIconName()).toBe('eye-slash');
});
});
describe('when confidential is off', () => {
beforeEach(() => {
store.state.noteableData.confidential = false;
createComponent();
});
it('does not find the component', () => {
expect(findConfidential().exists()).toBe(false);
});
});
describe('when discussion locked is on', () => {
beforeEach(() => {
store.state.noteableData.discussion_locked = true;
createComponent();
});
it('renders the locked icon', () => {
expect(lockedIconName()).toBe('lock');
});
});
describe('when discussion locked is off', () => {
beforeEach(() => {
store.state.noteableData.discussion_locked = false;
createComponent();
});
it('does not find the component', () => {
expect(findLocked().exists()).toBe(false);
});
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import IssuableHeaderWarnings from '~/vue_shared/components/issuable/issuable_header_warnings.vue';
import createIssueStore from '~/notes/stores';
import { createStore as createMrStore } from '~/mr_notes/stores';
const ISSUABLE_TYPE_ISSUE = 'issue';
const ISSUABLE_TYPE_MR = 'merge request';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('IssuableHeaderWarnings', () => {
let wrapper;
let store;
const findConfidentialIcon = () => wrapper.find('[data-testid="confidential"]');
const findLockedIcon = () => wrapper.find('[data-testid="locked"]');
const renderTestMessage = renders => (renders ? 'renders' : 'does not render');
const setLock = locked => {
store.getters.getNoteableData.discussion_locked = locked;
};
const setConfidential = confidential => {
store.getters.getNoteableData.confidential = confidential;
};
const createComponent = () => {
wrapper = shallowMount(IssuableHeaderWarnings, { store, localVue });
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
store = null;
});
describe.each`
issuableType
${ISSUABLE_TYPE_ISSUE} | ${ISSUABLE_TYPE_MR}
`(`when issuableType=$issuableType`, ({ issuableType }) => {
beforeEach(() => {
store = issuableType === ISSUABLE_TYPE_ISSUE ? createIssueStore() : createMrStore();
createComponent();
});
describe.each`
lockStatus | confidentialStatus
${true} | ${true}
${true} | ${false}
${false} | ${true}
${false} | ${false}
`(
`when locked=$lockStatus and confidential=$confidentialStatus`,
({ lockStatus, confidentialStatus }) => {
beforeEach(() => {
setLock(lockStatus);
setConfidential(confidentialStatus);
});
it(`${renderTestMessage(lockStatus)} the locked icon`, () => {
expect(findLockedIcon().exists()).toBe(lockStatus);
});
it(`${renderTestMessage(confidentialStatus)} the confidential icon`, () => {
expect(findConfidentialIcon().exists()).toBe(confidentialStatus);
});
},
);
});
});
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