summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/alerts_settings
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/alerts_settings')
-rw-r--r--app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue77
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue47
-rw-r--r--app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue26
-rw-r--r--app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json112
-rw-r--r--app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json78
-rw-r--r--app/assets/javascripts/alerts_settings/graphql.js2
-rw-r--r--app/assets/javascripts/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql18
-rw-r--r--app/assets/javascripts/alerts_settings/index.js12
-rw-r--r--app/assets/javascripts/alerts_settings/utils/mapping_transformations.js61
9 files changed, 208 insertions, 225 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 c52e9f5c264..1135562834a 100644
--- a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
+++ b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue
@@ -1,5 +1,4 @@
<script>
-import Vue from 'vue';
import {
GlIcon,
GlFormInput,
@@ -8,11 +7,15 @@ import {
GlSearchBoxByType,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
+import { cloneDeep } from 'lodash';
+import Vue from 'vue';
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { s__, __ } from '~/locale';
-// 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 gitlabFieldsMock from './mocks/gitlabFields.json';
+import {
+ getMappingData,
+ getPayloadFields,
+ transformForSave,
+} from '../utils/mapping_transformations';
export const i18n = {
columns: {
@@ -40,18 +43,25 @@ export default {
directives: {
GlTooltip,
},
- inject: {
- gitlabAlertFields: {
- default: gitlabFieldsMock,
- },
- },
props: {
- payloadFields: {
+ alertFields: {
+ type: Array,
+ required: true,
+ validator: (fields) => {
+ return (
+ fields.length &&
+ fields.every(({ name, types, label }) => {
+ return typeof name === 'string' && Array.isArray(types) && typeof label === 'string';
+ })
+ );
+ },
+ },
+ parsedPayload: {
type: Array,
required: false,
default: () => [],
},
- mapping: {
+ savedMapping: {
type: Array,
required: false,
default: () => [],
@@ -59,31 +69,18 @@ export default {
},
data() {
return {
- gitlabFields: this.gitlabAlertFields,
+ gitlabFields: cloneDeep(this.alertFields),
};
},
computed: {
+ payloadFields() {
+ return getPayloadFields(this.parsedPayload);
+ },
mappingData() {
- return this.gitlabFields.map((gitlabField) => {
- const mappingFields = this.payloadFields.filter(({ type }) =>
- type.some((t) => gitlabField.compatibleTypes.includes(t)),
- );
-
- const foundMapping = this.mapping.find(
- ({ alertFieldName }) => alertFieldName === gitlabField.name,
- );
-
- const { fallbackAlertPaths, payloadAlertPaths } = foundMapping || {};
-
- return {
- mapping: payloadAlertPaths,
- fallback: fallbackAlertPaths,
- searchTerm: '',
- fallbackSearchTerm: '',
- mappingFields,
- ...gitlabField,
- };
- });
+ return getMappingData(this.gitlabFields, this.payloadFields, this.savedMapping);
+ },
+ hasFallbackColumn() {
+ return this.gitlabFields.some(({ numberOfFallbacks }) => Boolean(numberOfFallbacks));
},
},
methods: {
@@ -91,6 +88,7 @@ export default {
const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey);
const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [valueKey]: mappingKey } };
Vue.set(this.gitlabFields, fieldIndex, updatedField);
+ this.$emit('onMappingUpdate', transformForSave(this.mappingData));
},
setSearchTerm(search = '', searchFieldKey, gitlabKey) {
const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey);
@@ -99,7 +97,6 @@ export default {
},
filterFields(searchTerm = '', fields) {
const search = searchTerm.toLowerCase();
-
return fields.filter((field) => field.label.toLowerCase().includes(search));
},
isSelected(fieldValue, mapping) {
@@ -111,8 +108,10 @@ export default {
this.$options.i18n.makeSelection
);
},
- getFieldValue({ label, type }) {
- return `${label} (${type.join(__(' or '))})`;
+ getFieldValue({ label, types }) {
+ const type = types.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(__(' or '));
+
+ return `${label} (${type})`;
},
noResults(searchTerm, fields) {
return !this.filterFields(searchTerm, fields).length;
@@ -131,7 +130,11 @@ export default {
<h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
{{ $options.i18n.columns.payloadKeyTitle }}
</h5>
- <h5 id="fallbackFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
+ <h5
+ v-if="hasFallbackColumn"
+ id="fallbackFieldsHeader"
+ class="gl-display-table-cell gl-py-3 gl-pr-3"
+ >
{{ $options.i18n.columns.fallbackKeyTitle }}
<gl-icon
v-gl-tooltip
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 1ae7f826ce6..18372c54b84 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue
@@ -12,18 +12,18 @@ import {
GlModalDirective,
GlToggle,
} from '@gitlab/ui';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import MappingBuilder from './alert_mapping_builder.vue';
-import AlertSettingsFormHelpBlock from './alert_settings_form_help_block.vue';
-import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
integrationTypes,
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
typeSet,
} from '../constants';
+import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.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
@@ -125,6 +125,9 @@ export default {
prometheus: {
default: {},
},
+ multiIntegrations: {
+ default: false,
+ },
},
props: {
loading: {
@@ -135,6 +138,11 @@ export default {
type: Boolean,
required: true,
},
+ alertFields: {
+ type: Array,
+ required: false,
+ default: null,
+ },
},
apollo: {
currentIntegration: {
@@ -152,6 +160,7 @@ export default {
},
resetSamplePayloadConfirmed: false,
customMapping: null,
+ mapping: [],
parsingPayload: false,
currentIntegration: null,
};
@@ -195,14 +204,16 @@ export default {
},
showMappingBuilder() {
return (
+ this.multiIntegrations &&
this.glFeatures.multipleHttpIntegrationsCustomMapping &&
- this.selectedIntegration === typeSet.http
+ this.selectedIntegration === typeSet.http &&
+ this.alertFields?.length
);
},
- mappingBuilderFields() {
+ parsedSamplePayload() {
return this.customMapping?.samplePayload?.payloadAlerFields?.nodes;
},
- mappingBuilderMapping() {
+ savedMapping() {
return this.customMapping?.storedMapping?.nodes;
},
hasSamplePayload() {
@@ -255,9 +266,20 @@ export default {
},
submit() {
const { name, apiUrl } = this.integrationForm;
+ const customMappingVariables = this.glFeatures.multipleHttpIntegrationsCustomMapping
+ ? {
+ payloadAttributeMappings: this.mapping,
+ payloadExample: this.integrationTestPayload.json,
+ }
+ : {};
+
const variables =
this.selectedIntegration === typeSet.http
- ? { name, active: this.active }
+ ? {
+ name,
+ active: this.active,
+ ...customMappingVariables,
+ }
: { apiUrl, active: this.active };
const integrationPayload = { type: this.selectedIntegration, variables };
@@ -336,6 +358,9 @@ export default {
this.integrationTestPayload.json = res?.samplePayload.body;
});
},
+ updateMapping(mapping) {
+ this.mapping = mapping;
+ },
},
};
</script>
@@ -541,8 +566,10 @@ export default {
>
<span>{{ $options.i18n.integrationFormSteps.step5.intro }}</span>
<mapping-builder
- :payload-fields="mappingBuilderFields"
- :mapping="mappingBuilderMapping"
+ :parsed-payload="parsedSamplePayload"
+ :saved-mapping="savedMapping"
+ :alert-fields="alertFields"
+ @onMappingUpdate="updateMapping"
/>
</gl-form-group>
</div>
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 d0cac066ffa..366f2209fb2 100644
--- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
+++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue
@@ -1,21 +1,19 @@
<script>
-import { s__ } from '~/locale';
-import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
-import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
-import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
+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 updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql';
-import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql';
import updateCurrentIntergrationMutation from '../graphql/mutations/update_current_intergration.mutation.graphql';
-import IntegrationsList from './alerts_integrations_list.vue';
-import AlertSettingsForm from './alerts_settings_form.vue';
+import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql';
+import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql';
+import getCurrentIntegrationQuery from '../graphql/queries/get_current_integration.query.graphql';
+import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import service from '../services';
-import { typeSet } from '../constants';
import {
updateStoreAfterIntegrationDelete,
updateStoreAfterIntegrationAdd,
@@ -27,6 +25,8 @@ import {
UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR,
} from '../utils/error_messages';
+import IntegrationsList from './alerts_integrations_list.vue';
+import AlertSettingsForm from './alerts_settings_form.vue';
export default {
typeSet,
@@ -57,6 +57,13 @@ export default {
default: false,
},
},
+ props: {
+ alertFields: {
+ type: Array,
+ required: false,
+ default: null,
+ },
+ },
apollo: {
integrations: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
@@ -312,6 +319,7 @@ export default {
<alert-settings-form
:loading="isUpdating"
:can-add-integration="canAddIntegration"
+ :alert-fields="alertFields"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
@reset-token="resetToken"
diff --git a/app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json b/app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json
deleted file mode 100644
index ac559a30eda..00000000000
--- a/app/assets/javascripts/alerts_settings/components/mocks/gitlabFields.json
+++ /dev/null
@@ -1,112 +0,0 @@
-[
- {
- "name": "title",
- "label": "Title",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ],
- "numberOfFallbacks": 1
- },
- {
- "name": "description",
- "label": "Description",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "startTime",
- "label": "Start time",
- "type": [
- "DateTime"
- ],
- "compatibleTypes": [
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "service",
- "label": "Service",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "monitoringTool",
- "label": "Monitoring tool",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "hosts",
- "label": "Hosts",
- "type": [
- "String",
- "Array"
- ],
- "compatibleTypes": [
- "String",
- "Array",
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "severity",
- "label": "Severity",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "fingerprint",
- "label": "Fingerprint",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ]
- },
- {
- "name": "environment",
- "label": "Environment",
- "type": [
- "String"
- ],
- "compatibleTypes": [
- "String",
- "Number",
- "DateTime"
- ]
- }
-]
diff --git a/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json b/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
index 5326678155d..80fbebf2a60 100644
--- a/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
+++ b/app/assets/javascripts/alerts_settings/components/mocks/parsedMapping.json
@@ -4,95 +4,69 @@
"payloadAlerFields": {
"nodes": [
{
- "name": "dashboardId",
+ "path": ["dashboardId"],
"label": "Dashboard Id",
- "type": [
- "Number"
- ]
+ "type": "string"
},
{
- "name": "evalMatches",
+ "path": ["evalMatches"],
"label": "Eval Matches",
- "type": [
- "Array"
- ]
+ "type": "array"
},
{
- "name": "createdAt",
+ "path": ["createdAt"],
"label": "Created At",
- "type": [
- "DateTime"
- ]
+ "type": "datetime"
},
{
- "name": "imageUrl",
+ "path": ["imageUrl"],
"label": "Image Url",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "message",
+ "path": ["message"],
"label": "Message",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "orgId",
+ "path": ["orgId"],
"label": "Org Id",
- "type": [
- "Number"
- ]
+ "type": "string"
},
{
- "name": "panelId",
+ "path": ["panelId"],
"label": "Panel Id",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "ruleId",
+ "path": ["ruleId"],
"label": "Rule Id",
- "type": [
- "Number"
- ]
+ "type": "string"
},
{
- "name": "ruleName",
+ "path": ["ruleName"],
"label": "Rule Name",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "ruleUrl",
+ "path": ["ruleUrl"],
"label": "Rule Url",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "state",
+ "path": ["state"],
"label": "State",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "title",
+ "path": ["title"],
"label": "Title",
- "type": [
- "String"
- ]
+ "type": "string"
},
{
- "name": "tags",
+ "path": ["tags", "tag"],
"label": "Tags",
- "type": [
- "Object"
- ]
+ "type": "string"
}
]
}
diff --git a/app/assets/javascripts/alerts_settings/graphql.js b/app/assets/javascripts/alerts_settings/graphql.js
index 3dbfa69a8e9..5fd05169533 100644
--- a/app/assets/javascripts/alerts_settings/graphql.js
+++ b/app/assets/javascripts/alerts_settings/graphql.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import produce from 'immer';
+import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql';
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 d1dacbad40a..f3fc10b4bd4 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,7 +1,21 @@
#import "../fragments/integration_item.fragment.graphql"
-mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) {
- httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) {
+mutation createHttpIntegration(
+ $projectPath: ID!
+ $name: String!
+ $active: Boolean!
+ $payloadExample: JsonString
+ $payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!]
+) {
+ httpIntegrationCreate(
+ input: {
+ projectPath: $projectPath
+ name: $name
+ active: $active
+ payloadExample: $payloadExample
+ payloadAttributeMappings: $payloadAttributeMappings
+ }
+ ) {
errors
integration {
...IntegrationItem
diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js
index 85858956987..8506b3fda01 100644
--- a/app/assets/javascripts/alerts_settings/index.js
+++ b/app/assets/javascripts/alerts_settings/index.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
+import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
import apolloProvider from './graphql';
@@ -31,6 +31,7 @@ export default (el) => {
url,
projectPath,
multiIntegrations,
+ alertFields,
} = el.dataset;
return new Vue({
@@ -60,7 +61,14 @@ export default (el) => {
},
apolloProvider,
render(createElement) {
- return createElement('alert-settings-wrapper');
+ return createElement('alert-settings-wrapper', {
+ props: {
+ alertFields:
+ gon.features?.multipleHttpIntegrationsCustomMapping && parseBoolean(multiIntegrations)
+ ? JSON.parse(alertFields)
+ : null,
+ },
+ });
},
});
};
diff --git a/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js b/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
new file mode 100644
index 00000000000..a86103540c0
--- /dev/null
+++ b/app/assets/javascripts/alerts_settings/utils/mapping_transformations.js
@@ -0,0 +1,61 @@
+/**
+ * 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
+ * @param {Object} gitlabFields - structure describing GitLab alert fields
+ * @param {Object} payloadFields - parsed from sample JSON sample alert fields
+ * @param {Object} savedMapping - GitLab fields to parsed fields mapping
+ *
+ * @return {Object} mapping data for UI mapping builder
+ */
+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));
+
+ // find the mapping that was previously stored
+ const foundMapping = savedMapping.find(({ fieldName }) => fieldName === gitlabField.name);
+
+ const { fallbackAlertPaths, payloadAlertPaths } = foundMapping || {};
+
+ return {
+ mapping: payloadAlertPaths,
+ fallback: fallbackAlertPaths,
+ searchTerm: '',
+ fallbackSearchTerm: '',
+ mappingFields,
+ ...gitlabField,
+ };
+ });
+};
+
+/**
+ * Based on mapping data configured by the user creates an object in a format suitable for save on BE
+ * @param {Object} mappingData - structure describing mapping between GitLab fields and parsed payload fields
+ *
+ * @return {Object} mapping data to send to BE
+ */
+export const transformForSave = (mappingData) => {
+ return mappingData.reduce((acc, field) => {
+ const mapped = field.mappingFields.find(({ name }) => name === field.mapping);
+ if (mapped) {
+ const { path, type, label } = mapped;
+ acc.push({
+ fieldName: field.name.toUpperCase(),
+ path,
+ type: type.toUpperCase(),
+ label,
+ });
+ }
+ 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('_') }));
+};