Commit 6f4aa7aa authored by Tom Quirk's avatar Tom Quirk

Improvements to integrations_table.vue

- Change :active prop name to :show-updated-at
- improve column widths
- Make integrations list slightly more efficient
- Add tooltip to integration status icon
- add specs

Also, include JS integrations table in groups, admin
parent cc61a2a1
<script> <script>
import { s__ } from '~/locale';
import IntegrationsTable from './integrations_table.vue'; import IntegrationsTable from './integrations_table.vue';
export default { export default {
...@@ -13,20 +14,44 @@ export default { ...@@ -13,20 +14,44 @@ export default {
}, },
}, },
computed: { computed: {
activeIntegrations() { integrationsGrouped() {
return this.integrations.filter((integration) => integration.active); return this.integrations.reduce(
}, (integrations, integration) => {
inactiveIntegrations() { if (integration.active) {
return this.integrations.filter((integration) => !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 🎉"),
},
}; };
</script> </script>
<template> <template>
<div> <div>
<integrations-table :active="true" :integrations="activeIntegrations" /> <h4>{{ s__('Integrations|Active integrations') }}</h4>
<h5>{{ s__('Integrations|Add an integration') }}</h5> <integrations-table
<integrations-table :active="false" :integrations="inactiveIntegrations" /> class="gl-mb-7!"
:integrations="integrationsGrouped.active"
:empty-text="$options.i18n.activeTableEmptyText"
show-updated-at
data-testid="active-integrations-table"
/>
<h4>{{ s__('Integrations|Add an integration') }}</h4>
<integrations-table
:integrations="integrationsGrouped.inactive"
:empty-text="$options.i18n.inactiveTableEmptyText"
data-testid="inactive-integrations-table"
/>
</div> </div>
</template> </template>
<script> <script>
import { GlIcon, GlLink, GlTable } from '@gitlab/ui'; import { GlIcon, GlLink, GlTable, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
...@@ -10,15 +10,23 @@ export default { ...@@ -10,15 +10,23 @@ export default {
GlTable, GlTable,
TimeAgoTooltip, TimeAgoTooltip,
}, },
directives: {
GlTooltip: GlTooltipDirective,
},
props: { props: {
active: { integrations: {
type: Array,
required: true,
},
showUpdatedAt: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
integrations: { emptyText: {
type: Array, type: String,
required: true, required: false,
default: undefined,
}, },
}, },
computed: { computed: {
...@@ -27,21 +35,24 @@ export default { ...@@ -27,21 +35,24 @@ export default {
{ {
key: 'active', key: 'active',
label: '', label: '',
thClass: 'gl-w-10',
}, },
{ {
key: 'name', key: 'name',
label: __('Integration'), label: __('Integration'),
thClass: 'gl-w-quarter',
}, },
{ {
key: 'description', key: 'description',
label: __('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',
}, },
this.active
? {
key: 'updated_at',
label: __('Last updated'),
}
: {},
]; ];
}, },
}, },
...@@ -49,9 +60,19 @@ export default { ...@@ -49,9 +60,19 @@ export default {
</script> </script>
<template> <template>
<gl-table :items="integrations" :fields="fields"> <gl-table :items="integrations" :fields="fields" :empty-text="emptyText" show-empty fixed>
<template #cell(active)="{ item }"> <template #cell(active)="{ item }">
<gl-icon v-if="item.active" name="check" class="gl-text-green-500" /> <gl-icon
v-if="item.active"
v-gl-tooltip
name="check"
class="gl-text-green-500"
:title="
sprintf(s__('Integrations|%{integrationName}: active'), {
integrationName: item.name,
})
"
/>
</template> </template>
<template #cell(name)="{ item }"> <template #cell(name)="{ item }">
...@@ -59,12 +80,13 @@ export default { ...@@ -59,12 +80,13 @@ export default {
:href="item.edit_path" :href="item.edit_path"
class="gl-font-weight-bold" class="gl-font-weight-bold"
:data-qa-selector="`${item.type}_link`" :data-qa-selector="`${item.type}_link`"
>{{ item.name }}</gl-link
> >
{{ item.name }}
</gl-link>
</template> </template>
<template #cell(updated_at)="{ item }"> <template #cell(updated_at)="{ item }">
<time-ago-tooltip v-if="item.updated_at" :time="item.updated_at" /> <time-ago-tooltip v-if="showUpdatedAt && item.updated_at" :time="item.updated_at" />
</template> </template>
</gl-table> </gl-table>
</template> </template>
import initIntegrationsList from '~/integrations/index';
initIntegrationsList();
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
.gl-alert-actions .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' = 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') %h3= s_('Integrations|Project integration management')
%p
= s_('AdminSettings|Integrations configured here will automatically apply to all projects on this instance.') - integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
= link_to _('Learn more'), integrations_help_page_path, target: '_blank', rel: 'noopener noreferrer' %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 = render 'shared/integrations/index', integrations: @integrations
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
- page_title _('Integrations') - page_title _('Integrations')
- @content_class = 'limit-container-width' unless fluid_layout - @content_class = 'limit-container-width' unless fluid_layout
%h4= s_('GroupSettings|Apply integration settings to all Projects') %h3= s_('Integrations|Project integration management')
%p
= s_('GroupSettings|Integrations configured here will automatically apply to all projects in this group.') - integrations_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: integrations_help_page_path }
= link_to _('Learn more'), integrations_help_page_path, target: '_blank', rel: 'noopener noreferrer' %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 = render 'shared/integrations/index', integrations: @integrations
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.gl-alert-actions .gl-alert-actions
= link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'gl-button btn gl-alert-action btn-info' = link_to _('Go to Webhooks'), project_hooks_path(@project), class: 'gl-button btn gl-alert-action btn-info'
%h4= s_('Integrations|Active integrations') %h3= s_('Integrations|Integrations')
- integrations_link_start = '<a href="%{url}">'.html_safe % { url: help_page_url('user/project/integrations/overview') } - 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) } - 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 } %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 }
......
import $ from 'jquery'; import $ from 'jquery';
import initIntegrationsList from '~/integrations/index';
import { loadCSSFile } from '~/lib/utils/css_utils'; import { loadCSSFile } from '~/lib/utils/css_utils';
import { select2AxiosTransport } from '~/lib/utils/select2_utils'; import { select2AxiosTransport } from '~/lib/utils/select2_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -41,6 +42,8 @@ const getDropdownConfig = (placeholder, url) => ({ ...@@ -41,6 +42,8 @@ const getDropdownConfig = (placeholder, url) => ({
const callout = document.querySelector('.js-admin-integrations-moved'); const callout = document.querySelector('.js-admin-integrations-moved');
PersistentUserCallout.factory(callout); PersistentUserCallout.factory(callout);
initIntegrationsList();
// ElasticSearch // ElasticSearch
const $container = $('#js-elasticsearch-settings'); const $container = $('#js-elasticsearch-settings');
......
...@@ -2285,9 +2285,6 @@ msgstr "" ...@@ -2285,9 +2285,6 @@ msgstr ""
msgid "AdminProjects|Delete Project %{projectName}?" msgid "AdminProjects|Delete Project %{projectName}?"
msgstr "" msgstr ""
msgid "AdminSettings|Apply integration settings to all Projects"
msgstr ""
msgid "AdminSettings|Auto DevOps domain" msgid "AdminSettings|Auto DevOps domain"
msgstr "" msgstr ""
...@@ -2306,9 +2303,6 @@ msgstr "" ...@@ -2306,9 +2303,6 @@ msgstr ""
msgid "AdminSettings|Go to General Settings" msgid "AdminSettings|Go to General Settings"
msgstr "" 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" msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
msgstr "" msgstr ""
...@@ -15457,9 +15451,6 @@ msgstr "" ...@@ -15457,9 +15451,6 @@ msgstr ""
msgid "GroupSettings|Allow project access token creation" msgid "GroupSettings|Allow project access token creation"
msgstr "" msgstr ""
msgid "GroupSettings|Apply integration settings to all Projects"
msgstr ""
msgid "GroupSettings|Auto DevOps pipeline was updated for the group" msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
msgstr "" msgstr ""
...@@ -15508,9 +15499,6 @@ 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." 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 "" 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." msgid "GroupSettings|Learn more about group-level project templates."
msgstr "" msgstr ""
...@@ -17068,6 +17056,9 @@ msgstr "" ...@@ -17068,6 +17056,9 @@ msgstr ""
msgid "Integrations" msgid "Integrations"
msgstr "" msgstr ""
msgid "Integrations|%{integrationName}: active"
msgstr ""
msgid "Integrations|%{integration} settings saved and active." msgid "Integrations|%{integration} settings saved and active."
msgstr "" msgstr ""
...@@ -17092,6 +17083,9 @@ msgstr "" ...@@ -17092,6 +17083,9 @@ msgstr ""
msgid "Integrations|All projects inheriting these settings will also be reset." msgid "Integrations|All projects inheriting these settings will also be reset."
msgstr "" 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" msgid "Integrations|Browser limitations"
msgstr "" msgstr ""
...@@ -17131,6 +17125,9 @@ msgstr "" ...@@ -17131,6 +17125,9 @@ msgstr ""
msgid "Integrations|Includes commit title and branch" msgid "Integrations|Includes commit title and branch"
msgstr "" msgstr ""
msgid "Integrations|Integrations"
msgstr ""
msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira." msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira."
msgstr "" msgstr ""
...@@ -17155,6 +17152,9 @@ msgstr "" ...@@ -17155,6 +17152,9 @@ msgstr ""
msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)." msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)."
msgstr "" msgstr ""
msgid "Integrations|Project integration management"
msgstr ""
msgid "Integrations|Projects using custom settings will not be affected." msgid "Integrations|Projects using custom settings will not be affected."
msgstr "" msgstr ""
...@@ -17209,12 +17209,18 @@ msgstr "" ...@@ -17209,12 +17209,18 @@ msgstr ""
msgid "Integrations|You can now close this window and return to the GitLab for Jira application." msgid "Integrations|You can now close this window and return to the GitLab for Jira application."
msgstr "" msgstr ""
msgid "Integrations|You haven't activated any integrations yet."
msgstr ""
msgid "Integrations|You must have owner or maintainer permissions to link namespaces." msgid "Integrations|You must have owner or maintainer permissions to link namespaces."
msgstr "" msgstr ""
msgid "Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}" msgid "Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}"
msgstr "" msgstr ""
msgid "Integrations|You've activated every integration 🎉"
msgstr ""
msgid "Interactive mode" msgid "Interactive mode"
msgstr "" msgstr ""
...@@ -24596,12 +24602,6 @@ msgstr "" ...@@ -24596,12 +24602,6 @@ msgstr ""
msgid "ProjectSelect|Search for project" msgid "ProjectSelect|Search for project"
msgstr "" msgstr ""
msgid "ProjectService|%{service_title}: status off"
msgstr ""
msgid "ProjectService|%{service_title}: status on"
msgstr ""
msgid "ProjectService|Drone URL" msgid "ProjectService|Drone URL"
msgstr "" msgstr ""
......
...@@ -24,7 +24,7 @@ RSpec.describe 'User searches group settings', :js do ...@@ -24,7 +24,7 @@ RSpec.describe 'User searches group settings', :js do
visit group_settings_integrations_path(group) visit group_settings_integrations_path(group)
end end
it_behaves_like 'can highlight results', 'integration settings' it_behaves_like 'can highlight results', 'set default configuration'
end end
context 'in Repository page' do context 'in Repository page' 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,
name: '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',
type: 'asana',
},
{
active: true,
name: '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',
type: 'jira',
},
];
export const mockInactiveIntegrations = [
{
active: false,
name: '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',
type: 'webex_teams',
},
{
active: false,
name: 'YouTrack',
description: 'YouTrack issue tracker',
updated_at: null,
edit_path:
'/gitlab-qa-sandbox-group/project_with_jenkins_6a55a67c-57c6ed0597c9319a/-/services/youtrack/edit',
type: 'youtrack',
},
{
active: false,
name: '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',
type: '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