summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue13
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description_template.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue3
-rw-r--r--app/assets/javascripts/issue_show/components/locked_warning.vue20
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js8
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js32
-rw-r--r--spec/javascripts/issue_show/components/fields/description_template_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/form_spec.js23
8 files changed, 99 insertions, 4 deletions
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index d9bfc29130f..d7b94a85024 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -103,12 +103,12 @@ export default {
openForm() {
if (!this.showForm) {
this.showForm = true;
- this.store.formState = {
+ this.store.formState = Object.assign(this.store.formState, {
title: this.state.titleText,
confidential: this.isConfidential,
description: this.state.descriptionText,
move_to_project_id: 0,
- };
+ });
}
},
closeForm() {
@@ -152,7 +152,14 @@ export default {
resource: this.service,
method: 'getData',
successCallback: (res) => {
- this.store.updateState(res.json());
+ const data = res.json();
+ const shouldUpdate = this.store.stateShouldUpdate(data);
+
+ this.store.updateState(data);
+
+ if (this.showForm && (shouldUpdate.title || shouldUpdate.description)) {
+ this.store.formState.lockedWarningVisible = true;
+ }
},
errorCallback(err) {
throw new Error(err);
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
index c679616cca6..1c40b286513 100644
--- a/app/assets/javascripts/issue_show/components/fields/description_template.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -26,7 +26,7 @@
},
mounted() {
// Create the editor for the template
- const editor = document.querySelector('.detail-page-description .note-textarea');
+ const editor = document.querySelector('.detail-page-description .note-textarea') || {};
editor.setValue = (val) => {
this.formState.description = val;
};
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index e0a603f61d4..76ec3dc9a5d 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -1,4 +1,5 @@
<script>
+ import lockedWarning from './locked_warning.vue';
import titleField from './fields/title.vue';
import descriptionField from './fields/description.vue';
import editActions from './edit_actions.vue';
@@ -47,6 +48,7 @@
},
},
components: {
+ lockedWarning,
titleField,
descriptionField,
descriptionTemplate,
@@ -64,6 +66,7 @@
<template>
<form>
+ <locked-warning v-if="formState.lockedWarningVisible" />
<div class="row">
<div
class="col-sm-4 col-lg-3"
diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue
new file mode 100644
index 00000000000..1c2789f154a
--- /dev/null
+++ b/app/assets/javascripts/issue_show/components/locked_warning.vue
@@ -0,0 +1,20 @@
+<script>
+ export default {
+ computed: {
+ currentPath() {
+ return location.pathname;
+ },
+ },
+ };
+</script>
+
+<template>
+ <div class="alert alert-danger">
+ Someone edited the issue at the same time you did. Please check out
+ <a
+ :href="currentPath"
+ target="_blank"
+ rel="nofollow">the issue</a>
+ and make sure your changes will not unintentionally remove theirs.
+ </div>
+</template>
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index 1135bc0bfb5..ea6f4e6a4fa 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -16,6 +16,7 @@ export default class Store {
title: '',
confidential: false,
description: '',
+ lockedWarningVisible: false,
move_to_project_id: 0,
};
}
@@ -28,4 +29,11 @@ export default class Store {
this.state.taskStatus = data.task_status;
this.state.updatedAt = data.updated_at;
}
+
+ stateShouldUpdate(data) {
+ return {
+ title: this.state.titleText !== data.title_text,
+ description: this.state.descriptionText !== data.description_text,
+ };
+ }
}
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 91ae3cfd97c..3247f83d551 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -285,4 +285,36 @@ describe('Issuable output', () => {
});
});
});
+
+ describe('open form', () => {
+ it('shows locked warning if form is open & data is different', (done) => {
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.initialRequest));
+
+ Vue.nextTick()
+ .then(() => new Promise((resolve) => {
+ setTimeout(resolve);
+ }))
+ .then(() => {
+ vm.openForm();
+
+ Vue.http.interceptors.push(issueShowInterceptor(issueShowData.secondRequest));
+
+ return new Promise((resolve) => {
+ setTimeout(resolve);
+ });
+ })
+ .then(() => {
+ expect(
+ vm.formState.lockedWarningVisible,
+ ).toBeTruthy();
+
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).not.toBeNull();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
});
diff --git a/spec/javascripts/issue_show/components/fields/description_template_spec.js b/spec/javascripts/issue_show/components/fields/description_template_spec.js
index d6702e4a8f7..2b7ee65094b 100644
--- a/spec/javascripts/issue_show/components/fields/description_template_spec.js
+++ b/spec/javascripts/issue_show/components/fields/description_template_spec.js
@@ -17,6 +17,8 @@ describe('Issue description template component', () => {
propsData: {
formState,
issuableTemplates: [{ name: 'test' }],
+ projectPath: '/',
+ projectNamespace: '/',
},
}).$mount();
diff --git a/spec/javascripts/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index a2bf1de0576..9a85223208c 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -12,12 +12,17 @@ describe('Inline edit form component', () => {
vm = new Component({
propsData: {
canDestroy: true,
+ canMove: true,
formState: {
title: 'b',
description: 'a',
+ lockedWarningVisible: false,
},
markdownPreviewUrl: '/',
markdownDocs: '/',
+ projectsAutocompleteUrl: '/',
+ projectPath: '/',
+ projectNamespace: '/',
},
}).$mount();
@@ -42,4 +47,22 @@ describe('Inline edit form component', () => {
done();
});
});
+
+ it('hides locked warning by default', () => {
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).toBeNull();
+ });
+
+ it('shows locked warning if formState is different', (done) => {
+ vm.formState.lockedWarningVisible = true;
+
+ Vue.nextTick(() => {
+ expect(
+ vm.$el.querySelector('.alert'),
+ ).not.toBeNull();
+
+ done();
+ });
+ });
});