Merge branch 'issue-edit-inline' into issue-edit-inline-move-project

[ci skip]
This commit is contained in:
Phil Hughes 2017-05-18 12:44:27 +01:00
commit b8647134d9
11 changed files with 142 additions and 10 deletions

View file

@ -45,6 +45,10 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
isConfidential: {
type: Boolean,
required: true,
},
markdownPreviewUrl: { markdownPreviewUrl: {
type: String, type: String,
required: true, required: true,
@ -83,12 +87,15 @@ export default {
}, },
methods: { methods: {
openForm() { openForm() {
this.showForm = true; if (!this.showForm) {
this.store.formState = { this.showForm = true;
title: this.state.titleText, this.store.formState = {
description: this.state.descriptionText, title: this.state.titleText,
move_to_project_id: 0, confidential: this.isConfidential,
}; description: this.state.descriptionText,
move_to_project_id: 0,
};
}
}, },
closeForm() { closeForm() {
this.showForm = false; this.showForm = false;
@ -99,6 +106,8 @@ export default {
.then((data) => { .then((data) => {
if (location.pathname !== data.path) { if (location.pathname !== data.path) {
gl.utils.visitUrl(data.path); gl.utils.visitUrl(data.path);
} if (data.confidential !== this.isConfidential) {
gl.utils.visitUrl(location.href);
} }
eventHub.$emit('close.form'); eventHub.$emit('close.form');

View file

@ -7,6 +7,10 @@
type: Boolean, type: Boolean,
required: true, required: true,
}, },
formState: {
type: Object,
required: true,
},
}, },
data() { data() {
return { return {
@ -14,6 +18,11 @@
updateLoading: false, updateLoading: false,
}; };
}, },
computed: {
isSubmitEnabled() {
return this.formState.title.trim() !== '';
},
},
methods: { methods: {
updateIssuable() { updateIssuable() {
this.updateLoading = true; this.updateLoading = true;
@ -38,9 +47,9 @@
<div class="prepend-top-default append-bottom-default clearfix"> <div class="prepend-top-default append-bottom-default clearfix">
<button <button
class="btn btn-save pull-left" class="btn btn-save pull-left"
:class="{ disabled: updateLoading }" :class="{ disabled: updateLoading || !isSubmitEnabled }"
type="submit" type="submit"
:disabled="updateLoading" :disabled="updateLoading || !isSubmitEnabled"
@click="updateIssuable"> @click="updateIssuable">
Save changes Save changes
<i <i

View file

@ -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>

View file

@ -20,6 +20,9 @@
components: { components: {
markdownField, markdownField,
}, },
mounted() {
this.$refs.textarea.focus();
},
}; };
</script> </script>
@ -39,7 +42,7 @@
data-supports-slash-commands="false" data-supports-slash-commands="false"
aria-label="Description" aria-label="Description"
v-model="formState.description" v-model="formState.description"
ref="textatea" ref="textarea"
slot="textarea"> slot="textarea">
</textarea> </textarea>
</markdown-field> </markdown-field>

View file

@ -3,6 +3,7 @@
import descriptionField from './fields/description.vue'; import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue'; import editActions from './edit_actions.vue';
import projectMove from './fields/project_move.vue'; import projectMove from './fields/project_move.vue';
import confidentialCheckbox from './fields/confidential_checkbox.vue';
export default { export default {
props: { props: {
@ -36,6 +37,7 @@
descriptionField, descriptionField,
editActions, editActions,
projectMove, projectMove,
confidentialCheckbox,
}, },
}; };
</script> </script>
@ -44,6 +46,8 @@
<form> <form>
<title-field <title-field
:form-state="formState" /> :form-state="formState" />
<confidential-checkbox
:form-state="formState" />
<description-field <description-field
:form-state="formState" :form-state="formState"
:markdown-preview-url="markdownPreviewUrl" :markdown-preview-url="markdownPreviewUrl"
@ -53,6 +57,7 @@
:form-state="formState" :form-state="formState"
:projects-autocomplete-url="projectsAutocompleteUrl" /> :projects-autocomplete-url="projectsAutocompleteUrl" />
<edit-actions <edit-actions
:form-state="formState"
:can-destroy="canDestroy" /> :can-destroy="canDestroy" />
</form> </form>
</template> </template>

View file

@ -26,6 +26,7 @@ document.addEventListener('DOMContentLoaded', () => {
canMove, canMove,
endpoint, endpoint,
issuableRef, issuableRef,
isConfidential,
markdownPreviewUrl, markdownPreviewUrl,
markdownDocs, markdownDocs,
projectsAutocompleteUrl, projectsAutocompleteUrl,
@ -40,6 +41,7 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: issuableTitleElement.innerHTML, initialTitle: issuableTitleElement.innerHTML,
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '', initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '', initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
isConfidential: gl.utils.convertPermissionToBoolean(isConfidential),
markdownPreviewUrl, markdownPreviewUrl,
markdownDocs, markdownDocs,
projectsAutocompleteUrl, projectsAutocompleteUrl,
@ -56,6 +58,7 @@ document.addEventListener('DOMContentLoaded', () => {
initialTitle: this.initialTitle, initialTitle: this.initialTitle,
initialDescriptionHtml: this.initialDescriptionHtml, initialDescriptionHtml: this.initialDescriptionHtml,
initialDescriptionText: this.initialDescriptionText, initialDescriptionText: this.initialDescriptionText,
isConfidential: this.isConfidential,
markdownPreviewUrl: this.markdownPreviewUrl, markdownPreviewUrl: this.markdownPreviewUrl,
markdownDocs: this.markdownDocs, markdownDocs: this.markdownDocs,
projectsAutocompleteUrl: this.projectsAutocompleteUrl, projectsAutocompleteUrl: this.projectsAutocompleteUrl,

View file

@ -14,6 +14,7 @@ export default class Store {
}; };
this.formState = { this.formState = {
title: '', title: '',
confidential: false,
description: '', description: '',
move_to_project_id: 0, move_to_project_id: 0,
}; };

View file

@ -56,6 +56,7 @@
"can-destroy" => can?(current_user, :destroy_issue, @issue).to_s, "can-destroy" => can?(current_user, :destroy_issue, @issue).to_s,
"can-move" => @issue.can_move?(current_user).to_s, "can-move" => @issue.can_move?(current_user).to_s,
"issuable-ref" => @issue.to_reference, "issuable-ref" => @issue.to_reference,
"is-confidential" => @issue.confidential.to_s,
"markdown-preview-url" => preview_markdown_path(@project), "markdown-preview-url" => preview_markdown_path(@project),
"markdown-docs" => help_page_path('user/markdown'), "markdown-docs" => help_page_path('user/markdown'),
"projects-autocomplete-url" => autocomplete_projects_path(project_id: @project.id), "projects-autocomplete-url" => autocomplete_projects_path(project_id: @project.id),

View file

@ -14,7 +14,7 @@ const issueShowInterceptor = data => (request, next) => {
})); }));
}; };
describe('Issuable output', () => { fdescribe('Issuable output', () => {
document.body.innerHTML = '<span id="task_status"></span>'; document.body.innerHTML = '<span id="task_status"></span>';
let vm; let vm;
@ -38,6 +38,7 @@ describe('Issuable output', () => {
markdownPreviewUrl: '/', markdownPreviewUrl: '/',
markdownDocs: '/', markdownDocs: '/',
projectsAutocompleteUrl: '/', projectsAutocompleteUrl: '/',
isConfidential: false,
}, },
}).$mount(); }).$mount();
}); });
@ -92,6 +93,29 @@ describe('Issuable output', () => {
}); });
describe('updateIssuable', () => { describe('updateIssuable', () => {
it('reloads the page if the confidential status has changed', (done) => {
spyOn(gl.utils, 'visitUrl');
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({
json() {
return {
confidential: true,
};
},
});
}));
vm.updateIssuable();
setTimeout(() => {
expect(
gl.utils.visitUrl,
).toHaveBeenCalledWith(location.href);
done();
});
});
it('correctly updates issuable data', (done) => { it('correctly updates issuable data', (done) => {
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve(); resolve();

View file

@ -1,18 +1,26 @@
import Vue from 'vue'; import Vue from 'vue';
import editActions from '~/issue_show/components/edit_actions.vue'; import editActions from '~/issue_show/components/edit_actions.vue';
import eventHub from '~/issue_show/event_hub'; import eventHub from '~/issue_show/event_hub';
import Store from '~/issue_show/stores';
describe('Edit Actions components', () => { describe('Edit Actions components', () => {
let vm; let vm;
beforeEach((done) => { beforeEach((done) => {
const Component = Vue.extend(editActions); const Component = Vue.extend(editActions);
const store = new Store({
titleHtml: '',
descriptionHtml: '',
issuableRef: '',
});
store.formState.title = 'test';
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
vm = new Component({ vm = new Component({
propsData: { propsData: {
canDestroy: true, canDestroy: true,
formState: store.formState,
}, },
}).$mount(); }).$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', () => { describe('updateIssuable', () => {
it('sends update.issauble event when clicking save button', () => { it('sends update.issauble event when clicking save button', () => {
vm.$el.querySelector('.btn-save').click(); vm.$el.querySelector('.btn-save').click();

View file

@ -0,0 +1,34 @@
import Vue from 'vue';
import descriptionField from '~/issue_show/components/fields/description.vue';
describe('Description field component', () => {
let vm;
beforeEach((done) => {
const Component = Vue.extend(descriptionField);
// Needs an el in the DOM to be able to test the element is focused
const el = document.createElement('div');
document.body.appendChild(el);
vm = new Component({
el,
propsData: {
formState: {
description: '',
},
markdownDocs: '/',
markdownPreviewUrl: '/',
},
}).$mount();
Vue.nextTick(done);
});
it('focuses field when mounted', () => {
expect(
document.activeElement,
).toBe(vm.$refs.textarea);
});
});