diff options
author | Phil Hughes <me@iamphill.com> | 2017-05-18 12:05:51 +0100 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-05-18 12:05:51 +0100 |
commit | 203a94e3c2a183efefc0ca3cf89ce73e759ba3e1 (patch) | |
tree | 2520dc3908371d4fca48dd5fb4dc20a28d028b22 | |
parent | 6c34f5a212c04a5f384999146c1957cf983dcbf3 (diff) | |
parent | f5675666a11ff296e27b6dbdf0f789242fcd1b7f (diff) | |
download | gitlab-ce-203a94e3c2a183efefc0ca3cf89ce73e759ba3e1.tar.gz |
Merge branch 'issue-edit-inline' into issue-edit-inline-description-field-specsissue-edit-inline-description-field-specs
[ci skip]
11 files changed, 140 insertions, 11 deletions
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index eb594cfb60b..47d9a27e99e 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -41,6 +41,10 @@ export default { required: false, default: '', }, + isConfidential: { + type: Boolean, + required: true, + }, markdownPreviewUrl: { type: String, required: true, @@ -75,18 +79,27 @@ export default { }, methods: { openForm() { - this.showForm = true; - this.store.formState = { - title: this.state.titleText, - description: this.state.descriptionText, - }; + if (!this.showForm) { + this.showForm = true; + this.store.formState = { + title: this.state.titleText, + confidential: this.isConfidential, + description: this.state.descriptionText, + }; + } }, closeForm() { this.showForm = false; }, updateIssuable() { this.service.updateIssuable(this.store.formState) - .then(() => { + .then((res) => { + const data = res.json(); + + if (data.confidential !== this.isConfidential) { + location.reload(); + } + eventHub.$emit('close.form'); }) .catch(() => { diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index 4cefb236d32..e57b867f93b 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -7,6 +7,10 @@ type: Boolean, required: true, }, + formState: { + type: Object, + required: true, + }, }, data() { return { @@ -14,6 +18,11 @@ updateLoading: false, }; }, + computed: { + isSubmitEnabled() { + return this.formState.title.trim() !== ''; + }, + }, methods: { updateIssuable() { this.updateLoading = true; @@ -38,9 +47,9 @@ <div class="prepend-top-default append-bottom-default clearfix"> <button class="btn btn-save pull-left" - :class="{ disabled: updateLoading }" + :class="{ disabled: updateLoading || !isSubmitEnabled }" type="submit" - :disabled="updateLoading" + :disabled="updateLoading || !isSubmitEnabled" @click="updateIssuable"> Save changes <i diff --git a/app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue b/app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue new file mode 100644 index 00000000000..a0ff08e9111 --- /dev/null +++ b/app/assets/javascripts/issue_show/components/fields/confidential_checkbox.vue @@ -0,0 +1,23 @@ +<script> + export default { + props: { + formState: { + type: Object, + required: true, + }, + }, + }; +</script> + +<template> + <fieldset class="checkbox"> + <label for="issue-confidential"> + <input + type="checkbox" + value="1" + id="issue-confidential" + v-model="formState.confidential" /> + This issue is confidential and should only be visible to team members with at least Reporter access. + </label> + </fieldset> +</template> diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue index b4c31811a0b..35b1ea6ff2b 100644 --- a/app/assets/javascripts/issue_show/components/fields/description.vue +++ b/app/assets/javascripts/issue_show/components/fields/description.vue @@ -20,6 +20,9 @@ components: { markdownField, }, + mounted() { + this.$refs.textarea.focus(); + }, }; </script> @@ -39,7 +42,7 @@ data-supports-slash-commands="false" aria-label="Description" v-model="formState.description" - ref="textatea" + ref="textarea" slot="textarea"> </textarea> </markdown-field> diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue index 7732a1c194a..4288c5f8d90 100644 --- a/app/assets/javascripts/issue_show/components/form.vue +++ b/app/assets/javascripts/issue_show/components/form.vue @@ -2,6 +2,7 @@ import titleField from './fields/title.vue'; import descriptionField from './fields/description.vue'; import editActions from './edit_actions.vue'; + import confidentialCheckbox from './fields/confidential_checkbox.vue'; export default { props: { @@ -26,6 +27,7 @@ titleField, descriptionField, editActions, + confidentialCheckbox, }, }; </script> @@ -34,11 +36,14 @@ <form> <title-field :form-state="formState" /> + <confidential-checkbox + :form-state="formState" /> <description-field :form-state="formState" :markdown-preview-url="markdownPreviewUrl" :markdown-docs="markdownDocs" /> <edit-actions + :form-state="formState" :can-destroy="canDestroy" /> </form> </template> diff --git a/app/assets/javascripts/issue_show/index.js b/app/assets/javascripts/issue_show/index.js index d13e24a468b..3b69be05cf3 100644 --- a/app/assets/javascripts/issue_show/index.js +++ b/app/assets/javascripts/issue_show/index.js @@ -25,6 +25,7 @@ document.addEventListener('DOMContentLoaded', () => { canDestroy, endpoint, issuableRef, + isConfidential, markdownPreviewUrl, markdownDocs, } = issuableElement.dataset; @@ -37,6 +38,7 @@ document.addEventListener('DOMContentLoaded', () => { initialTitle: issuableTitleElement.innerHTML, initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '', initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '', + isConfidential: gl.utils.convertPermissionToBoolean(isConfidential), markdownPreviewUrl, markdownDocs, }; @@ -51,6 +53,7 @@ document.addEventListener('DOMContentLoaded', () => { initialTitle: this.initialTitle, initialDescriptionHtml: this.initialDescriptionHtml, initialDescriptionText: this.initialDescriptionText, + isConfidential: this.isConfidential, markdownPreviewUrl: this.markdownPreviewUrl, markdownDocs: this.markdownDocs, }, diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js index 3232875000d..d90716bef80 100644 --- a/app/assets/javascripts/issue_show/stores/index.js +++ b/app/assets/javascripts/issue_show/stores/index.js @@ -14,6 +14,7 @@ export default class Store { }; this.formState = { title: '', + confidential: false, description: '', }; } diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 2b095648dcf..9afffdba354 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -55,6 +55,7 @@ "can-update" => can?(current_user, :update_issue, @issue).to_s, "can-destroy" => can?(current_user, :destroy_issue, @issue).to_s, "issuable-ref" => @issue.to_reference, + "is-confidential" => @issue.confidential.to_s, "markdown-preview-url" => preview_markdown_path(@project), "markdown-docs" => help_page_path('user/markdown'), } } diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js index 22b0a0f7046..646fb455d7c 100644 --- a/spec/javascripts/issue_show/components/app_spec.js +++ b/spec/javascripts/issue_show/components/app_spec.js @@ -23,7 +23,7 @@ describe('Issuable output', () => { const IssuableDescriptionComponent = Vue.extend(issuableApp); Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest)); - spyOn(eventHub, '$emit'); + spyOn(eventHub, '$emit').and.callThrough(); vm = new IssuableDescriptionComponent({ propsData: { @@ -34,7 +34,9 @@ describe('Issuable output', () => { initialTitle: '', initialDescriptionHtml: '', initialDescriptionText: '', - showForm: false, + isConfidential: false, + markdownPreviewUrl: '/', + markdownDocs: '/', }, }).$mount(); }); @@ -88,6 +90,22 @@ describe('Issuable output', () => { }); }); + it('does not update formState if form is already open', (done) => { + vm.openForm(); + + vm.state.titleText = 'testing 123'; + + vm.openForm(); + + Vue.nextTick(() => { + expect( + vm.store.formState.title, + ).not.toBe('testing 123'); + + done(); + }); + }); + describe('updateIssuable', () => { it('correctly updates issuable data', (done) => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { @@ -108,6 +126,29 @@ describe('Issuable output', () => { }); }); + it('reloads the page if the confidential status has changed', (done) => { + spyOn(window.location, 'reload'); + spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { + resolve({ + json() { + return { + confidential: true, + }; + }, + }); + })); + + vm.updateIssuable(); + + setTimeout(() => { + expect( + window.location.reload, + ).toHaveBeenCalled(); + + done(); + }); + }); + it('closes form on error', (done) => { spyOn(window, 'Flash').and.callThrough(); spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve, reject) => { diff --git a/spec/javascripts/issue_show/components/edit_actions_spec.js b/spec/javascripts/issue_show/components/edit_actions_spec.js index 8fbaf6cfb2b..f6625b748b6 100644 --- a/spec/javascripts/issue_show/components/edit_actions_spec.js +++ b/spec/javascripts/issue_show/components/edit_actions_spec.js @@ -1,18 +1,26 @@ import Vue from 'vue'; import editActions from '~/issue_show/components/edit_actions.vue'; import eventHub from '~/issue_show/event_hub'; +import Store from '~/issue_show/stores'; describe('Edit Actions components', () => { let vm; beforeEach((done) => { const Component = Vue.extend(editActions); + const store = new Store({ + titleHtml: '', + descriptionHtml: '', + issuableRef: '', + }); + store.formState.title = 'test'; spyOn(eventHub, '$emit'); vm = new Component({ propsData: { canDestroy: true, + formState: store.formState, }, }).$mount(); @@ -41,6 +49,18 @@ describe('Edit Actions components', () => { }); }); + it('disables submit button when title is blank', (done) => { + vm.formState.title = ''; + + Vue.nextTick(() => { + expect( + vm.$el.querySelector('.btn-save').getAttribute('disabled'), + ).toBe('disabled'); + + done(); + }); + }); + describe('updateIssuable', () => { it('sends update.issauble event when clicking save button', () => { vm.$el.querySelector('.btn-save').click(); diff --git a/spec/javascripts/issue_show/components/fields/description_spec.js b/spec/javascripts/issue_show/components/fields/description_spec.js index 05e628ca34c..f5b35b1e8b0 100644 --- a/spec/javascripts/issue_show/components/fields/description_spec.js +++ b/spec/javascripts/issue_show/components/fields/description_spec.js @@ -8,6 +8,7 @@ describe('Description field component', () => { beforeEach((done) => { const Component = Vue.extend(descriptionField); + const el = document.createElement('div'); store = new Store({ titleHtml: '', descriptionHtml: '', @@ -15,7 +16,10 @@ describe('Description field component', () => { }); store.formState.description = 'test'; + document.body.appendChild(el); + vm = new Component({ + el, propsData: { markdownPreviewUrl: '/', markdownDocs: '/', @@ -43,4 +47,10 @@ describe('Description field component', () => { done(); }); }); + + it('focuses field when mounted', () => { + expect( + document.activeElement, + ).toBe(vm.$refs.textarea); + }); }); |