Commit c741466d authored by Derek Knox's avatar Derek Knox Committed by Enrique Alcántara

Initial edit-meta UI implementation

Added modal and controls components for
overall editing merge request metadata
parent ccf9b3bc
<script>
import { GlForm, GlFormGroup, GlFormInput, GlFormTextarea } from '@gitlab/ui';
export default {
components: {
GlForm,
GlFormGroup,
GlFormInput,
GlFormTextarea,
},
props: {
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
},
data() {
return {
editable: {
title: this.title,
description: this.description,
},
};
},
methods: {
getId(type, key) {
return `sse-merge-request-meta-${type}-${key}`;
},
onUpdate() {
this.$emit('updateSettings', { ...this.editable });
},
},
};
</script>
<template>
<gl-form>
<gl-form-group
key="title"
:label="__('Brief title about the change')"
:label-for="getId('control', 'title')"
>
<gl-form-input
:id="getId('control', 'title')"
v-model.lazy="editable.title"
type="text"
@input="onUpdate"
/>
</gl-form-group>
<gl-form-group
key="description"
:label="__('Goal of the changes and what reviewers should be aware of')"
:label-for="getId('control', 'description')"
>
<gl-form-textarea
:id="getId('control', 'description')"
v-model.lazy="editable.description"
@input="onUpdate"
/>
</gl-form-group>
</gl-form>
</template>
<script>
import { GlModal } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
import EditMetaControls from './edit_meta_controls.vue';
export default {
components: {
GlModal,
EditMetaControls,
},
props: {
sourcePath: {
type: String,
required: true,
},
},
data() {
return {
mergeRequestMeta: {
title: sprintf(s__(`StaticSiteEditor|Update %{sourcePath} file`), {
sourcePath: this.sourcePath,
}),
description: s__('StaticSiteEditor|Copy update'),
},
};
},
computed: {
disabled() {
return this.mergeRequestMeta.title === '';
},
primaryProps() {
return {
text: __('Submit changes'),
attributes: [{ variant: 'success' }, { disabled: this.disabled }],
};
},
},
methods: {
hide() {
this.$refs.modal.hide();
},
show() {
this.$refs.modal.show();
},
onUpdateSettings(mergeRequestMeta) {
this.mergeRequestMeta = { ...mergeRequestMeta };
},
},
};
</script>
<template>
<gl-modal
ref="modal"
modal-id="edit-meta-modal"
:title="__('Submit your changes')"
:action-primary="primaryProps"
size="sm"
@primary="() => $emit('primary', mergeRequestMeta)"
@hide="() => $emit('hide')"
>
<edit-meta-controls
:title="mergeRequestMeta.title"
:description="mergeRequestMeta.description"
@updateSettings="onUpdateSettings"
/>
</gl-modal>
</template>
......@@ -50,7 +50,7 @@ export default {
:loading="savingChanges"
@click="$emit('submit')"
>
{{ __('Submit changes') }}
{{ __('Submit changes...') }}
</gl-button>
</div>
</div>
......
<script>
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { s__, sprintf } from '~/locale';
import Tracking from '~/tracking';
import SkeletonLoader from '../components/skeleton_loader.vue';
import EditArea from '../components/edit_area.vue';
import EditMetaModal from '../components/edit_meta_modal.vue';
import InvalidContentMessage from '../components/invalid_content_message.vue';
import SubmitChangesError from '../components/submit_changes_error.vue';
import appDataQuery from '../graphql/queries/app_data.query.graphql';
......@@ -18,6 +18,7 @@ export default {
components: {
SkeletonLoader,
EditArea,
EditMetaModal,
InvalidContentMessage,
SubmitChangesError,
},
......@@ -51,6 +52,7 @@ export default {
data() {
return {
content: null,
images: null,
submitChangesError: null,
isSavingChanges: false,
};
......@@ -67,16 +69,21 @@ export default {
Tracking.event(document.body.dataset.page, TRACKING_ACTION_INITIALIZE_EDITOR);
},
methods: {
onHideModal() {
this.isSavingChanges = false;
this.$refs.editMetaModal.hide();
},
onDismissError() {
this.submitChangesError = null;
},
onSubmit({ content, images }) {
onPrepareSubmit({ content, images }) {
this.content = content;
this.submitChanges(images);
},
submitChanges(images) {
this.isSavingChanges = true;
this.images = images;
this.isSavingChanges = true;
this.$refs.editMetaModal.show();
},
onSubmit(mergeRequestMeta) {
// eslint-disable-next-line promise/catch-or-return
this.$apollo
.mutate({
......@@ -100,13 +107,8 @@ export default {
username: this.appData.username,
sourcePath: this.appData.sourcePath,
content: this.content,
images,
mergeRequestMeta: {
title: sprintf(s__(`StaticSiteEditor|Update %{sourcePath} file`), {
sourcePath: this.appData.sourcePath,
}),
description: s__('StaticSiteEditor|Copy update'),
},
images: this.images,
mergeRequestMeta,
},
},
})
......@@ -127,7 +129,7 @@ export default {
<submit-changes-error
v-if="submitChangesError"
:error="submitChangesError"
@retry="submitChanges"
@retry="onSubmit"
@dismiss="onDismissError"
/>
<edit-area
......@@ -136,7 +138,13 @@ export default {
:content="sourceContent.content"
:saving-changes="isSavingChanges"
:return-url="appData.returnUrl"
@submit="onSubmit"
@submit="onPrepareSubmit"
/>
<edit-meta-modal
ref="editMetaModal"
:source-path="appData.sourcePath"
@primary="onSubmit"
@hide="onHideModal"
/>
</template>
......
---
title: Add merge request title and description UI to Static Site Editor submission flow
merge_request: 44071
author:
type: added
......@@ -4335,6 +4335,9 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
msgid "Brief title about the change"
msgstr ""
msgid "Broadcast Message was successfully created."
msgstr ""
......@@ -12418,6 +12421,9 @@ msgstr ""
msgid "Go to your snippets"
msgstr ""
msgid "Goal of the changes and what reviewers should be aware of"
msgstr ""
msgid "Google Cloud Platform"
msgstr ""
......@@ -24869,6 +24875,9 @@ msgstr ""
msgid "Submit changes"
msgstr ""
msgid "Submit changes..."
msgstr ""
msgid "Submit feedback"
msgstr ""
......@@ -24884,6 +24893,9 @@ msgstr ""
msgid "Submit the current review."
msgstr ""
msgid "Submit your changes"
msgstr ""
msgid "Submitted the current review."
msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlFormInput, GlFormTextarea } from '@gitlab/ui';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
import { mergeRequestMeta } from '../mock_data';
describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
let wrapper;
const { title, description } = mergeRequestMeta;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(EditMetaControls, {
propsData: {
title,
description,
...propsData,
},
});
};
const findGlFormInputTitle = () => wrapper.find(GlFormInput);
const findGlFormTextAreaDescription = () => wrapper.find(GlFormTextarea);
beforeEach(() => {
buildWrapper();
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders the title input', () => {
expect(findGlFormInputTitle().exists()).toBe(true);
});
it('renders the description input', () => {
expect(findGlFormTextAreaDescription().exists()).toBe(true);
});
it('forwards the title prop to the title input', () => {
expect(findGlFormInputTitle().attributes().value).toBe(title);
});
it('forwards the description prop to the description input', () => {
expect(findGlFormTextAreaDescription().attributes().value).toBe(description);
});
it('emits updated settings when title input updates', () => {
const newTitle = 'New title';
findGlFormInputTitle().vm.$emit('input', newTitle);
const newSettings = { description, title: newTitle };
expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings);
});
it('emits updated settings when description textarea updates', () => {
const newDescription = 'New description';
findGlFormTextAreaDescription().vm.$emit('input', newDescription);
const newSettings = { description: newDescription, title };
expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings);
});
});
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
import { sourcePath, mergeRequestMeta } from '../mock_data';
describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
let wrapper;
const { title, description } = mergeRequestMeta;
const buildWrapper = (propsData = {}) => {
wrapper = shallowMount(EditMetaModal, {
propsData: {
sourcePath,
...propsData,
},
});
};
const findGlModal = () => wrapper.find(GlModal);
const findEditMetaControls = () => wrapper.find(EditMetaControls);
beforeEach(() => {
buildWrapper();
return wrapper.vm.$nextTick();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('renders the modal', () => {
expect(findGlModal().exists()).toBe(true);
});
it('renders the edit meta controls', () => {
expect(findEditMetaControls().exists()).toBe(true);
});
it('contains the sourcePath in the title', () => {
expect(findEditMetaControls().props('title')).toContain(sourcePath);
});
it('forwards the title prop', () => {
expect(findEditMetaControls().props('title')).toBe(title);
});
it('forwards the description prop', () => {
expect(findEditMetaControls().props('description')).toBe(description);
});
it('emits the primary event with mergeRequestMeta', () => {
findGlModal().vm.$emit('primary', mergeRequestMeta);
expect(wrapper.emitted('primary')).toEqual([[mergeRequestMeta]]);
});
it('emits the hide event', () => {
findGlModal().vm.$emit('hide');
expect(wrapper.emitted('hide')).toEqual([[]]);
});
});
......@@ -3,6 +3,7 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import Home from '~/static_site_editor/pages/home.vue';
import SkeletonLoader from '~/static_site_editor/components/skeleton_loader.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue';
import SubmitChangesError from '~/static_site_editor/components/submit_changes_error.vue';
import submitContentChangesMutation from '~/static_site_editor/graphql/mutations/submit_content_changes.mutation.graphql';
......@@ -21,6 +22,7 @@ import {
savedContentMeta,
submitChangesError,
trackingCategory,
images,
} from '../mock_data';
const localVue = createLocalVue();
......@@ -85,6 +87,7 @@ describe('static_site_editor/pages/home', () => {
};
const findEditArea = () => wrapper.find(EditArea);
const findEditMetaModal = () => wrapper.find(EditMetaModal);
const findInvalidContentMessage = () => wrapper.find(InvalidContentMessage);
const findSkeletonLoader = () => wrapper.find(SkeletonLoader);
const findSubmitChangesError = () => wrapper.find(SubmitChangesError);
......@@ -152,17 +155,38 @@ describe('static_site_editor/pages/home', () => {
});
it('displays invalid content message when content is not supported', () => {
buildWrapper({ appData: { isSupportedContent: false } });
buildWrapper({ appData: { ...defaultAppData, isSupportedContent: false } });
expect(findInvalidContentMessage().exists()).toBe(true);
});
it('does not display invalid content message when content is supported', () => {
buildWrapper({ appData: { isSupportedContent: true } });
buildWrapper();
expect(findInvalidContentMessage().exists()).toBe(false);
});
it('renders an EditMetaModal component', () => {
buildWrapper();
expect(findEditMetaModal().exists()).toBe(true);
});
describe('when preparing submission', () => {
it('calls the show method when the edit-area submit event is emitted', () => {
buildWrapper();
const mockInstance = { show: jest.fn() };
wrapper.vm.$refs.editMetaModal = mockInstance;
findEditArea().vm.$emit('submit', { content });
return wrapper.vm.$nextTick().then(() => {
expect(mockInstance.show).toHaveBeenCalled();
});
});
});
describe('when submitting changes fails', () => {
const setupMutateMock = () => {
mutateMock
......@@ -173,8 +197,8 @@ describe('static_site_editor/pages/home', () => {
beforeEach(() => {
setupMutateMock();
buildWrapper();
findEditArea().vm.$emit('submit', { content });
buildWrapper({ content });
findEditMetaModal().vm.$emit('primary', mergeRequestMeta);
return wrapper.vm.$nextTick();
});
......@@ -200,12 +224,6 @@ describe('static_site_editor/pages/home', () => {
});
});
it('does not display submit changes error when an error does not exist', () => {
buildWrapper();
expect(findSubmitChangesError().exists()).toBe(false);
});
describe('when submitting changes succeeds', () => {
const newContent = `new ${content}`;
......@@ -216,8 +234,8 @@ describe('static_site_editor/pages/home', () => {
},
});
buildWrapper();
findEditArea().vm.$emit('submit', { content: newContent });
buildWrapper({ content: newContent, images });
findEditMetaModal().vm.$emit('primary', mergeRequestMeta);
return wrapper.vm.$nextTick();
});
......@@ -242,7 +260,7 @@ describe('static_site_editor/pages/home', () => {
project,
sourcePath,
username,
images: undefined,
images,
mergeRequestMeta,
},
},
......@@ -254,6 +272,12 @@ describe('static_site_editor/pages/home', () => {
});
});
it('does not display submit changes error when an error does not exist', () => {
buildWrapper();
expect(findSubmitChangesError().exists()).toBe(false);
});
it('tracks when editor is initialized on the mounted lifecycle hook', () => {
buildWrapper();
expect(trackingSpy).toHaveBeenCalledWith(
......
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