Commit 23babc2b authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '262707-get-GL-fields-from-data-attributes' into 'master'

Add integration with BE based on mocks

See merge request gitlab-org/gitlab!47019
parents 52cd3d90 23a92496
...@@ -10,10 +10,9 @@ import { ...@@ -10,10 +10,9 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
// Mocks will be removed when integrating with BE is ready // Mocks will be removed when integrating with BE is ready
// data format most likely will differ but UI will not // 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 gitlabFields from './mocks/gitlabFields.json'; import gitlabFieldsMock from './mocks/gitlabFields.json';
import parsedMapping from './mocks/parsedMapping.json';
export const i18n = { export const i18n = {
columns: { columns: {
...@@ -41,19 +40,44 @@ export default { ...@@ -41,19 +40,44 @@ export default {
directives: { directives: {
GlTooltip, GlTooltip,
}, },
props: {
payloadFields: {
type: Array,
required: false,
default: () => [],
},
mapping: {
type: Array,
required: false,
default: () => [],
},
},
data() { data() {
return { return {
gitlabFields, gitlabFields: this.gitlabAlertFields,
}; };
}, },
inject: {
gitlabAlertFields: {
default: gitlabFieldsMock,
},
},
computed: { computed: {
mappingData() { mappingData() {
return this.gitlabFields.map(gitlabField => { return this.gitlabFields.map(gitlabField => {
const mappingFields = parsedMapping.filter(field => field.type === gitlabField.type); 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 { return {
mapping: null, mapping: payloadAlertPaths,
fallback: null, fallback: fallbackAlertPaths,
searchTerm: '', searchTerm: '',
fallbackSearchTerm: '', fallbackSearchTerm: '',
mappingFields, mappingFields,
...@@ -64,12 +88,12 @@ export default { ...@@ -64,12 +88,12 @@ export default {
}, },
methods: { methods: {
setMapping(gitlabKey, mappingKey, valueKey) { setMapping(gitlabKey, mappingKey, valueKey) {
const fieldIndex = this.gitlabFields.findIndex(field => field.key === 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);
}, },
setSearchTerm(search = '', searchFieldKey, gitlabKey) { setSearchTerm(search = '', searchFieldKey, gitlabKey) {
const fieldIndex = this.gitlabFields.findIndex(field => field.key === gitlabKey); const fieldIndex = this.gitlabFields.findIndex(field => field.name === gitlabKey);
const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [searchFieldKey]: search } }; const updatedField = { ...this.gitlabFields[fieldIndex], ...{ [searchFieldKey]: search } };
Vue.set(this.gitlabFields, fieldIndex, updatedField); Vue.set(this.gitlabFields, fieldIndex, updatedField);
}, },
...@@ -81,13 +105,14 @@ export default { ...@@ -81,13 +105,14 @@ export default {
isSelected(fieldValue, mapping) { isSelected(fieldValue, mapping) {
return fieldValue === mapping; return fieldValue === mapping;
}, },
selectedValue(key) { selectedValue(name) {
return ( return (
parsedMapping.find(item => item.key === key)?.label || this.$options.i18n.makeSelection this.payloadFields.find(item => item.name === name)?.label ||
this.$options.i18n.makeSelection
); );
}, },
getFieldValue({ label, type }) { getFieldValue({ label, type }) {
return `${label} (${type})`; return `${label} (${type.join(__(' or '))})`;
}, },
noResults(searchTerm, fields) { noResults(searchTerm, fields) {
return !this.filterFields(searchTerm, fields).length; return !this.filterFields(searchTerm, fields).length;
...@@ -116,7 +141,7 @@ export default { ...@@ -116,7 +141,7 @@ export default {
/> />
</h5> </h5>
</div> </div>
<div v-for="gitlabField in mappingData" :key="gitlabField.key" class="gl-display-table-row"> <div v-for="gitlabField in mappingData" :key="gitlabField.name" class="gl-display-table-row">
<div class="gl-display-table-cell gl-py-3 gl-pr-3 w-30p"> <div class="gl-display-table-cell gl-py-3 gl-pr-3 w-30p">
<gl-form-input <gl-form-input
disabled disabled
...@@ -137,18 +162,17 @@ export default { ...@@ -137,18 +162,17 @@ export default {
class="gl-w-full" class="gl-w-full"
:header-text="$options.i18n.selectMappingKey" :header-text="$options.i18n.selectMappingKey"
> >
<gl-search-box-by-type @input="setSearchTerm($event, 'searchTerm', gitlabField.key)" /> <gl-search-box-by-type @input="setSearchTerm($event, 'searchTerm', gitlabField.name)" />
<gl-dropdown-item <gl-dropdown-item
v-for="mappingField in filterFields(gitlabField.searchTerm, gitlabField.mappingFields)" v-for="mappingField in filterFields(gitlabField.searchTerm, gitlabField.mappingFields)"
:key="`${mappingField.key}__mapping`" :key="`${mappingField.name}__mapping`"
:is-checked="isSelected(gitlabField.mapping, mappingField.key)" :is-checked="isSelected(gitlabField.mapping, mappingField.name)"
is-check-item is-check-item
@click="setMapping(gitlabField.key, mappingField.key, 'mapping')" @click="setMapping(gitlabField.name, mappingField.name, 'mapping')"
> >
{{ mappingField.label }} {{ mappingField.label }}
</gl-dropdown-item> </gl-dropdown-item>
<gl-dropdown-item v-if="noResults(gitlabField.searchTerm, gitlabField.mappingFields)"> <gl-dropdown-item v-if="noResults(gitlabField.searchTerm, gitlabField.mappingFields)">
>
{{ $options.i18n.noResults }} {{ $options.i18n.noResults }}
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
...@@ -156,23 +180,23 @@ export default { ...@@ -156,23 +180,23 @@ export default {
<div class="gl-display-table-cell gl-py-3 w-30p"> <div class="gl-display-table-cell gl-py-3 w-30p">
<gl-dropdown <gl-dropdown
v-if="gitlabField.hasFallback" v-if="Boolean(gitlabField.numberOfFallbacks)"
:text="selectedValue(gitlabField.fallback)" :text="selectedValue(gitlabField.fallback)"
class="gl-w-full" class="gl-w-full"
:header-text="$options.i18n.selectMappingKey" :header-text="$options.i18n.selectMappingKey"
> >
<gl-search-box-by-type <gl-search-box-by-type
@input="setSearchTerm($event, 'fallbackSearchTerm', gitlabField.key)" @input="setSearchTerm($event, 'fallbackSearchTerm', gitlabField.name)"
/> />
<gl-dropdown-item <gl-dropdown-item
v-for="mappingField in filterFields( v-for="mappingField in filterFields(
gitlabField.fallbackSearchTerm, gitlabField.fallbackSearchTerm,
gitlabField.mappingFields, gitlabField.mappingFields,
)" )"
:key="`${mappingField.key}__fallback`" :key="`${mappingField.name}__fallback`"
:is-checked="isSelected(gitlabField.fallback, mappingField.key)" :is-checked="isSelected(gitlabField.fallback, mappingField.name)"
is-check-item is-check-item
@click="setMapping(gitlabField.key, mappingField.key, 'fallback')" @click="setMapping(gitlabField.name, mappingField.name, 'fallback')"
> >
{{ mappingField.label }} {{ mappingField.label }}
</gl-dropdown-item> </gl-dropdown-item>
......
...@@ -24,6 +24,10 @@ import { ...@@ -24,6 +24,10 @@ import {
targetPrometheusUrlPlaceholder, targetPrometheusUrlPlaceholder,
typeSet, typeSet,
} from '../constants'; } from '../constants';
// 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 default { export default {
targetPrometheusUrlPlaceholder, targetPrometheusUrlPlaceholder,
...@@ -56,6 +60,13 @@ export default { ...@@ -56,6 +60,13 @@ export default {
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with to send a test alert to the %{linkStart}alerts page%{linkEnd}. This payload can be used to test the integration using the "save and test payload" button.', 'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with to send a test alert to the %{linkStart}alerts page%{linkEnd}. This payload can be used to test the integration using the "save and test payload" button.',
), ),
placeholder: s__('AlertSettings|Enter test alert JSON....'), placeholder: s__('AlertSettings|Enter test alert JSON....'),
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'),
}, },
step5: { step5: {
label: s__('AlertSettings|5. Map fields (optional)'), label: s__('AlertSettings|5. Map fields (optional)'),
...@@ -126,6 +137,9 @@ export default { ...@@ -126,6 +137,9 @@ export default {
json: null, json: null,
error: null, error: null,
}, },
resetSamplePayloadConfirmed: false,
customMapping: null,
parsingPayload: false,
}; };
}, },
computed: { computed: {
...@@ -158,6 +172,27 @@ export default { ...@@ -158,6 +172,27 @@ export default {
token: this.integrationForm.token, token: this.integrationForm.token,
}; };
}, },
showMappingBuilder() {
return (
this.glFeatures.multipleHttpIntegrationsCustomMapping &&
this.selectedIntegration === typeSet.http
);
},
mappingBuilderFields() {
return this.customMapping?.samplePayload?.payloadAlerFields?.nodes || [];
},
mappingBuilderMapping() {
return this.customMapping?.storedMapping?.nodes || [];
},
hasSamplePayload() {
return Boolean(this.customMapping?.samplePayload);
},
canEditPayload() {
return this.hasSamplePayload && !this.resetSamplePayloadConfirmed;
},
isPayloadEditDisabled() {
return !this.active || this.canEditPayload;
},
}, },
watch: { watch: {
currentIntegration(val) { currentIntegration(val) {
...@@ -166,6 +201,7 @@ export default { ...@@ -166,6 +201,7 @@ export default {
} }
this.selectedIntegration = val.type; this.selectedIntegration = val.type;
this.active = val.active; this.active = val.active;
if (val.type === typeSet.http) this.getIntegrationMapping(val.id);
return this.integrationTypeSelect(); return this.integrationTypeSelect();
}, },
}, },
...@@ -242,6 +278,31 @@ export default { ...@@ -242,6 +278,31 @@ export default {
this.integrationTestPayload.error = JSON.stringify(e.message); this.integrationTestPayload.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;
})
.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;
});
},
}, },
}; };
</script> </script>
...@@ -358,9 +419,9 @@ export default { ...@@ -358,9 +419,9 @@ export default {
</template> </template>
</gl-form-input-group> </gl-form-input-group>
<gl-button v-gl-modal.authKeyModal :disabled="!integrationForm.active" class="gl-mt-3">{{ <gl-button v-gl-modal.authKeyModal :disabled="!active" class="gl-mt-3">
$options.i18n.integrationFormSteps.step3.reset {{ $options.i18n.integrationFormSteps.step3.reset }}
}}</gl-button> </gl-button>
<gl-modal <gl-modal
modal-id="authKeyModal" modal-id="authKeyModal"
:title="$options.i18n.integrationFormSteps.step3.reset" :title="$options.i18n.integrationFormSteps.step3.reset"
...@@ -373,6 +434,7 @@ export default { ...@@ -373,6 +434,7 @@ export default {
</div> </div>
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
id="test-integration"
:label="$options.i18n.integrationFormSteps.step4.label" :label="$options.i18n.integrationFormSteps.step4.label"
label-for="test-integration" label-for="test-integration"
:invalid-feedback="integrationTestPayload.error" :invalid-feedback="integrationTestPayload.error"
...@@ -383,9 +445,9 @@ export default { ...@@ -383,9 +445,9 @@ export default {
/> />
<gl-form-textarea <gl-form-textarea
id="test-integration" id="test-payload"
v-model.trim="integrationTestPayload.json" v-model.trim="integrationTestPayload.json"
:disabled="!active" :disabled="isPayloadEditDisabled"
:state="jsonIsValid" :state="jsonIsValid"
:placeholder="$options.i18n.integrationFormSteps.step4.placeholder" :placeholder="$options.i18n.integrationFormSteps.step4.placeholder"
class="gl-my-4" class="gl-my-4"
...@@ -394,16 +456,46 @@ export default { ...@@ -394,16 +456,46 @@ export default {
max-rows="10" max-rows="10"
@input="validateJson" @input="validateJson"
/> />
<template v-if="showMappingBuilder">
<gl-button
v-if="canEditPayload"
v-gl-modal.resetPayloadModal
:disabled="!active"
class="gl-mt-3"
>
{{ $options.i18n.integrationFormSteps.step4.editPayload }}
</gl-button>
<gl-button
v-else
:disabled="!active"
:loading="parsingPayload"
class="gl-mt-3"
@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> </gl-form-group>
<gl-form-group <gl-form-group
v-if="glFeatures.multipleHttpIntegrationsCustomMapping" v-if="showMappingBuilder"
id="mapping-builder" id="mapping-builder"
:label="$options.i18n.integrationFormSteps.step5.label" :label="$options.i18n.integrationFormSteps.step5.label"
label-for="mapping-builder" label-for="mapping-builder"
> >
<span class="gl-text-gray-500">{{ $options.i18n.integrationFormSteps.step5.intro }}</span> <span class="gl-text-gray-500">{{ $options.i18n.integrationFormSteps.step5.intro }}</span>
<mapping-builder /> <mapping-builder :payload-fields="mappingBuilderFields" :mapping="mappingBuilderMapping" />
</gl-form-group> </gl-form-group>
<div class="gl-display-flex gl-justify-content-end"> <div class="gl-display-flex gl-justify-content-end">
<gl-button type="reset" class="gl-mr-3 js-no-auto-disable">{{ __('Cancel') }}</gl-button> <gl-button type="reset" class="gl-mr-3 js-no-auto-disable">{{ __('Cancel') }}</gl-button>
...@@ -421,8 +513,8 @@ export default { ...@@ -421,8 +513,8 @@ export default {
variant="success" variant="success"
class="js-no-auto-disable" class="js-no-auto-disable"
data-testid="integration-form-submit" data-testid="integration-form-submit"
>{{ s__('AlertSettings|Save integration') }}</gl-button >{{ s__('AlertSettings|Save integration') }}
> </gl-button>
</div> </div>
</gl-collapse> </gl-collapse>
</gl-form> </gl-form>
......
[ [
{ {
"key":"title", "name": "title",
"label":"Title", "label": "Title",
"type":"String", "type": [
"hasFallback": true "String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
],
"numberOfFallbacks": 1
}, },
{ {
"key":"description", "name": "description",
"label":"Description", "label": "Description",
"type":"String" "type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
}, },
{ {
"key":"startTime", "name": "startTime",
"label":"Start time", "label": "Start time",
"type":"DateTime" "type": [
"DateTime"
],
"compatibleTypes": [
"Number",
"DateTime"
]
}, },
{ {
"key":"service", "name": "service",
"label":"Service", "label": "Service",
"type":"String" "type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
}, },
{ {
"key":"monitoringTool", "name": "monitoringTool",
"label":"Monitoring tool", "label": "Monitoring tool",
"type":"String" "type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
}, },
{ {
"key":"hosts", "name": "hosts",
"label":"Hosts", "label": "Hosts",
"type":"String or Array" "type": [
"String",
"Array"
],
"compatibleTypes": [
"String",
"Array",
"Number",
"DateTime"
]
}, },
{ {
"key":"severity", "name": "severity",
"label":"Severity", "label": "Severity",
"type":"String" "type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
}, },
{ {
"key":"fingerprint", "name": "fingerprint",
"label":"Fingerprint", "label": "Fingerprint",
"type":"String" "type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
}, },
{ {
"key":"environment", "name": "environment",
"label":"Environment", "label": "Environment",
"type":"String" "type": [
"String"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
]
} }
] ]
[ {
{ "samplePayload": {
"key":"title", "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",
"label":"Title", "payloadAlerFields": {
"type":"String" "nodes": [
{
"name": "dashboardId",
"label": "Dashboard Id",
"type": [
"Number"
]
},
{
"name": "evalMatches",
"label": "Eval Matches",
"type": [
"Array"
]
},
{
"name": "createdAt",
"label": "Created At",
"type": [
"DateTime"
]
},
{
"name": "imageUrl",
"label": "Image Url",
"type": [
"String"
]
},
{
"name": "message",
"label": "Message",
"type": [
"String"
]
},
{
"name": "orgId",
"label": "Org Id",
"type": [
"Number"
]
},
{
"name": "panelId",
"label": "Panel Id",
"type": [
"String"
]
},
{
"name": "ruleId",
"label": "Rule Id",
"type": [
"Number"
]
},
{
"name": "ruleName",
"label": "Rule Name",
"type": [
"String"
]
},
{
"name": "ruleUrl",
"label": "Rule Url",
"type": [
"String"
]
},
{
"name": "state",
"label": "State",
"type": [
"String"
]
},
{
"name": "title",
"label": "Title",
"type": [
"String"
]
},
{
"name": "tags",
"label": "Tags",
"type": [
"Object"
]
}
]
}
}, },
{ "storedMapping": {
"key":"description", "nodes": [
"label":"Description", {
"type":"String" "alertFieldName": "title",
}, "payloadAlertPaths": "title",
{ "fallbackAlertPaths": "ruleUrl"
"key":"startTime", },
"label":"Start time", {
"type":"DateTime" "alertFieldName": "description",
}, "payloadAlertPaths": "message"
{ },
"key":"service", {
"label":"Service", "alertFieldName": "hosts",
"type":"String" "payloadAlertPaths": "evalMatches"
}, },
{ {
"key":"monitoringTool", "alertFieldName": "startTime",
"label":"Monitoring tool", "payloadAlertPaths": "createdAt"
"type":"String" }
}, ]
{
"key":"hosts",
"label":"Hosts",
"type":"String or Array"
},
{
"key":"severity",
"label":"Severity",
"type":"String"
},
{
"key":"fingerprint",
"label":"Fingerprint",
"type":"String"
},
{
"key":"environment",
"label":"Environment",
"type":"String"
} }
] }
...@@ -2541,6 +2541,9 @@ msgstr "" ...@@ -2541,6 +2541,9 @@ msgstr ""
msgid "AlertSettings|Delete integration" msgid "AlertSettings|Delete integration"
msgstr "" msgstr ""
msgid "AlertSettings|Edit payload"
msgstr ""
msgid "AlertSettings|Enter integration name" msgid "AlertSettings|Enter integration name"
msgstr "" msgstr ""
...@@ -2556,6 +2559,9 @@ msgstr "" ...@@ -2556,6 +2559,9 @@ msgstr ""
msgid "AlertSettings|HTTP endpoint" msgid "AlertSettings|HTTP endpoint"
msgstr "" msgstr ""
msgid "AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields."
msgstr ""
msgid "AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations." msgid "AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations."
msgstr "" msgstr ""
...@@ -2571,6 +2577,9 @@ msgstr "" ...@@ -2571,6 +2577,9 @@ msgstr ""
msgid "AlertSettings|Opsgenie" msgid "AlertSettings|Opsgenie"
msgstr "" msgstr ""
msgid "AlertSettings|Proceed with editing"
msgstr ""
msgid "AlertSettings|Prometheus API base URL" msgid "AlertSettings|Prometheus API base URL"
msgstr "" msgstr ""
...@@ -2583,6 +2592,9 @@ msgstr "" ...@@ -2583,6 +2592,9 @@ msgstr ""
msgid "AlertSettings|Reset key" msgid "AlertSettings|Reset key"
msgstr "" msgstr ""
msgid "AlertSettings|Reset the mapping"
msgstr ""
msgid "AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in." msgid "AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr "" msgstr ""
...@@ -2598,6 +2610,9 @@ msgstr "" ...@@ -2598,6 +2610,9 @@ msgstr ""
msgid "AlertSettings|Select integration type" msgid "AlertSettings|Select integration type"
msgstr "" msgstr ""
msgid "AlertSettings|Submit payload"
msgstr ""
msgid "AlertSettings|Test alert payload" msgid "AlertSettings|Test alert payload"
msgstr "" msgstr ""
......
...@@ -64,7 +64,9 @@ exports[`AlertsSettingsFormNew with default values renders the initial template ...@@ -64,7 +64,9 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
</div> </div>
</div> <button type=\\"button\\" disabled=\\"disabled\\" class=\\"btn gl-mt-3 btn-default btn-md disabled gl-button\\"> </div> <button type=\\"button\\" disabled=\\"disabled\\" class=\\"btn gl-mt-3 btn-default btn-md disabled gl-button\\">
<!----> <!---->
<!----> <span class=\\"gl-button-text\\">Reset Key</span></button> <!----> <span class=\\"gl-button-text\\">
Reset Key
</span></button>
<!----> <!---->
</div> </div>
<!----> <!---->
...@@ -72,8 +74,9 @@ exports[`AlertsSettingsFormNew with default values renders the initial template ...@@ -72,8 +74,9 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<!----> <!---->
</div> </div>
</div> </div>
<div role=\\"group\\" class=\\"form-group gl-form-group\\" id=\\"__BVID__40\\"><label for=\\"test-integration\\" class=\\"d-block col-form-label\\" id=\\"__BVID__40__BV_label_\\">4. Test integration(optional)</label> <div id=\\"test-integration\\" role=\\"group\\" class=\\"form-group gl-form-group\\"><label id=\\"test-integration__BV_label_\\" for=\\"test-integration\\" class=\\"d-block col-form-label\\">4. Test integration(optional)</label>
<div class=\\"bv-no-focus-ring\\"><span class=\\"gl-text-gray-500\\">Provide an example payload from the monitoring tool you intend to integrate with to send a test alert to the <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"http://invalid\\" class=\\"gl-link gl-display-inline-block\\">alerts page</a>. This payload can be used to test the integration using the \\"save and test payload\\" button.</span> <textarea id=\\"test-integration\\" disabled=\\"disabled\\" placeholder=\\"Enter test alert JSON....\\" wrap=\\"soft\\" class=\\"gl-form-input gl-form-textarea gl-my-4 form-control is-valid\\" style=\\"resize: none; overflow-y: scroll;\\"></textarea> <div class=\\"bv-no-focus-ring\\"><span class=\\"gl-text-gray-500\\">Provide an example payload from the monitoring tool you intend to integrate with to send a test alert to the <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"http://invalid\\" class=\\"gl-link gl-display-inline-block\\">alerts page</a>. This payload can be used to test the integration using the \\"save and test payload\\" button.</span> <textarea id=\\"test-payload\\" disabled=\\"disabled\\" placeholder=\\"Enter test alert JSON....\\" wrap=\\"soft\\" class=\\"gl-form-input gl-form-textarea gl-my-4 form-control is-valid\\" style=\\"resize: none; overflow-y: scroll;\\"></textarea>
<!---->
<!----> <!---->
<!----> <!---->
<!----> <!---->
...@@ -86,7 +89,8 @@ exports[`AlertsSettingsFormNew with default values renders the initial template ...@@ -86,7 +89,8 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<!----> <!---->
<!----> <span class=\\"gl-button-text\\">Save and test payload</span></button> <button data-testid=\\"integration-form-submit\\" type=\\"submit\\" class=\\"btn js-no-auto-disable btn-success btn-md gl-button\\"> <!----> <span class=\\"gl-button-text\\">Save and test payload</span></button> <button data-testid=\\"integration-form-submit\\" type=\\"submit\\" class=\\"btn js-no-auto-disable btn-success btn-md gl-button\\">
<!----> <!---->
<!----> <span class=\\"gl-button-text\\">Save integration</span></button></div> <!----> <span class=\\"gl-button-text\\">Save integration
</span></button></div>
</div> </div>
</form>" </form>"
`; `;
...@@ -8,7 +8,12 @@ describe('AlertMappingBuilder', () => { ...@@ -8,7 +8,12 @@ describe('AlertMappingBuilder', () => {
let wrapper; let wrapper;
function mountComponent() { function mountComponent() {
wrapper = shallowMount(AlertMappingBuilder); wrapper = shallowMount(AlertMappingBuilder, {
propsData: {
payloadFields: parsedMapping.samplePayload.payloadAlerFields.nodes,
mapping: parsedMapping.storedMapping.nodes,
},
});
} }
afterEach(() => { afterEach(() => {
...@@ -29,11 +34,6 @@ describe('AlertMappingBuilder', () => { ...@@ -29,11 +34,6 @@ describe('AlertMappingBuilder', () => {
.findAll('.gl-display-table-cell ') .findAll('.gl-display-table-cell ')
.at(column); .at(column);
const fieldsByTypeCount = parsedMapping.reduce((acc, { type }) => {
acc[type] = (acc[type] || 0) + 1;
return acc;
}, {});
it('renders column captions', () => { it('renders column captions', () => {
expect(findColumnInRow(0, 0).text()).toContain(i18n.columns.gitlabKeyTitle); expect(findColumnInRow(0, 0).text()).toContain(i18n.columns.gitlabKeyTitle);
expect(findColumnInRow(0, 2).text()).toContain(i18n.columns.payloadKeyTitle); expect(findColumnInRow(0, 2).text()).toContain(i18n.columns.payloadKeyTitle);
...@@ -48,7 +48,7 @@ describe('AlertMappingBuilder', () => { ...@@ -48,7 +48,7 @@ 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})`); expect(input.attributes('value')).toBe(`${field.label} (${field.type.join(' or ')})`);
expect(input.attributes('disabled')).toBe(''); expect(input.attributes('disabled')).toBe('');
}); });
}); });
...@@ -61,27 +61,36 @@ describe('AlertMappingBuilder', () => { ...@@ -61,27 +61,36 @@ describe('AlertMappingBuilder', () => {
}); });
it('renders mapping dropdown for each field', () => { it('renders mapping dropdown for each field', () => {
gitlabFields.forEach(({ type }, 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.find(GlSearchBoxByType);
const dropdownItems = dropdown.findAll(GlDropdownItem); const dropdownItems = dropdown.findAll(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
const numberOfMappingOptions = nodes.filter(({ 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.length).toBe(fieldsByTypeCount[type]); expect(dropdownItems).toHaveLength(numberOfMappingOptions.length);
}); });
}); });
it('renders fallback dropdown only for the fields that have fallback', () => { it('renders fallback dropdown only for the fields that have fallback', () => {
gitlabFields.forEach(({ type, hasFallback }, index) => { gitlabFields.forEach(({ compatibleTypes, numberOfFallbacks }, index) => {
const dropdown = findColumnInRow(index + 1, 3).find(GlDropdown); const dropdown = findColumnInRow(index + 1, 3).find(GlDropdown);
expect(dropdown.exists()).toBe(Boolean(hasFallback)); expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks));
if (hasFallback) { if (numberOfFallbacks) {
const searchBox = dropdown.find(GlSearchBoxByType); const searchBox = dropdown.find(GlSearchBoxByType);
const dropdownItems = dropdown.findAll(GlDropdownItem); const dropdownItems = dropdown.findAll(GlDropdownItem);
expect(searchBox.exists()).toBe(hasFallback); const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
expect(dropdownItems.length).toBe(fieldsByTypeCount[type]); const numberOfMappingOptions = nodes.filter(({ type }) =>
type.some(t => compatibleTypes.includes(t)),
);
expect(searchBox.exists()).toBe(Boolean(numberOfFallbacks));
expect(dropdownItems).toHaveLength(numberOfMappingOptions.length);
} }
}); });
}); });
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlForm, GlFormSelect, GlCollapse, GlFormInput, GlToggle } from '@gitlab/ui'; import {
GlForm,
GlFormSelect,
GlCollapse,
GlFormInput,
GlToggle,
GlFormTextarea,
GlButton,
} from '@gitlab/ui';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue';
import { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
import { typeSet } from '~/alerts_settings/constants'; import { typeSet } from '~/alerts_settings/constants';
...@@ -33,12 +41,13 @@ describe('AlertsSettingsFormNew', () => { ...@@ -33,12 +41,13 @@ describe('AlertsSettingsFormNew', () => {
const findFormSteps = () => wrapper.find(GlCollapse); const findFormSteps = () => wrapper.find(GlCollapse);
const findFormFields = () => wrapper.findAll(GlFormInput); const findFormFields = () => wrapper.findAll(GlFormInput);
const findFormToggle = () => wrapper.find(GlToggle); const findFormToggle = () => wrapper.find(GlToggle);
const findTestPayloadSection = () => wrapper.find(`[id = "test-integration"]`);
const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`); const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`);
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"]`);
const findJsonTestSubmit = () => wrapper.find(`[data-testid="integration-test-and-submit"]`); const findJsonTestSubmit = () => wrapper.find(`[data-testid="integration-test-and-submit"]`);
const findJsonTextArea = () => wrapper.find(`[id = "test-integration"]`); const findJsonTextArea = () => wrapper.find(`[id = "test-payload"]`);
afterEach(() => { afterEach(() => {
if (wrapper) { if (wrapper) {
...@@ -241,18 +250,91 @@ describe('AlertsSettingsFormNew', () => { ...@@ -241,18 +250,91 @@ describe('AlertsSettingsFormNew', () => {
}); });
}); });
describe('Mapping builder section', () => { describe('Test payload section for HTTP integration', () => {
beforeEach(() => { beforeEach(() => {
createComponent({}); createComponent({
multipleHttpIntegrationsCustomMapping: true,
props: {
currentIntegration: {
type: typeSet.http,
},
},
});
}); });
it('should NOT render when feature flag disabled', () => { describe.each`
expect(findMappingBuilderSection().exists()).toBe(false); active | resetSamplePayloadConfirmed | disabled
${true} | ${true} | ${undefined}
${false} | ${true} | ${'disabled'}
${true} | ${false} | ${'disabled'}
${false} | ${false} | ${'disabled'}
`('', ({ active, resetSamplePayloadConfirmed, disabled }) => {
const payloadResetMsg = resetSamplePayloadConfirmed ? 'was confirmed' : 'was not confirmed';
const enabledState = disabled === 'disabled' ? 'disabled' : 'enabled';
const activeState = active ? 'active' : 'not active';
it(`textarea should be ${enabledState} when payload reset ${payloadResetMsg} and current integration is ${activeState}`, async () => {
wrapper.setData({
customMapping: { samplePayload: true },
active,
resetSamplePayloadConfirmed,
});
await wrapper.vm.$nextTick();
expect(
findTestPayloadSection()
.find(GlFormTextarea)
.attributes('disabled'),
).toBe(disabled);
});
}); });
it('should render when feature flag is enabled', () => { describe('action buttons for sample payload', () => {
createComponent({ multipleHttpIntegrationsCustomMapping: true }); describe.each`
expect(findMappingBuilderSection().exists()).toBe(true); resetSamplePayloadConfirmed | samplePayload | caption
${false} | ${true} | ${'Edit payload'}
${true} | ${false} | ${'Submit payload'}
${true} | ${true} | ${'Submit payload'}
${false} | ${false} | ${'Submit payload'}
`('', ({ resetSamplePayloadConfirmed, samplePayload, caption }) => {
const samplePayloadMsg = samplePayload ? 'was provided' : 'was not provided';
const payloadResetMsg = resetSamplePayloadConfirmed ? 'was confirmed' : 'was not confirmed';
it(`shows ${caption} button when sample payload ${samplePayloadMsg} and payload reset ${payloadResetMsg}`, async () => {
wrapper.setData({
selectedIntegration: typeSet.http,
customMapping: { samplePayload },
resetSamplePayloadConfirmed,
});
await wrapper.vm.$nextTick();
expect(
findTestPayloadSection()
.find(GlButton)
.text(),
).toBe(caption);
});
});
});
});
describe('Mapping builder section', () => {
describe.each`
featureFlag | integrationOption | visible
${true} | ${1} | ${true}
${true} | ${2} | ${false}
${false} | ${1} | ${false}
${false} | ${2} | ${false}
`('', ({ featureFlag, integrationOption, visible }) => {
const visibleMsg = visible ? 'is rendered' : 'is not rendered';
const featureFlagMsg = featureFlag ? 'is enabled' : 'is disabled';
const integrationType = integrationOption === 1 ? typeSet.http : typeSet.prometheus;
it(`${visibleMsg} when multipleHttpIntegrationsCustomMapping feature flag ${featureFlagMsg} and integration type is ${integrationType}`, async () => {
createComponent({ multipleHttpIntegrationsCustomMapping: featureFlag });
const options = findSelect().findAll('option');
options.at(integrationOption).setSelected();
await wrapper.vm.$nextTick();
expect(findMappingBuilderSection().exists()).toBe(visible);
});
}); });
}); });
}); });
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