Merge branch 'issue-edit-inline' into issue-edit-inline-move-project
[ci skip]
This commit is contained in:
commit
b8647134d9
11 changed files with 142 additions and 10 deletions
|
@ -45,6 +45,10 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
isConfidential: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
markdownPreviewUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -83,12 +87,15 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
openForm() {
|
||||
if (!this.showForm) {
|
||||
this.showForm = true;
|
||||
this.store.formState = {
|
||||
title: this.state.titleText,
|
||||
confidential: this.isConfidential,
|
||||
description: this.state.descriptionText,
|
||||
move_to_project_id: 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
closeForm() {
|
||||
this.showForm = false;
|
||||
|
@ -99,6 +106,8 @@ export default {
|
|||
.then((data) => {
|
||||
if (location.pathname !== data.path) {
|
||||
gl.utils.visitUrl(data.path);
|
||||
} if (data.confidential !== this.isConfidential) {
|
||||
gl.utils.visitUrl(location.href);
|
||||
}
|
||||
|
||||
eventHub.$emit('close.form');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import descriptionField from './fields/description.vue';
|
||||
import editActions from './edit_actions.vue';
|
||||
import projectMove from './fields/project_move.vue';
|
||||
import confidentialCheckbox from './fields/confidential_checkbox.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -36,6 +37,7 @@
|
|||
descriptionField,
|
||||
editActions,
|
||||
projectMove,
|
||||
confidentialCheckbox,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -44,6 +46,8 @@
|
|||
<form>
|
||||
<title-field
|
||||
:form-state="formState" />
|
||||
<confidential-checkbox
|
||||
:form-state="formState" />
|
||||
<description-field
|
||||
:form-state="formState"
|
||||
:markdown-preview-url="markdownPreviewUrl"
|
||||
|
@ -53,6 +57,7 @@
|
|||
:form-state="formState"
|
||||
:projects-autocomplete-url="projectsAutocompleteUrl" />
|
||||
<edit-actions
|
||||
:form-state="formState"
|
||||
:can-destroy="canDestroy" />
|
||||
</form>
|
||||
</template>
|
||||
|
|
|
@ -26,6 +26,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
canMove,
|
||||
endpoint,
|
||||
issuableRef,
|
||||
isConfidential,
|
||||
markdownPreviewUrl,
|
||||
markdownDocs,
|
||||
projectsAutocompleteUrl,
|
||||
|
@ -40,6 +41,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
initialTitle: issuableTitleElement.innerHTML,
|
||||
initialDescriptionHtml: issuableDescriptionElement ? issuableDescriptionElement.innerHTML : '',
|
||||
initialDescriptionText: issuableDescriptionTextarea ? issuableDescriptionTextarea.textContent : '',
|
||||
isConfidential: gl.utils.convertPermissionToBoolean(isConfidential),
|
||||
markdownPreviewUrl,
|
||||
markdownDocs,
|
||||
projectsAutocompleteUrl,
|
||||
|
@ -56,6 +58,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
initialTitle: this.initialTitle,
|
||||
initialDescriptionHtml: this.initialDescriptionHtml,
|
||||
initialDescriptionText: this.initialDescriptionText,
|
||||
isConfidential: this.isConfidential,
|
||||
markdownPreviewUrl: this.markdownPreviewUrl,
|
||||
markdownDocs: this.markdownDocs,
|
||||
projectsAutocompleteUrl: this.projectsAutocompleteUrl,
|
||||
|
|
|
@ -14,6 +14,7 @@ export default class Store {
|
|||
};
|
||||
this.formState = {
|
||||
title: '',
|
||||
confidential: false,
|
||||
description: '',
|
||||
move_to_project_id: 0,
|
||||
};
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"can-destroy" => can?(current_user, :destroy_issue, @issue).to_s,
|
||||
"can-move" => @issue.can_move?(current_user).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'),
|
||||
"projects-autocomplete-url" => autocomplete_projects_path(project_id: @project.id),
|
||||
|
|
|
@ -14,7 +14,7 @@ const issueShowInterceptor = data => (request, next) => {
|
|||
}));
|
||||
};
|
||||
|
||||
describe('Issuable output', () => {
|
||||
fdescribe('Issuable output', () => {
|
||||
document.body.innerHTML = '<span id="task_status"></span>';
|
||||
|
||||
let vm;
|
||||
|
@ -38,6 +38,7 @@ describe('Issuable output', () => {
|
|||
markdownPreviewUrl: '/',
|
||||
markdownDocs: '/',
|
||||
projectsAutocompleteUrl: '/',
|
||||
isConfidential: false,
|
||||
},
|
||||
}).$mount();
|
||||
});
|
||||
|
@ -92,6 +93,29 @@ describe('Issuable output', () => {
|
|||
});
|
||||
|
||||
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) => {
|
||||
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
|
||||
resolve();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue