diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-06 21:09:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-11-06 21:09:07 +0300 |
commit | f3db01da507f86cfed412c7d337e3747744cc914 (patch) | |
tree | 3862e3ca223038c1390e2d19708ebeeecb040e00 /app/assets/javascripts/alerts_settings | |
parent | a268b09416c8dc3da3af38933028fa26375b88e0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/alerts_settings')
7 files changed, 262 insertions, 75 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue index 5ecb2dd3e58..f24c52f61da 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue @@ -4,12 +4,15 @@ import { GlButton, GlIcon, GlLoadingIcon, + GlModal, + GlModalDirective, GlTable, GlTooltipDirective, + GlSprintf, } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import Tracking from '~/tracking'; -import { trackAlertIntegrationsViewsOptions } from '../constants'; +import { trackAlertIntegrationsViewsOptions, integrationToDeleteDefault } from '../constants'; export const i18n = { title: s__('AlertsIntegrations|Current integrations'), @@ -36,10 +39,13 @@ export default { GlButton, GlIcon, GlLoadingIcon, + GlModal, GlTable, + GlSprintf, }, directives: { GlTooltip: GlTooltipDirective, + GlModal: GlModalDirective, }, props: { integrations: { @@ -71,6 +77,11 @@ export default { label: __('Actions'), }, ], + data() { + return { + integrationToDelete: integrationToDeleteDefault, + }; + }, computed: { tbodyTrClass() { return { @@ -86,6 +97,14 @@ export default { const { category, action } = trackAlertIntegrationsViewsOptions; Tracking.event(category, action); }, + intergrationToDelete({ name, id }) { + this.integrationToDelete.id = id; + this.integrationToDelete.name = name; + }, + deleteIntergration() { + this.$emit('delete-integration', { id: this.integrationToDelete.id }); + this.integrationToDelete = { ...integrationToDeleteDefault }; + }, }, }; </script> @@ -127,7 +146,11 @@ export default { <template #cell(actions)="{ item }"> <gl-button-group> <gl-button icon="pencil" @click="$emit('edit-integration', { id: item.id })" /> - <gl-button icon="remove" @click="$emit('delete-integration', { id: item.id })" /> + <gl-button + v-gl-modal.deleteIntegration + icon="remove" + @click="intergrationToDelete(item)" + /> </gl-button-group> </template> @@ -143,5 +166,22 @@ export default { </div> </template> </gl-table> + <gl-modal + modal-id="deleteIntegration" + :title="__('Are you sure?')" + :ok-title="s__('AlertSettings|Delete integration')" + ok-variant="danger" + @ok="deleteIntergration" + > + <gl-sprintf + :message=" + s__( + 'AlertsIntegrations|You have opted to delete the %{integrationName} integration. Do you want to proceed? It means you will no longer receive alerts from this endpoint in your alert list, and this action cannot be undone.', + ) + " + > + <template #integrationName>{{ integrationToDelete.name }}</template> + </gl-sprintf> + </gl-modal> </div> </template> diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue index 059623ba11c..946da8ef34c 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form_new.vue @@ -22,14 +22,12 @@ import { JSON_VALIDATE_DELAY, targetPrometheusUrlPlaceholder, typeSet, - defaultFormState, } from '../constants'; export default { targetPrometheusUrlPlaceholder, JSON_VALIDATE_DELAY, typeSet, - defaultFormState, i18n: { integrationFormSteps: { step1: { @@ -113,14 +111,18 @@ export default { data() { return { selectedIntegration: integrationTypesNew[0].value, - active: false, options: integrationTypesNew, + active: false, formVisible: false, + integrationTestPayload: { + json: null, + error: null, + }, }; }, computed: { jsonIsValid() { - return this.integrationForm.integrationTestPayload.error === null; + return this.integrationTestPayload.error === null; }, selectedIntegrationType() { switch (this.selectedIntegration) { @@ -129,43 +131,42 @@ export default { case this.$options.typeSet.prometheus: return this.prometheus; default: - return this.defaultFormState; + return {}; } }, integrationForm() { return { name: this.currentIntegration?.name || '', - integrationTestPayload: { - json: null, - error: null, - }, active: this.currentIntegration?.active || false, - token: this.currentIntegration?.token || '', - url: this.currentIntegration?.url || '', + token: this.currentIntegration?.token || this.selectedIntegrationType.token, + url: this.currentIntegration?.url || this.selectedIntegrationType.url, apiUrl: this.currentIntegration?.apiUrl || '', }; }, }, watch: { currentIntegration(val) { + if (val === null) { + return this.reset(); + } this.selectedIntegration = val.type; this.active = val.active; - this.onIntegrationTypeSelect(); + return this.integrationTypeSelect(); }, }, methods: { - onIntegrationTypeSelect() { + integrationTypeSelect() { if (this.selectedIntegration === integrationTypesNew[0].value) { this.formVisible = false; } else { this.formVisible = true; } }, - onSubmitWithTestPayload() { + submitWithTestPayload() { // TODO: Test payload before saving via GraphQL - this.onSubmit(); + this.submit(); }, - onSubmit() { + submit() { const { name, apiUrl } = this.integrationForm; const variables = this.selectedIntegration === this.$options.typeSet.http @@ -179,27 +180,45 @@ export default { return this.$emit('create-new-integration', integrationPayload); }, - onReset() { - this.integrationForm = this.defaultFormState; + reset() { this.selectedIntegration = integrationTypesNew[0].value; - this.onIntegrationTypeSelect(); + this.integrationTypeSelect(); + + if (this.currentIntegration) { + return this.$emit('clear-current-integration'); + } + + return this.resetFormValues(); + }, + resetFormValues() { + this.integrationForm.name = ''; + this.integrationForm.apiUrl = ''; + this.integrationTestPayload = { + json: null, + error: null, + }; + this.active = false; }, - onResetAuthKey() { + resetAuthKey() { + if (!this.currentIntegration) { + return; + } + this.$emit('reset-token', { type: this.selectedIntegration, variables: { id: this.currentIntegration.id }, }); }, validateJson() { - this.integrationForm.integrationTestPayload.error = null; - if (this.integrationForm.integrationTestPayload.json === '') { + this.integrationTestPayload.error = null; + if (this.integrationTestPayload.json === '') { return; } try { - JSON.parse(this.integrationForm.integrationTestPayload.json); + JSON.parse(this.integrationTestPayload.json); } catch (e) { - this.integrationForm.integrationTestPayload.error = JSON.stringify(e.message); + this.integrationTestPayload.error = JSON.stringify(e.message); } }, }, @@ -207,7 +226,7 @@ export default { </script> <template> - <gl-form class="gl-mt-6" @submit.prevent="onSubmit" @reset.prevent="onReset"> + <gl-form class="gl-mt-6" @submit.prevent="submit" @reset.prevent="reset"> <h5 class="gl-font-lg gl-my-5">{{ s__('AlertSettings|Add new integrations') }}</h5> <gl-form-group @@ -217,8 +236,9 @@ export default { > <gl-form-select v-model="selectedIntegration" + :disabled="currentIntegration !== null" :options="options" - @change="onIntegrationTypeSelect" + @change="integrationTypeSelect" /> <alert-settings-form-help-block @@ -279,7 +299,11 @@ export default { <gl-form-input-group id="url" readonly :value="integrationForm.url"> <template #append> - <clipboard-button :text="integrationForm.url" :title="__('Copy')" class="gl-m-0!" /> + <clipboard-button + :text="integrationForm.url || ''" + :title="__('Copy')" + class="gl-m-0!" + /> </template> </gl-form-input-group> </div> @@ -296,7 +320,11 @@ export default { :value="integrationForm.token" > <template #append> - <clipboard-button :text="integrationForm.token" :title="__('Copy')" class="gl-m-0!" /> + <clipboard-button + :text="integrationForm.token || ''" + :title="__('Copy')" + class="gl-m-0!" + /> </template> </gl-form-input-group> @@ -308,7 +336,7 @@ export default { :title="$options.i18n.integrationFormSteps.step3.reset" :ok-title="$options.i18n.integrationFormSteps.step3.reset" ok-variant="danger" - @ok="onResetAuthKey" + @ok="resetAuthKey" > {{ $options.i18n.integrationFormSteps.restKeyInfo.label }} </gl-modal> @@ -318,7 +346,7 @@ export default { id="test-integration" :label="$options.i18n.integrationFormSteps.step4.label" label-for="test-integration" - :invalid-feedback="integrationForm.integrationTestPayload.error" + :invalid-feedback="integrationTestPayload.error" > <alert-settings-form-help-block :message="$options.i18n.integrationFormSteps.step4.help" @@ -327,8 +355,8 @@ export default { <gl-form-textarea id="test-integration" - v-model.trim="integrationForm.integrationTestPayload.json" - :disabled="!integrationForm.active" + v-model.trim="integrationTestPayload.json" + :disabled="!active" :state="jsonIsValid" :placeholder="$options.i18n.integrationFormSteps.step4.placeholder" class="gl-my-4" @@ -354,7 +382,7 @@ export default { category="secondary" variant="success" class="gl-mr-1 js-no-auto-disable" - @click="onSubmitWithTestPayload" + @click="submitWithTestPayload" >{{ s__('AlertSettings|Save and test payload') }}</gl-button > <gl-button diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue index 0a59a5981ef..e9e7b1407bc 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue @@ -1,5 +1,4 @@ <script> -import produce from 'immer'; import { s__ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { fetchPolicies } from '~/lib/graphql'; @@ -9,12 +8,17 @@ import createHttpIntegrationMutation from '../graphql/mutations/create_http_inte import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql'; import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql'; import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql'; +import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql'; import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql'; import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql'; import IntegrationsList from './alerts_integrations_list.vue'; import SettingsFormOld from './alerts_settings_form_old.vue'; import SettingsFormNew from './alerts_settings_form_new.vue'; import { typeSet } from '../constants'; +import { + updateStoreAfterIntegrationDelete, + updateStoreAfterIntegrationAdd, +} from '../utils/cache_updates'; export default { typeSet, @@ -22,6 +26,7 @@ export default { changesSaved: s__( 'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.', ), + integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'), }, components: { IntegrationsList, @@ -89,6 +94,8 @@ export default { }, methods: { createNewIntegration({ type, variables }) { + const { projectPath } = this; + this.isUpdating = true; this.$apollo .mutate({ @@ -98,9 +105,11 @@ export default { : createPrometheusIntegrationMutation, variables: { ...variables, - projectPath: this.projectPath, + projectPath, + }, + update(store, { data }) { + updateStoreAfterIntegrationAdd(store, getIntegrationsQuery, data, { projectPath }); }, - update: this.updateIntegrations, }) .then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => { const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0]; @@ -119,41 +128,6 @@ export default { this.isUpdating = false; }); }, - updateIntegrations( - store, - { - data: { httpIntegrationCreate, prometheusIntegrationCreate }, - }, - ) { - const integration = - httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration; - if (!integration) { - return; - } - - const sourceData = store.readQuery({ - query: getIntegrationsQuery, - variables: { - projectPath: this.projectPath, - }, - }); - - const data = produce(sourceData, draftData => { - // eslint-disable-next-line no-param-reassign - draftData.project.alertManagementIntegrations.nodes = [ - integration, - ...draftData.project.alertManagementIntegrations.nodes, - ]; - }); - - store.writeQuery({ - query: getIntegrationsQuery, - variables: { - projectPath: this.projectPath, - }, - data, - }); - }, updateIntegration({ type, variables }) { this.isUpdating = true; this.$apollo @@ -201,6 +175,12 @@ export default { if (error) { return createFlash({ message: error }); } + + const integration = + httpIntegrationResetToken?.integration || + prometheusIntegrationResetToken?.integration; + this.currentIntegration = integration; + return createFlash({ message: this.$options.i18n.changesSaved, type: FLASH_TYPES.SUCCESS, @@ -217,8 +197,41 @@ export default { editIntegration({ id }) { this.currentIntegration = this.integrations.list.find(integration => integration.id === id); }, - deleteIntegration() { - // TODO, handle delete via GraphQL + deleteIntegration({ id }) { + const { projectPath } = this; + + this.isUpdating = true; + this.$apollo + .mutate({ + mutation: destroyHttpIntegrationMutation, + variables: { + id, + }, + update(store, { data }) { + updateStoreAfterIntegrationDelete(store, getIntegrationsQuery, data, { projectPath }); + }, + }) + .then(({ data: { httpIntegrationDestroy } = {} } = {}) => { + const error = httpIntegrationDestroy?.errors[0]; + if (error) { + return createFlash({ message: error }); + } + this.currentIntegration = null; + return createFlash({ + message: this.$options.i18n.integrationRemoved, + type: FLASH_TYPES.SUCCESS, + }); + }) + .catch(err => { + this.errored = true; + createFlash({ message: err }); + }) + .finally(() => { + this.isUpdating = false; + }); + }, + clearCurrentIntegration() { + this.currentIntegration = null; }, }, }; @@ -239,6 +252,7 @@ export default { @create-new-integration="createNewIntegration" @update-integration="updateIntegration" @reset-token="resetToken" + @clear-current-integration="clearCurrentIntegration" /> <settings-form-old v-else /> </div> diff --git a/app/assets/javascripts/alerts_settings/constants.js b/app/assets/javascripts/alerts_settings/constants.js index 4ab8d215572..9cf2f356e0a 100644 --- a/app/assets/javascripts/alerts_settings/constants.js +++ b/app/assets/javascripts/alerts_settings/constants.js @@ -66,6 +66,8 @@ export const defaultFormState = { integrationTestPayload: { json: null, error: null }, }; +export const integrationToDeleteDefault = { id: null, name: '' }; + export const JSON_VALIDATE_DELAY = 250; export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/'; diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql new file mode 100644 index 00000000000..0a49c140e6a --- /dev/null +++ b/app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql @@ -0,0 +1,10 @@ +#import "../fragments/integration_item.fragment.graphql" + +mutation destroyHttpIntegration($id: ID!) { + httpIntegrationDestroy(input: { id: $id }) { + errors + integration { + ...IntegrationItem + } + } +} diff --git a/app/assets/javascripts/alerts_settings/utils/cache_updates.js b/app/assets/javascripts/alerts_settings/utils/cache_updates.js new file mode 100644 index 00000000000..18054b29fe9 --- /dev/null +++ b/app/assets/javascripts/alerts_settings/utils/cache_updates.js @@ -0,0 +1,84 @@ +import produce from 'immer'; +import createFlash from '~/flash'; + +import { DELETE_INTEGRATION_ERROR, ADD_INTEGRATION_ERROR } from './error_messages'; + +const deleteIntegrationFromStore = (store, query, { httpIntegrationDestroy }, variables) => { + const integration = httpIntegrationDestroy?.integration; + if (!integration) { + return; + } + + const sourceData = store.readQuery({ + query, + variables, + }); + + const data = produce(sourceData, draftData => { + // eslint-disable-next-line no-param-reassign + draftData.project.alertManagementIntegrations.nodes = draftData.project.alertManagementIntegrations.nodes.filter( + ({ id }) => id !== integration.id, + ); + }); + + store.writeQuery({ + query, + variables, + data, + }); +}; + +const addIntegrationToStore = ( + store, + query, + { httpIntegrationCreate, prometheusIntegrationCreate }, + variables, +) => { + const integration = + httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration; + if (!integration) { + return; + } + + const sourceData = store.readQuery({ + query, + variables, + }); + + const data = produce(sourceData, draftData => { + // eslint-disable-next-line no-param-reassign + draftData.project.alertManagementIntegrations.nodes = [ + integration, + ...draftData.project.alertManagementIntegrations.nodes, + ]; + }); + + store.writeQuery({ + query, + variables, + data, + }); +}; + +const onError = (data, message) => { + createFlash({ message }); + throw new Error(data.errors); +}; + +export const hasErrors = ({ errors = [] }) => errors?.length; + +export const updateStoreAfterIntegrationDelete = (store, query, data, variables) => { + if (hasErrors(data)) { + onError(data, DELETE_INTEGRATION_ERROR); + } else { + deleteIntegrationFromStore(store, query, data, variables); + } +}; + +export const updateStoreAfterIntegrationAdd = (store, query, data, variables) => { + if (hasErrors(data)) { + onError(data, ADD_INTEGRATION_ERROR); + } else { + addIntegrationToStore(store, query, data, variables); + } +}; diff --git a/app/assets/javascripts/alerts_settings/utils/error_messages.js b/app/assets/javascripts/alerts_settings/utils/error_messages.js new file mode 100644 index 00000000000..2e6058fc81a --- /dev/null +++ b/app/assets/javascripts/alerts_settings/utils/error_messages.js @@ -0,0 +1,9 @@ +import { s__ } from '~/locale'; + +export const DELETE_INTEGRATION_ERROR = s__( + 'AlertsIntegrations|The integration could not be deleted. Please try again.', +); + +export const ADD_INTEGRATION_ERROR = s__( + 'AlertsIntegrations|The integration could not be added. Please try again.', +); |