summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/alerts_settings
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 18:18:33 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 18:18:33 +0000
commitf64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch)
treea2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/alerts_settings
parentbfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff)
downloadgitlab-ce-f64a639bcfa1fc2bc89ca7db268f594306edfd7c.tar.gz
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/alerts_settings')
-rw-r--r--app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue43
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue15
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue636
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue189
-rw-r--r--app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json95
-rw-r--r--app/assets/javascripts/alerts_settings/constants.js152
-rw-r--r--app/assets/javascripts/alerts_settings/graphql.js18
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql7
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql3
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql22
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql4
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql4
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql25
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/update_current_prometheus_integration.mutation.graphql (renamed from app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql)4
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql4
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql12
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/queries/parse_sample_payload.query.graphql9
-rw-r--r--app/assets/javascripts/alerts_settings/index.js5
-rw-r--r--app/assets/javascripts/alerts_settings/utils/cache_updates.js35
-rw-r--r--app/assets/javascripts/alerts_settings/utils/error_messages.js2
-rw-r--r--app/assets/javascripts/alerts_settings/utils/mapping_transformations.js28
21 files changed, 705 insertions, 607 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
index 1135562834a..07b2e59671e 100644
--- a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
+++ b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
@@ -7,15 +7,12 @@ import {
GlSearchBoxByType,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
-import { cloneDeep } from 'lodash';
+import { cloneDeep, isEqual } from 'lodash';
import Vue from 'vue';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { s__, __ } from '~/locale';
-import {
- getMappingData,
- getPayloadFields,
- transformForSave,
-} from '../utils/mapping_transformations';
+import { mappingFields } from '../constants';
+import { getMappingData, transformForSave } from '../utils/mapping_transformations';
export const i18n = {
columns: {
@@ -33,6 +30,7 @@ export const i18n = {
export default {
i18n,
+ mappingFields,
components: {
GlIcon,
GlFormInput,
@@ -73,18 +71,15 @@ export default {
};
},
computed: {
- payloadFields() {
- return getPayloadFields(this.parsedPayload);
- },
mappingData() {
- return getMappingData(this.gitlabFields, this.payloadFields, this.savedMapping);
+ return getMappingData(this.gitlabFields, this.parsedPayload, this.savedMapping);
},
hasFallbackColumn() {
return this.gitlabFields.some(({ numberOfFallbacks }) => Boolean(numberOfFallbacks));
},
},
methods: {
- setMapping(gitlabKey, mappingKey, valueKey) {
+ setMapping(gitlabKey, mappingKey, valueKey = mappingFields.mapping) {
const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey);
const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [valueKey]: mappingKey } };
Vue.set(this.gitlabFields, fieldIndex, updatedField);
@@ -100,11 +95,11 @@ export default {
return fields.filter((field) => field.label.toLowerCase().includes(search));
},
isSelected(fieldValue, mapping) {
- return fieldValue === mapping;
+ return isEqual(fieldValue, mapping);
},
- selectedValue(name) {
+ selectedValue(mapping) {
return (
- this.payloadFields.find((item) => item.name === name)?.label ||
+ this.parsedPayload.find((item) => isEqual(item.path, mapping))?.label ||
this.$options.i18n.makeSelection
);
},
@@ -150,7 +145,7 @@ export default {
:key="gitlabField.name"
class="gl-display-table-row"
>
- <div class="gl-display-table-cell gl-py-3 gl-pr-3 w-30p gl-vertical-align-middle">
+ <div class="gl-display-table-cell gl-py-3 gl-pr-3 gl-w-30p gl-vertical-align-middle">
<gl-form-input
aria-labelledby="gitlabFieldsHeader"
disabled
@@ -164,7 +159,7 @@ export default {
</div>
</div>
- <div class="gl-display-table-cell gl-py-3 gl-pr-3 w-30p gl-vertical-align-middle">
+ <div class="gl-display-table-cell gl-py-3 gl-pr-3 gl-w-30p gl-vertical-align-middle">
<gl-dropdown
:disabled="!gitlabField.mappingFields.length"
aria-labelledby="parsedFieldsHeader"
@@ -175,10 +170,10 @@ export default {
<gl-search-box-by-type @input="setSearchTerm($event, 'searchTerm', gitlabField.name)" />
<gl-dropdown-item
v-for="mappingField in filterFields(gitlabField.searchTerm, gitlabField.mappingFields)"
- :key="`${mappingField.name}__mapping`"
- :is-checked="isSelected(gitlabField.mapping, mappingField.name)"
+ :key="`${mappingField.path}__mapping`"
+ :is-checked="isSelected(gitlabField.mapping, mappingField.path)"
is-check-item
- @click="setMapping(gitlabField.name, mappingField.name, 'mapping')"
+ @click="setMapping(gitlabField.name, mappingField.path)"
>
{{ mappingField.label }}
</gl-dropdown-item>
@@ -188,7 +183,7 @@ export default {
</gl-dropdown>
</div>
- <div class="gl-display-table-cell gl-py-3 w-30p">
+ <div class="gl-display-table-cell gl-py-3 gl-w-30p">
<gl-dropdown
v-if="Boolean(gitlabField.numberOfFallbacks)"
:disabled="!gitlabField.mappingFields.length"
@@ -205,10 +200,12 @@ export default {
gitlabField.fallbackSearchTerm,
gitlabField.mappingFields,
)"
- :key="`${mappingField.name}__fallback`"
- :is-checked="isSelected(gitlabField.fallback, mappingField.name)"
+ :key="`${mappingField.path}__fallback`"
+ :is-checked="isSelected(gitlabField.fallback, mappingField.path)"
is-check-item
- @click="setMapping(gitlabField.name, mappingField.name, 'fallback')"
+ @click="
+ setMapping(gitlabField.name, mappingField.path, $options.mappingFields.fallback)
+ "
>
{{ mappingField.label }}
</gl-dropdown-item>
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 6cfb4601192..a5e17d80f86 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue
@@ -10,6 +10,7 @@ import {
GlTooltipDirective,
GlSprintf,
} from '@gitlab/ui';
+import { capitalize } from 'lodash';
import { s__, __ } from '~/locale';
import Tracking from '~/tracking';
import {
@@ -77,6 +78,7 @@ export default {
{
key: 'type',
label: __('Type'),
+ formatter: (value) => (value === typeSet.prometheus ? capitalize(value) : value),
},
{
key: 'actions',
@@ -120,14 +122,17 @@ export default {
const { category, action } = trackAlertIntegrationsViewsOptions;
Tracking.event(category, action);
},
- setIntegrationToDelete({ name, id }) {
- this.integrationToDelete.id = id;
- this.integrationToDelete.name = name;
+ setIntegrationToDelete(integration) {
+ this.integrationToDelete = integration;
},
deleteIntegration() {
- this.$emit('delete-integration', { id: this.integrationToDelete.id });
+ const { id, type } = this.integrationToDelete;
+ this.$emit('delete-integration', { id, type });
this.integrationToDelete = { ...integrationToDeleteDefault };
},
+ editIntegration({ id, type }) {
+ this.$emit('edit-integration', { id, type });
+ },
},
};
</script>
@@ -169,7 +174,7 @@ export default {
<template #cell(actions)="{ item }">
<gl-button-group class="gl-ml-3">
- <gl-button icon="pencil" @click="$emit('edit-integration', { id: item.id })" />
+ <gl-button icon="settings" @click="editIntegration(item)" />
<gl-button
v-gl-modal.deleteIntegration
:disabled="item.type === $options.typeSet.prometheus"
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
index 18372c54b84..5d9513e5b53 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
@@ -1,7 +1,6 @@
<script>
import {
GlButton,
- GlCollapse,
GlForm,
GlFormGroup,
GlFormSelect,
@@ -11,98 +10,39 @@ import {
GlModal,
GlModalDirective,
GlToggle,
+ GlTabs,
+ GlTab,
} from '@gitlab/ui';
-import { s__ } from '~/locale';
+import * as Sentry from '@sentry/browser';
+import { isEmpty, omit } from 'lodash';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
integrationTypes,
+ integrationSteps,
+ createStepNumbers,
+ editStepNumbers,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
typeSet,
+ viewCredentialsTabIndex,
+ i18n,
} from '../constants';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
+import parseSamplePayloadQuery from '../graphql/queries/parse_sample_payload.query.graphql';
import MappingBuilder from './alert_mapping_builder.vue';
import AlertSettingsFormHelpBlock from './alert_settings_form_help_block.vue';
-// Mocks will be removed when integrating with BE is ready
-// data format is defined and will be the same as mocked (maybe with some minor changes)
-// feature rollout plan - https://gitlab.com/gitlab-org/gitlab/-/issues/262707#note_442529171
-import mockedCustomMapping from './mocks/parsedMapping.json';
-
-export const i18n = {
- integrationFormSteps: {
- step1: {
- label: s__('AlertSettings|1. Select integration type'),
- enterprise: s__(
- 'AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations.',
- ),
- },
- step2: {
- label: s__('AlertSettings|2. Name integration'),
- placeholder: s__('AlertSettings|Enter integration name'),
- prometheus: s__('AlertSettings|Prometheus'),
- },
- step3: {
- label: s__('AlertSettings|3. Set up webhook'),
- help: s__(
- "AlertSettings|Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
- ),
- prometheusHelp: s__(
- 'AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.',
- ),
- info: s__('AlertSettings|Authorization key'),
- reset: s__('AlertSettings|Reset Key'),
- },
- step4: {
- label: s__('AlertSettings|4. Sample alert payload (optional)'),
- help: s__(
- 'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional), or to test the integration (also optional).',
- ),
- prometheusHelp: s__(
- 'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional).',
- ),
- placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
- resetHeader: s__('AlertSettings|Reset the mapping'),
- resetBody: s__(
- "AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields.",
- ),
- resetOk: s__('AlertSettings|Proceed with editing'),
- editPayload: s__('AlertSettings|Edit payload'),
- submitPayload: s__('AlertSettings|Submit payload'),
- payloadParsedSucessMsg: s__(
- 'AlertSettings|Sample payload has been parsed. You can now map the fields.',
- ),
- },
- step5: {
- label: s__('AlertSettings|5. Map fields (optional)'),
- intro: s__(
- "AlertSettings|If you've provided a sample alert payload, you can create a custom mapping for your endpoint. The default GitLab alert keys are listed below. Please define which payload key should map to the specified GitLab key.",
- ),
- },
- prometheusFormUrl: {
- label: s__('AlertSettings|Prometheus API base URL'),
- help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
- },
- restKeyInfo: {
- label: s__(
- 'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
- ),
- },
- },
-};
export default {
- integrationTypes,
placeholders: {
prometheus: targetPrometheusUrlPlaceholder,
},
JSON_VALIDATE_DELAY,
typeSet,
+ integrationSteps,
i18n,
components: {
ClipboardButton,
GlButton,
- GlCollapse,
GlForm,
GlFormGroup,
GlFormInput,
@@ -111,13 +51,14 @@ export default {
GlFormSelect,
GlModal,
GlToggle,
+ GlTabs,
+ GlTab,
AlertSettingsFormHelpBlock,
MappingBuilder,
},
directives: {
GlModal: GlModalDirective,
},
- mixins: [glFeatureFlagsMixin()],
inject: {
generic: {
default: {},
@@ -128,6 +69,9 @@ export default {
multiIntegrations: {
default: false,
},
+ projectPath: {
+ default: '',
+ },
},
props: {
loading: {
@@ -151,26 +95,40 @@ export default {
},
data() {
return {
- selectedIntegration: integrationTypes[0].value,
+ integrationTypesOptions: Object.values(integrationTypes),
+ selectedIntegration: integrationTypes.none.value,
active: false,
- formVisible: false,
- integrationTestPayload: {
+ samplePayload: {
json: null,
error: null,
},
- resetSamplePayloadConfirmed: false,
- customMapping: null,
+ testPayload: {
+ json: null,
+ error: null,
+ },
+ resetPayloadAndMappingConfirmed: false,
mapping: [],
parsingPayload: false,
currentIntegration: null,
+ parsedPayload: [],
+ activeTabIndex: 0,
};
},
computed: {
isPrometheus() {
return this.selectedIntegration === this.$options.typeSet.prometheus;
},
- jsonIsValid() {
- return this.integrationTestPayload.error === null;
+ isHttp() {
+ return this.selectedIntegration === this.$options.typeSet.http;
+ },
+ isCreating() {
+ return !this.currentIntegration;
+ },
+ isSampePayloadValid() {
+ return this.samplePayload.error === null;
+ },
+ isTestPayloadValid() {
+ return this.testPayload.error === null;
},
selectedIntegrationType() {
switch (this.selectedIntegration) {
@@ -197,90 +155,88 @@ export default {
},
testAlertPayload() {
return {
- data: this.integrationTestPayload.json,
+ data: this.testPayload.json,
endpoint: this.integrationForm.url,
token: this.integrationForm.token,
};
},
showMappingBuilder() {
- return (
- this.multiIntegrations &&
- this.glFeatures.multipleHttpIntegrationsCustomMapping &&
- this.selectedIntegration === typeSet.http &&
- this.alertFields?.length
- );
- },
- parsedSamplePayload() {
- return this.customMapping?.samplePayload?.payloadAlerFields?.nodes;
- },
- savedMapping() {
- return this.customMapping?.storedMapping?.nodes;
+ return this.multiIntegrations && this.isHttp && this.alertFields?.length;
},
hasSamplePayload() {
- return Boolean(this.customMapping?.samplePayload);
+ return this.isValidNonEmptyJSON(this.currentIntegration?.payloadExample);
},
canEditPayload() {
- return this.hasSamplePayload && !this.resetSamplePayloadConfirmed;
+ return this.hasSamplePayload && !this.resetPayloadAndMappingConfirmed;
+ },
+ canParseSamplePayload() {
+ return !this.active || !this.isSampePayloadValid || !this.samplePayload.json;
},
isResetAuthKeyDisabled() {
return !this.active && !this.integrationForm.token !== '';
},
isPayloadEditDisabled() {
- return this.glFeatures.multipleHttpIntegrationsCustomMapping
- ? !this.active || this.canEditPayload
- : !this.active;
- },
- isSubmitTestPayloadDisabled() {
- return (
- !this.active ||
- Boolean(this.integrationTestPayload.error) ||
- this.integrationTestPayload.json === ''
- );
+ return !this.active || this.canEditPayload;
},
isSelectDisabled() {
return this.currentIntegration !== null || !this.canAddIntegration;
},
+ viewCredentialsHelpMsg() {
+ return this.isPrometheus
+ ? i18n.integrationFormSteps.setupCredentials.prometheusHelp
+ : i18n.integrationFormSteps.setupCredentials.help;
+ },
},
watch: {
currentIntegration(val) {
if (val === null) {
- return this.reset();
+ this.reset();
+ return;
+ }
+ const { type, active, payloadExample, payloadAlertFields, payloadAttributeMappings } = val;
+ this.selectedIntegration = type;
+ this.active = active;
+
+ if (type === typeSet.http && this.showMappingBuilder) {
+ this.parsedPayload = payloadAlertFields;
+ this.samplePayload.json = this.isValidNonEmptyJSON(payloadExample) ? payloadExample : null;
+ const mapping = payloadAttributeMappings.map((mappingItem) =>
+ omit(mappingItem, '__typename'),
+ );
+ this.updateMapping(mapping);
}
- this.selectedIntegration = val.type;
- this.active = val.active;
- if (val.type === typeSet.http && this.showMappingBuilder) this.getIntegrationMapping(val.id);
- return this.integrationTypeSelect();
+ this.activeTabIndex = viewCredentialsTabIndex;
+ this.$el.scrollIntoView({ block: 'center' });
},
},
methods: {
- integrationTypeSelect() {
- if (this.selectedIntegration === integrationTypes[0].value) {
- this.formVisible = false;
- } else {
- this.formVisible = true;
+ isValidNonEmptyJSON(JSONString) {
+ if (JSONString) {
+ let parsed;
+ try {
+ parsed = JSON.parse(JSONString);
+ } catch (error) {
+ Sentry.captureException(error);
+ }
+ if (parsed) return !isEmpty(parsed);
}
+ return false;
},
- submitWithTestPayload() {
- this.$emit('set-test-alert-payload', this.testAlertPayload);
- this.submit();
+ sendTestAlert() {
+ this.$emit('test-alert-payload', this.testAlertPayload);
},
submit() {
const { name, apiUrl } = this.integrationForm;
- const customMappingVariables = this.glFeatures.multipleHttpIntegrationsCustomMapping
- ? {
- payloadAttributeMappings: this.mapping,
- payloadExample: this.integrationTestPayload.json,
- }
- : {};
+ const customMappingVariables = {
+ payloadAttributeMappings: this.mapping,
+ payloadExample: this.samplePayload.json || '{}',
+ };
const variables =
this.selectedIntegration === typeSet.http
- ? {
- name,
- active: this.active,
- ...customMappingVariables,
- }
+ ? { name, active: this.active, ...customMappingVariables }
: { apiUrl, active: this.active };
+
const integrationPayload = { type: this.selectedIntegration, variables };
if (this.currentIntegration) {
@@ -291,19 +247,15 @@ export default {
return this.$emit('create-new-integration', integrationPayload);
},
reset() {
- this.selectedIntegration = integrationTypes[0].value;
- this.integrationTypeSelect();
-
- if (this.currentIntegration) {
- return this.$emit('clear-current-integration');
- }
-
- return this.resetFormValues();
+ this.resetFormValues();
+ this.resetPayloadAndMapping();
+ this.$emit('clear-current-integration', { type: this.currentIntegration?.type });
},
resetFormValues() {
+ this.selectedIntegration = integrationTypes.none.value;
this.integrationForm.name = '';
this.integrationForm.apiUrl = '';
- this.integrationTestPayload = {
+ this.samplePayload = {
json: null,
error: null,
};
@@ -319,117 +271,135 @@ export default {
variables: { id: this.currentIntegration.id },
});
},
- validateJson() {
- this.integrationTestPayload.error = null;
- if (this.integrationTestPayload.json === '') {
+ validateJson(isSamplePayload = true) {
+ const payload = isSamplePayload ? this.samplePayload : this.testPayload;
+
+ payload.error = null;
+ if (payload.json === '') {
return;
}
try {
- JSON.parse(this.integrationTestPayload.json);
+ JSON.parse(payload.json);
} catch (e) {
- this.integrationTestPayload.error = JSON.stringify(e.message);
+ payload.error = JSON.stringify(e.message);
}
},
parseMapping() {
- // TODO: replace with real BE mutation when ready;
this.parsingPayload = true;
- return new Promise((resolve) => {
- setTimeout(() => resolve(mockedCustomMapping), 1000);
- })
- .then((res) => {
- const mapping = { ...res };
- delete mapping.storedMapping;
- this.customMapping = res;
- this.integrationTestPayload.json = res?.samplePayload.body;
- this.resetSamplePayloadConfirmed = false;
-
- this.$toast.show(this.$options.i18n.integrationFormSteps.step4.payloadParsedSucessMsg);
+ return this.$apollo
+ .query({
+ query: parseSamplePayloadQuery,
+ variables: { projectPath: this.projectPath, payload: this.samplePayload.json },
+ })
+ .then(
+ ({
+ data: {
+ project: { alertManagementPayloadFields },
+ },
+ }) => {
+ this.parsedPayload = alertManagementPayloadFields;
+ this.resetPayloadAndMappingConfirmed = false;
+
+ this.$toast.show(
+ this.$options.i18n.integrationFormSteps.setSamplePayload.payloadParsedSucessMsg,
+ );
+ },
+ )
+ .catch(({ message }) => {
+ this.samplePayload.error = message;
})
.finally(() => {
this.parsingPayload = false;
});
},
- getIntegrationMapping() {
- // TODO: replace with real BE mutation when ready;
- return Promise.resolve(mockedCustomMapping).then((res) => {
- this.customMapping = res;
- this.integrationTestPayload.json = res?.samplePayload.body;
- });
- },
updateMapping(mapping) {
this.mapping = mapping;
},
+ resetPayloadAndMapping() {
+ this.resetPayloadAndMappingConfirmed = true;
+ this.parsedPayload = [];
+ this.updateMapping([]);
+ },
+ getLabelWithStepNumber(step, label) {
+ let stepNumber = editStepNumbers[step];
+
+ if (this.isCreating) {
+ stepNumber = createStepNumbers[step];
+ }
+
+ return stepNumber ? `${stepNumber}.${label}` : label;
+ },
},
};
</script>
<template>
<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
- id="integration-type"
- :label="$options.i18n.integrationFormSteps.step1.label"
- label-for="integration-type"
- >
- <gl-form-select
- v-model="selectedIntegration"
- :disabled="isSelectDisabled"
- class="mw-100"
- :options="$options.integrationTypes"
- @change="integrationTypeSelect"
- />
-
- <div v-if="!canAddIntegration" class="gl-my-4" data-testid="multi-integrations-not-supported">
- <alert-settings-form-help-block
- :message="$options.i18n.integrationFormSteps.step1.enterprise"
- link="https://about.gitlab.com/pricing"
- />
- </div>
- </gl-form-group>
- <gl-collapse v-model="formVisible" class="gl-mt-3">
- <div>
+ <gl-tabs v-model="activeTabIndex">
+ <gl-tab :title="$options.i18n.integrationTabs.configureDetails">
<gl-form-group
- id="name-integration"
- :label="$options.i18n.integrationFormSteps.step2.label"
- label-for="name-integration"
+ v-if="isCreating"
+ id="integration-type"
+ :label="
+ getLabelWithStepNumber(
+ $options.integrationSteps.selectType,
+ $options.i18n.integrationFormSteps.selectType.label,
+ )
+ "
+ label-for="integration-type"
>
- <gl-form-input
- v-model="integrationForm.name"
- :disabled="isPrometheus"
- type="text"
- :placeholder="
- isPrometheus
- ? $options.i18n.integrationFormSteps.step2.prometheus
- : $options.i18n.integrationFormSteps.step2.placeholder
- "
+ <gl-form-select
+ v-model="selectedIntegration"
+ :disabled="isSelectDisabled"
+ class="gl-max-w-full"
+ :options="integrationTypesOptions"
/>
- </gl-form-group>
- <gl-form-group
- id="integration-webhook"
- :label="$options.i18n.integrationFormSteps.step3.label"
- label-for="integration-webhook"
- >
+
<alert-settings-form-help-block
- :message="
- isPrometheus
- ? $options.i18n.integrationFormSteps.step3.prometheusHelp
- : $options.i18n.integrationFormSteps.step3.help
- "
- link="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
+ v-if="!canAddIntegration"
+ disabled="true"
+ class="gl-display-inline-block gl-my-4"
+ :message="$options.i18n.integrationFormSteps.selectType.enterprise"
+ link="https://about.gitlab.com/pricing"
+ data-testid="multi-integrations-not-supported"
/>
+ </gl-form-group>
+ <div class="gl-mt-3">
+ <gl-form-group
+ v-if="isHttp"
+ id="name-integration"
+ :label="
+ getLabelWithStepNumber(
+ $options.integrationSteps.nameIntegration,
+ $options.i18n.integrationFormSteps.nameIntegration.label,
+ )
+ "
+ label-for="name-integration"
+ >
+ <gl-form-input
+ v-model="integrationForm.name"
+ type="text"
+ :placeholder="$options.i18n.integrationFormSteps.nameIntegration.placeholder"
+ />
+ </gl-form-group>
<gl-toggle
v-model="active"
:is-loading="loading"
- :label="__('Active')"
+ :label="$options.i18n.integrationFormSteps.nameIntegration.activeToggle"
class="gl-my-4 gl-font-weight-normal"
/>
<div v-if="isPrometheus" class="gl-my-4">
<span class="gl-font-weight-bold">
- {{ $options.i18n.integrationFormSteps.prometheusFormUrl.label }}
+ {{
+ getLabelWithStepNumber(
+ $options.integrationSteps.setPrometheusApiUrl,
+ $options.i18n.integrationFormSteps.prometheusFormUrl.label,
+ )
+ }}
</span>
<gl-form-input
@@ -444,16 +414,123 @@ export default {
</span>
</div>
+ <template v-if="showMappingBuilder">
+ <gl-form-group
+ data-testid="sample-payload-section"
+ :label="
+ getLabelWithStepNumber(
+ $options.integrationSteps.setSamplePayload,
+ $options.i18n.integrationFormSteps.setSamplePayload.label,
+ )
+ "
+ label-for="sample-payload"
+ class="gl-mb-0!"
+ :invalid-feedback="samplePayload.error"
+ >
+ <alert-settings-form-help-block
+ :message="$options.i18n.integrationFormSteps.setSamplePayload.testPayloadHelpHttp"
+ :link="generic.alertsUsageUrl"
+ />
+
+ <gl-form-textarea
+ id="sample-payload"
+ v-model.trim="samplePayload.json"
+ :disabled="isPayloadEditDisabled"
+ :state="isSampePayloadValid"
+ :placeholder="$options.i18n.integrationFormSteps.setSamplePayload.placeholder"
+ class="gl-my-3"
+ :debounce="$options.JSON_VALIDATE_DELAY"
+ rows="6"
+ max-rows="10"
+ @input="validateJson"
+ />
+ </gl-form-group>
+
+ <gl-button
+ v-if="canEditPayload"
+ v-gl-modal.resetPayloadModal
+ data-testid="payload-action-btn"
+ :disabled="!active"
+ class="gl-mt-3"
+ >
+ {{ $options.i18n.integrationFormSteps.setSamplePayload.editPayload }}
+ </gl-button>
+
+ <gl-button
+ v-else
+ data-testid="payload-action-btn"
+ :class="{ 'gl-mt-3': samplePayload.error }"
+ :disabled="canParseSamplePayload"
+ :loading="parsingPayload"
+ @click="parseMapping"
+ >
+ {{ $options.i18n.integrationFormSteps.setSamplePayload.parsePayload }}
+ </gl-button>
+ <gl-modal
+ modal-id="resetPayloadModal"
+ :title="$options.i18n.integrationFormSteps.setSamplePayload.resetHeader"
+ :ok-title="$options.i18n.integrationFormSteps.setSamplePayload.resetOk"
+ ok-variant="danger"
+ @ok="resetPayloadAndMapping"
+ >
+ {{ $options.i18n.integrationFormSteps.setSamplePayload.resetBody }}
+ </gl-modal>
+
+ <gl-form-group
+ id="mapping-builder"
+ class="gl-mt-5"
+ :label="
+ getLabelWithStepNumber(
+ $options.integrationSteps.customizeMapping,
+ $options.i18n.integrationFormSteps.mapFields.label,
+ )
+ "
+ label-for="mapping-builder"
+ >
+ <span>{{ $options.i18n.integrationFormSteps.mapFields.intro }}</span>
+ <mapping-builder
+ :parsed-payload="parsedPayload"
+ :saved-mapping="mapping"
+ :alert-fields="alertFields"
+ @onMappingUpdate="updateMapping"
+ />
+ </gl-form-group>
+ </template>
+ </div>
+
+ <div class="gl-display-flex gl-justify-content-start gl-py-3">
+ <gl-button
+ type="submit"
+ variant="confirm"
+ class="js-no-auto-disable"
+ data-testid="integration-form-submit"
+ >
+ {{ $options.i18n.saveIntegration }}
+ </gl-button>
+
+ <gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{
+ $options.i18n.cancelAndClose
+ }}</gl-button>
+ </div>
+ </gl-tab>
+
+ <gl-tab :title="$options.i18n.integrationTabs.viewCredentials" :disabled="isCreating">
+ <alert-settings-form-help-block
+ :message="viewCredentialsHelpMsg"
+ link="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html"
+ />
+
+ <gl-form-group id="integration-webhook">
<div class="gl-my-4">
<span class="gl-font-weight-bold">
- {{ s__('AlertSettings|Webhook URL') }}
+ {{ $options.i18n.integrationFormSteps.setupCredentials.webhookUrl }}
</span>
<gl-form-input-group id="url" readonly :value="integrationForm.url">
<template #append>
<clipboard-button
:text="integrationForm.url || ''"
- :title="__('Copy')"
+ :title="$options.i18n.copy"
class="gl-m-0!"
/>
</template>
@@ -462,7 +539,7 @@ export default {
<div class="gl-my-4">
<span class="gl-font-weight-bold">
- {{ $options.i18n.integrationFormSteps.step3.info }}
+ {{ $options.i18n.integrationFormSteps.setupCredentials.authorizationKey }}
</span>
<gl-form-input-group
@@ -474,124 +551,67 @@ export default {
<template #append>
<clipboard-button
:text="integrationForm.token || ''"
- :title="__('Copy')"
+ :title="$options.i18n.copy"
class="gl-m-0!"
/>
</template>
</gl-form-input-group>
-
- <gl-button v-gl-modal.authKeyModal :disabled="isResetAuthKeyDisabled">
- {{ $options.i18n.integrationFormSteps.step3.reset }}
- </gl-button>
- <gl-modal
- modal-id="authKeyModal"
- :title="$options.i18n.integrationFormSteps.step3.reset"
- :ok-title="$options.i18n.integrationFormSteps.step3.reset"
- ok-variant="danger"
- @ok="resetAuthKey"
- >
- {{ $options.i18n.integrationFormSteps.restKeyInfo.label }}
- </gl-modal>
</div>
</gl-form-group>
- <gl-form-group
- id="test-integration"
- :label="$options.i18n.integrationFormSteps.step4.label"
- label-for="test-integration"
- :class="{ 'gl-mb-0!': showMappingBuilder }"
- :invalid-feedback="integrationTestPayload.error"
+ <gl-button v-gl-modal.authKeyModal :disabled="isResetAuthKeyDisabled" variant="danger">
+ {{ $options.i18n.integrationFormSteps.setupCredentials.reset }}
+ </gl-button>
+
+ <gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{
+ $options.i18n.cancelAndClose
+ }}</gl-button>
+
+ <gl-modal
+ modal-id="authKeyModal"
+ :title="$options.i18n.integrationFormSteps.setupCredentials.reset"
+ :ok-title="$options.i18n.integrationFormSteps.setupCredentials.reset"
+ ok-variant="danger"
+ @ok="resetAuthKey"
>
+ {{ $options.i18n.integrationFormSteps.restKeyInfo.label }}
+ </gl-modal>
+ </gl-tab>
+
+ <gl-tab :title="$options.i18n.integrationTabs.sendTestAlert" :disabled="isCreating">
+ <gl-form-group id="test-integration" :invalid-feedback="testPayload.error">
<alert-settings-form-help-block
- :message="
- isPrometheus || !showMappingBuilder
- ? $options.i18n.integrationFormSteps.step4.prometheusHelp
- : $options.i18n.integrationFormSteps.step4.help
- "
+ :message="$options.i18n.integrationFormSteps.setSamplePayload.testPayloadHelp"
:link="generic.alertsUsageUrl"
/>
<gl-form-textarea
id="test-payload"
- v-model.trim="integrationTestPayload.json"
- :disabled="isPayloadEditDisabled"
- :state="jsonIsValid"
- :placeholder="$options.i18n.integrationFormSteps.step4.placeholder"
+ v-model.trim="testPayload.json"
+ :state="isTestPayloadValid"
+ :placeholder="$options.i18n.integrationFormSteps.setSamplePayload.placeholder"
class="gl-my-3"
:debounce="$options.JSON_VALIDATE_DELAY"
rows="6"
max-rows="10"
- @input="validateJson"
+ @input="validateJson(false)"
/>
</gl-form-group>
- <template v-if="showMappingBuilder">
- <gl-button
- v-if="canEditPayload"
- v-gl-modal.resetPayloadModal
- data-testid="payload-action-btn"
- :disabled="!active"
- class="gl-mt-3"
- >
- {{ $options.i18n.integrationFormSteps.step4.editPayload }}
- </gl-button>
-
- <gl-button
- v-else
- data-testid="payload-action-btn"
- :class="{ 'gl-mt-3': integrationTestPayload.error }"
- :disabled="!active"
- :loading="parsingPayload"
- @click="parseMapping"
- >
- {{ $options.i18n.integrationFormSteps.step4.submitPayload }}
- </gl-button>
- <gl-modal
- modal-id="resetPayloadModal"
- :title="$options.i18n.integrationFormSteps.step4.resetHeader"
- :ok-title="$options.i18n.integrationFormSteps.step4.resetOk"
- ok-variant="danger"
- @ok="resetSamplePayloadConfirmed = true"
- >
- {{ $options.i18n.integrationFormSteps.step4.resetBody }}
- </gl-modal>
- </template>
-
- <gl-form-group
- v-if="showMappingBuilder"
- id="mapping-builder"
- class="gl-mt-5"
- :label="$options.i18n.integrationFormSteps.step5.label"
- label-for="mapping-builder"
- >
- <span>{{ $options.i18n.integrationFormSteps.step5.intro }}</span>
- <mapping-builder
- :parsed-payload="parsedSamplePayload"
- :saved-mapping="savedMapping"
- :alert-fields="alertFields"
- @onMappingUpdate="updateMapping"
- />
- </gl-form-group>
- </div>
- <div class="gl-display-flex gl-justify-content-start gl-py-3">
<gl-button
- type="submit"
- variant="success"
+ :disabled="!isTestPayloadValid"
+ data-testid="send-test-alert"
+ variant="confirm"
class="js-no-auto-disable"
- data-testid="integration-form-submit"
- >{{ s__('AlertSettings|Save integration') }}
- </gl-button>
- <gl-button
- data-testid="integration-test-and-submit"
- :disabled="isSubmitTestPayloadDisabled"
- category="secondary"
- variant="success"
- class="gl-mx-3 js-no-auto-disable"
- @click="submitWithTestPayload"
- >{{ s__('AlertSettings|Save and test payload') }}</gl-button
+ @click="sendTestAlert"
>
- <gl-button type="reset" class="js-no-auto-disable">{{ __('Cancel') }}</gl-button>
- </div>
- </gl-collapse>
+ {{ $options.i18n.send }}
+ </gl-button>
+
+ <gl-button type="reset" class="gl-ml-3 js-no-auto-disable">{{
+ $options.i18n.cancelAndClose
+ }}</gl-button>
+ </gl-tab>
+ </gl-tabs>
</gl-form>
</template>
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 366f2209fb2..3ffb652e61b 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
@@ -1,22 +1,26 @@
<script>
+import { GlButton } from '@gitlab/ui';
+import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
+import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { s__ } from '~/locale';
import { typeSet } from '../constants';
-import createHttpIntegrationMutation from '../graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_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 updateCurrentIntergrationMutation from '../graphql/mutations/update_current_intergration.mutation.graphql';
-import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql';
+import updateCurrentHttpIntegrationMutation from '../graphql/mutations/update_current_http_integration.mutation.graphql';
+import updateCurrentPrometheusIntegrationMutation from '../graphql/mutations/update_current_prometheus_integration.mutation.graphql';
import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql';
import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
+import getHttpIntegrationsQuery from '../graphql/queries/get_http_integrations.query.graphql';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import service from '../services';
import {
updateStoreAfterIntegrationDelete,
updateStoreAfterIntegrationAdd,
+ updateStoreAfterHttpIntegrationAdd,
} from '../utils/cache_updates';
import {
DELETE_INTEGRATION_ERROR,
@@ -28,20 +32,24 @@ import {
import IntegrationsList from './alerts_integrations_list.vue';
import AlertSettingsForm from './alerts_settings_form.vue';
+export const i18n = {
+ 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.'),
+ alertSent: s__(
+ 'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
+ ),
+ addNewIntegration: s__('AlertSettings|Add new integration'),
+};
+
export default {
typeSet,
- i18n: {
- 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.'),
- alertSent: s__(
- 'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
- ),
- },
+ i18n,
components: {
IntegrationsList,
AlertSettingsForm,
+ GlButton,
},
inject: {
generic: {
@@ -84,6 +92,28 @@ export default {
createFlash({ message: err });
},
},
+ // TODO: we'll need to update the logic to request specific http integration by its id on edit
+ // when BE adds support for it https://gitlab.com/gitlab-org/gitlab/-/issues/321674
+ // currently the request for ALL http integrations is made and on specific integration edit we search it in the list
+ httpIntegrations: {
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ query: getHttpIntegrationsQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ const { alertManagementHttpIntegrations: { nodes: list = [] } = {} } = data.project || {};
+
+ return {
+ list,
+ };
+ },
+ error(err) {
+ createFlash({ message: err });
+ },
+ },
currentIntegration: {
query: getCurrentIntegrationQuery,
},
@@ -91,9 +121,10 @@ export default {
data() {
return {
isUpdating: false,
- testAlertPayload: null,
integrations: {},
+ httpIntegrations: {},
currentIntegration: null,
+ formVisible: false,
};
},
computed: {
@@ -105,22 +136,28 @@ export default {
},
},
methods: {
+ isHttp(type) {
+ return type === typeSet.http;
+ },
createNewIntegration({ type, variables }) {
const { projectPath } = this;
+ const isHttp = this.isHttp(type);
this.isUpdating = true;
this.$apollo
.mutate({
- mutation:
- type === this.$options.typeSet.http
- ? createHttpIntegrationMutation
- : createPrometheusIntegrationMutation,
+ mutation: isHttp ? createHttpIntegrationMutation : createPrometheusIntegrationMutation,
variables: {
...variables,
projectPath,
},
update(store, { data }) {
updateStoreAfterIntegrationAdd(store, getIntegrationsQuery, data, { projectPath });
+ if (isHttp) {
+ updateStoreAfterHttpIntegrationAdd(store, getHttpIntegrationsQuery, data, {
+ projectPath,
+ });
+ }
},
})
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
@@ -128,18 +165,9 @@ export default {
if (error) {
return createFlash({ message: error });
}
+ const { integration } = httpIntegrationCreate || prometheusIntegrationCreate;
- if (this.testAlertPayload) {
- const integration =
- httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
-
- const payload = {
- ...this.testAlertPayload,
- endpoint: integration.url,
- token: integration.token,
- };
- return this.validateAlertPayload(payload);
- }
+ this.editIntegration(integration);
return createFlash({
message: this.$options.i18n.changesSaved,
@@ -157,10 +185,9 @@ export default {
this.isUpdating = true;
this.$apollo
.mutate({
- mutation:
- type === this.$options.typeSet.http
- ? updateHttpIntegrationMutation
- : updatePrometheusIntegrationMutation,
+ mutation: this.isHttp(type)
+ ? updateHttpIntegrationMutation
+ : updatePrometheusIntegrationMutation,
variables: {
...variables,
id: this.currentIntegration.id,
@@ -172,11 +199,7 @@ export default {
return createFlash({ message: error });
}
- if (this.testAlertPayload) {
- return this.validateAlertPayload();
- }
-
- this.clearCurrentIntegration();
+ this.clearCurrentIntegration({ type });
return createFlash({
message: this.$options.i18n.changesSaved,
@@ -188,23 +211,19 @@ export default {
})
.finally(() => {
this.isUpdating = false;
- this.testAlertPayload = null;
});
},
resetToken({ type, variables }) {
this.isUpdating = true;
this.$apollo
.mutate({
- mutation:
- type === this.$options.typeSet.http
- ? resetHttpTokenMutation
- : resetPrometheusTokenMutation,
+ mutation: this.isHttp(type) ? resetHttpTokenMutation : resetPrometheusTokenMutation,
variables,
})
.then(
({ data: { httpIntegrationResetToken, prometheusIntegrationResetToken } = {} } = {}) => {
- const error =
- httpIntegrationResetToken?.errors[0] || prometheusIntegrationResetToken?.errors[0];
+ const [error] =
+ httpIntegrationResetToken?.errors || prometheusIntegrationResetToken?.errors;
if (error) {
return createFlash({ message: error });
}
@@ -214,10 +233,10 @@ export default {
prometheusIntegrationResetToken?.integration;
this.$apollo.mutate({
- mutation: updateCurrentIntergrationMutation,
- variables: {
- ...integration,
- },
+ mutation: this.isHttp(type)
+ ? updateCurrentHttpIntegrationMutation
+ : updateCurrentPrometheusIntegrationMutation,
+ variables: integration,
});
return createFlash({
@@ -233,33 +252,31 @@ export default {
this.isUpdating = false;
});
},
- editIntegration({ id }) {
- const currentIntegration = this.integrations.list.find(
- (integration) => integration.id === id,
- );
+ editIntegration({ id, type }) {
+ let currentIntegration = this.integrations.list.find((integration) => integration.id === id);
+ if (this.isHttp(type)) {
+ const httpIntegrationMappingData = this.httpIntegrations.list.find(
+ (integration) => integration.id === id,
+ );
+ currentIntegration = { ...currentIntegration, ...httpIntegrationMappingData };
+ }
+
this.$apollo.mutate({
- mutation: updateCurrentIntergrationMutation,
- variables: {
- id: currentIntegration.id,
- name: currentIntegration.name,
- active: currentIntegration.active,
- token: currentIntegration.token,
- type: currentIntegration.type,
- url: currentIntegration.url,
- apiUrl: currentIntegration.apiUrl,
- },
+ mutation: this.isHttp(type)
+ ? updateCurrentHttpIntegrationMutation
+ : updateCurrentPrometheusIntegrationMutation,
+ variables: currentIntegration,
});
+ this.setFormVisibility(true);
},
- deleteIntegration({ id }) {
+ deleteIntegration({ id, type }) {
const { projectPath } = this;
this.isUpdating = true;
this.$apollo
.mutate({
mutation: destroyHttpIntegrationMutation,
- variables: {
- id,
- },
+ variables: { id },
update(store, { data }) {
updateStoreAfterIntegrationDelete(store, getIntegrationsQuery, data, { projectPath });
},
@@ -269,7 +286,7 @@ export default {
if (error) {
return createFlash({ message: error });
}
- this.clearCurrentIntegration();
+ this.clearCurrentIntegration({ type });
return createFlash({
message: this.$options.i18n.integrationRemoved,
type: FLASH_TYPES.SUCCESS,
@@ -282,18 +299,20 @@ export default {
this.isUpdating = false;
});
},
- clearCurrentIntegration() {
- this.$apollo.mutate({
- mutation: updateCurrentIntergrationMutation,
- variables: {},
- });
- },
- setTestAlertPayload(payload) {
- this.testAlertPayload = payload;
+ clearCurrentIntegration({ type }) {
+ if (type) {
+ this.$apollo.mutate({
+ mutation: this.isHttp(type)
+ ? updateCurrentHttpIntegrationMutation
+ : updateCurrentPrometheusIntegrationMutation,
+ variables: {},
+ });
+ }
+ this.setFormVisibility(false);
},
- validateAlertPayload(payload) {
+ testAlertPayload(payload) {
return service
- .updateTestAlert(payload ?? this.testAlertPayload)
+ .updateTestAlert(payload)
.then(() => {
return createFlash({
message: this.$options.i18n.alertSent,
@@ -304,6 +323,9 @@ export default {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
});
},
+ setFormVisibility(visible) {
+ this.formVisible = visible;
+ },
},
};
</script>
@@ -316,7 +338,18 @@ export default {
@edit-integration="editIntegration"
@delete-integration="deleteIntegration"
/>
+ <gl-button
+ v-if="canAddIntegration && !formVisible"
+ category="secondary"
+ variant="confirm"
+ data-testid="add-integration-btn"
+ class="gl-mt-3"
+ @click="setFormVisibility(true)"
+ >
+ {{ $options.i18n.addNewIntegration }}
+ </gl-button>
<alert-settings-form
+ v-if="formVisible"
:loading="isUpdating"
:can-add-integration="canAddIntegration"
:alert-fields="alertFields"
@@ -324,7 +357,7 @@ export default {
@update-integration="updateIntegration"
@reset-token="resetToken"
@clear-current-integration="clearCurrentIntegration"
- @set-test-alert-payload="setTestAlertPayload"
+ @test-alert-payload="testAlertPayload"
/>
</div>
</template>
diff --git a/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json b/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
deleted file mode 100644
index 80fbebf2a60..00000000000
--- a/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
+++ /dev/null
@@ -1,95 +0,0 @@
-{
- "samplePayload": {
- "body": "{\n \"dashboardId\":1,\n \"evalMatches\":[\n {\n \"value\":1,\n \"metric\":\"Count\",\n \"tags\":{}\n }\n ],\n \"imageUrl\":\"https://grafana.com/static/assets/img/blog/mixed_styles.png\",\n \"message\":\"Notification Message\",\n \"orgId\":1,\n \"panelId\":2,\n \"ruleId\":1,\n \"ruleName\":\"Panel Title alert\",\n \"ruleUrl\":\"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\\u0026edit\\u0026tab=alert\\u0026panelId=2\\u0026orgId=1\",\n \"state\":\"alerting\",\n \"tags\":{\n \"tag name\":\"tag value\"\n },\n \"title\":\"[Alerting] Panel Title alert\"\n}\n",
- "payloadAlerFields": {
- "nodes": [
- {
- "path": ["dashboardId"],
- "label": "Dashboard Id",
- "type": "string"
- },
- {
- "path": ["evalMatches"],
- "label": "Eval Matches",
- "type": "array"
- },
- {
- "path": ["createdAt"],
- "label": "Created At",
- "type": "datetime"
- },
- {
- "path": ["imageUrl"],
- "label": "Image Url",
- "type": "string"
- },
- {
- "path": ["message"],
- "label": "Message",
- "type": "string"
- },
- {
- "path": ["orgId"],
- "label": "Org Id",
- "type": "string"
- },
- {
- "path": ["panelId"],
- "label": "Panel Id",
- "type": "string"
- },
- {
- "path": ["ruleId"],
- "label": "Rule Id",
- "type": "string"
- },
- {
- "path": ["ruleName"],
- "label": "Rule Name",
- "type": "string"
- },
- {
- "path": ["ruleUrl"],
- "label": "Rule Url",
- "type": "string"
- },
- {
- "path": ["state"],
- "label": "State",
- "type": "string"
- },
- {
- "path": ["title"],
- "label": "Title",
- "type": "string"
- },
- {
- "path": ["tags", "tag"],
- "label": "Tags",
- "type": "string"
- }
- ]
- }
- },
- "storedMapping": {
- "nodes": [
- {
- "alertFieldName": "title",
- "payloadAlertPaths": "title",
- "fallbackAlertPaths": "ruleUrl"
- },
- {
- "alertFieldName": "description",
- "payloadAlertPaths": "message"
- },
- {
- "alertFieldName": "hosts",
- "payloadAlertPaths": "evalMatches"
- },
- {
- "alertFieldName": "startTime",
- "payloadAlertPaths": "createdAt"
- }
- ]
- }
-}
diff --git a/app/assets/javascripts/alerts_settings/constants.js b/app/assets/javascripts/alerts_settings/constants.js
index ecd7c921b2f..ce6cf61b5dd 100644
--- a/app/assets/javascripts/alerts_settings/constants.js
+++ b/app/assets/javascripts/alerts_settings/constants.js
@@ -1,50 +1,106 @@
-import { s__ } from '~/locale';
+import { s__, __ } from '~/locale';
-// TODO: Remove this as part of the form old removal
export const i18n = {
- usageSection: s__(
- 'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
- ),
- setupSection: s__(
- "AlertSettings|Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
- ),
- errorMsg: s__('AlertSettings|There was an error updating the alert settings.'),
- errorKeyMsg: s__(
- 'AlertSettings|There was an error while trying to reset the key. Please refresh the page to try again.',
- ),
- restKeyInfo: s__(
- 'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
- ),
+ integrationTabs: {
+ configureDetails: s__('AlertSettings|Configure details'),
+ viewCredentials: s__('AlertSettings|View credentials'),
+ sendTestAlert: s__('AlertSettings|Send test alert'),
+ },
+ integrationFormSteps: {
+ selectType: {
+ label: s__('AlertSettings|Select integration type'),
+ enterprise: s__(
+ 'AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations.',
+ ),
+ },
+ nameIntegration: {
+ label: s__('AlertSettings|Name integration'),
+ placeholder: s__('AlertSettings|Enter integration name'),
+ activeToggle: __('Active'),
+ },
+ setupCredentials: {
+ help: s__(
+ "AlertSettings|Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.",
+ ),
+ prometheusHelp: s__(
+ 'AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.',
+ ),
+ webhookUrl: s__('AlertSettings|Webhook URL'),
+ authorizationKey: s__('AlertSettings|Authorization key'),
+ reset: s__('AlertSettings|Reset Key'),
+ },
+ setSamplePayload: {
+ label: s__('AlertSettings|Sample alert payload (optional)'),
+ testPayloadHelpHttp: s__(
+ 'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional).',
+ ),
+ testPayloadHelp: s__(
+ 'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This will allow you to send an alert to an active GitLab alerting point.',
+ ),
+ placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
+ resetHeader: s__('AlertSettings|Reset the mapping'),
+ resetBody: s__(
+ "AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields.",
+ ),
+ resetOk: s__('AlertSettings|Proceed with editing'),
+ editPayload: s__('AlertSettings|Edit payload'),
+ parsePayload: s__('AlertSettings|Parse payload for custom mapping'),
+ payloadParsedSucessMsg: s__(
+ 'AlertSettings|Sample payload has been parsed. You can now map the fields.',
+ ),
+ },
+ mapFields: {
+ label: s__('AlertSettings|Customize alert payload mapping (optional)'),
+ intro: s__(
+ 'AlertSettings|If you intend to create a custom mapping, provide an example payload from your monitoring tool and click "parse payload fields" button to continue. The sample payload is required for completing the custom mapping; if you want to skip the mapping step, progress straight to saving your integration.',
+ ),
+ },
+ prometheusFormUrl: {
+ label: s__('AlertSettings|Prometheus API base URL'),
+ help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
+ },
+ restKeyInfo: {
+ label: s__(
+ 'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
+ ),
+ },
+ },
+ saveIntegration: s__('AlertSettings|Save integration'),
changesSaved: s__('AlertSettings|Your integration was successfully updated.'),
- prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'),
- integrationsInfo: s__(
- 'AlertSettings|Learn more about our our upcoming %{linkStart}integrations%{linkEnd}',
- ),
- resetKey: s__('AlertSettings|Reset key'),
- copyToClipboard: s__('AlertSettings|Copy'),
- apiBaseUrlLabel: s__('AlertSettings|API URL'),
- authKeyLabel: s__('AlertSettings|Authorization key'),
- urlLabel: s__('AlertSettings|Webhook URL'),
- activeLabel: s__('AlertSettings|Active'),
- apiBaseUrlHelpText: s__('AlertSettings|URL cannot be blank and must start with http or https'),
- testAlertInfo: s__('AlertSettings|Test alert payload'),
- alertJson: s__('AlertSettings|Alert test payload'),
- alertJsonPlaceholder: s__('AlertSettings|Enter test alert JSON....'),
- testAlertFailed: s__('AlertSettings|Test failed. Do you still want to save your changes anyway?'),
- testAlertSuccess: s__(
- 'AlertSettings|Test alert sent successfully. If you have made other changes, please save them now.',
- ),
- authKeyRest: s__(
- 'AlertSettings|Authorization key has been successfully reset. Please save your changes now.',
- ),
- integration: s__('AlertSettings|Integration'),
+ cancelAndClose: __('Cancel and close'),
+ send: s__('AlertSettings|Send'),
+ copy: __('Copy'),
};
-export const integrationTypes = [
- { value: '', text: s__('AlertSettings|Select integration type') },
- { value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
- { value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
-];
+export const integrationSteps = {
+ selectType: 'SELECT_TYPE',
+ nameIntegration: 'NAME_INTEGRATION',
+ setPrometheusApiUrl: 'SET_PROMETHEUS_API_URL',
+ setSamplePayload: 'SET_SAMPLE_PAYLOAD',
+ customizeMapping: 'CUSTOMIZE_MAPPING',
+};
+
+export const createStepNumbers = {
+ [integrationSteps.selectType]: 1,
+ [integrationSteps.nameIntegration]: 2,
+ [integrationSteps.setPrometheusApiUrl]: 2,
+ [integrationSteps.setSamplePayload]: 3,
+ [integrationSteps.customizeMapping]: 4,
+};
+
+export const editStepNumbers = {
+ [integrationSteps.selectType]: 1,
+ [integrationSteps.nameIntegration]: 1,
+ [integrationSteps.setPrometheusApiUrl]: null,
+ [integrationSteps.setSamplePayload]: 2,
+ [integrationSteps.customizeMapping]: 3,
+};
+
+export const integrationTypes = {
+ none: { value: '', text: s__('AlertSettings|Select integration type') },
+ http: { value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
+ prometheus: { value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
+};
export const typeSet = {
http: 'HTTP',
@@ -57,14 +113,18 @@ export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
-export const sectionHash = 'js-alert-management-settings';
-
-/* eslint-disable @gitlab/require-i18n-strings */
-
/**
* Tracks snowplow event when user views alerts integration list
*/
export const trackAlertIntegrationsViewsOptions = {
+ /* eslint-disable-next-line @gitlab/require-i18n-strings */
category: 'Alert Integrations',
action: 'view_alert_integrations_list',
};
+
+export const mappingFields = {
+ mapping: 'mapping',
+ fallback: 'fallback',
+};
+
+export const viewCredentialsTabIndex = 1;
diff --git a/app/assets/javascripts/alerts_settings/graphql.js b/app/assets/javascripts/alerts_settings/graphql.js
index 5fd05169533..72817f636ff 100644
--- a/app/assets/javascripts/alerts_settings/graphql.js
+++ b/app/assets/javascripts/alerts_settings/graphql.js
@@ -10,16 +10,25 @@ const resolvers = {
Mutation: {
updateCurrentIntegration: (
_,
- { id = null, name, active, token, type, url, apiUrl },
+ {
+ id = null,
+ name,
+ active,
+ token,
+ type,
+ url,
+ apiUrl,
+ payloadExample,
+ payloadAttributeMappings,
+ payloadAlertFields,
+ },
{ cache },
) => {
const sourceData = cache.readQuery({ query: getCurrentIntegrationQuery });
const data = produce(sourceData, (draftData) => {
if (id === null) {
- // eslint-disable-next-line no-param-reassign
draftData.currentIntegration = null;
} else {
- // eslint-disable-next-line no-param-reassign
draftData.currentIntegration = {
id,
name,
@@ -28,6 +37,9 @@ const resolvers = {
type,
url,
apiUrl,
+ payloadExample,
+ payloadAttributeMappings,
+ payloadAlertFields,
};
}
});
diff --git a/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql
new file mode 100644
index 00000000000..742228e2928
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_item.fragment.graphql
@@ -0,0 +1,7 @@
+#import "./integration_item.fragment.graphql"
+#import "ee_else_ce/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql"
+
+fragment HttpIntegrationItem on AlertManagementHttpIntegration {
+ ...IntegrationItem
+ ...HttpIntegrationPayloadData
+}
diff --git a/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql
new file mode 100644
index 00000000000..df6ad0b712d
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql
@@ -0,0 +1,3 @@
+fragment HttpIntegrationPayloadData on AlertManagementHttpIntegration {
+ id
+}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql
index f3fc10b4bd4..babcdea935d 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql
@@ -1,24 +1,10 @@
-#import "../fragments/integration_item.fragment.graphql"
+#import "../fragments/http_integration_item.fragment.graphql"
-mutation createHttpIntegration(
- $projectPath: ID!
- $name: String!
- $active: Boolean!
- $payloadExample: JsonString
- $payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!]
-) {
- httpIntegrationCreate(
- input: {
- projectPath: $projectPath
- name: $name
- active: $active
- payloadExample: $payloadExample
- payloadAttributeMappings: $payloadAttributeMappings
- }
- ) {
+mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) {
+ httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) {
errors
integration {
- ...IntegrationItem
+ ...HttpIntegrationItem
}
}
}
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
index 0a49c140e6a..a3a50651fd0 100644
--- 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
@@ -1,10 +1,10 @@
-#import "../fragments/integration_item.fragment.graphql"
+#import "../fragments/http_integration_item.fragment.graphql"
mutation destroyHttpIntegration($id: ID!) {
httpIntegrationDestroy(input: { id: $id }) {
errors
integration {
- ...IntegrationItem
+ ...HttpIntegrationItem
}
}
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql
index 178d1e13047..c0754d8e32b 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql
@@ -1,10 +1,10 @@
-#import "../fragments/integration_item.fragment.graphql"
+#import "../fragments/http_integration_item.fragment.graphql"
mutation resetHttpIntegrationToken($id: ID!) {
httpIntegrationResetToken(input: { id: $id }) {
errors
integration {
- ...IntegrationItem
+ ...HttpIntegrationItem
}
}
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql
new file mode 100644
index 00000000000..5f3d305993c
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql
@@ -0,0 +1,25 @@
+mutation updateCurrentHttpIntegration(
+ $id: String
+ $name: String
+ $active: Boolean
+ $token: String
+ $type: String
+ $url: String
+ $apiUrl: String
+ $payloadExample: JsonString
+ $payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!]
+ $payloadAlertFields: [AlertManagementPayloadAlertField!]
+) {
+ updateCurrentIntegration(
+ id: $id
+ name: $name
+ active: $active
+ token: $token
+ type: $type
+ url: $url
+ apiUrl: $apiUrl
+ payloadExample: $payloadExample
+ payloadAttributeMappings: $payloadAttributeMappings
+ payloadAlertFields: $payloadAlertFields
+ ) @client
+}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_prometheus_integration.mutation.graphql
index 3505241309e..5bd63820629 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_intergration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_current_prometheus_integration.mutation.graphql
@@ -1,4 +1,4 @@
-mutation updateCurrentIntegration(
+mutation updateCurrentPrometheusIntegration(
$id: String
$name: String
$active: Boolean
@@ -6,6 +6,7 @@ mutation updateCurrentIntegration(
$type: String
$url: String
$apiUrl: String
+ $samplePayload: String
) {
updateCurrentIntegration(
id: $id
@@ -15,5 +16,6 @@ mutation updateCurrentIntegration(
type: $type
url: $url
apiUrl: $apiUrl
+ samplePayload: $samplePayload
) @client
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql
index bb5b334deeb..37df9ec25eb 100644
--- a/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql
+++ b/app/assets/javascripts/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql
@@ -1,10 +1,10 @@
-#import "../fragments/integration_item.fragment.graphql"
+#import "../fragments/http_integration_item.fragment.graphql"
mutation updateHttpIntegration($id: ID!, $name: String!, $active: Boolean!) {
httpIntegrationUpdate(input: { id: $id, name: $name, active: $active }) {
errors
integration {
- ...IntegrationItem
+ ...HttpIntegrationItem
}
}
}
diff --git a/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql b/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql
new file mode 100644
index 00000000000..833a2d6c12f
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/queries/get_http_integrations.query.graphql
@@ -0,0 +1,12 @@
+#import "ee_else_ce/alerts_settings/graphql/fragments/http_integration_payload_data.fragment.graphql"
+
+# TODO: this query need to accept http integration id to request a sepcific integration
+query getHttpIntegrations($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ alertManagementHttpIntegrations {
+ nodes {
+ ...HttpIntegrationPayloadData
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/alerts_settings/graphql/queries/parse_sample_payload.query.graphql b/app/assets/javascripts/alerts_settings/graphql/queries/parse_sample_payload.query.graphql
new file mode 100644
index 00000000000..159b2661f0b
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/graphql/queries/parse_sample_payload.query.graphql
@@ -0,0 +1,9 @@
+query parsePayloadFields($projectPath: ID!, $payload: String!) {
+ project(fullPath: $projectPath) {
+ alertManagementPayloadFields(payloadExample: $payload) {
+ path
+ label
+ type
+ }
+ }
+}
diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js
index 8506b3fda01..321af9fedb6 100644
--- a/app/assets/javascripts/alerts_settings/index.js
+++ b/app/assets/javascripts/alerts_settings/index.js
@@ -63,10 +63,7 @@ export default (el) => {
render(createElement) {
return createElement('alert-settings-wrapper', {
props: {
- alertFields:
- gon.features?.multipleHttpIntegrationsCustomMapping && parseBoolean(multiIntegrations)
- ? JSON.parse(alertFields)
- : null,
+ alertFields: parseBoolean(multiIntegrations) ? JSON.parse(alertFields) : null,
},
});
},
diff --git a/app/assets/javascripts/alerts_settings/utils/cache_updates.js b/app/assets/javascripts/alerts_settings/utils/cache_updates.js
index 758f3eb6dd4..716c709a931 100644
--- a/app/assets/javascripts/alerts_settings/utils/cache_updates.js
+++ b/app/assets/javascripts/alerts_settings/utils/cache_updates.js
@@ -15,7 +15,6 @@ const deleteIntegrationFromStore = (store, query, { httpIntegrationDestroy }, va
});
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,
);
@@ -46,7 +45,6 @@ const addIntegrationToStore = (
});
const data = produce(sourceData, (draftData) => {
- // eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = [
integration,
...draftData.project.alertManagementIntegrations.nodes,
@@ -60,6 +58,31 @@ const addIntegrationToStore = (
});
};
+const addHttpIntegrationToStore = (store, query, { httpIntegrationCreate }, variables) => {
+ const integration = httpIntegrationCreate?.integration;
+ if (!integration) {
+ return;
+ }
+
+ const sourceData = store.readQuery({
+ query,
+ variables,
+ });
+
+ const data = produce(sourceData, (draftData) => {
+ draftData.project.alertManagementHttpIntegrations.nodes = [
+ integration,
+ ...draftData.project.alertManagementHttpIntegrations.nodes,
+ ];
+ });
+
+ store.writeQuery({
+ query,
+ variables,
+ data,
+ });
+};
+
const onError = (data, message) => {
createFlash({ message });
throw new Error(data.errors);
@@ -82,3 +105,11 @@ export const updateStoreAfterIntegrationAdd = (store, query, data, variables) =>
addIntegrationToStore(store, query, data, variables);
}
};
+
+export const updateStoreAfterHttpIntegrationAdd = (store, query, data, variables) => {
+ if (hasErrors(data)) {
+ onError(data, ADD_INTEGRATION_ERROR);
+ } else {
+ addHttpIntegrationToStore(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
index 979d1ca3ccc..e380257f983 100644
--- a/app/assets/javascripts/alerts_settings/utils/error_messages.js
+++ b/app/assets/javascripts/alerts_settings/utils/error_messages.js
@@ -17,5 +17,5 @@ export const RESET_INTEGRATION_TOKEN_ERROR = s__(
);
export const INTEGRATION_PAYLOAD_TEST_ERROR = s__(
- 'AlertsIntegrations|Integration payload is invalid. You can still save your changes.',
+ 'AlertsIntegrations|Integration payload is invalid.',
);
diff --git a/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js b/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
index a86103540c0..5c4b9bcd505 100644
--- a/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
+++ b/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
@@ -1,3 +1,4 @@
+import { isEqual } from 'lodash';
/**
* Given data for GitLab alert fields, parsed payload fields data and previously stored mapping (if any)
* creates an object in a form convenient to build UI && interact with it
@@ -10,16 +11,19 @@
export const getMappingData = (gitlabFields, payloadFields, savedMapping) => {
return gitlabFields.map((gitlabField) => {
// find fields from payload that match gitlab alert field by type
- const mappingFields = payloadFields.filter(({ type }) => gitlabField.types.includes(type));
+ const mappingFields = payloadFields.filter(({ type }) =>
+ gitlabField.types.includes(type.toLowerCase()),
+ );
// find the mapping that was previously stored
- const foundMapping = savedMapping.find(({ fieldName }) => fieldName === gitlabField.name);
-
- const { fallbackAlertPaths, payloadAlertPaths } = foundMapping || {};
+ const foundMapping = savedMapping.find(
+ ({ fieldName }) => fieldName.toLowerCase() === gitlabField.name,
+ );
+ const { path: mapping, fallbackPath: fallback } = foundMapping || {};
return {
- mapping: payloadAlertPaths,
- fallback: fallbackAlertPaths,
+ mapping,
+ fallback,
searchTerm: '',
fallbackSearchTerm: '',
mappingFields,
@@ -36,7 +40,7 @@ export const getMappingData = (gitlabFields, payloadFields, savedMapping) => {
*/
export const transformForSave = (mappingData) => {
return mappingData.reduce((acc, field) => {
- const mapped = field.mappingFields.find(({ name }) => name === field.mapping);
+ const mapped = field.mappingFields.find(({ path }) => isEqual(path, field.mapping));
if (mapped) {
const { path, type, label } = mapped;
acc.push({
@@ -49,13 +53,3 @@ export const transformForSave = (mappingData) => {
return acc;
}, []);
};
-
-/**
- * Adds `name` prop to each provided by BE parsed payload field
- * @param {Object} payload - parsed sample payload
- *
- * @return {Object} same as input with an extra `name` property which basically serves as a key to make a match
- */
-export const getPayloadFields = (payload) => {
- return payload.map((field) => ({ ...field, name: field.path.join('_') }));
-};