Commit 9cfaccf4 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 4c839b62
......@@ -33,6 +33,9 @@ export default {
step1: {
label: s__('AlertSettings|1. Select integration type'),
help: s__('AlertSettings|Learn more about our upcoming %{linkStart}integrations%{linkEnd}'),
enterprise: s__(
'AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations.',
),
},
step2: {
label: s__('AlertSettings|2. Name integration'),
......@@ -107,6 +110,10 @@ export default {
required: false,
default: null,
},
canAddIntegration: {
type: Boolean,
required: true,
},
},
data() {
return {
......@@ -236,15 +243,24 @@ export default {
>
<gl-form-select
v-model="selectedIntegration"
:disabled="currentIntegration !== null"
:disabled="currentIntegration !== null || !canAddIntegration"
:options="options"
@change="integrationTypeSelect"
/>
<alert-settings-form-help-block
:message="$options.i18n.integrationFormSteps.step1.help"
link="https://gitlab.com/groups/gitlab-org/-/epics/4390"
/>
<div class="gl-my-4">
<alert-settings-form-help-block
:message="$options.i18n.integrationFormSteps.step1.help"
link="https://gitlab.com/groups/gitlab-org/-/epics/4390"
/>
</div>
<div v-if="!canAddIntegration" class="gl-my-4" data-testid="multi-integrations-not-supported">
<alert-settings-form-help-block
:message="$options.i18n.integrationFormSteps.step1.enterprise"
link="https://about.gitlab.com/pricing"
/>
</div>
</gl-form-group>
<gl-collapse v-model="formVisible" class="gl-mt-3">
<gl-form-group
......
......@@ -19,6 +19,11 @@ import {
updateStoreAfterIntegrationDelete,
updateStoreAfterIntegrationAdd,
} from '../utils/cache_updates';
import {
DELETE_INTEGRATION_ERROR,
ADD_INTEGRATION_ERROR,
RESET_INTEGRATION_TOKEN_ERROR,
} from '../utils/error_messages';
export default {
typeSet,
......@@ -44,6 +49,9 @@ export default {
projectPath: {
default: '',
},
multiIntegrations: {
default: false,
},
},
apollo: {
integrations: {
......@@ -91,6 +99,9 @@ export default {
},
];
},
canAddIntegration() {
return this.multiIntegrations || this.integrations?.list?.length < 2;
},
},
methods: {
createNewIntegration({ type, variables }) {
......@@ -121,8 +132,8 @@ export default {
type: FLASH_TYPES.SUCCESS,
});
})
.catch(err => {
createFlash({ message: err });
.catch(() => {
createFlash({ message: ADD_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
......@@ -187,8 +198,8 @@ export default {
});
},
)
.catch(err => {
createFlash({ message: err });
.catch(() => {
createFlash({ message: RESET_INTEGRATION_TOKEN_ERROR });
})
.finally(() => {
this.isUpdating = false;
......@@ -222,9 +233,9 @@ export default {
type: FLASH_TYPES.SUCCESS,
});
})
.catch(err => {
.catch(() => {
this.errored = true;
createFlash({ message: err });
createFlash({ message: DELETE_INTEGRATION_ERROR });
})
.finally(() => {
this.isUpdating = false;
......@@ -249,6 +260,7 @@ export default {
v-if="glFeatures.httpIntegrationsList"
:loading="isUpdating"
:current-integration="currentIntegration"
:can-add-integration="canAddIntegration"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
@reset-token="resetToken"
......
......@@ -29,19 +29,16 @@ export default el => {
opsgenieMvcEnabled,
opsgenieMvcTargetUrl,
projectPath,
multiIntegrations,
} = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {},
},
),
});
const resolvers = {};
apolloProvider.clients.defaultClient.cache.writeData({
data: {},
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(resolvers, {
cacheConfig: {},
assumeImmutableResults: true,
}),
});
return new Vue({
......@@ -70,6 +67,7 @@ export default el => {
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
},
projectPath,
multiIntegrations: parseBoolean(multiIntegrations),
},
apolloProvider,
components: {
......
......@@ -7,3 +7,7 @@ export const DELETE_INTEGRATION_ERROR = s__(
export const ADD_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be added. Please try again.',
);
export const RESET_INTEGRATION_TOKEN_ERROR = s__(
'AlertsIntegrations|The integration token could not be reset. Please try again.',
);
......@@ -30,7 +30,8 @@ module OperationsHelper
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s,
'project_path' => @project.full_path
'project_path' => @project.full_path,
'multi_integrations' => 'false'
}
end
......
......@@ -37,7 +37,7 @@ module EE
override :alerts_settings_data
def alerts_settings_data(disabled: false)
super.merge(opsgenie_mvc_data)
super.merge(opsgenie_mvc_data, alert_management_multiple_integrations_data)
end
override :operations_settings_data
......@@ -71,5 +71,11 @@ module EE
'opsgenie_mvc_target_url' => alerts_service.opsgenie_mvc_target_url.to_s
}
end
def alert_management_multiple_integrations_data
{
'multi_integrations' => @project.feature_available?(:multiple_alert_http_integrations).to_s
}
end
end
end
......@@ -115,6 +115,24 @@ RSpec.describe OperationsHelper, :routing do
it { is_expected.not_to include(opsgenie_keys) }
end
end
describe 'Multiple Integrations Support' do
before do
stub_licensed_features(multiple_alert_http_integrations: multi_integrations)
end
context 'when available' do
let(:multi_integrations) { true }
it { is_expected.to include('multi_integrations' => 'true') }
end
context 'when not available' do
let(:multi_integrations) { false }
it { is_expected.to include('multi_integrations' => 'false') }
end
end
end
describe '#operations_settings_data' do
......
......@@ -2566,6 +2566,9 @@ msgstr ""
msgid "AlertSettings|HTTP endpoint"
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."
msgstr ""
msgid "AlertSettings|Integration"
msgstr ""
......@@ -2680,6 +2683,9 @@ msgstr ""
msgid "AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list."
msgstr ""
msgid "AlertsIntegrations|The integration token could not be reset. Please try again."
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 ""
......
......@@ -9,7 +9,9 @@ exports[`AlertsSettingsFormNew with default values renders the initial template
<option value=\\"HTTP\\">HTTP Endpoint</option>
<option value=\\"PROMETHEUS\\">External Prometheus</option>
<option value=\\"OPSGENIE\\">Opsgenie</option>
</select> <span class=\\"gl-text-gray-500\\">Learn more about our upcoming <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"https://gitlab.com/groups/gitlab-org/-/epics/4390\\" class=\\"gl-link gl-display-inline-block\\">integrations</a></span>
</select>
<div class=\\"gl-my-4\\"><span class=\\"gl-text-gray-500\\">Learn more about our upcoming <a rel=\\"noopener noreferrer\\" target=\\"_blank\\" href=\\"https://gitlab.com/groups/gitlab-org/-/epics/4390\\" class=\\"gl-link gl-display-inline-block\\">integrations</a></span></div>
<!---->
<!---->
<!---->
<!---->
......
......@@ -9,7 +9,7 @@ describe('AlertsSettingsFormNew', () => {
const createComponent = ({
data = {},
props = { loading: false },
props = {},
multipleHttpIntegrationsCustomMapping = false,
} = {}) => {
wrapper = mount(AlertsSettingsForm, {
......@@ -17,6 +17,8 @@ describe('AlertsSettingsFormNew', () => {
return { ...data };
},
propsData: {
loading: false,
canAddIntegration: true,
...props,
},
provide: {
......@@ -33,6 +35,8 @@ describe('AlertsSettingsFormNew', () => {
const findFormToggle = () => wrapper.find(GlToggle);
const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`);
const findSubmitButton = () => wrapper.find(`[type = "submit"]`);
const findMultiSupportText = () =>
wrapper.find(`[data-testid="multi-integrations-not-supported"]`);
afterEach(() => {
if (wrapper) {
......@@ -53,6 +57,7 @@ describe('AlertsSettingsFormNew', () => {
it('render the initial form with only an integration type dropdown', () => {
expect(findForm().exists()).toBe(true);
expect(findSelect().exists()).toBe(true);
expect(findMultiSupportText().exists()).toBe(false);
expect(findFormSteps().attributes('visible')).toBeUndefined();
});
......@@ -68,6 +73,12 @@ describe('AlertsSettingsFormNew', () => {
.isVisible(),
).toBe(true);
});
it('disabled the dropdown and shows help text when multi integrations are not supported', async () => {
createComponent({ props: { canAddIntegration: false } });
expect(findSelect().attributes('disabled')).toBe('disabled');
expect(findMultiSupportText().exists()).toBe(true);
});
});
describe('submitting integration form', () => {
......
import VueApollo from 'vue-apollo';
import { mount, createLocalVue } from '@vue/test-utils';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { GlLoadingIcon } from '@gitlab/ui';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import AlertsSettingsFormOld from '~/alerts_settings/components/alerts_settings_form_old.vue';
......@@ -15,6 +16,10 @@ import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/
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 { typeSet } from '~/alerts_settings/constants';
import {
ADD_INTEGRATION_ERROR,
RESET_INTEGRATION_TOKEN_ERROR,
} from '~/alerts_settings/utils/error_messages';
import createFlash from '~/flash';
import { defaultAlertSettingsConfig } from './util';
import mockIntegrations from './mocks/integrations.json';
......@@ -294,31 +299,30 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(ADD_INTEGRATION_ERROR);
wrapper.find(AlertsSettingsFormNew).vm.$emit('create-new-integration', {});
setImmediate(() => {
expect(createFlash).toHaveBeenCalledWith({ message: errorMsg });
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: ADD_INTEGRATION_ERROR });
});
it('shows error alert when integration token reset fails ', () => {
it('shows error alert when integration token reset fails ', async () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg);
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(RESET_INTEGRATION_TOKEN_ERROR);
wrapper.find(AlertsSettingsFormNew).vm.$emit('reset-token', {});
setImmediate(() => {
expect(createFlash).toHaveBeenCalledWith({ message: errorMsg });
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR });
});
it('shows error alert when integration update fails ', () => {
it('shows error alert when integration update fails ', async () => {
createComponent({
data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] },
provide: { glFeatures: { httpIntegrationsList: true } },
......@@ -329,9 +333,8 @@ describe('AlertsSettingsWrapper', () => {
wrapper.find(AlertsSettingsFormNew).vm.$emit('update-integration', {});
setImmediate(() => {
expect(createFlash).toHaveBeenCalledWith({ message: errorMsg });
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ message: errorMsg });
});
});
......
......@@ -25,4 +25,6 @@ export const defaultAlertSettingsConfig = {
active: ACTIVE,
opsgenieMvcTargetUrl: GENERIC_URL,
},
projectPath: '',
multiIntegrations: true,
};
......@@ -44,7 +44,8 @@ RSpec.describe OperationsHelper do
'prometheus_activated' => 'false',
'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json),
'disabled' => 'false',
'project_path' => project.full_path
'project_path' => project.full_path,
'multi_integrations' => 'false'
)
end
end
......
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