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>
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 createFlash from '~/flash';
import { __ } from '~/locale';
......@@ -14,6 +21,7 @@ export default {
GlForm,
GlFormCheckbox,
GlFormInput,
GlFormGroup,
MarkdownField,
LabelsSelectVue,
},
......@@ -81,16 +89,15 @@ export default {
const { errors, epic } = data.createEpic;
if (errors?.length > 0) {
createFlash(errors[0]);
this.loading = false;
return;
}
visitUrl(epic.webUrl);
})
.catch(() => {
createFlash(__('Unable to save epic. Please try again'));
})
.finally(() => {
this.loading = false;
createFlash(__('Unable to save epic. Please try again'));
});
},
updateDueDate(val) {
......@@ -118,152 +125,130 @@ export default {
<div>
<h3 class="page-title">{{ __('New Epic') }}</h3>
<hr />
<gl-form class="common-note-form">
<div class="form-group row">
<div class="col-form-label col-sm-2">
<label for="epic-title">{{ __('Title') }}</label>
</div>
<div class="col-sm-10">
<gl-form-input
id="epic-title"
v-model="title"
:placeholder="__('Title')"
autocomplete="off"
autofocus
/>
</div>
</div>
<div class="form-group row">
<div class="col-form-label col-sm-2">
<label for="epic-description">{{ __('Description') }}</label>
</div>
<div class="col-sm-10">
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:can-suggest="false"
:can-attach-file="true"
:enable-autocomplete="true"
:add-spacing-classes="false"
:textarea-value="description"
:label="__('Description')"
class="md-area"
<gl-form class="common-note-form" @submit="save">
<gl-form-group :label="__('Title')" label-for="epic-title">
<gl-form-input
id="epic-title"
v-model="title"
:placeholder="__('Enter a title for your epic')"
autocomplete="off"
autofocus
/>
</gl-form-group>
<gl-form-group :label="__('Description')" label-for="epic-description">
<markdown-field
:markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"
:can-suggest="false"
:can-attach-file="true"
:enable-autocomplete="true"
:add-spacing-classes="false"
:textarea-value="description"
:label="__('Description')"
class="md-area"
>
<template #textarea>
<textarea
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>
</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>
<textarea
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>
{{ __('None') }}
</labels-select-vue>
</div>
</div>
<div class="form-group row gl-mt-7">
<div class="col-sm-10 offset-sm-2">
<gl-form-checkbox v-model="confidential" data-testid="epic-confidentiality">{{
__(
'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>
</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>
</gl-form-group>
<gl-form-group
:label="__('Start date')"
:description="__('Leave empty to inherit from milestone dates')"
>
<div class="gl-display-inline-block gl-mr-2">
<gl-datepicker v-model="startDateFixed" data-testid="epic-start-date" />
</div>
<div class="col-lg-6">
<div class="form-group row">
<div class="col-form-label col-md-2 col-lg-4">
<label>{{ __('Start date') }}</label>
</div>
<div class="col-md-8 col-sm-10">
<div class="issuable-form-select-holder gl-mr-2">
<gl-datepicker v-model="startDateFixed" data-testid="epic-start-date" />
</div>
<a
v-show="startDateFixed"
class="gl-white-space-nowrap"
data-testid="clear-start-date"
href="#"
@click="updateStartDate(null)"
>{{ __('Clear start 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 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>
<gl-button
v-show="startDateFixed"
variant="link"
class="gl-white-space-nowrap"
data-testid="clear-start-date"
@click="updateStartDate(null)"
>
{{ __('Clear start date') }}
</gl-button>
</gl-form-group>
<gl-form-group
:label="__('Due date')"
:description="__('Leave empty to inherit from milestone dates')"
>
<div class="gl-display-inline-block gl-mr-2">
<gl-datepicker v-model="dueDateFixed" data-testid="epic-due-date" />
</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>
</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>
</template>
......@@ -97,6 +97,10 @@
}
}
.issuable-form-select-holder {
width: $gl-dropdown-width;
}
.labels-select-wrapper.is-embedded {
.labels-select-dropdown-button {
@include gl-bg-white;
......@@ -130,6 +134,7 @@
bottom: 100%;
width: 300px !important;
max-height: none;
margin-bottom: $gl-spacing-scale-6 !important;
a:not(.btn) {
......@@ -137,6 +142,11 @@
}
}
.dropdown-title {
padding-top: $gl-spacing-scale-2 !important;
padding-bottom: $gl-spacing-scale-4 !important;
}
.dropdown-footer .list-unstyled {
@include gl-m-0;
}
......
......@@ -41,7 +41,7 @@ describe('ee/epic/components/epic_form.vue', () => {
const findTitle = () => wrapper.find('#epic-title');
const findDescription = () => wrapper.find('#epic-description');
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 findStartDateReset = () => wrapper.find('[data-testid="clear-start-date"]');
const findDueDate = () => wrapper.find('[data-testid="epic-due-date"]');
......@@ -75,14 +75,16 @@ describe('ee/epic/components/epic_form.vue', () => {
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', () => {
it('submits successfully if form data is provided', () => {
it('submits successfully if form data is provided', async () => {
createWrapper();
const addLabelIds = [1];
......@@ -101,7 +103,7 @@ describe('ee/epic/components/epic_form.vue', () => {
findStartDate().vm.$emit('input', startDateFixed);
findDueDate().vm.$emit('input', dueDateFixed);
findSaveButton().vm.$emit('click');
wrapper.vm.save();
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createEpic,
......@@ -122,18 +124,18 @@ describe('ee/epic/components/epic_form.vue', () => {
});
it.each`
status | result
${'succeeds'} | ${TEST_NEW_EPIC}
${'fails'} | ${TEST_FAILED}
`('resets loading indicator when $status', ({ result }) => {
status | result | loading
${'succeeds'} | ${TEST_NEW_EPIC} | ${true}
${'fails'} | ${TEST_FAILED} | ${false}
`('resets loading indicator when $status', ({ result, loading }) => {
createWrapper({ mutationResult: result });
const savePromise = wrapper.vm.save();
expect(wrapper.vm.loading).toBeTruthy();
expect(wrapper.vm.loading).toBe(true);
return savePromise.then(() => {
expect(findSaveButton().props('loading')).toBe(false);
expect(findSaveButton().props('loading')).toBe(loading);
});
});
});
......
......@@ -4598,6 +4598,9 @@ msgstr ""
msgid "Choose file…"
msgstr ""
msgid "Choose labels"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
......@@ -8868,6 +8871,9 @@ msgstr ""
msgid "Enter a number"
msgstr ""
msgid "Enter a title for your epic"
msgstr ""
msgid "Enter a whole number between 0 and 100"
msgstr ""
......@@ -20966,6 +20972,9 @@ msgstr ""
msgid "Select health status"
msgstr ""
msgid "Select label"
msgstr ""
msgid "Select labels"
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