Commit 2b98addb authored by Axel García's avatar Axel García

Update epic form to use stacked form groups

This also updates tests and strings accordingly
parent ec86096e
<script> <script>
import { GlButton, GlDatepicker, GlForm, GlFormCheckbox, GlFormInput } from '@gitlab/ui'; import {
GlButton,
GlDatepicker,
GlForm,
GlFormCheckbox,
GlFormGroup,
GlFormInput,
} from '@gitlab/ui';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -14,6 +21,7 @@ export default { ...@@ -14,6 +21,7 @@ export default {
GlForm, GlForm,
GlFormCheckbox, GlFormCheckbox,
GlFormInput, GlFormInput,
GlFormGroup,
MarkdownField, MarkdownField,
LabelsSelectVue, LabelsSelectVue,
}, },
...@@ -81,16 +89,15 @@ export default { ...@@ -81,16 +89,15 @@ export default {
const { errors, epic } = data.createEpic; const { errors, epic } = data.createEpic;
if (errors?.length > 0) { if (errors?.length > 0) {
createFlash(errors[0]); createFlash(errors[0]);
this.loading = false;
return; return;
} }
visitUrl(epic.webUrl); visitUrl(epic.webUrl);
}) })
.catch(() => { .catch(() => {
createFlash(__('Unable to save epic. Please try again'));
})
.finally(() => {
this.loading = false; this.loading = false;
createFlash(__('Unable to save epic. Please try again'));
}); });
}, },
updateDueDate(val) { updateDueDate(val) {
...@@ -118,152 +125,130 @@ export default { ...@@ -118,152 +125,130 @@ export default {
<div> <div>
<h3 class="page-title">{{ __('New Epic') }}</h3> <h3 class="page-title">{{ __('New Epic') }}</h3>
<hr /> <hr />
<gl-form class="common-note-form"> <gl-form class="common-note-form" @submit="save">
<div class="form-group row"> <gl-form-group :label="__('Title')" label-for="epic-title">
<div class="col-form-label col-sm-2"> <gl-form-input
<label for="epic-title">{{ __('Title') }}</label> id="epic-title"
</div> v-model="title"
<div class="col-sm-10"> :placeholder="__('Enter a title for your epic')"
<gl-form-input autocomplete="off"
id="epic-title" autofocus
v-model="title" />
:placeholder="__('Title')" </gl-form-group>
autocomplete="off"
autofocus <gl-form-group :label="__('Description')" label-for="epic-description">
/> <markdown-field
</div> :markdown-preview-path="markdownPreviewPath"
</div> :markdown-docs-path="markdownDocsPath"
<div class="form-group row"> :can-suggest="false"
<div class="col-form-label col-sm-2"> :can-attach-file="true"
<label for="epic-description">{{ __('Description') }}</label> :enable-autocomplete="true"
</div> :add-spacing-classes="false"
<div class="col-sm-10"> :textarea-value="description"
<markdown-field :label="__('Description')"
:markdown-preview-path="markdownPreviewPath" class="md-area"
:markdown-docs-path="markdownDocsPath" >
:can-suggest="false" <template #textarea>
:can-attach-file="true" <textarea
:enable-autocomplete="true" id="epic-description"
:add-spacing-classes="false" v-model="description"
:textarea-value="description" class="note-textarea js-gfm-input js-autosize markdown-area"
:label="__('Description')" dir="auto"
class="md-area" data-supports-quick-actions="true"
:placeholder="__('Write a comment or drag your files here…')"
:aria-label="__('Description')"
>
</textarea>
</template>
</markdown-field>
</gl-form-group>
<gl-form-group :label="__('Confidentiality')" label-for="epic-confidentiality">
<gl-form-checkbox id="epic-confidentiality" v-model="confidential">{{
__(
'This epic and any containing child epics are confidential' +
' and should only be visible to team members' +
' with at least Reporter access.',
)
}}</gl-form-checkbox>
</gl-form-group>
<hr />
<gl-form-group :label="__('Labels')">
<div class="issuable-form-select-holder">
<labels-select-vue
:allow-label-edit="false"
:allow-label-create="true"
:allow-multiselect="true"
:allow-scoped-labels="false"
:selected-labels="labels"
:labels-fetch-path="
groupUrl('labels.json?include_ancestor_groups=true&only_group_labels=true')
"
:labels-manage-path="groupUrl('labels')"
:labels-filter-base-path="groupUrl('epics')"
:labels-list-title="__('Select label')"
:dropdown-button-text="__('Choose labels')"
variant="embedded"
class="block labels js-labels-block"
@updateSelectedLabels="handleUpdateSelectedLabels"
> >
<template #textarea> {{ __('None') }}
<textarea </labels-select-vue>
id="epic-description"
v-model="description"
class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto"
data-supports-quick-actions="true"
:placeholder="__('Write a comment or drag your files here…')"
:aria-label="__('Description')"
>
</textarea>
</template>
</markdown-field>
</div> </div>
</div> </gl-form-group>
<div class="form-group row gl-mt-7"> <gl-form-group
<div class="col-sm-10 offset-sm-2"> :label="__('Start date')"
<gl-form-checkbox v-model="confidential" data-testid="epic-confidentiality">{{ :description="__('Leave empty to inherit from milestone dates')"
__( >
'This epic and any containing child epics are confidential and should only be visible to team members with at least Reporter access.', <div class="gl-display-inline-block gl-mr-2">
) <gl-datepicker v-model="startDateFixed" data-testid="epic-start-date" />
}}</gl-form-checkbox>
</div>
</div>
<hr />
<div class="row">
<div class="col-lg-6">
<div class="form-group row">
<div class="col-form-label col-md-2 col-lg-4">
<label>{{ __('Labels') }}</label>
</div>
<div class="col-md-8 col-sm-10">
<div class="issuable-form-select-holder">
<labels-select-vue
:allow-label-edit="false"
:allow-label-create="true"
:allow-multiselect="true"
:allow-scoped-labels="false"
:selected-labels="labels"
:labels-fetch-path="
groupUrl('labels.json?include_ancestor_groups=true&only_group_labels=true')
"
:labels-manage-path="groupUrl('labels')"
:labels-filter-base-path="groupUrl('epics')"
:labels-list-title="__('Select label')"
:dropdown-button-text="__('Labels')"
variant="embedded"
class="block labels js-labels-block"
@updateSelectedLabels="handleUpdateSelectedLabels"
>{{ __('None') }}</labels-select-vue
>
</div>
</div>
</div>
</div> </div>
<div class="col-lg-6"> <gl-button
<div class="form-group row"> v-show="startDateFixed"
<div class="col-form-label col-md-2 col-lg-4"> variant="link"
<label>{{ __('Start date') }}</label> class="gl-white-space-nowrap"
</div> data-testid="clear-start-date"
<div class="col-md-8 col-sm-10"> @click="updateStartDate(null)"
<div class="issuable-form-select-holder gl-mr-2"> >
<gl-datepicker v-model="startDateFixed" data-testid="epic-start-date" /> {{ __('Clear start date') }}
</div> </gl-button>
<a </gl-form-group>
v-show="startDateFixed" <gl-form-group
class="gl-white-space-nowrap" :label="__('Due date')"
data-testid="clear-start-date" :description="__('Leave empty to inherit from milestone dates')"
href="#" >
@click="updateStartDate(null)" <div class="gl-display-inline-block gl-mr-2">
>{{ __('Clear start date') }}</a <gl-datepicker v-model="dueDateFixed" data-testid="epic-due-date" />
>
<span class="block gl-line-height-normal gl-mt-3 gl-text-gray-500">{{
__('Leave empty to inherit from milestone dates')
}}</span>
</div>
</div>
<div class="form-group row">
<div class="col-form-label col-md-2 col-lg-4">
<label>{{ __('Due Date') }}</label>
</div>
<div class="col-md-8 col-sm-10">
<div class="issuable-form-select-holder gl-mr-2">
<gl-datepicker v-model="dueDateFixed" data-testid="epic-due-date" />
</div>
<a
v-show="dueDateFixed"
class="gl-white-space-nowrap"
data-testid="clear-due-date"
href="#"
@click="updateDueDate(null)"
>{{ __('Clear due date') }}</a
>
<span class="block gl-line-height-normal gl-mt-3 gl-text-gray-500">{{
__('Leave empty to inherit from milestone dates')
}}</span>
</div>
</div>
</div> </div>
<gl-button
v-show="dueDateFixed"
variant="link"
class="gl-white-space-nowrap"
data-testid="clear-due-date"
@click="updateDueDate(null)"
>
{{ __('Clear due date') }}
</gl-button>
</gl-form-group>
<div class="footer-block row-content-block gl-display-flex">
<gl-button
type="submit"
variant="success"
:loading="loading"
:disabled="!title"
data-testid="save-epic"
>
{{ __('Create epic') }}
</gl-button>
<gl-button
type="button"
class="gl-ml-auto"
data-testid="cancel-epic"
:href="groupEpicsPath"
>
{{ __('Cancel') }}
</gl-button>
</div> </div>
</gl-form> </gl-form>
<div class="footer-block row-content-block gl-display-flex">
<gl-button
:loading="loading"
data-testid="save-epic"
variant="success"
:disabled="!title"
@click="save"
>
{{ __('Create epic') }}
</gl-button>
<gl-button class="gl-ml-auto" data-testid="cancel-epic" :href="groupEpicsPath">{{
__('Cancel')
}}</gl-button>
</div>
</div> </div>
</template> </template>
...@@ -97,6 +97,10 @@ ...@@ -97,6 +97,10 @@
} }
} }
.issuable-form-select-holder {
width: $gl-dropdown-width;
}
.labels-select-wrapper.is-embedded { .labels-select-wrapper.is-embedded {
.labels-select-dropdown-button { .labels-select-dropdown-button {
@include gl-bg-white; @include gl-bg-white;
...@@ -130,6 +134,7 @@ ...@@ -130,6 +134,7 @@
bottom: 100%; bottom: 100%;
width: 300px !important; width: 300px !important;
max-height: none;
margin-bottom: $gl-spacing-scale-6 !important; margin-bottom: $gl-spacing-scale-6 !important;
a:not(.btn) { a:not(.btn) {
...@@ -137,6 +142,11 @@ ...@@ -137,6 +142,11 @@
} }
} }
.dropdown-title {
padding-top: $gl-spacing-scale-2 !important;
padding-bottom: $gl-spacing-scale-4 !important;
}
.dropdown-footer .list-unstyled { .dropdown-footer .list-unstyled {
@include gl-m-0; @include gl-m-0;
} }
......
...@@ -41,7 +41,7 @@ describe('ee/epic/components/epic_form.vue', () => { ...@@ -41,7 +41,7 @@ describe('ee/epic/components/epic_form.vue', () => {
const findTitle = () => wrapper.find('#epic-title'); const findTitle = () => wrapper.find('#epic-title');
const findDescription = () => wrapper.find('#epic-description'); const findDescription = () => wrapper.find('#epic-description');
const findLabels = () => wrapper.find(LabelsSelectVue); const findLabels = () => wrapper.find(LabelsSelectVue);
const findConfidentialityCheck = () => wrapper.find('[data-testid="epic-confidentiality"]'); const findConfidentialityCheck = () => wrapper.find('#epic-confidentiality');
const findStartDate = () => wrapper.find('[data-testid="epic-start-date"]'); const findStartDate = () => wrapper.find('[data-testid="epic-start-date"]');
const findStartDateReset = () => wrapper.find('[data-testid="clear-start-date"]'); const findStartDateReset = () => wrapper.find('[data-testid="clear-start-date"]');
const findDueDate = () => wrapper.find('[data-testid="epic-due-date"]'); const findDueDate = () => wrapper.find('[data-testid="epic-due-date"]');
...@@ -75,14 +75,16 @@ describe('ee/epic/components/epic_form.vue', () => { ...@@ -75,14 +75,16 @@ describe('ee/epic/components/epic_form.vue', () => {
expect(wrapper.vm[field]).toBeTruthy(); expect(wrapper.vm[field]).toBeTruthy();
findResetter().trigger('click'); findResetter().vm.$emit('click');
expect(wrapper.vm[field]).toBe(null); return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm[field]).toBe(null);
});
}); });
}); });
describe('save', () => { describe('save', () => {
it('submits successfully if form data is provided', () => { it('submits successfully if form data is provided', async () => {
createWrapper(); createWrapper();
const addLabelIds = [1]; const addLabelIds = [1];
...@@ -101,7 +103,7 @@ describe('ee/epic/components/epic_form.vue', () => { ...@@ -101,7 +103,7 @@ describe('ee/epic/components/epic_form.vue', () => {
findStartDate().vm.$emit('input', startDateFixed); findStartDate().vm.$emit('input', startDateFixed);
findDueDate().vm.$emit('input', dueDateFixed); findDueDate().vm.$emit('input', dueDateFixed);
findSaveButton().vm.$emit('click'); wrapper.vm.save();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createEpic, mutation: createEpic,
...@@ -122,18 +124,18 @@ describe('ee/epic/components/epic_form.vue', () => { ...@@ -122,18 +124,18 @@ describe('ee/epic/components/epic_form.vue', () => {
}); });
it.each` it.each`
status | result status | result | loading
${'succeeds'} | ${TEST_NEW_EPIC} ${'succeeds'} | ${TEST_NEW_EPIC} | ${true}
${'fails'} | ${TEST_FAILED} ${'fails'} | ${TEST_FAILED} | ${false}
`('resets loading indicator when $status', ({ result }) => { `('resets loading indicator when $status', ({ result, loading }) => {
createWrapper({ mutationResult: result }); createWrapper({ mutationResult: result });
const savePromise = wrapper.vm.save(); const savePromise = wrapper.vm.save();
expect(wrapper.vm.loading).toBeTruthy(); expect(wrapper.vm.loading).toBe(true);
return savePromise.then(() => { return savePromise.then(() => {
expect(findSaveButton().props('loading')).toBe(false); expect(findSaveButton().props('loading')).toBe(loading);
}); });
}); });
}); });
......
...@@ -4598,6 +4598,9 @@ msgstr "" ...@@ -4598,6 +4598,9 @@ msgstr ""
msgid "Choose file…" msgid "Choose file…"
msgstr "" msgstr ""
msgid "Choose labels"
msgstr ""
msgid "Choose the top-level group for your repository imports." msgid "Choose the top-level group for your repository imports."
msgstr "" msgstr ""
...@@ -8868,6 +8871,9 @@ msgstr "" ...@@ -8868,6 +8871,9 @@ msgstr ""
msgid "Enter a number" msgid "Enter a number"
msgstr "" msgstr ""
msgid "Enter a title for your epic"
msgstr ""
msgid "Enter a whole number between 0 and 100" msgid "Enter a whole number between 0 and 100"
msgstr "" msgstr ""
...@@ -20966,6 +20972,9 @@ msgstr "" ...@@ -20966,6 +20972,9 @@ msgstr ""
msgid "Select health status" msgid "Select health status"
msgstr "" msgstr ""
msgid "Select label"
msgstr ""
msgid "Select labels" msgid "Select labels"
msgstr "" msgstr ""
......
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