Commit 22a90aa5 authored by Lee Tickett's avatar Lee Tickett

Add customer relations contacts viewer

We recently introduced the root component as a placeholder.
This MR now wires it up to display a list of crm contacts.

Changelog: added
parent 5adcf589
<script>
export default {};
import { GlLoadingIcon, GlTable } from '@gitlab/ui';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
import getGroupContactsQuery from './queries/get_group_contacts.query.graphql';
export default {
components: {
GlLoadingIcon,
GlTable,
},
inject: ['groupFullPath'],
data() {
return { contacts: [] };
},
apollo: {
contacts: {
query() {
return getGroupContactsQuery;
},
variables() {
return {
groupFullPath: this.groupFullPath,
};
},
update(data) {
return this.extractContacts(data);
},
error(error) {
createFlash({
message: __('Something went wrong. Please try again.'),
error,
captureError: true,
});
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.contacts.loading;
},
},
methods: {
extractContacts(data) {
const contacts = data?.group?.contacts?.nodes || [];
return contacts.slice().sort((a, b) => a.firstName.localeCompare(b.firstName));
},
},
fields: [
{ key: 'firstName', sortable: true },
{ key: 'lastName', sortable: true },
{ key: 'email', sortable: true },
{ key: 'phone', sortable: true },
{ key: 'description', sortable: true },
{
key: 'organization',
formatter: (organization) => {
return organization?.name;
},
sortable: true,
},
],
i18n: {
emptyText: s__('Crm|No contacts found'),
},
};
</script>
<template>
<div></div>
<div>
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" />
<gl-table
v-else
:items="contacts"
:fields="$options.fields"
:empty-text="$options.i18n.emptyText"
show-empty
/>
</div>
</template>
query contacts($groupFullPath: ID!) {
group(fullPath: $groupFullPath) {
__typename
id
contacts {
nodes {
__typename
id
firstName
lastName
email
phone
description
organization {
__typename
id
name
}
}
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import CrmContactsRoot from './components/contacts_root.vue';
Vue.use(VueApollo);
export default () => {
const el = document.getElementById('js-crm-contacts-app');
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
if (!el) {
return false;
}
return new Vue({
el,
apolloProvider,
provide: { groupFullPath: el.dataset.groupFullPath },
render(createElement) {
return createElement(CrmContactsRoot);
},
......
- breadcrumb_title _('Customer Relations Contacts')
- page_title _('Customer Relations Contacts')
#js-crm-contacts-app
#js-crm-contacts-app{ data: { group_full_path: @group.full_path } }
......@@ -10032,6 +10032,9 @@ msgstr ""
msgid "Critical vulnerabilities present"
msgstr ""
msgid "Crm|No contacts found"
msgstr ""
msgid "Cron Timezone"
msgstr ""
......
import { GlLoadingIcon } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ContactsRoot from '~/crm/components/contacts_root.vue';
import getGroupContactsQuery from '~/crm/components/queries/get_group_contacts.query.graphql';
import { getGroupContactsQueryResponse } from './mock_data';
jest.mock('~/flash');
describe('Customer relations contacts root app', () => {
Vue.use(VueApollo);
let wrapper;
let fakeApollo;
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findRowByName = (rowName) => wrapper.findAllByRole('row', { name: rowName });
const successQueryHandler = jest.fn().mockResolvedValue(getGroupContactsQueryResponse);
const mountComponent = ({
queryHandler = successQueryHandler,
mountFunction = shallowMountExtended,
} = {}) => {
fakeApollo = createMockApollo([[getGroupContactsQuery, queryHandler]]);
wrapper = mountFunction(ContactsRoot, {
provide: { groupFullPath: 'flightjs' },
apolloProvider: fakeApollo,
});
};
afterEach(() => {
wrapper.destroy();
fakeApollo = null;
});
it('should render loading spinner', () => {
mountComponent();
expect(findLoadingIcon().exists()).toBe(true);
});
it('should render error message on reject', async () => {
mountComponent({ queryHandler: jest.fn().mockRejectedValue('ERROR') });
await waitForPromises();
expect(createFlash).toHaveBeenCalled();
});
it('renders correct results', async () => {
mountComponent({ mountFunction: mountExtended });
await waitForPromises();
expect(findRowByName(/Marty/i)).toHaveLength(1);
expect(findRowByName(/George/i)).toHaveLength(1);
expect(findRowByName(/jd@gitlab.com/i)).toHaveLength(1);
});
});
export const getGroupContactsQueryResponse = {
data: {
group: {
__typename: 'Group',
id: 'gid://gitlab/Group/26',
contacts: {
nodes: [
{
__typename: 'CustomerRelationsContact',
id: 'gid://gitlab/CustomerRelations::Contact/12',
firstName: 'Marty',
lastName: 'McFly',
email: 'example@gitlab.com',
phone: null,
description: null,
organization: {
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/2',
name: 'Tech Giant Inc',
},
},
{
__typename: 'CustomerRelationsContact',
id: 'gid://gitlab/CustomerRelations::Contact/16',
firstName: 'Boy',
lastName: 'George',
email: null,
phone: null,
description: null,
organization: null,
},
{
__typename: 'CustomerRelationsContact',
id: 'gid://gitlab/CustomerRelations::Contact/13',
firstName: 'Jane',
lastName: 'Doe',
email: 'jd@gitlab.com',
phone: '+44 44 4444 4444',
description: 'Vice President',
organization: null,
},
],
__typename: 'CustomerRelationsContactConnection',
},
},
},
};
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