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 @@ + + {{ $options.i18n.errorText }} + {{ message }} + + + + {{ $options.i18n.title }} + + + + {{ $options.i18n.newContact }} + + + + +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)'), + }, +}; + + + + + + + + + + + + + + + + + + + + + + {{ $options.i18n.buttonLabel }} + + {{ $options.i18n.cancel }} + + + + + + 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); }, diff --git a/app/assets/javascripts/editor/extensions/example_source_editor_extension.js b/app/assets/javascripts/editor/extensions/example_source_editor_extension.js index 119a2aea9eb..33be6cf9e5d 100644 --- a/app/assets/javascripts/editor/extensions/example_source_editor_extension.js +++ b/app/assets/javascripts/editor/extensions/example_source_editor_extension.js @@ -16,11 +16,11 @@ export class MyFancyExtension { * actions, keystrokes, update options, etc. * Is called only once before the extension gets registered * - * @param { Object } [setupOptions] The setupOptions object * @param { Object } [instance] The Source Editor instance + * @param { Object } [setupOptions] The setupOptions object */ // eslint-disable-next-line class-methods-use-this,no-unused-vars - onSetup(setupOptions, instance) {} + onSetup(instance, setupOptions) {} /** * The first thing called after the extension is diff --git a/app/assets/javascripts/editor/source_editor_instance.js b/app/assets/javascripts/editor/source_editor_instance.js index 052a73d7091..fcffdc587be 100644 --- a/app/assets/javascripts/editor/source_editor_instance.js +++ b/app/assets/javascripts/editor/source_editor_instance.js @@ -153,7 +153,7 @@ export default class EditorInstance { const extensionInstance = new EditorExtension(extension); const { setupOptions, obj: extensionObj } = extensionInstance; if (extensionObj.onSetup) { - extensionObj.onSetup(setupOptions, this); + extensionObj.onSetup(this, setupOptions); } if (extensionsStore) { this.registerExtension(extensionInstance, extensionsStore); diff --git a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue index 7f6dce05b6e..13e254f138a 100644 --- a/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue +++ b/app/assets/javascripts/pipeline_editor/components/lint/ci_lint_results.vue @@ -1,5 +1,5 @@