Commit cc4e8fd5 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Ezekiel Kigbo

Integrate mapping creation with BE

parent c200cda7
...@@ -13,6 +13,12 @@ import { s__, __ } from '~/locale'; ...@@ -13,6 +13,12 @@ import { s__, __ } from '~/locale';
// data format is defined and will be the same as mocked (maybe with some minor changes) // 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 // feature rollout plan - https://gitlab.com/gitlab-org/gitlab/-/issues/262707#note_442529171
import gitlabFieldsMock from './mocks/gitlabFields.json'; import gitlabFieldsMock from './mocks/gitlabFields.json';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import {
getMappingData,
getPayloadFields,
transformForSave,
} from '../utils/mapping_transformations';
export const i18n = { export const i18n = {
columns: { columns: {
...@@ -46,12 +52,12 @@ export default { ...@@ -46,12 +52,12 @@ export default {
}, },
}, },
props: { props: {
payloadFields: { parsedPayload: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
}, },
mapping: { savedMapping: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
...@@ -63,27 +69,11 @@ export default { ...@@ -63,27 +69,11 @@ export default {
}; };
}, },
computed: { computed: {
payloadFields() {
return getPayloadFields(this.parsedPayload);
},
mappingData() { mappingData() {
return this.gitlabFields.map((gitlabField) => { return getMappingData(this.gitlabFields, this.payloadFields, this.savedMapping);
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,
};
});
}, },
}, },
methods: { methods: {
...@@ -91,6 +81,7 @@ export default { ...@@ -91,6 +81,7 @@ export default {
const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey); const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey);
const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [valueKey]: mappingKey } }; const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [valueKey]: mappingKey } };
Vue.set(this.gitlabFields, fieldIndex, updatedField); Vue.set(this.gitlabFields, fieldIndex, updatedField);
this.$emit('onMappingUpdate', transformForSave(this.mappingData));
}, },
setSearchTerm(search = '', searchFieldKey, gitlabKey) { setSearchTerm(search = '', searchFieldKey, gitlabKey) {
const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey); const fieldIndex = this.gitlabFields.findIndex((field) => field.name === gitlabKey);
...@@ -99,7 +90,6 @@ export default { ...@@ -99,7 +90,6 @@ export default {
}, },
filterFields(searchTerm = '', fields) { filterFields(searchTerm = '', fields) {
const search = searchTerm.toLowerCase(); const search = searchTerm.toLowerCase();
return fields.filter((field) => field.label.toLowerCase().includes(search)); return fields.filter((field) => field.label.toLowerCase().includes(search));
}, },
isSelected(fieldValue, mapping) { isSelected(fieldValue, mapping) {
...@@ -112,7 +102,9 @@ export default { ...@@ -112,7 +102,9 @@ export default {
); );
}, },
getFieldValue({ label, type }) { getFieldValue({ label, type }) {
return `${label} (${type.join(__(' or '))})`; const types = type.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(__(' or '));
return `${label} (${types})`;
}, },
noResults(searchTerm, fields) { noResults(searchTerm, fields) {
return !this.filterFields(searchTerm, fields).length; return !this.filterFields(searchTerm, fields).length;
......
...@@ -152,6 +152,7 @@ export default { ...@@ -152,6 +152,7 @@ export default {
}, },
resetSamplePayloadConfirmed: false, resetSamplePayloadConfirmed: false,
customMapping: null, customMapping: null,
mapping: [],
parsingPayload: false, parsingPayload: false,
currentIntegration: null, currentIntegration: null,
}; };
...@@ -199,10 +200,10 @@ export default { ...@@ -199,10 +200,10 @@ export default {
this.selectedIntegration === typeSet.http this.selectedIntegration === typeSet.http
); );
}, },
mappingBuilderFields() { parsedSamplePayload() {
return this.customMapping?.samplePayload?.payloadAlerFields?.nodes; return this.customMapping?.samplePayload?.payloadAlerFields?.nodes;
}, },
mappingBuilderMapping() { savedMapping() {
return this.customMapping?.storedMapping?.nodes; return this.customMapping?.storedMapping?.nodes;
}, },
hasSamplePayload() { hasSamplePayload() {
...@@ -255,9 +256,20 @@ export default { ...@@ -255,9 +256,20 @@ export default {
}, },
submit() { submit() {
const { name, apiUrl } = this.integrationForm; const { name, apiUrl } = this.integrationForm;
const customMappingVariables = this.glFeatures.multipleHttpIntegrationsCustomMapping
? {
payloadAttributeMappings: this.mapping,
payloadExample: this.integrationTestPayload.json,
}
: {};
const variables = const variables =
this.selectedIntegration === typeSet.http this.selectedIntegration === typeSet.http
? { name, active: this.active } ? {
name,
active: this.active,
...customMappingVariables,
}
: { apiUrl, active: this.active }; : { apiUrl, active: this.active };
const integrationPayload = { type: this.selectedIntegration, variables }; const integrationPayload = { type: this.selectedIntegration, variables };
...@@ -336,6 +348,9 @@ export default { ...@@ -336,6 +348,9 @@ export default {
this.integrationTestPayload.json = res?.samplePayload.body; this.integrationTestPayload.json = res?.samplePayload.body;
}); });
}, },
updateMapping(mapping) {
this.mapping = mapping;
},
}, },
}; };
</script> </script>
...@@ -541,8 +556,9 @@ export default { ...@@ -541,8 +556,9 @@ export default {
> >
<span>{{ $options.i18n.integrationFormSteps.step5.intro }}</span> <span>{{ $options.i18n.integrationFormSteps.step5.intro }}</span>
<mapping-builder <mapping-builder
:payload-fields="mappingBuilderFields" :parsed-payload="parsedSamplePayload"
:mapping="mappingBuilderMapping" :saved-mapping="savedMapping"
@onMappingUpdate="updateMapping"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
......
[ [
{ {
"name": "title", "name": "TITLE",
"label": "Title", "label": "Title",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
], ],
"numberOfFallbacks": 1 "numberOfFallbacks": 1
}, },
{ {
"name": "description", "name": "DESCRIPTION",
"label": "Description", "label": "Description",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "startTime", "name": "START_TIME",
"label": "Start time", "label": "Start time",
"type": [ "type": [
"DateTime" "DATETIME"
], ],
"compatibleTypes": [ "compatibleTypes": [
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "service", "name": "END_TIME",
"label": "End time",
"type": [
"DATETIME"
],
"compatibleTypes": [
"NUMBER",
"DATETIME"
]
},
{
"name": "SERVICE",
"label": "Service", "label": "Service",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "monitoringTool", "name": "MONITORING_TOOL",
"label": "Monitoring tool", "label": "Monitoring tool",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "hosts", "name": "HOSTS",
"label": "Hosts", "label": "Hosts",
"type": [ "type": [
"String", "STRING",
"Array" "ARRAY"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Array", "ARRAY",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "severity", "name": "SEVERITY",
"label": "Severity", "label": "Severity",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "fingerprint", "name": "FINGERPRINT",
"label": "Fingerprint", "label": "Fingerprint",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
}, },
{ {
"name": "environment", "name": "GITLAB_ENVIRONMENT_NAME",
"label": "Environment", "label": "Environment",
"type": [ "type": [
"String" "STRING"
], ],
"compatibleTypes": [ "compatibleTypes": [
"String", "STRING",
"Number", "NUMBER",
"DateTime" "DATETIME"
] ]
} }
] ]
...@@ -4,95 +4,69 @@ ...@@ -4,95 +4,69 @@
"payloadAlerFields": { "payloadAlerFields": {
"nodes": [ "nodes": [
{ {
"name": "dashboardId", "path": ["dashboardId"],
"label": "Dashboard Id", "label": "Dashboard Id",
"type": [ "type": "STRING"
"Number"
]
}, },
{ {
"name": "evalMatches", "path": ["evalMatches"],
"label": "Eval Matches", "label": "Eval Matches",
"type": [ "type": "ARRAY"
"Array"
]
}, },
{ {
"name": "createdAt", "path": ["createdAt"],
"label": "Created At", "label": "Created At",
"type": [ "type": "DATETIME"
"DateTime"
]
}, },
{ {
"name": "imageUrl", "path": ["imageUrl"],
"label": "Image Url", "label": "Image Url",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "message", "path": ["message"],
"label": "Message", "label": "Message",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "orgId", "path": ["orgId"],
"label": "Org Id", "label": "Org Id",
"type": [ "type": "STRING"
"Number"
]
}, },
{ {
"name": "panelId", "path": ["panelId"],
"label": "Panel Id", "label": "Panel Id",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "ruleId", "path": ["ruleId"],
"label": "Rule Id", "label": "Rule Id",
"type": [ "type": "STRING"
"Number"
]
}, },
{ {
"name": "ruleName", "path": ["ruleName"],
"label": "Rule Name", "label": "Rule Name",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "ruleUrl", "path": ["ruleUrl"],
"label": "Rule Url", "label": "Rule Url",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "state", "path": ["state"],
"label": "State", "label": "State",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "title", "path": ["title"],
"label": "Title", "label": "Title",
"type": [ "type": "STRING"
"String"
]
}, },
{ {
"name": "tags", "path": ["tags", "tag"],
"label": "Tags", "label": "Tags",
"type": [ "type": "STRING"
"Object"
]
} }
] ]
} }
......
#import "../fragments/integration_item.fragment.graphql" #import "../fragments/integration_item.fragment.graphql"
mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) { mutation createHttpIntegration(
httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) { $projectPath: ID!
$name: String!
$active: Boolean!
$payloadExample: JsonString
$payloadAttributeMappings: [AlertManagementPayloadAlertFieldInput!]
) {
httpIntegrationCreate(
input: {
projectPath: $projectPath
name: $name
active: $active
payloadExample: $payloadExample
payloadAttributeMappings: $payloadAttributeMappings
}
) {
errors errors
integration { integration {
...IntegrationItem ...IntegrationItem
......
/**
* 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.compatibleTypes.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,
path,
type,
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('_') }));
};
...@@ -3,6 +3,8 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -3,6 +3,8 @@ import { shallowMount } from '@vue/test-utils';
import AlertMappingBuilder, { i18n } from '~/alerts_settings/components/alert_mapping_builder.vue'; import AlertMappingBuilder, { i18n } from '~/alerts_settings/components/alert_mapping_builder.vue';
import gitlabFields from '~/alerts_settings/components/mocks/gitlabFields.json'; import gitlabFields from '~/alerts_settings/components/mocks/gitlabFields.json';
import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json'; import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import * as transformationUtils from '~/alerts_settings/utils/mapping_transformations';
describe('AlertMappingBuilder', () => { describe('AlertMappingBuilder', () => {
let wrapper; let wrapper;
...@@ -10,8 +12,8 @@ describe('AlertMappingBuilder', () => { ...@@ -10,8 +12,8 @@ describe('AlertMappingBuilder', () => {
function mountComponent() { function mountComponent() {
wrapper = shallowMount(AlertMappingBuilder, { wrapper = shallowMount(AlertMappingBuilder, {
propsData: { propsData: {
payloadFields: parsedMapping.samplePayload.payloadAlerFields.nodes, parsedPayload: parsedMapping.samplePayload.payloadAlerFields.nodes,
mapping: parsedMapping.storedMapping.nodes, savedMapping: parsedMapping.storedMapping.nodes,
}, },
}); });
} }
...@@ -44,7 +46,8 @@ describe('AlertMappingBuilder', () => { ...@@ -44,7 +46,8 @@ describe('AlertMappingBuilder', () => {
it('renders disabled form input for each mapped field', () => { it('renders disabled form input for each mapped field', () => {
gitlabFields.forEach((field, index) => { gitlabFields.forEach((field, index) => {
const input = findColumnInRow(index + 1, 0).find(GlFormInput); const input = findColumnInRow(index + 1, 0).find(GlFormInput);
expect(input.attributes('value')).toBe(`${field.label} (${field.type.join(' or ')})`); const types = field.type.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(' or ');
expect(input.attributes('value')).toBe(`${field.label} (${types})`);
expect(input.attributes('disabled')).toBe(''); expect(input.attributes('disabled')).toBe('');
}); });
}); });
...@@ -59,16 +62,14 @@ describe('AlertMappingBuilder', () => { ...@@ -59,16 +62,14 @@ describe('AlertMappingBuilder', () => {
it('renders mapping dropdown for each field', () => { it('renders mapping dropdown for each field', () => {
gitlabFields.forEach(({ compatibleTypes }, index) => { gitlabFields.forEach(({ compatibleTypes }, index) => {
const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown); const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown);
const searchBox = dropdown.find(GlSearchBoxByType); const searchBox = dropdown.findComponent(GlSearchBoxByType);
const dropdownItems = dropdown.findAll(GlDropdownItem); const dropdownItems = dropdown.findAllComponents(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields; const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
const numberOfMappingOptions = nodes.filter(({ type }) => const mappingOptions = nodes.filter(({ type }) => compatibleTypes.includes(type));
type.some((t) => compatibleTypes.includes(t)),
);
expect(dropdown.exists()).toBe(true); expect(dropdown.exists()).toBe(true);
expect(searchBox.exists()).toBe(true); expect(searchBox.exists()).toBe(true);
expect(dropdownItems).toHaveLength(numberOfMappingOptions.length); expect(dropdownItems).toHaveLength(mappingOptions.length);
}); });
}); });
...@@ -78,16 +79,23 @@ describe('AlertMappingBuilder', () => { ...@@ -78,16 +79,23 @@ describe('AlertMappingBuilder', () => {
expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks)); expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks));
if (numberOfFallbacks) { if (numberOfFallbacks) {
const searchBox = dropdown.find(GlSearchBoxByType); const searchBox = dropdown.findComponent(GlSearchBoxByType);
const dropdownItems = dropdown.findAll(GlDropdownItem); const dropdownItems = dropdown.findAllComponents(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields; const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
const numberOfMappingOptions = nodes.filter(({ type }) => const mappingOptions = nodes.filter(({ type }) => compatibleTypes.includes(type));
type.some((t) => compatibleTypes.includes(t)),
);
expect(searchBox.exists()).toBe(Boolean(numberOfFallbacks)); expect(searchBox.exists()).toBe(Boolean(numberOfFallbacks));
expect(dropdownItems).toHaveLength(numberOfMappingOptions.length); expect(dropdownItems).toHaveLength(mappingOptions.length);
} }
}); });
}); });
it('emits event with selected mapping', () => {
const mappingToSave = { fieldName: 'TITLE', mapping: 'PARSED_TITLE' };
jest.spyOn(transformationUtils, 'transformForSave').mockReturnValue(mappingToSave);
const dropdown = findColumnInRow(1, 2).find(GlDropdown);
const option = dropdown.find(GlDropdownItem);
option.vm.$emit('click');
expect(wrapper.emitted('onMappingUpdate')[0]).toEqual([mappingToSave]);
});
}); });
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
import MappingBuilder from '~/alerts_settings/components/alert_mapping_builder.vue';
import { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
import { typeSet } from '~/alerts_settings/constants'; import { typeSet } from '~/alerts_settings/constants';
...@@ -49,6 +50,7 @@ describe('AlertsSettingsFormNew', () => { ...@@ -49,6 +50,7 @@ describe('AlertsSettingsFormNew', () => {
const findFormToggle = () => wrapper.find(GlToggle); const findFormToggle = () => wrapper.find(GlToggle);
const findTestPayloadSection = () => wrapper.find(`[id = "test-integration"]`); const findTestPayloadSection = () => wrapper.find(`[id = "test-integration"]`);
const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`); const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`);
const findMappingBuilder = () => wrapper.findComponent(MappingBuilder);
const findSubmitButton = () => wrapper.find(`[type = "submit"]`); const findSubmitButton = () => wrapper.find(`[type = "submit"]`);
const findMultiSupportText = () => const findMultiSupportText = () =>
wrapper.find(`[data-testid="multi-integrations-not-supported"]`); wrapper.find(`[data-testid="multi-integrations-not-supported"]`);
...@@ -63,6 +65,16 @@ describe('AlertsSettingsFormNew', () => { ...@@ -63,6 +65,16 @@ describe('AlertsSettingsFormNew', () => {
} }
}); });
const selectOptionAtIndex = async (index) => {
const options = findSelect().findAll('option');
await options.at(index).setSelected();
};
const enableIntegration = (index, value) => {
findFormFields().at(index).setValue(value);
findFormToggle().trigger('click');
};
describe('with default values', () => { describe('with default values', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
...@@ -80,10 +92,7 @@ describe('AlertsSettingsFormNew', () => { ...@@ -80,10 +92,7 @@ describe('AlertsSettingsFormNew', () => {
}); });
it('shows the rest of the form when the dropdown is used', async () => { it('shows the rest of the form when the dropdown is used', async () => {
const options = findSelect().findAll('option'); await selectOptionAtIndex(1);
await options.at(1).setSelected();
await wrapper.vm.$nextTick();
expect(findFormFields().at(0).isVisible()).toBe(true); expect(findFormFields().at(0).isVisible()).toBe(true);
}); });
...@@ -96,120 +105,128 @@ describe('AlertsSettingsFormNew', () => { ...@@ -96,120 +105,128 @@ describe('AlertsSettingsFormNew', () => {
it('disabled the name input when the selected value is prometheus', async () => { it('disabled the name input when the selected value is prometheus', async () => {
createComponent(); createComponent();
const options = findSelect().findAll('option'); await selectOptionAtIndex(2);
await options.at(2).setSelected();
expect(findFormFields().at(0).attributes('disabled')).toBe('disabled'); expect(findFormFields().at(0).attributes('disabled')).toBe('disabled');
}); });
}); });
describe('submitting integration form', () => { describe('submitting integration form', () => {
it('allows for create-new-integration with the correct form values for HTTP', async () => { describe('HTTP', () => {
createComponent(); it('create', async () => {
createComponent();
const options = findSelect().findAll('option'); const integrationName = 'Test integration';
await options.at(1).setSelected(); await selectOptionAtIndex(1);
enableIntegration(0, integrationName);
await findFormFields().at(0).setValue('Test integration'); const submitBtn = findSubmitButton();
await findFormToggle().trigger('click'); expect(submitBtn.exists()).toBe(true);
expect(submitBtn.text()).toBe('Save integration');
await wrapper.vm.$nextTick(); findForm().trigger('submit');
expect(findSubmitButton().exists()).toBe(true);
expect(findSubmitButton().text()).toBe('Save integration');
findForm().trigger('submit');
await wrapper.vm.$nextTick(); expect(wrapper.emitted('create-new-integration')[0]).toEqual([
{ type: typeSet.http, variables: { name: integrationName, active: true } },
expect(wrapper.emitted('create-new-integration')).toBeTruthy(); ]);
expect(wrapper.emitted('create-new-integration')[0]).toEqual([ });
{ type: typeSet.http, variables: { name: 'Test integration', active: true } },
]);
});
it('allows for create-new-integration with the correct form values for PROMETHEUS', async () => { it('create with custom mapping', async () => {
createComponent(); createComponent({ multipleHttpIntegrationsCustomMapping: true });
const options = findSelect().findAll('option'); const integrationName = 'Test integration';
await options.at(2).setSelected(); await selectOptionAtIndex(1);
await findFormFields().at(0).setValue('Test integration'); enableIntegration(0, integrationName);
await findFormFields().at(1).setValue('https://test.com');
await findFormToggle().trigger('click');
await wrapper.vm.$nextTick(); const sampleMapping = { field: 'test' };
findMappingBuilder().vm.$emit('onMappingUpdate', sampleMapping);
findForm().trigger('submit');
expect(findSubmitButton().exists()).toBe(true); expect(wrapper.emitted('create-new-integration')[0]).toEqual([
expect(findSubmitButton().text()).toBe('Save integration'); {
type: typeSet.http,
variables: {
name: integrationName,
active: true,
payloadAttributeMappings: sampleMapping,
payloadExample: null,
},
},
]);
});
findForm().trigger('submit'); it('update', () => {
createComponent({
data: {
selectedIntegration: typeSet.http,
currentIntegration: { id: '1', name: 'Test integration pre' },
},
props: {
loading: false,
},
});
const updatedIntegrationName = 'Test integration post';
enableIntegration(0, updatedIntegrationName);
await wrapper.vm.$nextTick(); const submitBtn = findSubmitButton();
expect(submitBtn.exists()).toBe(true);
expect(submitBtn.text()).toBe('Save integration');
expect(wrapper.emitted('create-new-integration')).toBeTruthy(); findForm().trigger('submit');
expect(wrapper.emitted('create-new-integration')[0]).toEqual([
{ type: typeSet.prometheus, variables: { apiUrl: 'https://test.com', active: true } },
]);
});
it('allows for update-integration with the correct form values for HTTP', async () => { expect(wrapper.emitted('update-integration')[0]).toEqual([
createComponent({ { type: typeSet.http, variables: { name: updatedIntegrationName, active: true } },
data: { ]);
selectedIntegration: typeSet.http,
currentIntegration: { id: '1', name: 'Test integration pre' },
},
props: {
loading: false,
},
}); });
});
await findFormFields().at(0).setValue('Test integration post'); describe('PROMETHEUS', () => {
await findFormToggle().trigger('click'); it('create', async () => {
createComponent();
await wrapper.vm.$nextTick(); await selectOptionAtIndex(2);
expect(findSubmitButton().exists()).toBe(true); const apiUrl = 'https://test.com';
expect(findSubmitButton().text()).toBe('Save integration'); enableIntegration(1, apiUrl);
findForm().trigger('submit'); findFormToggle().trigger('click');
await wrapper.vm.$nextTick(); const submitBtn = findSubmitButton();
expect(submitBtn.exists()).toBe(true);
expect(submitBtn.text()).toBe('Save integration');
expect(wrapper.emitted('update-integration')).toBeTruthy(); findForm().trigger('submit');
expect(wrapper.emitted('update-integration')[0]).toEqual([
{ type: typeSet.http, variables: { name: 'Test integration post', active: true } },
]);
});
it('allows for update-integration with the correct form values for PROMETHEUS', async () => { expect(wrapper.emitted('create-new-integration')[0]).toEqual([
createComponent({ { type: typeSet.prometheus, variables: { apiUrl, active: true } },
data: { ]);
selectedIntegration: typeSet.prometheus,
currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
},
props: {
loading: false,
},
}); });
await findFormFields().at(0).setValue('Test integration'); it('update', () => {
await findFormFields().at(1).setValue('https://test-post.com'); createComponent({
await findFormToggle().trigger('click'); data: {
selectedIntegration: typeSet.prometheus,
await wrapper.vm.$nextTick(); currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
},
props: {
loading: false,
},
});
expect(findSubmitButton().exists()).toBe(true); const apiUrl = 'https://test-post.com';
expect(findSubmitButton().text()).toBe('Save integration'); enableIntegration(1, apiUrl);
findForm().trigger('submit'); const submitBtn = findSubmitButton();
expect(submitBtn.exists()).toBe(true);
expect(submitBtn.text()).toBe('Save integration');
await wrapper.vm.$nextTick(); findForm().trigger('submit');
expect(wrapper.emitted('update-integration')).toBeTruthy(); expect(wrapper.emitted('update-integration')[0]).toEqual([
expect(wrapper.emitted('update-integration')[0]).toEqual([ { type: typeSet.prometheus, variables: { apiUrl, active: true } },
{ type: typeSet.prometheus, variables: { apiUrl: 'https://test-post.com', active: true } }, ]);
]); });
}); });
}); });
...@@ -234,9 +251,10 @@ describe('AlertsSettingsFormNew', () => { ...@@ -234,9 +251,10 @@ describe('AlertsSettingsFormNew', () => {
jest.runAllTimers(); jest.runAllTimers();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(findJsonTestSubmit().exists()).toBe(true); const jsonTestSubmit = findJsonTestSubmit();
expect(findJsonTestSubmit().text()).toBe('Save and test payload'); expect(jsonTestSubmit.exists()).toBe(true);
expect(findJsonTestSubmit().props('disabled')).toBe(true); expect(jsonTestSubmit.text()).toBe('Save and test payload');
expect(jsonTestSubmit.props('disabled')).toBe(true);
}); });
it('should allow for the form to be automatically saved if the test payload is successfully submitted', async () => { it('should allow for the form to be automatically saved if the test payload is successfully submitted', async () => {
...@@ -341,9 +359,8 @@ describe('AlertsSettingsFormNew', () => { ...@@ -341,9 +359,8 @@ describe('AlertsSettingsFormNew', () => {
it(`${visibleMsg} when multipleHttpIntegrationsCustomMapping feature flag ${featureFlagMsg} and integration type is ${integrationType}`, async () => { it(`${visibleMsg} when multipleHttpIntegrationsCustomMapping feature flag ${featureFlagMsg} and integration type is ${integrationType}`, async () => {
createComponent({ multipleHttpIntegrationsCustomMapping: featureFlag }); createComponent({ multipleHttpIntegrationsCustomMapping: featureFlag });
const options = findSelect().findAll('option'); await selectOptionAtIndex(integrationOption);
options.at(integrationOption).setSelected();
await wrapper.vm.$nextTick();
expect(findMappingBuilderSection().exists()).toBe(visible); expect(findMappingBuilderSection().exists()).toBe(visible);
}); });
}); });
......
import {
getMappingData,
getPayloadFields,
transformForSave,
} from '~/alerts_settings/utils/mapping_transformations';
import gitlabFieldsMock from '~/alerts_settings/components/mocks/gitlabFields.json';
import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json';
describe('Mapping Transformation Utilities', () => {
const nameField = {
label: 'Name',
path: ['alert', 'name'],
type: 'STRING',
};
const dashboardField = {
label: 'Dashboard Id',
path: ['alert', 'dashboardId'],
type: 'STRING',
};
describe('getMappingData', () => {
it('should return mapping data', () => {
const alertFields = gitlabFieldsMock.slice(0, 3);
const result = getMappingData(
alertFields,
getPayloadFields(parsedMapping.samplePayload.payloadAlerFields.nodes.slice(0, 3)),
parsedMapping.storedMapping.nodes.slice(0, 3),
);
result.forEach((data, index) => {
expect(data).toEqual(
expect.objectContaining({
...alertFields[index],
searchTerm: '',
fallbackSearchTerm: '',
}),
);
});
});
});
describe('transformForSave', () => {
it('should transform mapped data for save', () => {
const fieldName = 'title';
const mockMappingData = [
{
name: fieldName,
mapping: 'alert_name',
mappingFields: getPayloadFields([dashboardField, nameField]),
},
];
const result = transformForSave(mockMappingData);
const { path, type, label } = nameField;
expect(result).toEqual([{ fieldName, path, type, label }]);
});
it('should return empty array if no mapping provided', () => {
const fieldName = 'title';
const mockMappingData = [
{
name: fieldName,
mapping: null,
mappingFields: getPayloadFields([nameField, dashboardField]),
},
];
const result = transformForSave(mockMappingData);
expect(result).toEqual([]);
});
});
describe('getPayloadFields', () => {
it('should add name field to each payload field', () => {
const result = getPayloadFields([nameField, dashboardField]);
expect(result).toEqual([
{ ...nameField, name: 'alert_name' },
{ ...dashboardField, name: 'alert_dashboardId' },
]);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment