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'; import PersistentUserCallout from '~/persistent_user_callout';
const callout = document.querySelector('.js-webhooks-moved-alert'); const callout = document.querySelector('.js-webhooks-moved-alert');
PersistentUserCallout.factory(callout); PersistentUserCallout.factory(callout);
initIntegrationsList();
...@@ -115,6 +115,12 @@ module ServicesHelper ...@@ -115,6 +115,12 @@ module ServicesHelper
form_data form_data
end end
def integration_list_data(integrations)
{
integrations: integrations.map { |i| serialize_integration(i) }.to_json
}
end
def trigger_events_for_service(integration) def trigger_events_for_service(integration)
ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json ServiceEventSerializer.new(service: integration).represent(integration.configurable_events).to_json
end end
...@@ -155,6 +161,17 @@ module ServicesHelper ...@@ -155,6 +161,17 @@ module ServicesHelper
'project' 'project'
end end
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 end
ServicesHelper.prepend_if_ee('EE::ServicesHelper') ServicesHelper.prepend_if_ee('EE::ServicesHelper')
......
...@@ -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= _('Integrations') %h3= _('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 }
......
%table.table.b-table.gl-table{ role: 'table', 'aria-busy': false, 'aria-colcount': 4 } .js-integrations-list{ data: integration_list_data(integrations) }
%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
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');
......
---
title: Display active integrations in separate table
merge_request: 57198
author:
type: changed
...@@ -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,12 +17056,21 @@ msgstr "" ...@@ -17068,12 +17056,21 @@ msgstr ""
msgid "Integrations" msgid "Integrations"
msgstr "" msgstr ""
msgid "Integrations|%{integrationTitle}: active"
msgstr ""
msgid "Integrations|%{integration} settings saved and active." msgid "Integrations|%{integration} settings saved and active."
msgstr "" msgstr ""
msgid "Integrations|%{integration} settings saved, but not active." msgid "Integrations|%{integration} settings saved, but not active."
msgstr "" msgstr ""
msgid "Integrations|Active integrations"
msgstr ""
msgid "Integrations|Add an integration"
msgstr ""
msgid "Integrations|Add namespace" msgid "Integrations|Add namespace"
msgstr "" msgstr ""
...@@ -17086,6 +17083,9 @@ msgstr "" ...@@ -17086,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 ""
...@@ -17149,6 +17149,9 @@ msgstr "" ...@@ -17149,6 +17149,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 ""
...@@ -17203,12 +17206,18 @@ msgstr "" ...@@ -17203,12 +17206,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 ""
...@@ -24590,12 +24599,6 @@ msgstr "" ...@@ -24590,12 +24599,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 ""
......
...@@ -10,8 +10,9 @@ module QA ...@@ -10,8 +10,9 @@ module QA
def self.prepended(base) def self.prepended(base)
base.class_eval do base.class_eval do
view 'app/views/shared/integrations/_index.html.haml' do view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
element :jenkins_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern 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 end
end end
......
...@@ -5,9 +5,9 @@ module QA ...@@ -5,9 +5,9 @@ module QA
module Project module Project
module Settings module Settings
class Integrations < QA::Page::Base class Integrations < QA::Page::Base
view 'app/views/shared/integrations/_index.html.haml' do view 'app/assets/javascripts/integrations/index/components/integrations_table.vue' do
element :prometheus_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern element :prometheus_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
element :jira_link, 'data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern element :jira_link, %q(:data-qa-selector="`${item.name}_link`") # rubocop:disable QA/ElementWithPattern
end end
def click_on_prometheus_integration def click_on_prometheus_integration
......
...@@ -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
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User views services' do RSpec.describe 'User views services', :js do
include_context 'project service activation' include_context 'project service activation'
it 'shows the list of available services' do 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