Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-01-22 03:09:04 +00:00
parent 21796f414c
commit 6ef43e2aa1
22 changed files with 450 additions and 263 deletions

View File

@ -13,6 +13,12 @@ import { s__, __ } from '~/locale';
// 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 { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import {
getMappingData,
getPayloadFields,
transformForSave,
} from '../utils/mapping_transformations';
export const i18n = {
columns: {
@ -46,12 +52,12 @@ export default {
},
},
props: {
payloadFields: {
parsedPayload: {
type: Array,
required: false,
default: () => [],
},
mapping: {
savedMapping: {
type: Array,
required: false,
default: () => [],
@ -63,27 +69,11 @@ export default {
};
},
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);
},
},
methods: {
@ -91,6 +81,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 +90,6 @@ export default {
},
filterFields(searchTerm = '', fields) {
const search = searchTerm.toLowerCase();
return fields.filter((field) => field.label.toLowerCase().includes(search));
},
isSelected(fieldValue, mapping) {
@ -112,7 +102,9 @@ export default {
);
},
getFieldValue({ label, type }) {
return `${label} (${type.join(__(' or '))})`;
const types = type.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(__(' or '));
return `${label} (${types})`;
},
noResults(searchTerm, fields) {
return !this.filterFields(searchTerm, fields).length;

View File

@ -152,6 +152,7 @@ export default {
},
resetSamplePayloadConfirmed: false,
customMapping: null,
mapping: [],
parsingPayload: false,
currentIntegration: null,
};
@ -199,10 +200,10 @@ export default {
this.selectedIntegration === typeSet.http
);
},
mappingBuilderFields() {
parsedSamplePayload() {
return this.customMapping?.samplePayload?.payloadAlerFields?.nodes;
},
mappingBuilderMapping() {
savedMapping() {
return this.customMapping?.storedMapping?.nodes;
},
hasSamplePayload() {
@ -255,9 +256,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 +348,9 @@ export default {
this.integrationTestPayload.json = res?.samplePayload.body;
});
},
updateMapping(mapping) {
this.mapping = mapping;
},
},
};
</script>
@ -541,8 +556,9 @@ export default {
>
<span>{{ $options.i18n.integrationFormSteps.step5.intro }}</span>
<mapping-builder
:payload-fields="mappingBuilderFields"
:mapping="mappingBuilderMapping"
:parsed-payload="parsedSamplePayload"
:saved-mapping="savedMapping"
@onMappingUpdate="updateMapping"
/>
</gl-form-group>
</div>

View File

@ -1,112 +1,123 @@
[
{
"name": "title",
"name": "TITLE",
"label": "Title",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
],
"numberOfFallbacks": 1
},
{
"name": "description",
"name": "DESCRIPTION",
"label": "Description",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
]
},
{
"name": "startTime",
"name": "START_TIME",
"label": "Start time",
"type": [
"DateTime"
"DATETIME"
],
"compatibleTypes": [
"Number",
"DateTime"
"NUMBER",
"DATETIME"
]
},
{
"name": "service",
"name": "END_TIME",
"label": "End time",
"type": [
"DATETIME"
],
"compatibleTypes": [
"NUMBER",
"DATETIME"
]
},
{
"name": "SERVICE",
"label": "Service",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
]
},
{
"name": "monitoringTool",
"name": "MONITORING_TOOL",
"label": "Monitoring tool",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
]
},
{
"name": "hosts",
"name": "HOSTS",
"label": "Hosts",
"type": [
"String",
"Array"
"STRING",
"ARRAY"
],
"compatibleTypes": [
"String",
"Array",
"Number",
"DateTime"
"STRING",
"ARRAY",
"NUMBER",
"DATETIME"
]
},
{
"name": "severity",
"name": "SEVERITY",
"label": "Severity",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
]
},
{
"name": "fingerprint",
"name": "FINGERPRINT",
"label": "Fingerprint",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
]
},
{
"name": "environment",
"name": "GITLAB_ENVIRONMENT_NAME",
"label": "Environment",
"type": [
"String"
"STRING"
],
"compatibleTypes": [
"String",
"Number",
"DateTime"
"STRING",
"NUMBER",
"DATETIME"
]
}
]

View File

@ -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"
}
]
}

View File

@ -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

View File

@ -0,0 +1,63 @@
/**
* 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('_') }));
};

View File

@ -89,7 +89,7 @@ Before starting the flow, generate the `STATE`, the `CODE_VERIFIER` and the `COD
`/oauth/authorize` page with the following query parameters:
```plaintext
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH&scope=REQUESTED_SCOPES&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
```
This page asks the user to approve the request from the app to access their
@ -100,7 +100,7 @@ Before starting the flow, generate the `STATE`, the `CODE_VERIFIER` and the `COD
The redirect includes the authorization `code`, for example:
```plaintext
https://example.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH
https://example.com/oauth/redirect?code=1234567890&state=STATE
```
1. With the authorization `code` returned from the previous request (denoted as
@ -139,29 +139,31 @@ detailed flow description.
The authorization code flow is essentially the same as
[authorization code flow with PKCE](#authorization-code-with-proof-key-for-code-exchange-pkce),
Before starting the flow, generate the `STATE`. It is a value that can't be predicted
used by the client to maintain state between the request and callback. It should also
be used as a CSRF token.
1. Request authorization code. To do that, you should redirect the user to the
`/oauth/authorize` endpoint with the following GET parameters:
`/oauth/authorize` page with the following query parameters:
```plaintext
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES
```
This will ask the user to approve the applications access to their account
based on the scopes specified in `REQUESTED_SCOPES` and then redirect back to
the `REDIRECT_URI` you provided. The [scope parameter](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#requesting-particular-scopes)
is a space separated list of scopes you want to have access to (e.g. `scope=read_user+profile`
would request `read_user` and `profile` scopes). The redirect will
include the GET `code` parameter, for example:
This page asks the user to approve the request from the app to access their
account based on the scopes specified in `REQUESTED_SCOPES`. The user is then
redirected back to the specified `REDIRECT_URI`. The [scope parameter](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#requesting-particular-scopes)
is a space separated list of scopes associated with the user.
For example,`scope=read_user+profile` requests the `read_user` and `profile` scopes.
The redirect includes the authorization `code`, for example:
```plaintext
https://example.com/oauth/redirect?code=1234567890&state=STATE
```
You should then use `code` to request an access token.
1. After you have the authorization code you can request an `access_token` using the
code. You can do that by using any HTTP client. In the following example,
we are using Ruby's `rest-client`:
1. With the authorization `code` returned from the previous request (shown as
`RETURNED_CODE` in the following example), you can request an `access_token`, with
any HTTP client. The following example uses Ruby's `rest-client`:
```ruby
parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'

View File

@ -260,6 +260,8 @@ When executing command lines, scanners should use the `debug` level to log the c
For instance, the [bundler-audit](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit) scanner
uses the `debug` level to log the command line `bundle audit check --quiet`,
and what `bundle audit` writes to the standard output.
If the command line fails, then it should be logged with the `error` log level;
this makes it possible to debug the problem without having to change the log level to `debug` and rerun the scanning job.
#### common logutil package

View File

@ -0,0 +1,8 @@
---
redirect_to: 'paging.md'
---
This document was moved to [another location](paging.md).
<!-- This redirect file can be deleted after <2022-01-21>. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
describe 'Multiple file snippet' do
describe 'Multiple file snippet', quarantine: { only: { pipeline: :master }, issue: 'https://gitlab.com/gitlab-org/gitaly/-/issues/3143', type: :bug } do
let(:personal_snippet) do
Resource::Snippet.fabricate_via_api! do |snippet|
snippet.title = 'Personal snippet to add file to'

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
describe 'Version control for personal snippets' do
describe 'Version control for personal snippets', quarantine: { only: { pipeline: :master }, issue: 'https://gitlab.com/gitlab-org/gitaly/-/issues/3143', type: :bug } do
let(:new_file) { 'new_snippet_file' }
let(:changed_content) { 'changes' }
let(:commit_message) { 'Changes to snippets' }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
describe 'Version control for project snippets' do
describe 'Version control for project snippets', quarantine: { only: { pipeline: :master }, issue: 'https://gitlab.com/gitlab-org/gitaly/-/issues/3143', type: :bug } do
let(:new_file) { 'new_snippet_file' }
let(:changed_content) { 'changes' }
let(:commit_message) { 'Changes to snippets' }

View File

@ -2,7 +2,7 @@
module QA
RSpec.describe 'Create' do
describe 'Multiple file snippet' do
describe 'Multiple file snippet', quarantine: { only: { pipeline: :master }, issue: 'https://gitlab.com/gitlab-org/gitaly/-/issues/3143', type: :bug } do
let(:personal_snippet) do
Resource::Snippet.fabricate_via_api! do |snippet|
snippet.title = 'Personal snippet to delete file from'

View File

@ -3,6 +3,8 @@ import { shallowMount } from '@vue/test-utils';
import AlertMappingBuilder, { i18n } from '~/alerts_settings/components/alert_mapping_builder.vue';
import gitlabFields from '~/alerts_settings/components/mocks/gitlabFields.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', () => {
let wrapper;
@ -10,8 +12,8 @@ describe('AlertMappingBuilder', () => {
function mountComponent() {
wrapper = shallowMount(AlertMappingBuilder, {
propsData: {
payloadFields: parsedMapping.samplePayload.payloadAlerFields.nodes,
mapping: parsedMapping.storedMapping.nodes,
parsedPayload: parsedMapping.samplePayload.payloadAlerFields.nodes,
savedMapping: parsedMapping.storedMapping.nodes,
},
});
}
@ -44,7 +46,8 @@ describe('AlertMappingBuilder', () => {
it('renders disabled form input for each mapped field', () => {
gitlabFields.forEach((field, index) => {
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('');
});
});
@ -59,16 +62,14 @@ describe('AlertMappingBuilder', () => {
it('renders mapping dropdown for each field', () => {
gitlabFields.forEach(({ compatibleTypes }, index) => {
const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown);
const searchBox = dropdown.find(GlSearchBoxByType);
const dropdownItems = dropdown.findAll(GlDropdownItem);
const searchBox = dropdown.findComponent(GlSearchBoxByType);
const dropdownItems = dropdown.findAllComponents(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
const numberOfMappingOptions = nodes.filter(({ type }) =>
type.some((t) => compatibleTypes.includes(t)),
);
const mappingOptions = nodes.filter(({ type }) => compatibleTypes.includes(type));
expect(dropdown.exists()).toBe(true);
expect(searchBox.exists()).toBe(true);
expect(dropdownItems).toHaveLength(numberOfMappingOptions.length);
expect(dropdownItems).toHaveLength(mappingOptions.length);
});
});
@ -78,16 +79,23 @@ describe('AlertMappingBuilder', () => {
expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks));
if (numberOfFallbacks) {
const searchBox = dropdown.find(GlSearchBoxByType);
const dropdownItems = dropdown.findAll(GlDropdownItem);
const searchBox = dropdown.findComponent(GlSearchBoxByType);
const dropdownItems = dropdown.findAllComponents(GlDropdownItem);
const { nodes } = parsedMapping.samplePayload.payloadAlerFields;
const numberOfMappingOptions = nodes.filter(({ type }) =>
type.some((t) => compatibleTypes.includes(t)),
);
const mappingOptions = nodes.filter(({ type }) => compatibleTypes.includes(type));
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]);
});
});

View File

@ -9,6 +9,7 @@ import {
} from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises';
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 { typeSet } from '~/alerts_settings/constants';
@ -49,6 +50,7 @@ describe('AlertsSettingsFormNew', () => {
const findFormToggle = () => wrapper.find(GlToggle);
const findTestPayloadSection = () => wrapper.find(`[id = "test-integration"]`);
const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`);
const findMappingBuilder = () => wrapper.findComponent(MappingBuilder);
const findSubmitButton = () => wrapper.find(`[type = "submit"]`);
const findMultiSupportText = () =>
wrapper.find(`[data-testid="multi-integrations-not-supported"]`);
@ -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', () => {
beforeEach(() => {
createComponent();
@ -80,10 +92,7 @@ describe('AlertsSettingsFormNew', () => {
});
it('shows the rest of the form when the dropdown is used', async () => {
const options = findSelect().findAll('option');
await options.at(1).setSelected();
await wrapper.vm.$nextTick();
await selectOptionAtIndex(1);
expect(findFormFields().at(0).isVisible()).toBe(true);
});
@ -96,120 +105,128 @@ describe('AlertsSettingsFormNew', () => {
it('disabled the name input when the selected value is prometheus', async () => {
createComponent();
const options = findSelect().findAll('option');
await options.at(2).setSelected();
await selectOptionAtIndex(2);
expect(findFormFields().at(0).attributes('disabled')).toBe('disabled');
});
});
describe('submitting integration form', () => {
it('allows for create-new-integration with the correct form values for HTTP', async () => {
createComponent();
describe('HTTP', () => {
it('create', async () => {
createComponent();
const options = findSelect().findAll('option');
await options.at(1).setSelected();
const integrationName = 'Test integration';
await selectOptionAtIndex(1);
enableIntegration(0, integrationName);
await findFormFields().at(0).setValue('Test integration');
await findFormToggle().trigger('click');
const submitBtn = findSubmitButton();
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')).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 () => {
createComponent();
const options = findSelect().findAll('option');
await options.at(2).setSelected();
await findFormFields().at(0).setValue('Test integration');
await findFormFields().at(1).setValue('https://test.com');
await findFormToggle().trigger('click');
await wrapper.vm.$nextTick();
expect(findSubmitButton().exists()).toBe(true);
expect(findSubmitButton().text()).toBe('Save integration');
findForm().trigger('submit');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('create-new-integration')).toBeTruthy();
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 () => {
createComponent({
data: {
selectedIntegration: typeSet.http,
currentIntegration: { id: '1', name: 'Test integration pre' },
},
props: {
loading: false,
},
expect(wrapper.emitted('create-new-integration')[0]).toEqual([
{ type: typeSet.http, variables: { name: integrationName, active: true } },
]);
});
await findFormFields().at(0).setValue('Test integration post');
await findFormToggle().trigger('click');
it('create with custom mapping', async () => {
createComponent({ multipleHttpIntegrationsCustomMapping: true });
await wrapper.vm.$nextTick();
const integrationName = 'Test integration';
await selectOptionAtIndex(1);
expect(findSubmitButton().exists()).toBe(true);
expect(findSubmitButton().text()).toBe('Save integration');
enableIntegration(0, integrationName);
findForm().trigger('submit');
const sampleMapping = { field: 'test' };
findMappingBuilder().vm.$emit('onMappingUpdate', sampleMapping);
findForm().trigger('submit');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('update-integration')).toBeTruthy();
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 () => {
createComponent({
data: {
selectedIntegration: typeSet.prometheus,
currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
},
props: {
loading: false,
},
expect(wrapper.emitted('create-new-integration')[0]).toEqual([
{
type: typeSet.http,
variables: {
name: integrationName,
active: true,
payloadAttributeMappings: sampleMapping,
payloadExample: null,
},
},
]);
});
await findFormFields().at(0).setValue('Test integration');
await findFormFields().at(1).setValue('https://test-post.com');
await findFormToggle().trigger('click');
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(findSubmitButton().exists()).toBe(true);
expect(findSubmitButton().text()).toBe('Save integration');
findForm().trigger('submit');
findForm().trigger('submit');
expect(wrapper.emitted('update-integration')[0]).toEqual([
{ type: typeSet.http, variables: { name: updatedIntegrationName, active: true } },
]);
});
});
await wrapper.vm.$nextTick();
describe('PROMETHEUS', () => {
it('create', async () => {
createComponent();
expect(wrapper.emitted('update-integration')).toBeTruthy();
expect(wrapper.emitted('update-integration')[0]).toEqual([
{ type: typeSet.prometheus, variables: { apiUrl: 'https://test-post.com', active: true } },
]);
await selectOptionAtIndex(2);
const apiUrl = 'https://test.com';
enableIntegration(1, apiUrl);
findFormToggle().trigger('click');
const submitBtn = findSubmitButton();
expect(submitBtn.exists()).toBe(true);
expect(submitBtn.text()).toBe('Save integration');
findForm().trigger('submit');
expect(wrapper.emitted('create-new-integration')[0]).toEqual([
{ type: typeSet.prometheus, variables: { apiUrl, active: true } },
]);
});
it('update', () => {
createComponent({
data: {
selectedIntegration: typeSet.prometheus,
currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
},
props: {
loading: false,
},
});
const apiUrl = 'https://test-post.com';
enableIntegration(1, apiUrl);
const submitBtn = findSubmitButton();
expect(submitBtn.exists()).toBe(true);
expect(submitBtn.text()).toBe('Save integration');
findForm().trigger('submit');
expect(wrapper.emitted('update-integration')[0]).toEqual([
{ type: typeSet.prometheus, variables: { apiUrl, active: true } },
]);
});
});
});
@ -234,9 +251,10 @@ describe('AlertsSettingsFormNew', () => {
jest.runAllTimers();
await wrapper.vm.$nextTick();
expect(findJsonTestSubmit().exists()).toBe(true);
expect(findJsonTestSubmit().text()).toBe('Save and test payload');
expect(findJsonTestSubmit().props('disabled')).toBe(true);
const jsonTestSubmit = findJsonTestSubmit();
expect(jsonTestSubmit.exists()).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 () => {
@ -341,9 +359,8 @@ describe('AlertsSettingsFormNew', () => {
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();
await selectOptionAtIndex(integrationOption);
expect(findMappingBuilderSection().exists()).toBe(visible);
});
});

View File

@ -0,0 +1,80 @@
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' },
]);
});
});
});