Commit b2fc9d46 authored by Mireya Andres's avatar Mireya Andres Committed by Jose Ivan Vargas

Show confirmation dialog when exiting pipeline editor

If there are unsaved changes in the pipeline editor, the browser will
show a dialog box to confirm these changes before the user exits the
page.
parent 41137543
<script>
export default {
props: {
hasUnsavedChanges: {
type: Boolean,
required: true,
},
},
created() {
window.addEventListener('beforeunload', this.confirmChanges);
},
destroyed() {
window.removeEventListener('beforeunload', this.confirmChanges);
},
methods: {
confirmChanges(e = {}) {
if (this.hasUnsavedChanges) {
e.preventDefault();
// eslint-disable-next-line no-param-reassign
e.returnValue = ''; // Chrome requires returnValue to be set
}
},
},
render: () => null,
};
</script>
...@@ -8,6 +8,7 @@ import httpStatusCodes from '~/lib/utils/http_status'; ...@@ -8,6 +8,7 @@ import httpStatusCodes from '~/lib/utils/http_status';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import CiLint from './components/lint/ci_lint.vue'; import CiLint from './components/lint/ci_lint.vue';
import CommitForm from './components/commit/commit_form.vue'; import CommitForm from './components/commit/commit_form.vue';
import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue';
import EditorTab from './components/ui/editor_tab.vue'; import EditorTab from './components/ui/editor_tab.vue';
import TextEditor from './components/text_editor.vue'; import TextEditor from './components/text_editor.vue';
import ValidationSegment from './components/info/validation_segment.vue'; import ValidationSegment from './components/info/validation_segment.vue';
...@@ -30,6 +31,7 @@ export default { ...@@ -30,6 +31,7 @@ export default {
components: { components: {
CiLint, CiLint,
CommitForm, CommitForm,
ConfirmUnsavedChangesDialog,
EditorTab, EditorTab,
GlAlert, GlAlert,
GlLoadingIcon, GlLoadingIcon,
...@@ -66,6 +68,7 @@ export default { ...@@ -66,6 +68,7 @@ export default {
ciConfigData: {}, ciConfigData: {},
content: '', content: '',
contentModel: '', contentModel: '',
lastCommittedContent: '',
lastCommitSha: this.commitSha, lastCommitSha: this.commitSha,
isSaving: false, isSaving: false,
...@@ -88,7 +91,9 @@ export default { ...@@ -88,7 +91,9 @@ export default {
}; };
}, },
update(data) { update(data) {
return data?.blobContent?.rawData; const content = data?.blobContent?.rawData;
this.lastCommittedContent = content;
return content;
}, },
result({ data }) { result({ data }) {
this.contentModel = data?.blobContent?.rawData ?? ''; this.contentModel = data?.blobContent?.rawData ?? '';
...@@ -122,6 +127,9 @@ export default { ...@@ -122,6 +127,9 @@ export default {
}, },
}, },
computed: { computed: {
hasUnsavedChanges() {
return this.lastCommittedContent !== this.contentModel;
},
isBlobContentLoading() { isBlobContentLoading() {
return this.$apollo.queries.content.loading; return this.$apollo.queries.content.loading;
}, },
...@@ -264,6 +272,7 @@ export default { ...@@ -264,6 +272,7 @@ export default {
// Update latest commit // Update latest commit
this.lastCommitSha = commit.sha; this.lastCommitSha = commit.sha;
this.lastCommittedContent = this.contentModel;
} }
} catch (error) { } catch (error) {
this.reportFailure(COMMIT_FAILURE, [error?.message]); this.reportFailure(COMMIT_FAILURE, [error?.message]);
...@@ -342,5 +351,6 @@ export default { ...@@ -342,5 +351,6 @@ export default {
@submit="onCommitSubmit" @submit="onCommitSubmit"
/> />
</div> </div>
<confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div> </div>
</template> </template>
---
title: Show confirmation dialog when exiting pipeline editor
merge_request: 52458
author:
type: added
import { shallowMount } from '@vue/test-utils';
import ConfirmDialog from '~/pipeline_editor/components/ui/confirm_unsaved_changes_dialog.vue';
describe('pipeline_editor/components/ui/confirm_unsaved_changes_dialog', () => {
let beforeUnloadEvent;
let setDialogContent;
let wrapper;
const createComponent = (propsData = {}) => {
wrapper = shallowMount(ConfirmDialog, {
propsData,
});
};
beforeEach(() => {
beforeUnloadEvent = new Event('beforeunload');
jest.spyOn(beforeUnloadEvent, 'preventDefault');
setDialogContent = jest.spyOn(beforeUnloadEvent, 'returnValue', 'set');
});
afterEach(() => {
beforeUnloadEvent.preventDefault.mockRestore();
setDialogContent.mockRestore();
wrapper.destroy();
});
it('shows confirmation dialog when there are unsaved changes', () => {
createComponent({ hasUnsavedChanges: true });
window.dispatchEvent(beforeUnloadEvent);
expect(beforeUnloadEvent.preventDefault).toHaveBeenCalled();
expect(setDialogContent).toHaveBeenCalledWith('');
});
it('does not show confirmation dialog when there are no unsaved changes', () => {
createComponent({ hasUnsavedChanges: false });
window.dispatchEvent(beforeUnloadEvent);
expect(beforeUnloadEvent.preventDefault).not.toHaveBeenCalled();
expect(setDialogContent).not.toHaveBeenCalled();
});
});
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