Commit 9db2c7fe authored by Lee Tickett's avatar Lee Tickett

Add customer relations organizations viewer

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

Changelog: added
parent 2b62b3ab
<script>
export default {};
import { GlLoadingIcon, GlTable } from '@gitlab/ui';
import createFlash from '~/flash';
import { s__, __ } from '~/locale';
import getGroupOrganizationsQuery from './queries/get_group_organizations.query.graphql';
export default {
components: {
GlLoadingIcon,
GlTable,
},
inject: ['groupFullPath'],
data() {
return { organizations: [] };
},
apollo: {
organizations: {
query() {
return getGroupOrganizationsQuery;
},
variables() {
return {
groupFullPath: this.groupFullPath,
};
},
update(data) {
return this.extractOrganizations(data);
},
error(error) {
createFlash({
message: __('Something went wrong. Please try again.'),
error,
captureError: true,
});
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.organizations.loading;
},
},
methods: {
extractOrganizations(data) {
const organizations = data?.group?.organizations?.nodes || [];
return organizations.slice().sort((a, b) => a.name.localeCompare(b.name));
},
},
fields: [
{ key: 'name', sortable: true },
{ key: 'defaultRate', sortable: true },
{ key: 'description', sortable: true },
],
i18n: {
emptyText: s__('Crm|No organizations found'),
},
};
</script>
<template>
<div></div>
<div>
<gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" />
<gl-table
v-else
:items="organizations"
:fields="$options.fields"
:empty-text="$options.i18n.emptyText"
show-empty
/>
</div>
</template>
query organizations($groupFullPath: ID!) {
group(fullPath: $groupFullPath) {
__typename
id
organizations {
nodes {
__typename
id
name
defaultRate
description
}
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import CrmOrganizationsRoot from './components/organizations_root.vue';
Vue.use(VueApollo);
export default () => {
const el = document.getElementById('js-crm-organizations-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(CrmOrganizationsRoot);
},
......
- breadcrumb_title _('Customer Relations Organizations')
- page_title _('Customer Relations Organizations')
#js-crm-organizations-app
#js-crm-organizations-app{ data: { group_full_path: @group.full_path } }
......@@ -10095,6 +10095,9 @@ msgstr ""
msgid "Crm|No contacts found"
msgstr ""
msgid "Crm|No organizations found"
msgstr ""
msgid "Cron Timezone"
msgstr ""
......
......@@ -45,3 +45,37 @@ export const getGroupContactsQueryResponse = {
},
},
};
export const getGroupOrganizationsQueryResponse = {
data: {
group: {
__typename: 'Group',
id: 'gid://gitlab/Group/26',
organizations: {
nodes: [
{
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/1',
name: 'Test Inc',
defaultRate: 100,
description: null,
},
{
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/2',
name: 'ABC Company',
defaultRate: 110,
description: 'VIP',
},
{
__typename: 'CustomerRelationsOrganization',
id: 'gid://gitlab/CustomerRelations::Organization/3',
name: 'GitLab',
defaultRate: 120,
description: null,
},
],
},
},
},
};
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 OrganizationsRoot from '~/crm/components/organizations_root.vue';
import getGroupOrganizationsQuery from '~/crm/components/queries/get_group_organizations.query.graphql';
import { getGroupOrganizationsQueryResponse } from './mock_data';
jest.mock('~/flash');
describe('Customer relations organizations 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(getGroupOrganizationsQueryResponse);
const mountComponent = ({
queryHandler = successQueryHandler,
mountFunction = shallowMountExtended,
} = {}) => {
fakeApollo = createMockApollo([[getGroupOrganizationsQuery, queryHandler]]);
wrapper = mountFunction(OrganizationsRoot, {
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(/Test Inc/i)).toHaveLength(1);
expect(findRowByName(/VIP/i)).toHaveLength(1);
expect(findRowByName(/120/i)).toHaveLength(1);
});
});
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