Commit a319418d authored by Luke "Jared" Bennett's avatar Luke "Jared" Bennett

Merge FE

parent 994e7d13
...@@ -6,10 +6,11 @@ ...@@ -6,10 +6,11 @@
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import confidentialIssue from '../../vue_shared/components/issue/confidential_issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue'; import issueNoteSignedOutWidget from './issue_note_signed_out_widget.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 issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueCommentForm', name: 'issueCommentForm',
...@@ -25,7 +26,7 @@ ...@@ -25,7 +26,7 @@
}; };
}, },
components: { components: {
confidentialIssue, issueWarning,
issueNoteSignedOutWidget, issueNoteSignedOutWidget,
markdownField, markdownField,
userAvatarLink, userAvatarLink,
...@@ -54,6 +55,9 @@ ...@@ -54,6 +55,9 @@
isIssueOpen() { isIssueOpen() {
return this.issueState === constants.OPENED || this.issueState === constants.REOPENED; return this.issueState === constants.OPENED || this.issueState === constants.REOPENED;
}, },
canCreate() {
return this.getIssueData.current_user.can_create_note;
},
issueActionButtonTitle() { issueActionButtonTitle() {
if (this.note.length) { if (this.note.length) {
const actionText = this.isIssueOpen ? 'close' : 'reopen'; const actionText = this.isIssueOpen ? 'close' : 'reopen';
...@@ -89,9 +93,6 @@ ...@@ -89,9 +93,6 @@
endpoint() { endpoint() {
return this.getIssueData.create_note_path; return this.getIssueData.create_note_path;
}, },
isConfidentialIssue() {
return this.getIssueData.confidential;
},
}, },
methods: { methods: {
...mapActions([ ...mapActions([
...@@ -206,6 +207,9 @@ ...@@ -206,6 +207,9 @@
}); });
}, },
}, },
mixins: [
issuableStateMixin,
],
mounted() { mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery. // jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => { $(document).on('issuable:change', (e, isClosed) => {
...@@ -239,15 +243,22 @@ ...@@ -239,15 +243,22 @@
<div class="timeline-content timeline-content-form"> <div class="timeline-content timeline-content-form">
<form <form
ref="commentForm" ref="commentForm"
class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"> class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"
<confidentialIssue v-if="isConfidentialIssue" /> >
<issue-warning
v-if="hasIssueWarning(getIssueData)"
:is-locked="isIssueLocked(getIssueData)"
:is-confidential="isIssueConfidential(getIssueData)"
/>
<div class="error-alert"></div> <div class="error-alert"></div>
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath" :quick-actions-docs-path="quickActionsDocsPath"
:add-spacing-classes="false" :add-spacing-classes="false"
:is-confidential-issue="isConfidentialIssue"> :is-confidential-issue="isIssueConfidential(getIssueData)">
<textarea <textarea
id="note-body" id="note-body"
name="note[note]" name="note[note]"
......
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import confidentialIssue from '../../vue_shared/components/issue/confidential_issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_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';
export default { export default {
name: 'issueNoteForm', name: 'issueNoteForm',
...@@ -39,12 +40,13 @@ ...@@ -39,12 +40,13 @@
}; };
}, },
components: { components: {
confidentialIssue, issueWarning,
markdownField, markdownField,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
'getDiscussionLastNote', 'getDiscussionLastNote',
'getIssueData',
'getIssueDataByProp', 'getIssueDataByProp',
'getNotesDataByProp', 'getNotesDataByProp',
'getUserDataByProp', 'getUserDataByProp',
...@@ -67,9 +69,6 @@ ...@@ -67,9 +69,6 @@
isDisabled() { isDisabled() {
return !this.note.length || this.isSubmitting; return !this.note.length || this.isSubmitting;
}, },
isConfidentialIssue() {
return this.getIssueDataByProp('confidential');
},
}, },
methods: { methods: {
handleUpdate() { handleUpdate() {
...@@ -95,6 +94,9 @@ ...@@ -95,6 +94,9 @@
this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note); this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note);
}, },
}, },
mixins: [
issuableStateMixin,
],
mounted() { mounted() {
this.$refs.textarea.focus(); this.$refs.textarea.focus();
}, },
...@@ -125,7 +127,13 @@ ...@@ -125,7 +127,13 @@
<div class="flash-container timeline-content"></div> <div class="flash-container timeline-content"></div>
<form <form
class="edit-note common-note-form js-quick-submit gfm-form"> class="edit-note common-note-form js-quick-submit gfm-form">
<confidentialIssue v-if="isConfidentialIssue" />
<issue-warning
v-if="hasIssueWarning(getIssueData)"
:is-locked="isIssueLocked(getIssueData)"
:is-confidential="isIssueConfidential(getIssueData)"
/>
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
......
export default {
methods: {
isIssueConfidential(issue) {
return !!issue.confidential;
},
isIssueLocked(issue) {
return !!issue.discussion_locked;
},
hasIssueWarning(issue) {
return this.isIssueConfidential(issue) || this.isIssueLocked(issue);
},
},
};
...@@ -47,9 +47,9 @@ export default { ...@@ -47,9 +47,9 @@ export default {
</script> </script>
<template> <template>
<div class="block confidentiality"> <div class="block issuable-sidebar-item confidentiality">
<div class="sidebar-collapsed-icon"> <div class="sidebar-collapsed-icon">
<i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i> <i class="fa" :class="faEye" aria-hidden="true"></i>
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
Confidentiality Confidentiality
...@@ -62,19 +62,19 @@ export default { ...@@ -62,19 +62,19 @@ export default {
Edit Edit
</a> </a>
</div> </div>
<div class="value confidential-value hide-collapsed"> <div class="value sidebar-item-value hide-collapsed">
<editForm <editForm
v-if="edit" v-if="edit"
:toggle-form="toggleForm" :toggle-form="toggleForm"
:is-confidential="isConfidential" :is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute" :update-confidential-attribute="updateConfidentialAttribute"
/> />
<div v-if="!isConfidential" class="no-value confidential-value"> <div v-if="!isConfidential" class="no-value sidebar-item-value">
<i class="fa fa-eye is-not-confidential"></i> <i class="fa fa-eye not-active"></i>
Not confidential Not confidential
</div> </div>
<div v-else class="value confidential-value hide-collapsed"> <div v-else class="value sidebar-item-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i> <i aria-hidden="true" class="fa fa-eye-slash is-active"></i>
This issue is confidential This issue is confidential
</div> </div>
</div> </div>
......
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
import editFormButtons from './edit_form_buttons.vue'; import editFormButtons from './edit_form_buttons.vue';
export default { export default {
components: {
editFormButtons,
},
props: { props: {
isConfidential: { isConfidential: {
required: true, required: true,
...@@ -19,12 +16,16 @@ export default { ...@@ -19,12 +16,16 @@ export default {
type: Function, type: Function,
}, },
}, },
components: {
editFormButtons,
},
}; };
</script> </script>
<template> <template>
<div class="dropdown open"> <div class="dropdown open">
<div class="dropdown-menu confidential-warning-message"> <div class="dropdown-menu sidebar-item-warning-message">
<div> <div>
<p v-if="!isConfidential"> <p v-if="!isConfidential">
You are going to turn on the confidentiality. This means that only team members with You are going to turn on the confidentiality. This means that only team members with
......
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
}, },
}, },
computed: { computed: {
onOrOff() { buttonText() {
return this.isConfidential ? 'Turn Off' : 'Turn On'; return this.isConfidential ? 'Turn Off' : 'Turn On';
}, },
updateConfidentialBool() { updateConfidentialBool() {
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
</script> </script>
<template> <template>
<div class="confidential-warning-message-actions"> <div class="sidebar-item-warning-message-actions">
<button <button
type="button" type="button"
class="btn btn-default append-right-10" class="btn btn-default append-right-10"
...@@ -39,7 +39,7 @@ export default { ...@@ -39,7 +39,7 @@ export default {
class="btn btn-close" class="btn btn-close"
@click.prevent="updateConfidentialAttribute(updateConfidentialBool)" @click.prevent="updateConfidentialAttribute(updateConfidentialBool)"
> >
{{ onOrOff }} {{ buttonText }}
</button> </button>
</div> </div>
</template> </template>
<script>
import editFormButtons from './edit_form_buttons.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable';
export default {
props: {
isLocked: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateLockedAttribute: {
required: true,
type: Function,
},
issuableType: {
required: true,
type: String,
},
},
mixins: [
issuableMixin,
],
components: {
editFormButtons,
},
};
</script>
<template>
<div class="dropdown open">
<div class="dropdown-menu sidebar-item-warning-message">
<div>
<p v-if="isLocked">
{{ __(`Unlock this ${issuableDisplayName(issuableType)}?`) }}
<strong>{{ __('Everyone') }}</strong>
{{ __('will be able to comment.') }}
</p>
<p v-else>
{{ __(`Lock this ${issuableDisplayName(issuableType)}? Only`) }}
<strong>{{ __('project members') }}</strong>
{{ __('will be able to comment.') }}
</p>
<edit-form-buttons
:is-locked="isLocked"
:toggle-form="toggleForm"
:update-locked-attribute="updateLockedAttribute"
/>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isLocked: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateLockedAttribute: {
required: true,
type: Function,
},
},
computed: {
buttonText() {
return this.isLocked ? this.__('Unlock') : this.__('Lock');
},
updateLockedBool() {
return !this.isLocked;
},
},
};
</script>
<template>
<div class="sidebar-item-warning-message-actions">
<button
type="button"
class="btn btn-default append-right-10"
@click="toggleForm"
>
{{ __('Cancel') }}
</button>
<button
type="button"
class="btn btn-close"
@click.prevent="updateLockedAttribute(updateLockedBool)"
>
{{ buttonText }}
</button>
</div>
</template>
<script>
/* global Flash */
import editForm from './edit_form.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable';
export default {
props: {
isLocked: {
required: true,
type: Boolean,
},
isEditable: {
required: true,
type: Boolean,
},
mediator: {
required: true,
type: Object,
validator(mediatorObject) {
return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
},
},
issuableType: {
required: true,
type: String,
},
},
mixins: [
issuableMixin,
],
components: {
editForm,
},
computed: {
lockIconClass() {
return this.isLocked ? 'fa-lock' : 'fa-unlock';
},
isLockDialogOpen() {
return this.mediator.store.isLockDialogOpen;
},
},
methods: {
toggleForm() {
this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
},
updateLockedAttribute(locked) {
this.mediator.service.update(this.issuableType, {
discussion_locked: locked,
})
.then(() => location.reload())
.catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${issuableDisplayName(this.issuableType)}`)));
},
},
};
</script>
<template>
<div class="block issuable-sidebar-item lock">
<div class="sidebar-collapsed-icon">
<i
class="fa"
:class="lockIconClass"
aria-hidden="true"
></i>
</div>
<div class="title hide-collapsed">
{{ __(`Lock ${issuableDisplayName(issuableType)}`) }}
<button
v-if="isEditable"
class="pull-right lock-edit btn btn-blank"
type="button"
@click.prevent="toggleForm"
>
{{ __('Edit') }}
</button>
</div>
<div class="value sidebar-item-value hide-collapsed">
<editForm
v-if="isLockDialogOpen"
:toggle-form="toggleForm"
:is-locked="isLocked"
:update-locked-attribute="updateLockedAttribute"
:issuable-type="issuableType"
/>
<div v-if="isLocked" class="value sidebar-item-value">
<i aria-hidden="true" class="fa fa-lock is-active"></i>
{{ __('Locked') }}
</div>
<div v-else class="no-value sidebar-item-value hide-collapsed">
<i aria-hidden="true" class="fa fa-unlock not-active"></i>
{{ __('Unlocked') }}
</div>
</div>
</div>
</template>
...@@ -3,42 +3,72 @@ import sidebarTimeTracking from './components/time_tracking/sidebar_time_trackin ...@@ -3,42 +3,72 @@ import sidebarTimeTracking from './components/time_tracking/sidebar_time_trackin
import sidebarAssignees from './components/assignees/sidebar_assignees'; import sidebarAssignees from './components/assignees/sidebar_assignees';
import confidential from './components/confidential/confidential_issue_sidebar.vue'; import confidential from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue'; import SidebarMoveIssue from './lib/sidebar_move_issue';
import lockBlock from './components/lock/lock_issue_sidebar.vue';
import Translate from '../vue_shared/translate';
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
Vue.use(Translate);
function mountConfidentialComponent(mediator) {
const el = document.querySelector('#js-confidential-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(confidential);
new ConfidentialComp({
propsData: {
isConfidential: initialData.is_confidential,
isEditable: initialData.is_editable,
service: mediator.service,
},
}).$mount(el);
}
function mountLockComponent(mediator) {
const el = document.querySelector('#js-lock-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-lock-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const LockComp = Vue.extend(lockBlock);
new LockComp({
propsData: {
isLocked: initialData.is_locked,
isEditable: initialData.is_editable,
mediator,
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
},
}).$mount(el);
}
function domContentLoaded() { function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML); const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
const mediator = new Mediator(sidebarOptions); const mediator = new Mediator(sidebarOptions);
mediator.fetch(); mediator.fetch();
const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees'); const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
const confidentialEl = document.querySelector('#js-confidential-entry-point');
// Only create the sidebarAssignees vue app if it is found in the DOM // Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page // We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) { if (sidebarAssigneesEl) {
new Vue(sidebarAssignees).$mount(sidebarAssigneesEl); new Vue(sidebarAssignees).$mount(sidebarAssigneesEl);
} }
if (confidentialEl) { mountConfidentialComponent(mediator);
const dataNode = document.getElementById('js-confidential-issue-data'); mountLockComponent(mediator);
const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(confidential); new SidebarMoveIssue(
mediator,
new ConfidentialComp({ $('.js-move-issue'),
propsData: { $('.js-move-issue-confirmation-button'),
isConfidential: initialData.is_confidential, ).init();
isEditable: initialData.is_editable,
service: mediator.service,
},
}).$mount(confidentialEl);
new SidebarMoveIssue(
mediator,
$('.js-move-issue'),
$('.js-move-issue-confirmation-button'),
).init();
}
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker'); new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
} }
......
...@@ -15,6 +15,7 @@ export default class SidebarStore { ...@@ -15,6 +15,7 @@ export default class SidebarStore {
}; };
this.autocompleteProjects = []; this.autocompleteProjects = [];
this.moveToProjectId = 0; this.moveToProjectId = 0;
this.isLockDialogOpen = false;
SidebarStore.singleton = this; SidebarStore.singleton = this;
} }
......
<script>
export default {
name: 'confidentialIssueWarning',
};
</script>
<template>
<div class="confidential-issue-warning">
<i
aria-hidden="true"
class="fa fa-eye-slash">
</i>
<span>
This is a confidential issue. Your comment will not be visible to the public.
</span>
</div>
</template>
<script>
export default {
props: {
isLocked: {
type: Boolean,
default: false,
},
isConfidential: {
type: Boolean,
default: false,
},
},
computed: {
iconClass() {
return {
'fa-eye-slash': this.isConfidential,
'fa-lock': this.isLocked,
};
},
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
},
};
</script>
<template>
<div class="issuable-note-warning">
<i
aria-hidden="true"
class="fa"
:class="iconClass"
v-if="!isLockedAndConfidential"
></i>
<span v-if="isLockedAndConfidential">
{{ __('This issue is confidential and locked.') }}
{{ __('People without permission will never get a notification and not be able to comment.') }}
</span>
<span v-else-if="isConfidential">
{{ __('This is a confidential issue.') }}
{{ __('Your comment will not be visible to the public.') }}
</span>
<span v-else-if="isLocked">
{{ __('This issue is locked.') }}
{{ __('Only project members can comment.') }}
</span>
</div>
</template>
export default {
methods: {
issuableDisplayName(issuableType) {
return issuableType.replace(/_/, ' ');
},
},
};
...@@ -385,7 +385,11 @@ ...@@ -385,7 +385,11 @@
background: transparent; background: transparent;
border: 0; border: 0;
&:hover,
&:active,
&:focus { &:focus {
outline: 0; outline: 0;
background: transparent;
box-shadow: none;
} }
} }
...@@ -694,3 +694,8 @@ Project Templates Icons ...@@ -694,3 +694,8 @@ Project Templates Icons
$rails: #c00; $rails: #c00;
$node: #353535; $node: #353535;
$java: #70ad51; $java: #70ad51;
/*
Issuable warning
*/
$issuable-warning-size: 24px;
...@@ -5,27 +5,28 @@ ...@@ -5,27 +5,28 @@
margin-right: auto; margin-right: auto;
} }
.is-confidential { .issuable-warning-icon {
color: $orange-600; color: $orange-600;
background-color: $orange-50; background-color: $orange-50;
border-radius: $border-radius-default; border-radius: $border-radius-default;
padding: 5px; padding: 5px;
margin: 0 3px 0 -4px; margin: 0 $btn-side-margin 0 0;
width: $issuable-warning-size;
height: $issuable-warning-size;
text-align: center;
} }
.is-not-confidential { .issuable-sidebar-item {
border-radius: $border-radius-default; .not-active,
padding: 5px; .is-active {
margin: 0 3px 0 -4px; border-radius: $border-radius-default;
} padding: 5px;
margin: 0 3px 0 -4px;
.confidentiality {
.is-not-confidential {
margin: auto;
} }
.is-confidential { .is-active {
margin: auto; color: $orange-600;
background-color: $orange-50;
} }
} }
......
/** /**
* Note Form * Note Form
*/ */
.comment-btn { .comment-btn {
@extend .btn-create; @extend .btn-create;
} }
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
} }
} }
.confidential-issue-warning { .issuable-note-warning {
color: $orange-600; color: $orange-600;
background-color: $orange-50; background-color: $orange-50;
border-radius: $border-radius-default $border-radius-default 0 0; border-radius: $border-radius-default $border-radius-default 0 0;
...@@ -112,17 +112,21 @@ ...@@ -112,17 +112,21 @@
align-items: center; align-items: center;
} }
.confidential-value { .sidebar-item-value {
.fa { .fa {
background-color: inherit; background-color: inherit;
} }
} }
.confidential-warning-message { .sidebar-item-warning-message {
line-height: 1.5; line-height: 1.5;
padding: 16px; padding: 16px;
.confidential-warning-message-actions { p {
color: $text-color;
}
.sidebar-item-warning-message-actions {
display: flex; display: flex;
button { button {
...@@ -131,7 +135,7 @@ ...@@ -131,7 +135,7 @@
} }
} }
.confidential-issue-warning + .md-area { .issuable-note-warning + .md-area {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
......
- referenced_users = local_assigns.fetch(:referenced_users, nil) - referenced_users = local_assigns.fetch(:referenced_users, nil)
- if defined?(@merge_request) && @merge_request.discussion_locked?
.issuable-note-warning
= icon('lock')
%span
= _('This merge request is locked.')
= _('Only project members can comment.')
.md-area .md-area
.md-header .md-header
%ul.nav-links.clearfix %ul.nav-links.clearfix
......
...@@ -27,8 +27,9 @@ ...@@ -27,8 +27,9 @@
.issuable-meta .issuable-meta
- if @issue.confidential - if @issue.confidential
= icon('eye-slash', class: 'is-confidential') = icon('eye-slash', class: 'issuable-warning-icon')
= issuable_meta(@issue, @project, "Issue") - if @issue.discussion_locked?
= icon('lock', class: 'issuable-warning-icon')
.issuable-actions.js-issuable-actions .issuable-actions.js-issuable-actions
.clearfix.issue-btn-group.dropdown .clearfix.issue-btn-group.dropdown
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
= icon('angle-double-left') = icon('angle-double-left')
.issuable-meta .issuable-meta
- if @merge_request.discussion_locked
= icon('lock', class: 'issuable-warning-icon')
= issuable_meta(@merge_request, @project, "Merge request") = issuable_meta(@merge_request, @project, "Merge request")
.issuable-actions.js-issuable-actions .issuable-actions.js-issuable-actions
......
...@@ -119,6 +119,10 @@ ...@@ -119,6 +119,10 @@
%script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: @issue.confidential, is_editable: can_edit_issuable }.to_json.html_safe
#js-confidential-entry-point #js-confidential-entry-point
- if issuable.has_attribute?(:discussion_locked)
%script#js-lock-issue-data{ type: "application/json" }= { is_locked: issuable.discussion_locked?, is_editable: can_edit_issuable }.to_json.html_safe
#js-lock-entry-point
= render "shared/issuable/participants", participants: issuable.participants(current_user) = render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user - if current_user
- subscribed = issuable.subscribed?(current_user, @project) - subscribed = issuable.subscribed?(current_user, @project)
......
...@@ -725,14 +725,14 @@ describe 'Issues' do ...@@ -725,14 +725,14 @@ describe 'Issues' do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
expect(page).to have_css('.confidential-issue-warning') expect(page).to have_css('.issuable-note-warning')
expect(page).to have_css('.is-confidential') expect(find('.issuable-sidebar-item.confidentiality')).to have_css('.is-active')
expect(page).not_to have_css('.is-not-confidential') expect(find('.issuable-sidebar-item.confidentiality')).not_to have_css('.not-active')
find('.confidential-edit').click find('.confidential-edit').click
expect(page).to have_css('.confidential-warning-message') expect(page).to have_css('.sidebar-item-warning-message')
within('.confidential-warning-message') do within('.sidebar-item-warning-message') do
find('.btn-close').click find('.btn-close').click
end end
...@@ -740,7 +740,7 @@ describe 'Issues' do ...@@ -740,7 +740,7 @@ describe 'Issues' do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
expect(page).not_to have_css('.is-confidential') expect(page).not_to have_css('.is-active')
end end
end end
end end
import Vue from 'vue';
import editFormButtons from '~/sidebar/components/lock/edit_form_buttons.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('EditFormButtons', () => {
let vm1;
let vm2;
beforeEach(() => {
const Component = Vue.extend(editFormButtons);
const toggleForm = () => { };
const updateLockedAttribute = () => { };
vm1 = mountComponent(Component, {
isLocked: true,
toggleForm,
updateLockedAttribute,
});
vm2 = mountComponent(Component, {
isLocked: false,
toggleForm,
updateLockedAttribute,
});
});
it('renders unlock or lock text based on locked state', () => {
expect(
vm1.$el.innerHTML.includes('Unlock'),
).toBe(true);
expect(
vm2.$el.innerHTML.includes('Lock'),
).toBe(true);
});
});
import Vue from 'vue';
import editForm from '~/sidebar/components/lock/edit_form.vue';
describe('EditForm', () => {
let vm1;
let vm2;
beforeEach(() => {
const Component = Vue.extend(editForm);
const toggleForm = () => { };
const updateLockedAttribute = () => { };
vm1 = new Component({
propsData: {
isLocked: true,
toggleForm,
updateLockedAttribute,
issuableType: 'issue',
},
}).$mount();
vm2 = new Component({
propsData: {
isLocked: false,
toggleForm,
updateLockedAttribute,
issuableType: 'merge_request',
},
}).$mount();
});
it('renders on the appropriate warning text', () => {
expect(
vm1.$el.innerHTML.includes('Unlock this issue?'),
).toBe(true);
expect(
vm2.$el.innerHTML.includes('Lock this merge request?'),
).toBe(true);
});
});
import Vue from 'vue';
import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue';
describe('LockIssueSidebar', () => {
let vm1;
let vm2;
beforeEach(() => {
const Component = Vue.extend(lockIssueSidebar);
const mediator = {
service: {
update: () => new Promise((resolve) => {
resolve(true);
}),
},
store: {
isLockDialogOpen: false,
},
};
vm1 = new Component({
propsData: {
isLocked: true,
isEditable: true,
mediator,
issuableType: 'issue',
},
}).$mount();
vm2 = new Component({
propsData: {
isLocked: false,
isEditable: false,
mediator,
issuableType: 'merge_request',
},
}).$mount();
});
it('shows if locked and/or editable', () => {
expect(
vm1.$el.innerHTML.includes('Edit'),
).toBe(true);
expect(
vm1.$el.innerHTML.includes('Locked'),
).toBe(true);
expect(
vm2.$el.innerHTML.includes('Unlocked'),
).toBe(true);
});
it('displays the edit form when editable', (done) => {
expect(vm1.isLockDialogOpen).toBe(false);
vm1.$el.querySelector('.lock-edit').click();
expect(vm1.isLockDialogOpen).toBe(true);
vm1.$nextTick(() => {
expect(
vm1.$el
.innerHTML
.includes('Unlock this issue?'),
).toBe(true);
done();
});
});
});
import Vue from 'vue';
import confidentialIssue from '~/vue_shared/components/issue/confidential_issue_warning.vue';
describe('Confidential Issue Warning Component', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(confidentialIssue);
vm = new Component().$mount();
});
afterEach(() => {
vm.$destroy();
});
it('should render confidential issue warning information', () => {
expect(vm.$el.querySelector('i').className).toEqual('fa fa-eye-slash');
expect(vm.$el.querySelector('span').textContent.trim()).toEqual('This is a confidential issue. Your comment will not be visible to the public.');
});
});
import Vue from 'vue';
import issueWarning from '~/vue_shared/components/issue/issue_warning.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
const IssueWarning = Vue.extend(issueWarning);
function formatWarning(string) {
return string.trim().replace(/\n/g, ' ').replace(/\s\s+/g, ' ');
}
describe('Issue Warning Component', () => {
describe('isLocked', () => {
it('should render locked issue warning information', () => {
const vm = mountComponent(IssueWarning, {
isLocked: true,
});
expect(vm.$el.querySelector('i').className).toEqual('fa fa-lock');
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is locked. Only project members can comment.');
});
});
describe('isConfidential', () => {
it('should render confidential issue warning information', () => {
const vm = mountComponent(IssueWarning, {
isConfidential: true,
});
expect(vm.$el.querySelector('i').className).toEqual('fa fa-eye-slash');
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This is a confidential issue. Your comment will not be visible to the public.');
});
});
describe('isLocked and isConfidential', () => {
it('should render locked and confidential issue warning information', () => {
const vm = mountComponent(IssueWarning, {
isLocked: true,
isConfidential: true,
});
expect(vm.$el.querySelector('i')).toBeFalsy();
expect(formatWarning(vm.$el.querySelector('span').textContent)).toEqual('This issue is confidential and locked. People without permission will never get a notification and not be able to comment.');
});
});
});
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