Commit 5c44fe0d authored by David O'Regan's avatar David O'Regan Committed by Nicolò Maria Mezzopera

Add support for HTTP Create

Add support for alert HTTP
create supported via GraphQL
parent 497143d0
...@@ -4,12 +4,15 @@ import { ...@@ -4,12 +4,15 @@ import {
GlButton, GlButton,
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
GlModal,
GlModalDirective,
GlTable, GlTable,
GlTooltipDirective, GlTooltipDirective,
GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { trackAlertIntegrationsViewsOptions } from '../constants'; import { trackAlertIntegrationsViewsOptions, integrationToDeleteDefault } from '../constants';
export const i18n = { export const i18n = {
title: s__('AlertsIntegrations|Current integrations'), title: s__('AlertsIntegrations|Current integrations'),
...@@ -36,10 +39,13 @@ export default { ...@@ -36,10 +39,13 @@ export default {
GlButton, GlButton,
GlIcon, GlIcon,
GlLoadingIcon, GlLoadingIcon,
GlModal,
GlTable, GlTable,
GlSprintf,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
GlModal: GlModalDirective,
}, },
props: { props: {
integrations: { integrations: {
...@@ -71,6 +77,11 @@ export default { ...@@ -71,6 +77,11 @@ export default {
label: __('Actions'), label: __('Actions'),
}, },
], ],
data() {
return {
integrationToDelete: integrationToDeleteDefault,
};
},
computed: { computed: {
tbodyTrClass() { tbodyTrClass() {
return { return {
...@@ -86,6 +97,14 @@ export default { ...@@ -86,6 +97,14 @@ export default {
const { category, action } = trackAlertIntegrationsViewsOptions; const { category, action } = trackAlertIntegrationsViewsOptions;
Tracking.event(category, action); Tracking.event(category, action);
}, },
intergrationToDelete({ name, id }) {
this.integrationToDelete.id = id;
this.integrationToDelete.name = name;
},
deleteIntergration() {
this.$emit('delete-integration', { id: this.integrationToDelete.id });
this.integrationToDelete = { ...integrationToDeleteDefault };
},
}, },
}; };
</script> </script>
...@@ -127,7 +146,11 @@ export default { ...@@ -127,7 +146,11 @@ export default {
<template #cell(actions)="{ item }"> <template #cell(actions)="{ item }">
<gl-button-group> <gl-button-group>
<gl-button icon="pencil" @click="$emit('edit-integration', { id: item.id })" /> <gl-button icon="pencil" @click="$emit('edit-integration', { id: item.id })" />
<gl-button icon="remove" @click="$emit('delete-integration', { id: item.id })" /> <gl-button
v-gl-modal.deleteIntegration
icon="remove"
@click="intergrationToDelete(item)"
/>
</gl-button-group> </gl-button-group>
</template> </template>
...@@ -143,5 +166,22 @@ export default { ...@@ -143,5 +166,22 @@ export default {
</div> </div>
</template> </template>
</gl-table> </gl-table>
<gl-modal
modal-id="deleteIntegration"
:title="__('Are you sure?')"
:ok-title="s__('AlertSettings|Delete integration')"
ok-variant="danger"
@ok="deleteIntergration"
>
<gl-sprintf
:message="
s__(
'AlertsIntegrations|You have opted to delete the %{integrationName} integration. Do you want to proceed? It means you will no longer receive alerts from this endpoint in your alert list, and this action cannot be undone.',
)
"
>
<template #integrationName>{{ integrationToDelete.name }}</template>
</gl-sprintf>
</gl-modal>
</div> </div>
</template> </template>
...@@ -22,14 +22,12 @@ import { ...@@ -22,14 +22,12 @@ import {
JSON_VALIDATE_DELAY, JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder, targetPrometheusUrlPlaceholder,
typeSet, typeSet,
defaultFormState,
} from '../constants'; } from '../constants';
export default { export default {
targetPrometheusUrlPlaceholder, targetPrometheusUrlPlaceholder,
JSON_VALIDATE_DELAY, JSON_VALIDATE_DELAY,
typeSet, typeSet,
defaultFormState,
i18n: { i18n: {
integrationFormSteps: { integrationFormSteps: {
step1: { step1: {
...@@ -113,14 +111,18 @@ export default { ...@@ -113,14 +111,18 @@ export default {
data() { data() {
return { return {
selectedIntegration: integrationTypesNew[0].value, selectedIntegration: integrationTypesNew[0].value,
active: false,
options: integrationTypesNew, options: integrationTypesNew,
active: false,
formVisible: false, formVisible: false,
integrationTestPayload: {
json: null,
error: null,
},
}; };
}, },
computed: { computed: {
jsonIsValid() { jsonIsValid() {
return this.integrationForm.integrationTestPayload.error === null; return this.integrationTestPayload.error === null;
}, },
selectedIntegrationType() { selectedIntegrationType() {
switch (this.selectedIntegration) { switch (this.selectedIntegration) {
...@@ -129,43 +131,42 @@ export default { ...@@ -129,43 +131,42 @@ export default {
case this.$options.typeSet.prometheus: case this.$options.typeSet.prometheus:
return this.prometheus; return this.prometheus;
default: default:
return this.defaultFormState; return {};
} }
}, },
integrationForm() { integrationForm() {
return { return {
name: this.currentIntegration?.name || '', name: this.currentIntegration?.name || '',
integrationTestPayload: {
json: null,
error: null,
},
active: this.currentIntegration?.active || false, active: this.currentIntegration?.active || false,
token: this.currentIntegration?.token || '', token: this.currentIntegration?.token || this.selectedIntegrationType.token,
url: this.currentIntegration?.url || '', url: this.currentIntegration?.url || this.selectedIntegrationType.url,
apiUrl: this.currentIntegration?.apiUrl || '', apiUrl: this.currentIntegration?.apiUrl || '',
}; };
}, },
}, },
watch: { watch: {
currentIntegration(val) { currentIntegration(val) {
if (val === null) {
return this.reset();
}
this.selectedIntegration = val.type; this.selectedIntegration = val.type;
this.active = val.active; this.active = val.active;
this.onIntegrationTypeSelect(); return this.integrationTypeSelect();
}, },
}, },
methods: { methods: {
onIntegrationTypeSelect() { integrationTypeSelect() {
if (this.selectedIntegration === integrationTypesNew[0].value) { if (this.selectedIntegration === integrationTypesNew[0].value) {
this.formVisible = false; this.formVisible = false;
} else { } else {
this.formVisible = true; this.formVisible = true;
} }
}, },
onSubmitWithTestPayload() { submitWithTestPayload() {
// TODO: Test payload before saving via GraphQL // TODO: Test payload before saving via GraphQL
this.onSubmit(); this.submit();
}, },
onSubmit() { submit() {
const { name, apiUrl } = this.integrationForm; const { name, apiUrl } = this.integrationForm;
const variables = const variables =
this.selectedIntegration === this.$options.typeSet.http this.selectedIntegration === this.$options.typeSet.http
...@@ -179,27 +180,45 @@ export default { ...@@ -179,27 +180,45 @@ export default {
return this.$emit('create-new-integration', integrationPayload); return this.$emit('create-new-integration', integrationPayload);
}, },
onReset() { reset() {
this.integrationForm = this.defaultFormState;
this.selectedIntegration = integrationTypesNew[0].value; this.selectedIntegration = integrationTypesNew[0].value;
this.onIntegrationTypeSelect(); this.integrationTypeSelect();
if (this.currentIntegration) {
return this.$emit('clear-current-integration');
}
return this.resetFormValues();
},
resetFormValues() {
this.integrationForm.name = '';
this.integrationForm.apiUrl = '';
this.integrationTestPayload = {
json: null,
error: null,
};
this.active = false;
}, },
onResetAuthKey() { resetAuthKey() {
if (!this.currentIntegration) {
return;
}
this.$emit('reset-token', { this.$emit('reset-token', {
type: this.selectedIntegration, type: this.selectedIntegration,
variables: { id: this.currentIntegration.id }, variables: { id: this.currentIntegration.id },
}); });
}, },
validateJson() { validateJson() {
this.integrationForm.integrationTestPayload.error = null; this.integrationTestPayload.error = null;
if (this.integrationForm.integrationTestPayload.json === '') { if (this.integrationTestPayload.json === '') {
return; return;
} }
try { try {
JSON.parse(this.integrationForm.integrationTestPayload.json); JSON.parse(this.integrationTestPayload.json);
} catch (e) { } catch (e) {
this.integrationForm.integrationTestPayload.error = JSON.stringify(e.message); this.integrationTestPayload.error = JSON.stringify(e.message);
} }
}, },
}, },
...@@ -207,7 +226,7 @@ export default { ...@@ -207,7 +226,7 @@ export default {
</script> </script>
<template> <template>
<gl-form class="gl-mt-6" @submit.prevent="onSubmit" @reset.prevent="onReset"> <gl-form class="gl-mt-6" @submit.prevent="submit" @reset.prevent="reset">
<h5 class="gl-font-lg gl-my-5">{{ s__('AlertSettings|Add new integrations') }}</h5> <h5 class="gl-font-lg gl-my-5">{{ s__('AlertSettings|Add new integrations') }}</h5>
<gl-form-group <gl-form-group
...@@ -217,8 +236,9 @@ export default { ...@@ -217,8 +236,9 @@ export default {
> >
<gl-form-select <gl-form-select
v-model="selectedIntegration" v-model="selectedIntegration"
:disabled="currentIntegration !== null"
:options="options" :options="options"
@change="onIntegrationTypeSelect" @change="integrationTypeSelect"
/> />
<alert-settings-form-help-block <alert-settings-form-help-block
...@@ -279,7 +299,11 @@ export default { ...@@ -279,7 +299,11 @@ export default {
<gl-form-input-group id="url" readonly :value="integrationForm.url"> <gl-form-input-group id="url" readonly :value="integrationForm.url">
<template #append> <template #append>
<clipboard-button :text="integrationForm.url" :title="__('Copy')" class="gl-m-0!" /> <clipboard-button
:text="integrationForm.url || ''"
:title="__('Copy')"
class="gl-m-0!"
/>
</template> </template>
</gl-form-input-group> </gl-form-input-group>
</div> </div>
...@@ -296,7 +320,11 @@ export default { ...@@ -296,7 +320,11 @@ export default {
:value="integrationForm.token" :value="integrationForm.token"
> >
<template #append> <template #append>
<clipboard-button :text="integrationForm.token" :title="__('Copy')" class="gl-m-0!" /> <clipboard-button
:text="integrationForm.token || ''"
:title="__('Copy')"
class="gl-m-0!"
/>
</template> </template>
</gl-form-input-group> </gl-form-input-group>
...@@ -308,7 +336,7 @@ export default { ...@@ -308,7 +336,7 @@ export default {
:title="$options.i18n.integrationFormSteps.step3.reset" :title="$options.i18n.integrationFormSteps.step3.reset"
:ok-title="$options.i18n.integrationFormSteps.step3.reset" :ok-title="$options.i18n.integrationFormSteps.step3.reset"
ok-variant="danger" ok-variant="danger"
@ok="onResetAuthKey" @ok="resetAuthKey"
> >
{{ $options.i18n.integrationFormSteps.restKeyInfo.label }} {{ $options.i18n.integrationFormSteps.restKeyInfo.label }}
</gl-modal> </gl-modal>
...@@ -318,7 +346,7 @@ export default { ...@@ -318,7 +346,7 @@ export default {
id="test-integration" 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="integrationForm.integrationTestPayload.error" :invalid-feedback="integrationTestPayload.error"
> >
<alert-settings-form-help-block <alert-settings-form-help-block
:message="$options.i18n.integrationFormSteps.step4.help" :message="$options.i18n.integrationFormSteps.step4.help"
...@@ -327,8 +355,8 @@ export default { ...@@ -327,8 +355,8 @@ export default {
<gl-form-textarea <gl-form-textarea
id="test-integration" id="test-integration"
v-model.trim="integrationForm.integrationTestPayload.json" v-model.trim="integrationTestPayload.json"
:disabled="!integrationForm.active" :disabled="!active"
:state="jsonIsValid" :state="jsonIsValid"
:placeholder="$options.i18n.integrationFormSteps.step4.placeholder" :placeholder="$options.i18n.integrationFormSteps.step4.placeholder"
class="gl-my-4" class="gl-my-4"
...@@ -354,7 +382,7 @@ export default { ...@@ -354,7 +382,7 @@ export default {
category="secondary" category="secondary"
variant="success" variant="success"
class="gl-mr-1 js-no-auto-disable" class="gl-mr-1 js-no-auto-disable"
@click="onSubmitWithTestPayload" @click="submitWithTestPayload"
>{{ s__('AlertSettings|Save and test payload') }}</gl-button >{{ s__('AlertSettings|Save and test payload') }}</gl-button
> >
<gl-button <gl-button
......
<script> <script>
import produce from 'immer';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
...@@ -9,12 +8,17 @@ import createHttpIntegrationMutation from '../graphql/mutations/create_http_inte ...@@ -9,12 +8,17 @@ import createHttpIntegrationMutation from '../graphql/mutations/create_http_inte
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql'; import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql'; import updateHttpIntegrationMutation from '../graphql/mutations/update_http_integration.mutation.graphql';
import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql'; import updatePrometheusIntegrationMutation from '../graphql/mutations/update_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql'; import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql'; import resetPrometheusTokenMutation from '../graphql/mutations/reset_prometheus_token.mutation.graphql';
import IntegrationsList from './alerts_integrations_list.vue'; import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue'; import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue'; import SettingsFormNew from './alerts_settings_form_new.vue';
import { typeSet } from '../constants'; import { typeSet } from '../constants';
import {
updateStoreAfterIntegrationDelete,
updateStoreAfterIntegrationAdd,
} from '../utils/cache_updates';
export default { export default {
typeSet, typeSet,
...@@ -22,6 +26,7 @@ export default { ...@@ -22,6 +26,7 @@ export default {
changesSaved: s__( changesSaved: s__(
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.', 'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
), ),
integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'),
}, },
components: { components: {
IntegrationsList, IntegrationsList,
...@@ -89,6 +94,8 @@ export default { ...@@ -89,6 +94,8 @@ export default {
}, },
methods: { methods: {
createNewIntegration({ type, variables }) { createNewIntegration({ type, variables }) {
const { projectPath } = this;
this.isUpdating = true; this.isUpdating = true;
this.$apollo this.$apollo
.mutate({ .mutate({
...@@ -98,9 +105,11 @@ export default { ...@@ -98,9 +105,11 @@ export default {
: createPrometheusIntegrationMutation, : createPrometheusIntegrationMutation,
variables: { variables: {
...variables, ...variables,
projectPath: this.projectPath, projectPath,
},
update(store, { data }) {
updateStoreAfterIntegrationAdd(store, getIntegrationsQuery, data, { projectPath });
}, },
update: this.updateIntegrations,
}) })
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => { .then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0]; const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
...@@ -119,41 +128,6 @@ export default { ...@@ -119,41 +128,6 @@ export default {
this.isUpdating = false; this.isUpdating = false;
}); });
}, },
updateIntegrations(
store,
{
data: { httpIntegrationCreate, prometheusIntegrationCreate },
},
) {
const integration =
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query: getIntegrationsQuery,
variables: {
projectPath: this.projectPath,
},
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = [
integration,
...draftData.project.alertManagementIntegrations.nodes,
];
});
store.writeQuery({
query: getIntegrationsQuery,
variables: {
projectPath: this.projectPath,
},
data,
});
},
updateIntegration({ type, variables }) { updateIntegration({ type, variables }) {
this.isUpdating = true; this.isUpdating = true;
this.$apollo this.$apollo
...@@ -201,6 +175,12 @@ export default { ...@@ -201,6 +175,12 @@ export default {
if (error) { if (error) {
return createFlash({ message: error }); return createFlash({ message: error });
} }
const integration =
httpIntegrationResetToken?.integration ||
prometheusIntegrationResetToken?.integration;
this.currentIntegration = integration;
return createFlash({ return createFlash({
message: this.$options.i18n.changesSaved, message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS, type: FLASH_TYPES.SUCCESS,
...@@ -217,8 +197,41 @@ export default { ...@@ -217,8 +197,41 @@ export default {
editIntegration({ id }) { editIntegration({ id }) {
this.currentIntegration = this.integrations.list.find(integration => integration.id === id); this.currentIntegration = this.integrations.list.find(integration => integration.id === id);
}, },
deleteIntegration() { deleteIntegration({ id }) {
// TODO, handle delete via GraphQL const { projectPath } = this;
this.isUpdating = true;
this.$apollo
.mutate({
mutation: destroyHttpIntegrationMutation,
variables: {
id,
},
update(store, { data }) {
updateStoreAfterIntegrationDelete(store, getIntegrationsQuery, data, { projectPath });
},
})
.then(({ data: { httpIntegrationDestroy } = {} } = {}) => {
const error = httpIntegrationDestroy?.errors[0];
if (error) {
return createFlash({ message: error });
}
this.currentIntegration = null;
return createFlash({
message: this.$options.i18n.integrationRemoved,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(err => {
this.errored = true;
createFlash({ message: err });
})
.finally(() => {
this.isUpdating = false;
});
},
clearCurrentIntegration() {
this.currentIntegration = null;
}, },
}, },
}; };
...@@ -239,6 +252,7 @@ export default { ...@@ -239,6 +252,7 @@ export default {
@create-new-integration="createNewIntegration" @create-new-integration="createNewIntegration"
@update-integration="updateIntegration" @update-integration="updateIntegration"
@reset-token="resetToken" @reset-token="resetToken"
@clear-current-integration="clearCurrentIntegration"
/> />
<settings-form-old v-else /> <settings-form-old v-else />
</div> </div>
......
...@@ -66,6 +66,8 @@ export const defaultFormState = { ...@@ -66,6 +66,8 @@ export const defaultFormState = {
integrationTestPayload: { json: null, error: null }, integrationTestPayload: { json: null, error: null },
}; };
export const integrationToDeleteDefault = { id: null, name: '' };
export const JSON_VALIDATE_DELAY = 250; export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/'; export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
......
#import "../fragments/integration_item.fragment.graphql"
mutation destroyHttpIntegration($id: ID!) {
httpIntegrationDestroy(input: { id: $id }) {
errors
integration {
...IntegrationItem
}
}
}
import produce from 'immer';
import createFlash from '~/flash';
import { DELETE_INTEGRATION_ERROR, ADD_INTEGRATION_ERROR } from './error_messages';
const deleteIntegrationFromStore = (store, query, { httpIntegrationDestroy }, variables) => {
const integration = httpIntegrationDestroy?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = draftData.project.alertManagementIntegrations.nodes.filter(
({ id }) => id !== integration.id,
);
});
store.writeQuery({
query,
variables,
data,
});
};
const addIntegrationToStore = (
store,
query,
{ httpIntegrationCreate, prometheusIntegrationCreate },
variables,
) => {
const integration =
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = [
integration,
...draftData.project.alertManagementIntegrations.nodes,
];
});
store.writeQuery({
query,
variables,
data,
});
};
const onError = (data, message) => {
createFlash({ message });
throw new Error(data.errors);
};
export const hasErrors = ({ errors = [] }) => errors?.length;
export const updateStoreAfterIntegrationDelete = (store, query, data, variables) => {
if (hasErrors(data)) {
onError(data, DELETE_INTEGRATION_ERROR);
} else {
deleteIntegrationFromStore(store, query, data, variables);
}
};
export const updateStoreAfterIntegrationAdd = (store, query, data, variables) => {
if (hasErrors(data)) {
onError(data, ADD_INTEGRATION_ERROR);
} else {
addIntegrationToStore(store, query, data, variables);
}
};
import { s__ } from '~/locale';
export const DELETE_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be deleted. Please try again.',
);
export const ADD_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be added. Please try again.',
);
...@@ -2551,6 +2551,9 @@ msgstr "" ...@@ -2551,6 +2551,9 @@ msgstr ""
msgid "AlertSettings|Copy" msgid "AlertSettings|Copy"
msgstr "" msgstr ""
msgid "AlertSettings|Delete integration"
msgstr ""
msgid "AlertSettings|Enter integration name" msgid "AlertSettings|Enter integration name"
msgstr "" msgstr ""
...@@ -2668,9 +2671,21 @@ msgstr "" ...@@ -2668,9 +2671,21 @@ msgstr ""
msgid "AlertsIntegrations|Prometheus" msgid "AlertsIntegrations|Prometheus"
msgstr "" msgstr ""
msgid "AlertsIntegrations|The integration could not be added. Please try again."
msgstr ""
msgid "AlertsIntegrations|The integration could not be deleted. Please try again."
msgstr ""
msgid "AlertsIntegrations|The integration has been successfully removed."
msgstr ""
msgid "AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list." msgid "AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list."
msgstr "" msgstr ""
msgid "AlertsIntegrations|You have opted to delete the %{integrationName} integration. Do you want to proceed? It means you will no longer receive alerts from this endpoint in your alert list, and this action cannot be undone."
msgstr ""
msgid "Algorithm" msgid "Algorithm"
msgstr "" msgstr ""
......
...@@ -128,18 +128,18 @@ describe('AlertsSettingsFormNew', () => { ...@@ -128,18 +128,18 @@ describe('AlertsSettingsFormNew', () => {
it('allows for update-integration with the correct form values for HTTP', async () => { it('allows for update-integration with the correct form values for HTTP', async () => {
createComponent({ createComponent({
data: {
selectedIntegration: typeSet.http,
},
props: { props: {
currentIntegration: { id: '1' }, currentIntegration: { id: '1', name: 'Test integration pre' },
loading: false, loading: false,
}, },
}); });
const options = findSelect().findAll('option');
await options.at(1).setSelected();
await findFormFields() await findFormFields()
.at(0) .at(0)
.setValue('Test integration'); .setValue('Test integration post');
await findFormToggle().trigger('click'); await findFormToggle().trigger('click');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
...@@ -153,27 +153,27 @@ describe('AlertsSettingsFormNew', () => { ...@@ -153,27 +153,27 @@ describe('AlertsSettingsFormNew', () => {
expect(wrapper.emitted('update-integration')).toBeTruthy(); expect(wrapper.emitted('update-integration')).toBeTruthy();
expect(wrapper.emitted('update-integration')[0]).toEqual([ expect(wrapper.emitted('update-integration')[0]).toEqual([
{ type: typeSet.http, variables: { name: 'Test integration', active: true } }, { type: typeSet.http, variables: { name: 'Test integration post', active: true } },
]); ]);
}); });
it('allows for update-integration with the correct form values for PROMETHEUS', async () => { it('allows for update-integration with the correct form values for PROMETHEUS', async () => {
createComponent({ createComponent({
data: {
selectedIntegration: typeSet.prometheus,
},
props: { props: {
currentIntegration: { id: '1' }, currentIntegration: { id: '1', apiUrl: 'https://test-pre.com' },
loading: false, loading: false,
}, },
}); });
const options = findSelect().findAll('option');
await options.at(2).setSelected();
await findFormFields() await findFormFields()
.at(0) .at(0)
.setValue('Test integration'); .setValue('Test integration');
await findFormFields() await findFormFields()
.at(1) .at(1)
.setValue('https://test.com'); .setValue('https://test-post.com');
await findFormToggle().trigger('click'); await findFormToggle().trigger('click');
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
...@@ -187,7 +187,7 @@ describe('AlertsSettingsFormNew', () => { ...@@ -187,7 +187,7 @@ describe('AlertsSettingsFormNew', () => {
expect(wrapper.emitted('update-integration')).toBeTruthy(); 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: 'https://test.com', active: true } }, { type: typeSet.prometheus, variables: { apiUrl: 'https://test-post.com', active: true } },
]); ]);
}); });
}); });
......
import { mount } from '@vue/test-utils'; import VueApollo from 'vue-apollo';
import { mount, createLocalVue } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue'; import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue'; import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue';
import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue'; import AlertsSettingsFormNew from '~/alerts_settings/components/alerts_settings_form_new.vue';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue'; import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql';
import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql'; import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql';
import updateHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; import updateHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql';
import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql'; import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql'; import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql';
import resetPrometheusTokenMutation from '~/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql'; import resetPrometheusTokenMutation from '~/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql';
import { typeSet } from '~/alerts_settings/constants'; import { typeSet } from '~/alerts_settings/constants';
...@@ -20,16 +24,34 @@ import { ...@@ -20,16 +24,34 @@ import {
createPrometheusVariables, createPrometheusVariables,
updatePrometheusVariables, updatePrometheusVariables,
ID, ID,
errorMsg,
getIntegrationsQueryResponse,
destroyIntegrationResponse,
integrationToDestroy,
destroyIntegrationResponseWithErrors,
} from './mocks/apollo_mock'; } from './mocks/apollo_mock';
jest.mock('~/flash'); jest.mock('~/flash');
const localVue = createLocalVue();
describe('AlertsSettingsWrapper', () => { describe('AlertsSettingsWrapper', () => {
let wrapper; let wrapper;
let fakeApollo;
let destroyIntegrationHandler;
const findLoader = () => wrapper.find(IntegrationsList).find(GlLoadingIcon); const findLoader = () => wrapper.find(IntegrationsList).find(GlLoadingIcon);
const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr'); const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
async function destroyHttpIntegration(localWrapper) {
await jest.runOnlyPendingTimers();
await localWrapper.vm.$nextTick();
localWrapper
.find(IntegrationsList)
.vm.$emit('delete-integration', { id: integrationToDestroy.id });
}
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => { const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
wrapper = mount(AlertsSettingsWrapper, { wrapper = mount(AlertsSettingsWrapper, {
data() { data() {
...@@ -54,6 +76,29 @@ describe('AlertsSettingsWrapper', () => { ...@@ -54,6 +76,29 @@ describe('AlertsSettingsWrapper', () => {
}); });
}; };
function createComponentWithApollo({
destroyHandler = jest.fn().mockResolvedValue(destroyIntegrationResponse),
} = {}) {
localVue.use(VueApollo);
destroyIntegrationHandler = destroyHandler;
const requestHandlers = [
[getIntegrationsQuery, jest.fn().mockResolvedValue(getIntegrationsQueryResponse)],
[destroyHttpIntegrationMutation, destroyIntegrationHandler],
];
fakeApollo = createMockApollo(requestHandlers);
wrapper = mount(AlertsSettingsWrapper, {
localVue,
apolloProvider: fakeApollo,
provide: {
...defaultAlertSettingsConfig,
glFeatures: { httpIntegrationsList: true },
},
});
}
afterEach(() => { afterEach(() => {
if (wrapper) { if (wrapper) {
wrapper.destroy(); wrapper.destroy();
...@@ -243,7 +288,6 @@ describe('AlertsSettingsWrapper', () => { ...@@ -243,7 +288,6 @@ describe('AlertsSettingsWrapper', () => {
}); });
it('shows error alert when integration creation fails ', async () => { it('shows error alert when integration creation fails ', async () => {
const errorMsg = 'Something went wrong';
createComponent({ createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
provide: { glFeatures: { httpIntegrationsList: true } }, provide: { glFeatures: { httpIntegrationsList: true } },
...@@ -259,7 +303,6 @@ describe('AlertsSettingsWrapper', () => { ...@@ -259,7 +303,6 @@ describe('AlertsSettingsWrapper', () => {
}); });
it('shows error alert when integration token reset fails ', () => { it('shows error alert when integration token reset fails ', () => {
const errorMsg = 'Something went wrong';
createComponent({ createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
provide: { glFeatures: { httpIntegrationsList: true } }, provide: { glFeatures: { httpIntegrationsList: true } },
...@@ -276,7 +319,6 @@ describe('AlertsSettingsWrapper', () => { ...@@ -276,7 +319,6 @@ describe('AlertsSettingsWrapper', () => {
}); });
it('shows error alert when integration update fails ', () => { it('shows error alert when integration update fails ', () => {
const errorMsg = 'Something went wrong';
createComponent({ createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
provide: { glFeatures: { httpIntegrationsList: true } }, provide: { glFeatures: { httpIntegrationsList: true } },
...@@ -292,4 +334,41 @@ describe('AlertsSettingsWrapper', () => { ...@@ -292,4 +334,41 @@ describe('AlertsSettingsWrapper', () => {
}); });
}); });
}); });
describe('with mocked Apollo client', () => {
it('has a selection of integrations loaded via the getIntegrationsQuery', async () => {
createComponentWithApollo();
await jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
expect(findIntegrations()).toHaveLength(4);
});
it('calls a mutation with correct parameters and destroys a integration', async () => {
createComponentWithApollo();
await destroyHttpIntegration(wrapper);
expect(destroyIntegrationHandler).toHaveBeenCalled();
await wrapper.vm.$nextTick();
expect(findIntegrations()).toHaveLength(3);
});
it('displays flash if mutation had a recoverable error', async () => {
createComponentWithApollo({
destroyHandler: jest.fn().mockResolvedValue(destroyIntegrationResponseWithErrors),
});
await destroyHttpIntegration(wrapper);
await wrapper.vm.$nextTick(); // kick off the DOM update
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
await wrapper.vm.$nextTick(); // kick off the DOM update for flash
expect(createFlash).toHaveBeenCalledWith({ message: 'Houston, we have a problem' });
});
});
}); });
const projectPath = ''; const projectPath = '';
export const ID = 'gid://gitlab/AlertManagement::HttpIntegration/7'; export const ID = 'gid://gitlab/AlertManagement::HttpIntegration/7';
export const errorMsg = 'Something went wrong';
export const createHttpVariables = { export const createHttpVariables = {
name: 'Test Pre', name: 'Test Pre',
...@@ -24,3 +25,99 @@ export const updatePrometheusVariables = { ...@@ -24,3 +25,99 @@ export const updatePrometheusVariables = {
active: true, active: true,
id: ID, id: ID,
}; };
export const getIntegrationsQueryResponse = {
data: {
project: {
alertManagementIntegrations: {
nodes: [
{
id: '37',
type: 'HTTP',
active: true,
name: 'Test 5',
url:
'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-5/d4875758e67334f3.json',
token: '89eb01df471d990ff5162a1c640408cf',
apiUrl: null,
},
{
id: '41',
type: 'HTTP',
active: true,
name: 'Test 9999',
url:
'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-9999/b78a566e1776cfc2.json',
token: 'f7579aa03844e07af3b1f0fca3f79f81',
apiUrl: null,
},
{
id: '40',
type: 'HTTP',
active: true,
name: 'Test 6',
url:
'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-6/3e828ae28a240222.json',
token: '6536102a607a5dd74fcdde921f2349ee',
apiUrl: null,
},
{
id: '12',
type: 'PROMETHEUS',
active: false,
name: 'Prometheus',
url: 'http://127.0.0.1:3000/h5bp/html5-boilerplate/prometheus/alerts/notify.json',
token: '256f687c6225aa5d6ee50c3d68120c4c',
apiUrl: 'https://localhost.ieeeesassadasasa',
},
],
},
},
},
};
export const integrationToDestroy = {
id: '37',
type: 'HTTP',
active: true,
name: 'Test 5',
url: 'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-5/d4875758e67334f3.json',
token: '89eb01df471d990ff5162a1c640408cf',
apiUrl: null,
};
export const destroyIntegrationResponse = {
data: {
httpIntegrationDestroy: {
errors: [],
integration: {
id: '37',
type: 'HTTP',
active: true,
name: 'Test 5',
url:
'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-5/d4875758e67334f3.json',
token: '89eb01df471d990ff5162a1c640408cf',
apiUrl: null,
},
},
},
};
export const destroyIntegrationResponseWithErrors = {
data: {
httpIntegrationDestroy: {
errors: ['Houston, we have a problem'],
integration: {
id: '37',
type: 'HTTP',
active: true,
name: 'Test 5',
url:
'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-5/d4875758e67334f3.json',
token: '89eb01df471d990ff5162a1c640408cf',
apiUrl: null,
},
},
},
};
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