Commit f59bdbf0 authored by Regis Boudinot's avatar Regis Boudinot Committed by Jacob Schatz

33874 confidential issue redesign

parent 1bed998e
<script>
/* global Flash */
import editForm from './edit_form.vue';
export default {
components: {
editForm,
},
props: {
isConfidential: {
required: true,
type: Boolean,
},
isEditable: {
required: true,
type: Boolean,
},
service: {
required: true,
type: Object,
},
},
data() {
return {
edit: false,
};
},
computed: {
faEye() {
const eye = this.isConfidential ? 'fa-eye-slash' : 'fa-eye';
return {
[eye]: true,
};
},
},
methods: {
toggleForm() {
this.edit = !this.edit;
},
updateConfidentialAttribute(confidential) {
this.service.update('issue', { confidential })
.then(() => location.reload())
.catch(() => new Flash('Something went wrong trying to change the confidentiality of this issue'));
},
},
};
</script>
<template>
<div class="block confidentiality">
<div class="sidebar-collapsed-icon">
<i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i>
</div>
<div class="title hide-collapsed">
Confidentiality
<a
v-if="isEditable"
class="pull-right confidential-edit"
href="#"
@click.prevent="toggleForm"
>
Edit
</a>
</div>
<div class="value confidential-value hide-collapsed">
<editForm
v-if="edit"
:toggle-form="toggleForm"
:is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute"
/>
<div v-if="!isConfidential" class="no-value confidential-value">
<i class="fa fa-eye is-not-confidential"></i>
None
</div>
<div v-else class="value confidential-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i>
This issue is confidential
</div>
</div>
</div>
</template>
<script>
import editFormButtons from './edit_form_buttons.vue';
export default {
components: {
editFormButtons,
},
props: {
isConfidential: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: {
required: true,
type: Function,
},
},
};
</script>
<template>
<div class="dropdown open">
<div class="dropdown-menu confidential-warning-message">
<div>
<p v-if="!isConfidential">
You are going to turn on the confidentiality. This means that only team members with
<strong>at least Reporter access</strong>
are able to see and leave comments on the issue.
</p>
<p v-else>
You are going to turn off the confidentiality. This means
<strong>everyone</strong>
will be able to see and leave a comment on this issue.
</p>
<edit-form-buttons
:is-confidential="isConfidential"
:toggle-form="toggleForm"
:update-confidential-attribute="updateConfidentialAttribute"
/>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
isConfidential: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: {
required: true,
type: Function,
},
},
computed: {
onOrOff() {
return this.isConfidential ? 'Turn Off' : 'Turn On';
},
updateConfidentialBool() {
return !this.isConfidential;
},
},
};
</script>
<template>
<div class="confidential-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="updateConfidentialAttribute(updateConfidentialBool)"
>
{{ onOrOff }}
</button>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking'; import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import sidebarAssignees from './components/assignees/sidebar_assignees'; import sidebarAssignees from './components/assignees/sidebar_assignees';
import confidential from './components/confidential/confidential_issue_sidebar.vue';
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
...@@ -10,13 +11,28 @@ function domContentLoaded() { ...@@ -10,13 +11,28 @@ function domContentLoaded() {
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) {
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(confidentialEl);
}
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker'); new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker');
} }
......
...@@ -5,6 +5,30 @@ ...@@ -5,6 +5,30 @@
margin-right: auto; margin-right: auto;
} }
.is-confidential {
color: $orange-600;
background-color: $orange-50;
border-radius: 3px;
padding: 5px;
margin: 0 3px 0 -4px;
}
.is-not-confidential {
border-radius: 3px;
padding: 5px;
margin: 0 3px 0 -4px;
}
.confidentiality {
.is-not-confidential {
margin: auto;
}
.is-confidential {
margin: auto;
}
}
.limit-container-width { .limit-container-width {
.detail-page-header, .detail-page-header,
.page-content-header, .page-content-header,
......
...@@ -104,40 +104,51 @@ ...@@ -104,40 +104,51 @@
} }
.confidential-issue-warning { .confidential-issue-warning {
background-color: $gray-normal; color: $orange-600;
border-radius: 3px; background-color: $orange-50;
border-radius: $border-radius-default $border-radius-default 0 0;
border: 1px solid $border-gray-normal;
padding: 3px 12px; padding: 3px 12px;
margin: auto; margin: auto;
margin-top: 0;
text-align: center;
font-size: 12px;
align-items: center; align-items: center;
}
@media (max-width: $screen-md-max) { .confidential-value {
// On smaller devices the warning becomes the fourth item in the list, .fa {
// rather than centering, and grows to span the full width of the background-color: inherit;
// comment area.
order: 4;
margin: 6px auto;
width: 100%;
} }
}
.fa { .confidential-warning-message {
margin-right: 8px; line-height: 1.5;
padding: 16px;
.confidential-warning-message-actions {
display: flex;
button {
flex-grow: 1;
}
} }
} }
.not-confidential {
padding: 0;
border-top: none;
}
.right-sidebar-expanded { .right-sidebar-expanded {
.confidential-issue-warning { .md-area {
// When the sidebar is open the warning becomes the fourth item in the list, border-radius: 0;
// rather than centering, and grows to span the full width of the border-top: none;
// comment area.
order: 4;
margin: 6px auto;
width: 100%;
} }
} }
.right-sidebar-collapsed {
.confidential-issue-warning {
border-bottom: none;
}
}
.discussion-form { .discussion-form {
padding: $gl-padding-top $gl-padding $gl-padding; padding: $gl-padding-top $gl-padding $gl-padding;
......
- referenced_users = local_assigns.fetch(:referenced_users, nil) - referenced_users = local_assigns.fetch(:referenced_users, nil)
- if defined?(@issue) && @issue.confidential?
%li.confidential-issue-warning
= confidential_icon(@issue)
%span This is a confidential issue. Your comment will not be visible to the public.
- else
%li.confidential-issue-warning.not-confidential
.md-area .md-area
.md-header .md-header
%ul.nav-links.clearfix %ul.nav-links.clearfix
...@@ -10,11 +17,6 @@ ...@@ -10,11 +17,6 @@
%a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 } %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
Preview Preview
- if defined?(@issue) && @issue.confidential?
%li.confidential-issue-warning
= icon('warning')
%span This is a confidential issue. Your comment will not be visible to the public.
%li.pull-right %li.pull-right
.toolbar-group .toolbar-group
= markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) = markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
......
...@@ -19,7 +19,8 @@ ...@@ -19,7 +19,8 @@
= icon('angle-double-left') = icon('angle-double-left')
.issuable-meta .issuable-meta
= confidential_icon(@issue) - if @issue.confidential
= icon('eye-slash', class: 'is-confidential')
= issuable_meta(@issue, @project, "Issue") = issuable_meta(@issue, @project, "Issue")
.issuable-actions .issuable-actions
......
...@@ -115,6 +115,10 @@ ...@@ -115,6 +115,10 @@
- if can? current_user, :admin_label, @project and @project - if can? current_user, :admin_label, @project and @project
= render partial: "shared/issuable/label_page_create" = render partial: "shared/issuable/label_page_create"
- if issuable.has_attribute?(:confidential)
%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
= 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)
......
---
title: Update confidential issue UI - add confidential visibility and settings to
sidebar
merge_request:
author:
...@@ -706,4 +706,30 @@ describe 'Issues' do ...@@ -706,4 +706,30 @@ describe 'Issues' do
expect(page).to have_text("updated title") expect(page).to have_text("updated title")
end end
end end
describe 'confidential issue#show', js: true do
it 'shows confidential sibebar information as confidential and can be turned off' do
issue = create(:issue, :confidential, project: project)
visit project_issue_path(project, issue)
expect(page).to have_css('.confidential-issue-warning')
expect(page).to have_css('.is-confidential')
expect(page).not_to have_css('.is-not-confidential')
find('.confidential-edit').click
expect(page).to have_css('.confidential-warning-message')
within('.confidential-warning-message') do
find('.btn-close').click
end
wait_for_requests
visit project_issue_path(project, issue)
expect(page).not_to have_css('.is-confidential')
expect(page).to have_css('.is-not-confidential')
end
end
end end
import Vue from 'vue';
import editFormButtons from '~/sidebar/components/confidential/edit_form_buttons.vue';
describe('Edit Form Buttons', () => {
let vm1;
let vm2;
beforeEach(() => {
const Component = Vue.extend(editFormButtons);
const toggleForm = () => { };
const updateConfidentialAttribute = () => { };
vm1 = new Component({
propsData: {
isConfidential: true,
toggleForm,
updateConfidentialAttribute,
},
}).$mount();
vm2 = new Component({
propsData: {
isConfidential: false,
toggleForm,
updateConfidentialAttribute,
},
}).$mount();
});
it('renders on or off text based on confidentiality', () => {
expect(
vm1.$el.innerHTML.includes('Turn Off'),
).toBe(true);
expect(
vm2.$el.innerHTML.includes('Turn On'),
).toBe(true);
});
});
import Vue from 'vue';
import editForm from '~/sidebar/components/confidential/edit_form.vue';
describe('Edit Form Dropdown', () => {
let vm1;
let vm2;
beforeEach(() => {
const Component = Vue.extend(editForm);
const toggleForm = () => { };
const updateConfidentialAttribute = () => { };
vm1 = new Component({
propsData: {
isConfidential: true,
toggleForm,
updateConfidentialAttribute,
},
}).$mount();
vm2 = new Component({
propsData: {
isConfidential: false,
toggleForm,
updateConfidentialAttribute,
},
}).$mount();
});
it('renders on the appropriate warning text', () => {
expect(
vm1.$el.innerHTML.includes('You are going to turn off the confidentiality.'),
).toBe(true);
expect(
vm2.$el.innerHTML.includes('You are going to turn on the confidentiality.'),
).toBe(true);
});
});
import Vue from 'vue';
import confidentialIssueSidebar from '~/sidebar/components/confidential/confidential_issue_sidebar.vue';
describe('Confidential Issue Sidebar Block', () => {
let vm1;
let vm2;
beforeEach(() => {
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
update: () => new Promise((resolve, reject) => {
resolve(true);
reject('failed!');
}),
};
vm1 = new Component({
propsData: {
isConfidential: true,
isEditable: true,
service,
},
}).$mount();
vm2 = new Component({
propsData: {
isConfidential: false,
isEditable: false,
service,
},
}).$mount();
});
it('shows if confidential and/or editable', () => {
expect(
vm1.$el.innerHTML.includes('Edit'),
).toBe(true);
expect(
vm1.$el.innerHTML.includes('This issue is confidential'),
).toBe(true);
expect(
vm2.$el.innerHTML.includes('None'),
).toBe(true);
});
it('displays the edit form when editable', (done) => {
expect(vm1.edit).toBe(false);
vm1.$el.querySelector('.confidential-edit').click();
expect(vm1.edit).toBe(true);
setTimeout(() => {
expect(
vm1.$el
.innerHTML
.includes('You are going to turn off the confidentiality.'),
).toBe(true);
done();
});
});
});
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