Commit 30a156cd authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '263357-core-error-message' into 'master'

Resolve "No additional integrations for Core users"

See merge request gitlab-org/gitlab!46889
parents a4d3e18f 9cfaccf4
......@@ -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