summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/alerts_settings/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/alerts_settings/components')
-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
5 files changed, 469 insertions, 509 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"
- }
- ]
- }
-}