diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-03 15:10:23 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-03 15:10:23 +0300 |
commit | 5f0d27d131aced1a53e8cbc7db023d9f947f8a1a (patch) | |
tree | 7007c07fc37c95638f3e71c1902dcd055db1d8ca /app/assets/javascripts/crm | |
parent | cc8ea69201e2e4d020018c43efeb993c44cd8a71 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/crm')
6 files changed, 242 insertions, 24 deletions
diff --git a/app/assets/javascripts/crm/components/contacts_root.vue b/app/assets/javascripts/crm/components/contacts_root.vue index 97220a3409d..0242bdab541 100644 --- a/app/assets/javascripts/crm/components/contacts_root.vue +++ b/app/assets/javascripts/crm/components/contacts_root.vue @@ -1,22 +1,28 @@ <script> -import { GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui'; -import createFlash from '~/flash'; +import { GlAlert, GlButton, GlLoadingIcon, GlTable, GlTooltipDirective } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; +import NewContactForm from './new_contact_form.vue'; export default { components: { + GlAlert, GlButton, GlLoadingIcon, GlTable, + NewContactForm, }, directives: { GlTooltip: GlTooltipDirective, }, - inject: ['groupFullPath', 'groupIssuesPath'], + inject: ['groupFullPath', 'groupIssuesPath', 'canAdminCrmContact'], data() { - return { contacts: [] }; + return { + contacts: [], + error: false, + errorMessages: [], + }; }, apollo: { contacts: { @@ -31,12 +37,8 @@ export default { update(data) { return this.extractContacts(data); }, - error(error) { - createFlash({ - message: __('Something went wrong. Please try again.'), - error, - captureError: true, - }); + error() { + this.error = true; }, }, }, @@ -44,12 +46,31 @@ export default { isLoading() { return this.$apollo.queries.contacts.loading; }, + showNewForm() { + return this.$route.path.startsWith('/new'); + }, }, methods: { extractContacts(data) { const contacts = data?.group?.contacts?.nodes || []; return contacts.slice().sort((a, b) => a.firstName.localeCompare(b.firstName)); }, + displayNewForm() { + if (this.showNewForm) return; + + this.$router.push({ path: '/new' }); + }, + hideNewForm() { + this.$router.replace({ path: '/' }); + }, + handleError(errors) { + this.error = true; + if (errors) this.errorMessages = errors; + }, + dismissError() { + this.error = false; + this.errorMessages = []; + }, }, fields: [ { key: 'firstName', sortable: true }, @@ -75,15 +96,41 @@ export default { i18n: { emptyText: s__('Crm|No contacts found'), issuesButtonLabel: __('View issues'), + title: s__('Crm|Customer Relations Contacts'), + newContact: s__('Crm|New contact'), + errorText: __('Something went wrong. Please try again.'), }, }; </script> <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> + <div + class="gl-display-flex gl-align-items-baseline gl-flex-direction-row gl-justify-content-space-between gl-mt-6" + > + <h2 class="gl-font-size-h2 gl-my-0"> + {{ $options.i18n.title }} + </h2> + <div class="gl-display-none gl-md-display-flex gl-align-items-center gl-justify-content-end"> + <gl-button + v-if="canAdminCrmContact" + variant="confirm" + data-testid="new-contact-button" + @click="displayNewForm" + > + {{ $options.i18n.newContact }} + </gl-button> + </div> + </div> + <new-contact-form v-if="showNewForm" @close="hideNewForm" @error="handleError" /> <gl-loading-icon v-if="isLoading" class="gl-mt-5" size="lg" /> <gl-table v-else + class="gl-mt-5" :items="contacts" :fields="$options.fields" :empty-text="$options.i18n.emptyText" diff --git a/app/assets/javascripts/crm/components/new_contact_form.vue b/app/assets/javascripts/crm/components/new_contact_form.vue new file mode 100644 index 00000000000..77ff82c5af4 --- /dev/null +++ b/app/assets/javascripts/crm/components/new_contact_form.vue @@ -0,0 +1,140 @@ +<script> +import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { produce } from 'immer'; +import { __, s__ } from '~/locale'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_GROUP } from '~/graphql_shared/constants'; +import createContact from './queries/create_contact.mutation.graphql'; +import getGroupContactsQuery from './queries/get_group_contacts.query.graphql'; + +export default { + components: { + GlButton, + GlFormGroup, + GlFormInput, + }, + inject: ['groupFullPath', 'groupId'], + data() { + return { + firstName: '', + lastName: '', + phone: '', + email: '', + description: '', + submitting: false, + }; + }, + computed: { + invalid() { + return this.firstName === '' || this.lastName === '' || this.email === ''; + }, + }, + methods: { + save() { + this.submitting = true; + return this.$apollo + .mutate({ + mutation: createContact, + variables: { + input: { + groupId: convertToGraphQLId(TYPE_GROUP, this.groupId), + firstName: this.firstName, + lastName: this.lastName, + phone: this.phone, + email: this.email, + description: this.description, + }, + }, + update: this.updateCache, + }) + .then(({ data }) => { + if (data.customerRelationsContactCreate.errors.length === 0) this.close(); + + this.submitting = false; + }) + .catch(() => { + this.error(); + this.submitting = false; + }); + }, + close() { + this.$emit('close'); + }, + error(errors = null) { + this.$emit('error', errors); + }, + updateCache(store, { data: { customerRelationsContactCreate } }) { + if (customerRelationsContactCreate.errors.length > 0) { + this.error(customerRelationsContactCreate.errors); + return; + } + + const variables = { + groupFullPath: this.groupFullPath, + }; + const sourceData = store.readQuery({ + query: getGroupContactsQuery, + variables, + }); + + const data = produce(sourceData, (draftState) => { + draftState.group.contacts.nodes = [ + ...sourceData.group.contacts.nodes, + customerRelationsContactCreate.contact, + ]; + }); + + store.writeQuery({ + query: getGroupContactsQuery, + variables, + data, + }); + }, + }, + i18n: { + buttonLabel: s__('Crm|Create new contact'), + cancel: __('Cancel'), + firstName: s__('Crm|First name'), + lastName: s__('Crm|Last name'), + email: s__('Crm|Email'), + phone: s__('Crm|Phone number (optional)'), + description: s__('Crm|Description (optional)'), + }, +}; +</script> + +<template> + <div class="col-md-4"> + <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" /> + </gl-form-group> + <gl-form-group :label="$options.i18n.lastName" label-for="contact-last-name"> + <gl-form-input id="contact-last-name" v-model="lastName" /> + </gl-form-group> + <gl-form-group :label="$options.i18n.email" label-for="contact-email"> + <gl-form-input id="contact-email" v-model="email" /> + </gl-form-group> + <gl-form-group :label="$options.i18n.phone" label-for="contact-phone"> + <gl-form-input id="contact-phone" v-model="phone" /> + </gl-form-group> + <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"> + <gl-button + variant="confirm" + :disabled="invalid" + :loading="submitting" + data-testid="create-new-contact-button" + type="submit" + >{{ $options.i18n.buttonLabel }}</gl-button + > + <gl-button data-testid="cancel-button" @click="close"> + {{ $options.i18n.cancel }} + </gl-button> + </div> + </form> + <div class="gl-pb-5"></div> + </div> +</template> diff --git a/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql b/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql new file mode 100644 index 00000000000..e0192459609 --- /dev/null +++ b/app/assets/javascripts/crm/components/queries/create_contact.mutation.graphql @@ -0,0 +1,10 @@ +#import "./crm_contact_fields.fragment.graphql" + +mutation createContact($input: CustomerRelationsContactCreateInput!) { + customerRelationsContactCreate(input: $input) { + contact { + ...ContactFragment + } + errors + } +} diff --git a/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql b/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql new file mode 100644 index 00000000000..cef4083b446 --- /dev/null +++ b/app/assets/javascripts/crm/components/queries/crm_contact_fields.fragment.graphql @@ -0,0 +1,14 @@ +fragment ContactFragment on CustomerRelationsContact { + __typename + id + firstName + lastName + email + phone + description + organization { + __typename + id + name + } +} diff --git a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql b/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql index f6acd258585..2a8150e42e3 100644 --- a/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql +++ b/app/assets/javascripts/crm/components/queries/get_group_contacts.query.graphql @@ -1,21 +1,12 @@ +#import "./crm_contact_fields.fragment.graphql" + query contacts($groupFullPath: ID!) { group(fullPath: $groupFullPath) { __typename id contacts { nodes { - __typename - id - firstName - lastName - email - phone - description - organization { - __typename - id - name - } + ...ContactFragment } } } diff --git a/app/assets/javascripts/crm/contacts_bundle.js b/app/assets/javascripts/crm/contacts_bundle.js index b0edd0107b6..6ddc53840cc 100644 --- a/app/assets/javascripts/crm/contacts_bundle.js +++ b/app/assets/javascripts/crm/contacts_bundle.js @@ -1,9 +1,11 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import VueRouter from 'vue-router'; import createDefaultClient from '~/lib/graphql'; import CrmContactsRoot from './components/contacts_root.vue'; Vue.use(VueApollo); +Vue.use(VueRouter); export default () => { const el = document.getElementById('js-crm-contacts-app'); @@ -16,12 +18,26 @@ export default () => { return false; } - const { groupFullPath, groupIssuesPath } = el.dataset; + const { basePath, groupFullPath, groupIssuesPath, canAdminCrmContact, groupId } = el.dataset; + + const router = new VueRouter({ + base: basePath, + mode: 'history', + routes: [ + { + // eslint-disable-next-line @gitlab/require-i18n-strings + name: 'Contacts List', + path: '/', + component: CrmContactsRoot, + }, + ], + }); return new Vue({ el, + router, apolloProvider, - provide: { groupFullPath, groupIssuesPath }, + provide: { groupFullPath, groupIssuesPath, canAdminCrmContact, groupId }, render(createElement) { return createElement(CrmContactsRoot); }, |