Commit 4e6b1a50 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '2256-convert-new-contact-to-gl-drawer' into 'master'

Migrate new crm contact form to gl-drawer

See merge request gitlab-org/gitlab!75958
parents 2ff3f4fb 596d2758
<script> <script>
import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui'; import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
...@@ -21,7 +22,6 @@ export default { ...@@ -21,7 +22,6 @@ export default {
return { return {
contacts: [], contacts: [],
error: false, error: false,
errorMessages: [],
}; };
}, },
apollo: { apollo: {
...@@ -49,6 +49,9 @@ export default { ...@@ -49,6 +49,9 @@ export default {
showNewForm() { showNewForm() {
return this.$route.path.startsWith('/new'); return this.$route.path.startsWith('/new');
}, },
canCreateNew() {
return parseBoolean(this.canAdminCrmContact);
},
}, },
methods: { methods: {
extractContacts(data) { extractContacts(data) {
...@@ -60,17 +63,11 @@ export default { ...@@ -60,17 +63,11 @@ export default {
this.$router.push({ path: '/new' }); this.$router.push({ path: '/new' });
}, },
hideNewForm() { hideNewForm(success) {
if (success) this.$toast.show(s__('Crm|Contact has been added'));
this.$router.replace({ path: '/' }); this.$router.replace({ path: '/' });
}, },
handleError(errors) {
this.error = true;
if (errors) this.errorMessages = errors;
},
dismissError() {
this.error = false;
this.errorMessages = [];
},
getIssuesPath(path, value) { getIssuesPath(path, value) {
return `${path}?scope=all&state=opened&crm_contact_id=${value}`; return `${path}?scope=all&state=opened&crm_contact_id=${value}`;
}, },
...@@ -108,9 +105,8 @@ export default { ...@@ -108,9 +105,8 @@ export default {
<template> <template>
<div> <div>
<gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="dismissError"> <gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="error = false">
<div v-if="errorMessages.length == 0">{{ $options.i18n.errorText }}</div> {{ $options.i18n.errorText }}
<div v-for="(message, index) in errorMessages" :key="index">{{ message }}</div>
</gl-alert> </gl-alert>
<div <div
class="gl-display-flex gl-align-items-baseline gl-flex-direction-row gl-justify-content-space-between gl-mt-6" class="gl-display-flex gl-align-items-baseline gl-flex-direction-row gl-justify-content-space-between gl-mt-6"
...@@ -120,7 +116,7 @@ export default { ...@@ -120,7 +116,7 @@ export default {
</h2> </h2>
<div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end"> <div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end">
<gl-button <gl-button
v-if="canAdminCrmContact" v-if="canCreateNew"
variant="confirm" variant="confirm"
data-testid="new-contact-button" data-testid="new-contact-button"
@click="displayNewForm" @click="displayNewForm"
...@@ -129,7 +125,7 @@ export default { ...@@ -129,7 +125,7 @@ export default {
</gl-button> </gl-button>
</div> </div>
</div> </div>
<new-contact-form v-if="showNewForm" @close="hideNewForm" @error="handleError" /> <new-contact-form v-if="showNewForm" :drawer-open="showNewForm" @close="hideNewForm" />
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" /> <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" />
<gl-table <gl-table
v-else v-else
......
<script> <script>
import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui'; import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { produce } from 'immer'; import { produce } from 'immer';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
...@@ -9,11 +9,19 @@ import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; ...@@ -9,11 +9,19 @@ import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
export default { export default {
components: { components: {
GlAlert,
GlButton, GlButton,
GlDrawer,
GlFormGroup, GlFormGroup,
GlFormInput, GlFormInput,
}, },
inject: ['groupFullPath', 'groupId'], inject: ['groupFullPath', 'groupId'],
props: {
drawerOpen: {
type: Boolean,
required: true,
},
},
data() { data() {
return { return {
firstName: '', firstName: '',
...@@ -22,6 +30,7 @@ export default { ...@@ -22,6 +30,7 @@ export default {
email: '', email: '',
description: '', description: '',
submitting: false, submitting: false,
errorMessages: [],
}; };
}, },
computed: { computed: {
...@@ -48,24 +57,21 @@ export default { ...@@ -48,24 +57,21 @@ export default {
update: this.updateCache, update: this.updateCache,
}) })
.then(({ data }) => { .then(({ data }) => {
if (data.customerRelationsContactCreate.errors.length === 0) this.close(); if (data.customerRelationsContactCreate.errors.length === 0) this.close(true);
this.submitting = false; this.submitting = false;
}) })
.catch(() => { .catch(() => {
this.error(); this.errorMessages = [__('Something went wrong. Please try again.')];
this.submitting = false; this.submitting = false;
}); });
}, },
close() { close(success) {
this.$emit('close'); this.$emit('close', success);
},
error(errors = null) {
this.$emit('error', errors);
}, },
updateCache(store, { data: { customerRelationsContactCreate } }) { updateCache(store, { data: { customerRelationsContactCreate } }) {
if (customerRelationsContactCreate.errors.length > 0) { if (customerRelationsContactCreate.errors.length > 0) {
this.error(customerRelationsContactCreate.errors); this.errorMessages = customerRelationsContactCreate.errors;
return; return;
} }
...@@ -90,6 +96,15 @@ export default { ...@@ -90,6 +96,15 @@ export default {
data, data,
}); });
}, },
getDrawerHeaderHeight() {
const wrapperEl = document.querySelector('.content-wrapper');
if (wrapperEl) {
return `${wrapperEl.offsetTop}px`;
}
return '';
},
}, },
i18n: { i18n: {
buttonLabel: s__('Crm|Create new contact'), buttonLabel: s__('Crm|Create new contact'),
...@@ -99,12 +114,28 @@ export default { ...@@ -99,12 +114,28 @@ export default {
email: s__('Crm|Email'), email: s__('Crm|Email'),
phone: s__('Crm|Phone number (optional)'), phone: s__('Crm|Phone number (optional)'),
description: s__('Crm|Description (optional)'), description: s__('Crm|Description (optional)'),
title: s__('Crm|New Contact'),
}, },
}; };
</script> </script>
<template> <template>
<div class="col-md-4"> <gl-drawer
class="gl-drawer-responsive"
:open="drawerOpen"
:header-height="getDrawerHeaderHeight()"
@close="close(false)"
>
<template #title>
<h4>{{ $options.i18n.title }}</h4>
</template>
<gl-alert v-if="errorMessages.length" variant="danger" @dismiss="errorMessages = []">
<ul class="gl-mb-0! gl-ml-5">
<li v-for="error in errorMessages" :key="error">
{{ error }}
</li>
</ul>
</gl-alert>
<form @submit.prevent="save"> <form @submit.prevent="save">
<gl-form-group :label="$options.i18n.firstName" label-for="contact-first-name"> <gl-form-group :label="$options.i18n.firstName" label-for="contact-first-name">
<gl-form-input id="contact-first-name" v-model="firstName" /> <gl-form-input id="contact-first-name" v-model="firstName" />
...@@ -121,7 +152,10 @@ export default { ...@@ -121,7 +152,10 @@ export default {
<gl-form-group :label="$options.i18n.description" label-for="contact-description"> <gl-form-group :label="$options.i18n.description" label-for="contact-description">
<gl-form-input id="contact-description" v-model="description" /> <gl-form-input id="contact-description" v-model="description" />
</gl-form-group> </gl-form-group>
<div class="form-actions"> <span class="gl-float-right">
<gl-button data-testid="cancel-button" @click="close(false)">
{{ $options.i18n.cancel }}
</gl-button>
<gl-button <gl-button
variant="confirm" variant="confirm"
:disabled="invalid" :disabled="invalid"
...@@ -130,11 +164,7 @@ export default { ...@@ -130,11 +164,7 @@ export default {
type="submit" type="submit"
>{{ $options.i18n.buttonLabel }}</gl-button >{{ $options.i18n.buttonLabel }}</gl-button
> >
<gl-button data-testid="cancel-button" @click="close"> </span>
{{ $options.i18n.cancel }}
</gl-button>
</div>
</form> </form>
<div class="gl-pb-5"></div> </gl-drawer>
</div>
</template> </template>
import { GlToast } from '@gitlab/ui';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
...@@ -6,6 +7,7 @@ import CrmContactsRoot from './components/contacts_root.vue'; ...@@ -6,6 +7,7 @@ import CrmContactsRoot from './components/contacts_root.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
Vue.use(VueRouter); Vue.use(VueRouter);
Vue.use(GlToast);
export default () => { export default () => {
const el = document.getElementById('js-crm-contacts-app'); const el = document.getElementById('js-crm-contacts-app');
......
...@@ -174,3 +174,30 @@ body { ...@@ -174,3 +174,30 @@ body {
min-height: 0; min-height: 0;
} }
} }
.gl-drawer-responsive {
// Both width & min-width
// are defined as per Pajamas
// See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44902#note_429056182
width: 28%;
min-width: 400px;
padding-left: $gl-padding;
padding-right: $gl-padding;
box-shadow: none;
background-color: $gray-10;
border-left: 1px solid $gray-100;
@include media-breakpoint-down(sm) {
min-width: unset;
width: 100%;
}
// These overrides should not happen here,
// we should ideally have support for custom
// header and body classes in `GlDrawer`.
.gl-drawer-header,
.gl-drawer-body > * {
padding-left: 0;
padding-right: 0;
}
}
...@@ -230,7 +230,7 @@ export default { ...@@ -230,7 +230,7 @@ export default {
:open="drawerOpen" :open="drawerOpen"
:header-height="getDrawerHeaderHeight()" :header-height="getDrawerHeaderHeight()"
:class="{ 'zen-mode gl-absolute': zenModeEnabled }" :class="{ 'zen-mode gl-absolute': zenModeEnabled }"
class="requirement-form-drawer" class="requirement-form-drawer gl-drawer-responsive"
@close="handleDrawerClose" @close="handleDrawerClose"
> >
<template #title> <template #title>
......
...@@ -87,33 +87,6 @@ ...@@ -87,33 +87,6 @@
} }
} }
} }
.gl-drawer {
// Both width & min-width
// are defined as per Pajamas
// See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44902#note_429056182
width: 28%;
min-width: 400px;
padding-left: $gl-padding;
padding-right: $gl-padding;
box-shadow: none;
background-color: $gray-10;
border-left: 1px solid $gray-100;
@include media-breakpoint-down(sm) {
min-width: unset;
width: 100%;
}
// These overrides should not happen here,
// we should ideally have support for custom
// header and body classes in `GlDrawer`.
.gl-drawer-header,
.gl-drawer-body > * {
padding-left: 0;
padding-right: 0;
}
}
} }
.requirement-status-tooltip { .requirement-status-tooltip {
......
...@@ -10202,6 +10202,9 @@ msgstr "" ...@@ -10202,6 +10202,9 @@ msgstr ""
msgid "Critical vulnerabilities present" msgid "Critical vulnerabilities present"
msgstr "" msgstr ""
msgid "Crm|Contact has been added"
msgstr ""
msgid "Crm|Create new contact" msgid "Crm|Create new contact"
msgstr "" msgstr ""
...@@ -10220,6 +10223,9 @@ msgstr "" ...@@ -10220,6 +10223,9 @@ msgstr ""
msgid "Crm|Last name" msgid "Crm|Last name"
msgstr "" msgstr ""
msgid "Crm|New Contact"
msgstr ""
msgid "Crm|New contact" msgid "Crm|New contact"
msgstr "" msgstr ""
......
...@@ -122,16 +122,6 @@ describe('Customer relations contacts root app', () => { ...@@ -122,16 +122,6 @@ describe('Customer relations contacts root app', () => {
expect(findError().exists()).toBe(true); expect(findError().exists()).toBe(true);
}); });
it('should exist when new contact form emits error', async () => {
router.replace({ path: '/new' });
mountComponent();
findNewContactForm().vm.$emit('error');
await waitForPromises();
expect(findError().exists()).toBe(true);
});
}); });
describe('on successful load', () => { describe('on successful load', () => {
......
import { GlAlert } from '@gitlab/ui';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
...@@ -21,6 +22,7 @@ describe('Customer relations contacts root app', () => { ...@@ -21,6 +22,7 @@ describe('Customer relations contacts root app', () => {
const findCreateNewContactButton = () => wrapper.findByTestId('create-new-contact-button'); const findCreateNewContactButton = () => wrapper.findByTestId('create-new-contact-button');
const findCancelButton = () => wrapper.findByTestId('cancel-button'); const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findForm = () => wrapper.find('form'); const findForm = () => wrapper.find('form');
const findError = () => wrapper.findComponent(GlAlert);
const mountComponent = ({ mountFunction = shallowMountExtended } = {}) => { const mountComponent = ({ mountFunction = shallowMountExtended } = {}) => {
fakeApollo = createMockApollo([[createContactMutation, queryHandler]]); fakeApollo = createMockApollo([[createContactMutation, queryHandler]]);
...@@ -32,6 +34,7 @@ describe('Customer relations contacts root app', () => { ...@@ -32,6 +34,7 @@ describe('Customer relations contacts root app', () => {
wrapper = mountFunction(NewContactForm, { wrapper = mountFunction(NewContactForm, {
provide: { groupId: 26, groupFullPath: 'flightjs' }, provide: { groupId: 26, groupFullPath: 'flightjs' },
apolloProvider: fakeApollo, apolloProvider: fakeApollo,
propsData: { drawerOpen: true },
}); });
}; };
...@@ -83,26 +86,25 @@ describe('Customer relations contacts root app', () => { ...@@ -83,26 +86,25 @@ describe('Customer relations contacts root app', () => {
}); });
describe('when query fails', () => { describe('when query fails', () => {
it('should emit error on reject', async () => { it('should show error on reject', async () => {
queryHandler = jest.fn().mockRejectedValue('ERROR'); queryHandler = jest.fn().mockRejectedValue('ERROR');
mountComponent(); mountComponent();
findForm().trigger('submit'); findForm().trigger('submit');
await waitForPromises(); await waitForPromises();
expect(wrapper.emitted().error).toBeTruthy(); expect(findError().exists()).toBe(true);
}); });
it('should emit error on error response', async () => { it('should show error on error response', async () => {
queryHandler = jest.fn().mockResolvedValue(createContactMutationErrorResponse); queryHandler = jest.fn().mockResolvedValue(createContactMutationErrorResponse);
mountComponent(); mountComponent();
findForm().trigger('submit'); findForm().trigger('submit');
await waitForPromises(); await waitForPromises();
expect(wrapper.emitted().error[0][0]).toEqual( expect(findError().exists()).toBe(true);
createContactMutationErrorResponse.data.customerRelationsContactCreate.errors, expect(findError().text()).toBe('Phone is invalid.');
);
}); });
}); });
}); });
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