Commit a47044e2 authored by Arturo Herrero's avatar Arturo Herrero

Merge branch '214885-active-integrations-should-be-displayed-in-a-separate-table' into 'master'

Active integrations should be displayed in a separate table

See merge request gitlab-org/gitlab!57198
parents 1172f089 04542961
<script>
import { s__ } from '~/locale';
import IntegrationsTable from './integrations_table.vue';
export default {
name: 'IntegrationsList',
components: {
IntegrationsTable,
},
props: {
integrations: {
type: Array,
required: true,
},
},
computed: {
integrationsGrouped() {
return this.integrations.reduce(
(integrations, integration) => {
if (integration.active) {
integrations.active.push(integration);
} else {
integrations.inactive.push(integration);
}
return integrations;
},
{ active: [], inactive: [] },
);
},
},
i18n: {
activeTableEmptyText: s__("Integrations|You haven't activated any integrations yet."),
inactiveTableEmptyText: s__("Integrations|You've activated every integration 🎉"),
activeIntegrationsHeading: s__('Integrations|Active integrations'),
inactiveIntegrationsHeading: s__('Integrations|Add an integration'),
},
};
</script>
<template>
<div>
<h4>{{ $options.i18n.activeIntegrationsHeading }}</h4>
<integrations-table
class="gl-mb-7!"
:integrations="integrationsGrouped.active"
:empty-text="$options.i18n.activeTableEmptyText"
show-updated-at
data-testid="active-integrations-table"
/>
<h4>{{ $options.i18n.inactiveIntegrationsHeading }}</h4>
<integrations-table
:integrations="integrationsGrouped.inactive"
:empty-text="$options.i18n.inactiveTableEmptyText"
data-testid="inactive-integrations-table"
/>
</div>
</template>
<script>
import { GlIcon, GlLink, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { sprintf, s__, __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
components: {
GlIcon,
GlLink,
GlTable,
TimeAgoTooltip,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
integrations: {
type: Array,
required: true,
},
showUpdatedAt: {
type: Boolean,
required: false,
default: false,
},
emptyText: {
type: String,
required: false,
default: undefined,
},
},
computed: {
fields() {
return [
{
key: 'active',
label: '',
thClass: 'gl-w-10',
},
{
key: 'title',
label: __('Integration'),
thClass: 'gl-w-quarter',
},
{
key: 'description',
label: __('Description'),
thClass: 'gl-display-none d-sm-table-cell',
tdClass: 'gl-display-none d-sm-table-cell',
},
{
key: 'updated_at',
label: this.showUpdatedAt ? __('Last updated') : '',
thClass: 'gl-w-20p',
},
];
},
},
methods: {
getStatusTooltipTitle(integration) {
return sprintf(s__('Integrations|%{integrationTitle}: active'), {
integrationTitle: integration.title,
});
},
},
};
</script>
<template>
<gl-table :items="integrations" :fields="fields" :empty-text="emptyText" show-empty fixed>
<template #cell(active)="{ item }">
<gl-icon
v-if="item.active"
v-gl-tooltip
name="check"
class="gl-text-green-500"
:title="getStatusTooltipTitle(item)"
/>
</template>
<template #cell(title)="{ item }">
<gl-link
:href="item.edit_path"
class="gl-font-weight-bold"
:data-qa-selector="`${item.name}_link`"
>
{{ item.title }}
</gl-link>
</template>
<template #cell(updated_at)="{ item }">
<time-ago-tooltip v-if="showUpdatedAt && item.updated_at" :time="item.updated_at" />
</template>
</gl-table>
</template>
import Vue from 'vue';
import IntegrationList from './components/integrations_list.vue';
export default () => {
const el = document.querySelector('.js-integrations-list');
if (!el) {
return null;
}
const { integrations } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(IntegrationList, {
props: {
integrations: JSON.parse(integrations),
},
});
},
});
};
import initIntegrationsList from '~/integrations/index';
initIntegrationsList();
import initIntegrationsList from '~/integrations/index';
import PersistentUserCallout from '~/persistent_user_callout';
const callout = document.querySelector('.js-webhooks-moved-alert');
PersistentUserCallout.factory(callout);
initIntegrationsList();
......@@ -115,6 +115,12 @@ module ServicesHelper
form_data
end
def integration_list_data(integrations)
{
integrations: integrations.map { |i| serialize_integration(i) }.to_json
}
end
def trigger_events_for_service(integration)
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end
......@@ -155,6 +161,17 @@ module ServicesHelper
'project'
end
end
def serialize_integration(integration)
{
active: integration.operating?,
title: integration.title,
description: integration.description,
updated_at: integration.updated_at,
edit_path: scoped_edit_integration_path(integration),
name: integration.to_param
}
end
end
ServicesHelper.prepend_if_ee('EE::ServicesHelper')
......
......@@ -13,8 +13,8 @@
.gl-alert-actions
= link_to s_('AdminSettings|Go to General Settings'), general_admin_application_settings_path, class: 'btn gl-alert-action btn-info gl-button'
%h4= s_('AdminSettings|Apply integration settings to all Projects')
%p
= s_('AdminSettings|Integrations configured here will automatically apply to all projects on this instance.')
= link_to _('Learn more'), integrations_help_page_path, target: '_blank', rel: 'noopener noreferrer'
%h3= s_('Integrations|Project integration management')
- integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
%p= s_('Integrations|As a GitLab administrator, you can set default configuration parameters for a given integration that all projects can inherit and use. When you set these parameters, your changes update the integration for all projects that are not already using custom settings. Learn more about %{integrations_link_start}Project integration management%{link_end}.').html_safe % { integrations_link_start: integrations_link_start, link_end: '</a>'.html_safe }
= render 'shared/integrations/index', integrations: @integrations
......@@ -2,8 +2,8 @@
- page_title _('Integrations')
- @content_class = 'limit-container-width' unless fluid_layout
%h4= s_('GroupSettings|Apply integration settings to all Projects')
%p
= s_('GroupSettings|Integrations configured here will automatically apply to all projects in this group.')
= link_to _('Learn more'), integrations_help_page_path, target: '_blank', rel: 'noopener noreferrer'
%h3= s_('Integrations|Project integration management')
- integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
%p= s_('Integrations|As a GitLab administrator, you can set default configuration parameters for a given integration that all projects can inherit and use. When you set these parameters, your changes update the integration for all projects that are not already using custom settings. Learn more about %{integrations_link_start}Project integration management%{link_end}.').html_safe % { integrations_link_start: integrations_link_start, link_end: '</a>'.html_safe }
= render 'shared/integrations/index', integrations: @integrations
......@@ -12,7 +12,7 @@
.gl-alert-actions
= link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'gl-button btn gl-alert-action btn-info'
%h4= _('Integrations')
%h3= _('Integrations')
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('user/project/integrations/overview') }
- webhooks_link_start = '<a href="%{url}">'.html_safe % { url: project_hooks_path(@project) }
%p= _("%{integrations_link_start}Integrations%{link_end} enable you to make third-party applications part of your GitLab workflow. If the available integrations don't meet your needs, consider using a %{webhooks_link_start}webhook%{link_end}.").html_safe % { integrations_link_start: integrations_link_start, webhooks_link_start: webhooks_link_start, link_end: '</a>'.html_safe }
......
%table.table.b-table.gl-table{ role: 'table', 'aria-busy': false, 'aria-colcount': 4 }
%colgroup
%col
%col
%col.d-none.d-sm-table-column
%col{ width: 135 }
%thead{ role: 'rowgroup' }
%tr{ role: 'row' }
%th{ role: 'columnheader', scope: 'col', 'aria-colindex': 1 }
%th{ role: 'columnheader', scope: 'col', 'aria-colindex': 2 }= _('Integration')
%th.d-none.d-sm-block{ role: 'columnheader', scope: 'col', 'aria-colindex': 3 }= _('Description')
%th{ role: 'columnheader', scope: 'col', 'aria-colindex': 4 }= _('Last updated')
%tbody{ role: 'rowgroup' }
- integrations.each do |integration|
- activated_label = (integration.activated? ? s_("ProjectService|%{service_title}: status on") : s_("ProjectService|%{service_title}: status off")) % { service_title: integration.title }
%tr{ role: 'row' }
%td{ role: 'cell', 'aria-colindex': 1, 'aria-label': activated_label, title: activated_label }
- if integration.operating?
= sprite_icon('check', css_class: 'gl-text-green-500')
%td{ role: 'cell', 'aria-colindex': 2 }
= link_to integration.title, scoped_edit_integration_path(integration), class: 'gl-font-weight-bold', data: { qa_selector: "#{integration.to_param}_link" }
%td.d-none.d-sm-table-cell{ role: 'cell', 'aria-colindex': 3 }
= integration.description
%td{ role: 'cell', 'aria-colindex': 4 }
- if integration.updated_at.present?
= time_ago_with_tooltip integration.updated_at
.js-integrations-list{ data: integration_list_data(integrations) }
import $ from 'jquery';
import initIntegrationsList from '~/integrations/index';
import { loadCSSFile } from '~/lib/utils/css_utils';
import { select2AxiosTransport } from '~/lib/utils/select2_utils';
import { s__ } from '~/locale';
......@@ -41,6 +42,8 @@ const getDropdownConfig = (placeholder, url) => ({
const callout = document.querySelector('.js-admin-integrations-moved');
PersistentUserCallout.factory(callout);
initIntegrationsList();
// ElasticSearch
const $container = $('#js-elasticsearch-settings');
......
---
title: Display active integrations in separate table
merge_request: 57198
author:
type: changed
......@@ -2285,9 +2285,6 @@ msgstr ""
msgid "AdminProjects|Delete Project %{projectName}?"
msgstr ""
msgid "AdminSettings|Apply integration settings to all Projects"
msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
......@@ -2306,9 +2303,6 @@ msgstr ""
msgid "AdminSettings|Go to General Settings"
msgstr ""
msgid "AdminSettings|Integrations configured here will automatically apply to all projects on this instance."
msgstr ""
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
msgstr ""
......@@ -15457,9 +15451,6 @@ msgstr ""
msgid "GroupSettings|Allow project access token creation"
msgstr ""
msgid "GroupSettings|Apply integration settings to all Projects"
msgstr ""
msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
msgstr ""
......@@ -15508,9 +15499,6 @@ msgstr ""
msgid "GroupSettings|If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility."
msgstr ""
msgid "GroupSettings|Integrations configured here will automatically apply to all projects in this group."
msgstr ""
msgid "GroupSettings|Learn more about group-level project templates."
msgstr ""
......@@ -17068,12 +17056,21 @@ msgstr ""
msgid "Integrations"
msgstr ""
msgid "Integrations|%{integrationTitle}: active"
msgstr ""
msgid "Integrations|%{integration} settings saved and active."
msgstr ""
msgid "Integrations|%{integration} settings saved, but not active."
msgstr ""
msgid "Integrations|Active integrations"
msgstr ""
msgid "Integrations|Add an integration"
msgstr ""
msgid "Integrations|Add namespace"
msgstr ""
......@@ -17086,6 +17083,9 @@ msgstr ""
msgid "Integrations|All projects inheriting these settings will also be reset."
msgstr ""
msgid "Integrations|As a GitLab administrator, you can set default configuration parameters for a given integration that all projects can inherit and use. When you set these parameters, your changes update the integration for all projects that are not already using custom settings. Learn more about %{integrations_link_start}Project integration management%{link_end}."
msgstr ""
msgid "Integrations|Browser limitations"
msgstr ""
......@@ -17149,6 +17149,9 @@ msgstr ""
msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)."
msgstr ""
msgid "Integrations|Project integration management"
msgstr ""
msgid "Integrations|Projects using custom settings will not be affected."
msgstr ""
......@@ -17203,12 +17206,18 @@ msgstr ""
msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
msgstr ""
msgid "Integrations|You haven't activated any integrations yet."
msgstr ""
msgid "Integrations|You must have owner or maintainer permissions to link namespaces."
msgstr ""
msgid "Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}"
msgstr ""
msgid "Integrations|You've activated every integration 🎉"
msgstr ""
msgid "Interactive mode"
msgstr ""
......@@ -24590,12 +24599,6 @@ msgstr ""
msgid "ProjectSelect|Search for project"
msgstr ""
msgid "ProjectService|%{service_title}: status off"
msgstr ""
msgid "ProjectService|%{service_title}: status on"
msgstr ""
msgid "ProjectService|Drone URL"
msgstr ""
......
......@@ -10,8 +10,9 @@ module QA
def self.prepended(base)
base.class_eval do
view 'app/views/shared/integrations/_index.html.haml' do
element :jenkins_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
element :jenkins_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
end
end
end
......
......@@ -5,9 +5,9 @@ module QA
module Project
module Settings
class Integrations < QA::Page::Base
view 'app/views/shared/integrations/_index.html.haml' do
element :prometheus_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
element :jira_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern
view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
end
def click_on_prometheus_integration
......
......@@ -24,7 +24,7 @@ RSpec.describe 'User searches group settings', :js do
visit group_settings_integrations_path(group)
end
it_behaves_like 'can highlight results', 'integration settings'
it_behaves_like 'can highlight results', 'set default configuration'
end
context 'in Repository page' do
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe 'User views services' do
RSpec.describe 'User views services', :js do
include_context 'project service activation'
it 'shows the list of available services' do
......
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import IntegrationsList from '~/integrations/index/components/integrations_list.vue';
import { mockActiveIntegrations, mockInactiveIntegrations } from '../mock_data';
describe('IntegrationsList', () => {
let wrapper;
const findActiveIntegrationsTable = () => wrapper.findByTestId('active-integrations-table');
const findInactiveIntegrationsTable = () => wrapper.findByTestId('inactive-integrations-table');
const createComponent = (propsData = {}) => {
wrapper = extendedWrapper(shallowMount(IntegrationsList, { propsData }));
};
afterEach(() => {
wrapper.destroy();
});
it('provides correct `integrations` prop to the IntegrationsTable instance', () => {
createComponent({ integrations: [...mockInactiveIntegrations, ...mockActiveIntegrations] });
expect(findActiveIntegrationsTable().props('integrations')).toEqual(mockActiveIntegrations);
expect(findInactiveIntegrationsTable().props('integrations')).toEqual(mockInactiveIntegrations);
});
});
import { GlTable, GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import IntegrationsTable from '~/integrations/index/components/integrations_table.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockActiveIntegrations, mockInactiveIntegrations } from '../mock_data';
describe('IntegrationsTable', () => {
let wrapper;
const findTable = () => wrapper.findComponent(GlTable);
const createComponent = (propsData = {}) => {
wrapper = mount(IntegrationsTable, {
propsData: {
integrations: mockActiveIntegrations,
...propsData,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe.each([true, false])('when `showUpdatedAt` is %p', (showUpdatedAt) => {
beforeEach(() => {
createComponent({ showUpdatedAt });
});
it(`${showUpdatedAt ? 'renders' : 'does not render'} content in "Last updated" column`, () => {
const headers = findTable().findAll('th');
expect(headers.wrappers.some((header) => header.text() === 'Last updated')).toBe(
showUpdatedAt,
);
expect(wrapper.findComponent(TimeAgoTooltip).exists()).toBe(showUpdatedAt);
});
});
describe.each`
scenario | integrations | shouldRenderActiveIcon
${'when integration is active'} | ${[mockActiveIntegrations[0]]} | ${true}
${'when integration is inactive'} | ${[mockInactiveIntegrations[0]]} | ${false}
`('$scenario', ({ shouldRenderActiveIcon, integrations }) => {
beforeEach(() => {
createComponent({ integrations });
});
it(`${shouldRenderActiveIcon ? 'renders' : 'does not render'} icon in first column`, () => {
expect(findTable().findComponent(GlIcon).exists()).toBe(shouldRenderActiveIcon);
});
});
});
export const mockActiveIntegrations = [
{
active: true,
title: 'Asana',
description: 'Asana - Teamwork without email',
updated_at: '2021-03-18T00:27:09.634Z',
edit_path:
'/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/asana/edit',
name: 'asana',
},
{
active: true,
title: 'Jira',
description: 'Jira issue tracker',
updated_at: '2021-01-29T06:41:25.806Z',
edit_path:
'/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/jira/edit',
name: 'jira',
},
];
export const mockInactiveIntegrations = [
{
active: false,
title: 'Webex Teams',
description: 'Receive event notifications in Webex Teams',
updated_at: null,
edit_path:
'/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/webex_teams/edit',
name: 'webex_teams',
},
{
active: false,
title: 'YouTrack',
description: 'YouTrack issue tracker',
updated_at: null,
edit_path:
'/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/youtrack/edit',
name: 'youtrack',
},
{
active: false,
title: 'Atlassian Bamboo CI',
description: 'A continuous integration and build server',
updated_at: null,
edit_path:
'/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/bamboo/edit',
name: 'bamboo',
},
];
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