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>
import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import { s__, __ } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
......@@ -21,7 +22,6 @@ export default {
return {
contacts: [],
error: false,
errorMessages: [],
};
},
apollo: {
......@@ -49,6 +49,9 @@ export default {
showNewForm() {
return this.$route.path.startsWith('/new');
},
canCreateNew() {
return parseBoolean(this.canAdminCrmContact);
},
},
methods: {
extractContacts(data) {
......@@ -60,17 +63,11 @@ export default {
this.$router.push({ path: '/new' });
},
hideNewForm() {
hideNewForm(success) {
if (success) this.$toast.show(s__('Crm|Contact has been added'));
this.$router.replace({ path: '/' });
},
handleError(errors) {
this.error = true;
if (errors) this.errorMessages = errors;
},
dismissError() {
this.error = false;
this.errorMessages = [];
},
getIssuesPath(path, value) {
return `${path}?scope=all&state=opened&crm_contact_id=${value}`;
},
......@@ -108,9 +105,8 @@ export default {
<template>
<div>
<gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="dismissError">
<div v-if="errorMessages.length == 0">{{ $options.i18n.errorText }}</div>
<div v-for="(message, index) in errorMessages" :key="index">{{ message }}</div>
<gl-alert v-if="error" variant="danger" class="gl-mt-6" @dismiss="error = false">
{{ $options.i18n.errorText }}
</gl-alert>
<div
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 {
</h2>
<div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end">
<gl-button
v-if="canAdminCrmContact"
v-if="canCreateNew"
variant="confirm"
data-testid="new-contact-button"
@click="displayNewForm"
......@@ -129,7 +125,7 @@ export default {
</gl-button>
</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-table
v-else
......
<script>
import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { produce } from 'immer';
import { __, s__ } from '~/locale';
import { convertToGraphQLId } from '~/graphql_shared/utils';
......@@ -9,11 +9,19 @@ import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
export default {
components: {
GlAlert,
GlButton,
GlDrawer,
GlFormGroup,
GlFormInput,
},
inject: ['groupFullPath', 'groupId'],
props: {
drawerOpen: {
type: Boolean,
required: true,
},
},
data() {
return {
firstName: '',
......@@ -22,6 +30,7 @@ export default {
email: '',
description: '',
submitting: false,
errorMessages: [],
};
},
computed: {
......@@ -48,24 +57,21 @@ export default {
update: this.updateCache,
})
.then(({ data }) => {
if (data.customerRelationsContactCreate.errors.length === 0) this.close();
if (data.customerRelationsContactCreate.errors.length === 0) this.close(true);
this.submitting = false;
})
.catch(() => {
this.error();
this.errorMessages = [__('Something went wrong. Please try again.')];
this.submitting = false;
});
},
close() {
this.$emit('close');
},
error(errors = null) {
this.$emit('error', errors);
close(success) {
this.$emit('close', success);
},
updateCache(store, { data: { customerRelationsContactCreate } }) {
if (customerRelationsContactCreate.errors.length > 0) {
this.error(customerRelationsContactCreate.errors);
this.errorMessages = customerRelationsContactCreate.errors;
return;
}
......@@ -90,6 +96,15 @@ export default {
data,
});
},
getDrawerHeaderHeight() {
const wrapperEl = document.querySelector('.content-wrapper');
if (wrapperEl) {
return `${wrapperEl.offsetTop}px`;
}
return '';
},
},
i18n: {
buttonLabel: s__('Crm|Create new contact'),
......@@ -99,12 +114,28 @@ export default {
email: s__('Crm|Email'),
phone: s__('Crm|Phone number (optional)'),
description: s__('Crm|Description (optional)'),
title: s__('Crm|New Contact'),
},
};
</script>
<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">
<gl-form-group :label="$options.i18n.firstName" label-for="contact-first-name">
<gl-form-input id="contact-first-name" v-model="firstName" />
......@@ -121,7 +152,10 @@ export default {
<gl-form-group :label="$options.i18n.description" label-for="contact-description">
<gl-form-input id="contact-description" v-model="description" />
</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
variant="confirm"
:disabled="invalid"
......@@ -130,11 +164,7 @@ export default {
type="submit"
>{{ $options.i18n.buttonLabel }}</gl-button
>
<gl-button data-testid="cancel-button" @click="close">
{{ $options.i18n.cancel }}
</gl-button>
</div>
</span>
</form>
<div class="gl-pb-5"></div>
</div>
</gl-drawer>
</template>
import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import VueRouter from 'vue-router';
......@@ -6,6 +7,7 @@ import CrmContactsRoot from './components/contacts_root.vue';
Vue.use(VueApollo);
Vue.use(VueRouter);
Vue.use(GlToast);
export default () => {
const el = document.getElementById('js-crm-contacts-app');
......
......@@ -174,3 +174,30 @@ body {
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 {
:open="drawerOpen"
:header-height="getDrawerHeaderHeight()"
:class="{ 'zen-mode gl-absolute': zenModeEnabled }"
class="requirement-form-drawer"
class="requirement-form-drawer gl-drawer-responsive"
@close="handleDrawerClose"
>
<template #title>
......
......@@ -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 {
......
......@@ -10202,6 +10202,9 @@ msgstr ""
msgid "Critical vulnerabilities present"
msgstr ""
msgid "Crm|Contact has been added"
msgstr ""
msgid "Crm|Create new contact"
msgstr ""
......@@ -10220,6 +10223,9 @@ msgstr ""
msgid "Crm|Last name"
msgstr ""
msgid "Crm|New Contact"
msgstr ""
msgid "Crm|New contact"
msgstr ""
......
......@@ -122,16 +122,6 @@ describe('Customer relations contacts root app', () => {
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', () => {
......
import { GlAlert } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
......@@ -21,6 +22,7 @@ describe('Customer relations contacts root app', () => {
const findCreateNewContactButton = () => wrapper.findByTestId('create-new-contact-button');
const findCancelButton = () => wrapper.findByTestId('cancel-button');
const findForm = () => wrapper.find('form');
const findError = () => wrapper.findComponent(GlAlert);
const mountComponent = ({ mountFunction = shallowMountExtended } = {}) => {
fakeApollo = createMockApollo([[createContactMutation, queryHandler]]);
......@@ -32,6 +34,7 @@ describe('Customer relations contacts root app', () => {
wrapper = mountFunction(NewContactForm, {
provide: { groupId: 26, groupFullPath: 'flightjs' },
apolloProvider: fakeApollo,
propsData: { drawerOpen: true },
});
};
......@@ -83,26 +86,25 @@ describe('Customer relations contacts root app', () => {
});
describe('when query fails', () => {
it('should emit error on reject', async () => {
it('should show error on reject', async () => {
queryHandler = jest.fn().mockRejectedValue('ERROR');
mountComponent();
findForm().trigger('submit');
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);
mountComponent();
findForm().trigger('submit');
await waitForPromises();
expect(wrapper.emitted().error[0][0]).toEqual(
createContactMutationErrorResponse.data.customerRelationsContactCreate.errors,
);
expect(findError().exists()).toBe(true);
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