Commit b48820cf authored by David O'Regan's avatar David O'Regan Committed by Olena Horal-Koretska

Add multi HTTP support

Bootstrap the base for the
multi HTTP alert form
and hide it with a feature flag
parent 3dcc63fa
<script> <script>
import { GlTable, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { GlTable, GlIcon, GlTooltipDirective, GlLoadingIcon } 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 } from '../constants';
...@@ -27,6 +27,7 @@ export default { ...@@ -27,6 +27,7 @@ export default {
components: { components: {
GlTable, GlTable,
GlIcon, GlIcon,
GlLoadingIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -37,10 +38,15 @@ export default { ...@@ -37,10 +38,15 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
loading: {
type: Boolean,
required: false,
default: false,
},
}, },
fields: [ fields: [
{ {
key: 'activated', key: 'active',
label: __('Status'), label: __('Status'),
}, },
{ {
...@@ -78,12 +84,13 @@ export default { ...@@ -78,12 +84,13 @@ export default {
:empty-text="$options.i18n.emptyState" :empty-text="$options.i18n.emptyState"
:items="integrations" :items="integrations"
:fields="$options.fields" :fields="$options.fields"
:busy="loading"
stacked="md" stacked="md"
:tbody-tr-class="tbodyTrClass" :tbody-tr-class="tbodyTrClass"
show-empty show-empty
> >
<template #cell(activated)="{ item }"> <template #cell(active)="{ item }">
<span v-if="item.activated" data-testid="integration-activated-status"> <span v-if="item.active" data-testid="integration-activated-status">
<gl-icon <gl-icon
v-gl-tooltip v-gl-tooltip
name="check-circle-filled" name="check-circle-filled"
...@@ -104,6 +111,10 @@ export default { ...@@ -104,6 +111,10 @@ export default {
{{ $options.i18n.status.disabled.name }} {{ $options.i18n.status.disabled.name }}
</span> </span>
</template> </template>
<template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>
</gl-table> </gl-table>
</div> </div>
</template> </template>
<script> <script>
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 getIntegrationsQuery from '../graphql/queries/get_integrations.query.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';
...@@ -19,19 +21,52 @@ export default { ...@@ -19,19 +21,52 @@ export default {
prometheus: { prometheus: {
default: {}, default: {},
}, },
projectPath: {
default: '',
},
},
apollo: {
integrations: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getIntegrationsQuery,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
const { alertManagementIntegrations: { nodes: list = [] } = {} } = data.project || {};
return {
list,
};
},
error() {
this.errored = true;
},
},
},
data() {
return {
errored: false,
integrations: {},
};
}, },
computed: { computed: {
integrations() { loading() {
return this.$apollo.queries.integrations.loading;
},
intergrationsOptionsOld() {
return [ return [
{ {
name: s__('AlertSettings|HTTP endpoint'), name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'), type: s__('AlertsIntegrations|HTTP endpoint'),
activated: this.generic.activated, active: this.generic.activated,
}, },
{ {
name: s__('AlertSettings|External Prometheus'), name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'), type: s__('AlertsIntegrations|Prometheus'),
activated: this.prometheus.activated, active: this.prometheus.activated,
}, },
]; ];
}, },
...@@ -41,7 +76,10 @@ export default { ...@@ -41,7 +76,10 @@ export default {
<template> <template>
<div> <div>
<integrations-list :integrations="integrations" /> <integrations-list
:integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld"
:loading="loading"
/>
<settings-form-new v-if="glFeatures.httpIntegrationsList" /> <settings-form-new v-if="glFeatures.httpIntegrationsList" />
<settings-form-old v-else /> <settings-form-old v-else />
</div> </div>
......
fragment IntegrationItem on AlertManagementIntegration {
id
type
active
name
url
token
apiUrl
}
#import "../fragments/integration_item.fragment.graphql"
query getIntegrations($projectPath: ID!) {
project(fullPath: $projectPath) {
alertManagementIntegrations {
nodes {
...IntegrationItem
}
}
}
}
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue'; import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue';
Vue.use(VueApollo);
export default el => { export default el => {
if (!el) { if (!el) {
return null; return null;
...@@ -24,8 +28,22 @@ export default el => { ...@@ -24,8 +28,22 @@ export default el => {
opsgenieMvcFormPath, opsgenieMvcFormPath,
opsgenieMvcEnabled, opsgenieMvcEnabled,
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
projectPath,
} = el.dataset; } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
cacheConfig: {},
},
),
});
apolloProvider.clients.defaultClient.cache.writeData({
data: {},
});
return new Vue({ return new Vue({
el, el,
provide: { provide: {
...@@ -51,7 +69,9 @@ export default el => { ...@@ -51,7 +69,9 @@ export default el => {
opsgenieMvcTargetUrl, opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable), opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
}, },
projectPath,
}, },
apolloProvider,
components: { components: {
AlertSettingsWrapper, AlertSettingsWrapper,
}, },
......
...@@ -8,13 +8,12 @@ ...@@ -8,13 +8,12 @@
@include gl-text-gray-500; @include gl-text-gray-500;
tbody { tbody {
tr:not(.b-table-busy-slot) { tr:not(.b-table-busy-slot):not(.b-table-empty-row) {
// TODO replace with gitlab/ui utilities: https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1791
&:hover { &:hover {
border-top-style: double; @include gl-border-t-double;
td { td {
border-bottom-style: initial; @include gl-border-b-initial;
} }
} }
} }
...@@ -22,7 +21,7 @@ ...@@ -22,7 +21,7 @@
tr { tr {
&:focus { &:focus {
outline: none; @include gl-outline-none;
} }
td, td,
...@@ -118,22 +117,22 @@ ...@@ -118,22 +117,22 @@
} }
.gl-tabs-nav { .gl-tabs-nav {
border-bottom-width: 0; @include gl-border-b-0;
.gl-tab-nav-item { .gl-tab-nav-item {
color: $gray-500; @include gl-text-gray-500;
> .gl-tab-counter-badge { > .gl-tab-counter-badge {
color: inherit; @include gl-reset-color;
@include gl-font-sm; @include gl-font-sm;
background-color: $gray-50; @include gl-bg-gray-50;
} }
} }
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
.list-header { .list-header {
flex-direction: column-reverse; @include gl-flex-direction-column-reverse;
} }
.create-incident-button { .create-incident-button {
......
...@@ -29,7 +29,8 @@ module OperationsHelper ...@@ -29,7 +29,8 @@ module OperationsHelper
'url' => alerts_service.url, 'url' => alerts_service.url,
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'), '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), 'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s 'disabled' => disabled.to_s,
'project_path' => project_path(@project)
} }
end end
......
...@@ -8,12 +8,12 @@ import { trackAlertIntegrationsViewsOptions } from '~/alerts_settings/constants' ...@@ -8,12 +8,12 @@ import { trackAlertIntegrationsViewsOptions } from '~/alerts_settings/constants'
const mockIntegrations = [ const mockIntegrations = [
{ {
activated: true, active: true,
name: 'Integration 1', name: 'Integration 1',
type: 'HTTP endpoint', type: 'HTTP endpoint',
}, },
{ {
activated: false, active: false,
name: 'Integration 2', name: 'Integration 2',
type: 'HTTP endpoint', type: 'HTTP endpoint',
}, },
......
...@@ -3,8 +3,6 @@ import { GlForm, GlFormSelect, GlCollapse, GlFormInput } from '@gitlab/ui'; ...@@ -3,8 +3,6 @@ import { GlForm, GlFormSelect, GlCollapse, GlFormInput } from '@gitlab/ui';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form_new.vue';
import { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
jest.mock('~/alerts_settings/services');
describe('AlertsSettingsFormNew', () => { describe('AlertsSettingsFormNew', () => {
let wrapper; let wrapper;
......
import { shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
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 { defaultAlertSettingsConfig } from './util'; import { defaultAlertSettingsConfig } from './util';
import mockIntegrations from './mocks/integrations.json';
jest.mock('~/alerts_settings/services'); describe('AlertsSettingsWrapper', () => {
describe('AlertsSettingsFormWrapper', () => {
let wrapper; let wrapper;
const createComponent = (data = {}, provide = {}) => { const findLoader = () => wrapper.find(IntegrationsList).find(GlLoadingIcon);
wrapper = shallowMount(AlertsSettingsWrapper, { const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
wrapper = mount(AlertsSettingsWrapper, {
data() { data() {
return { ...data }; return { ...data };
}, },
...@@ -20,6 +23,16 @@ describe('AlertsSettingsFormWrapper', () => { ...@@ -20,6 +23,16 @@ describe('AlertsSettingsFormWrapper', () => {
glFeatures: { httpIntegrationsList: false }, glFeatures: { httpIntegrationsList: false },
...provide, ...provide,
}, },
mocks: {
$apollo: {
query: jest.fn(),
queries: {
integrations: {
loading,
},
},
},
},
}); });
}; };
...@@ -30,19 +43,41 @@ describe('AlertsSettingsFormWrapper', () => { ...@@ -30,19 +43,41 @@ describe('AlertsSettingsFormWrapper', () => {
} }
}); });
describe('with default values', () => { describe('with httpIntegrationsList feature flag disabled', () => {
it('renders alerts integrations list and old form by default', () => { it('renders data driven alerts integrations list and old form by default', () => {
createComponent(); createComponent();
expect(wrapper.find(IntegrationsList).exists()).toBe(true); expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(true); expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(true);
expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(false); expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(false);
}); });
});
it('renders alerts integrations list and new form when httpIntegrationsList feature flag is enabled', () => { describe('with httpIntegrationsList feature flag enabled', () => {
createComponent({}, { glFeatures: { httpIntegrationsList: true } }); it('renders the GraphQL alerts integrations list and new form', () => {
createComponent({ provide: { glFeatures: { httpIntegrationsList: true } } });
expect(wrapper.find(IntegrationsList).exists()).toBe(true); expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(false); expect(wrapper.find(AlertsSettingsFormOld).exists()).toBe(false);
expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(true); expect(wrapper.find(AlertsSettingsFormNew).exists()).toBe(true);
}); });
it('uses a loading state inside the IntegrationsList table', () => {
createComponent({
data: { integrations: {} },
provide: { glFeatures: { httpIntegrationsList: true } },
loading: true,
});
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
it('renders the IntegrationsList table using the API data', () => {
createComponent({
data: { integrations: { list: mockIntegrations } },
provide: { glFeatures: { httpIntegrationsList: true } },
loading: false,
});
expect(findLoader().exists()).toBe(false);
expect(findIntegrations()).toHaveLength(mockIntegrations.length);
});
}); });
}); });
[
{
"id": "gid://gitlab/AlertManagement::HttpIntegration/7",
"type": "HTTP",
"active": true,
"name": "test",
"url": "http://192.168.1.152:3000/root/autodevops/alerts/notify/test/eddd36969b2d3d6a.json",
"token": "7eb24af194116411ec8d66b58c6b0d2e",
"apiUrl": null
},
{
"id": "gid://gitlab/AlertManagement::HttpIntegration/6",
"type": "HTTP",
"active": false,
"name": "test",
"url": "http://192.168.1.152:3000/root/autodevops/alerts/notify/test/abce123.json",
"token": "8639e0ce06c731b00ee3e8dcdfd14fe0",
"apiUrl": null
},
{
"id": "gid://gitlab/AlertManagement::HttpIntegration/5",
"type": "HTTP",
"active": false,
"name": "test",
"url": "http://192.168.1.152:3000/root/autodevops/alerts/notify/test/bcd64c85f918a2e2.json",
"token": "5c8101533d970a55d5c105f8abff2192",
"apiUrl": null
},
{
"id": "gid://gitlab/PrometheusService/12",
"type": "PROMETHEUS",
"active": true,
"name": "Prometheus",
"url": "http://192.168.1.152:3000/root/autodevops/prometheus/alerts/notify.json",
"token": "0b18c37caa8fe980799b349916fe5ddf",
"apiUrl": "https://another-url-2.com"
}
]
...@@ -43,7 +43,8 @@ RSpec.describe OperationsHelper do ...@@ -43,7 +43,8 @@ RSpec.describe OperationsHelper do
'prometheus_api_url' => nil, 'prometheus_api_url' => nil,
'prometheus_activated' => 'false', 'prometheus_activated' => 'false',
'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json), 'prometheus_url' => notify_project_prometheus_alerts_url(project, format: :json),
'disabled' => 'false' 'disabled' => 'false',
'project_path' => project_path(project)
) )
end end
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