diff --git a/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue b/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bfb16779854fc35c22dde8d738d667d2adafe691
--- /dev/null
+++ b/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/integrations/overrides/index.js b/app/assets/javascripts/integrations/overrides/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..0f03b23ba218d3411bbfa35a1fec288fcd2ca097
--- /dev/null
+++ b/app/assets/javascripts/integrations/overrides/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import IntegrationOverrides from './components/integration_overrides.vue';
+
+export default () => {
+ const el = document.querySelector('.js-vue-integration-overrides');
+
+ if (!el) {
+ return null;
+ }
+
+ const { overridesPath } = el.dataset;
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(IntegrationOverrides, {
+ props: {
+ overridesPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/admin/integrations/overrides/index.js b/app/assets/javascripts/pages/admin/integrations/overrides/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..b150470914433fac8149178606c5bd5c652b5b8e
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/integrations/overrides/index.js
@@ -0,0 +1,3 @@
+import initIntegrationOverrides from '~/integrations/overrides';
+
+initIntegrationOverrides();
diff --git a/app/controllers/admin/integrations_controller.rb b/app/controllers/admin/integrations_controller.rb
index a605ee0297fe239c74a457f85ff22541da5ab624..e273c0269932032d2d8f073d7c2b84de735841ac 100644
--- a/app/controllers/admin/integrations_controller.rb
+++ b/app/controllers/admin/integrations_controller.rb
@@ -2,13 +2,14 @@
class Admin::IntegrationsController < Admin::ApplicationController
include IntegrationsActions
- include IntegrationsHelper
before_action :not_found, unless: -> { instance_level_integrations? }
feature_category :integrations
def overrides
+ return render_404 unless instance_level_integration_overrides?
+
respond_to do |format|
format.json do
projects = Project.with_active_integration(integration.class).merge(::Integration.not_inherited)
@@ -16,7 +17,7 @@ def overrides
render json: serializer.represent(projects)
end
- # TODO frontend will add format.html
+ format.html { render 'shared/integrations/overrides' }
end
end
@@ -26,7 +27,7 @@ def find_or_initialize_non_project_specific_integration(name)
Integration.find_or_initialize_non_project_specific_integration(name, instance: true)
end
- def scoped_edit_integration_path(integration)
- edit_admin_application_settings_integration_path(integration)
+ def instance_level_integration_overrides?
+ Feature.enabled?(:instance_level_integration_overrides, default_enabled: :yaml)
end
end
diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb
index f1fa5c845e22a93c29524f1cc586543c92d88af9..dd066cc1b02ccb1765155ea190de4bab70786cf4 100644
--- a/app/controllers/concerns/integrations_actions.rb
+++ b/app/controllers/concerns/integrations_actions.rb
@@ -5,8 +5,9 @@ module IntegrationsActions
included do
include Integrations::Params
+ include IntegrationsHelper
- before_action :integration, only: [:edit, :update, :test]
+ before_action :integration, only: [:edit, :update, :overrides, :test]
end
def edit
diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb
index 8e3b2cb5d1ba63c043c1a12fa19b298384f116f7..a7a1de03224e378498623fd39fb72dcb1fd65890 100644
--- a/app/controllers/groups/settings/integrations_controller.rb
+++ b/app/controllers/groups/settings/integrations_controller.rb
@@ -26,10 +26,6 @@ def edit
def find_or_initialize_non_project_specific_integration(name)
Integration.find_or_initialize_non_project_specific_integration(name, group_id: group.id)
end
-
- def scoped_edit_integration_path(integration)
- edit_group_settings_integration_path(group, integration)
- end
end
end
end
diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb
index ab305d822e834ab61e3623974e5cbc6081fdab79..734820f0e74b1f68fab6a24365b37caa8fd326ba 100644
--- a/app/helpers/integrations_helper.rb
+++ b/app/helpers/integrations_helper.rb
@@ -47,6 +47,10 @@ def scoped_edit_integration_path(integration)
end
end
+ def scoped_overrides_integration_path(integration, options = {})
+ overrides_admin_application_settings_integration_path(integration, options)
+ end
+
def scoped_test_integration_path(integration)
if @project.present?
test_project_service_path(@project, integration)
@@ -97,6 +101,12 @@ def integration_form_data(integration, group: nil)
form_data
end
+ def integration_overrides_data(integration)
+ {
+ overrides_path: scoped_overrides_integration_path(integration, format: :json)
+ }
+ end
+
def integration_list_data(integrations)
{
integrations: integrations.map { |i| serialize_integration(i) }.to_json
diff --git a/app/views/shared/integrations/_tabs.html.haml b/app/views/shared/integrations/_tabs.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..ff97e371374d12079f914d11e4e365338a5bd190
--- /dev/null
+++ b/app/views/shared/integrations/_tabs.html.haml
@@ -0,0 +1,14 @@
+.tabs.gl-tabs
+ %div
+ %ul.nav.gl-tabs-nav{ role: 'tablist' }
+ %li.nav-item{ role: 'presentation' }
+ %a.nav-link.gl-tab-nav-item{ role: 'tab', href: scoped_edit_integration_path(integration) }
+ = _('Settings')
+
+ %li.nav-item{ role: 'presentation' }
+ %a.nav-link.gl-tab-nav-item.gl-tab-nav-item-active.gl-tab-nav-item-active-indigo.active{ role: 'tab', href: scoped_overrides_integration_path(integration) }
+ = s_('Integrations|Projects using custom settings')
+
+ .tab-content.gl-tab-content
+ .tab-pane.active{ role: 'tabpanel' }
+ = yield
diff --git a/app/views/shared/integrations/overrides.html.haml b/app/views/shared/integrations/overrides.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..4d8cc94e967bbef562689dce642a4c9b316a7972
--- /dev/null
+++ b/app/views/shared/integrations/overrides.html.haml
@@ -0,0 +1,10 @@
+- add_to_breadcrumbs _('Integrations'), scoped_integrations_path
+- breadcrumb_title @integration.title
+- page_title @integration.title, _('Integrations')
+- @content_class = 'limit-container-width' unless fluid_layout
+
+%h3.page-title
+ = @integration.title
+
+= render 'shared/integrations/tabs', integration: @integration do
+ .js-vue-integration-overrides{ data: integration_overrides_data(@integration) }
diff --git a/config/feature_flags/development/instance_level_integration_overrides.yml b/config/feature_flags/development/instance_level_integration_overrides.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f99b85b3c051f3a96cfc19b8ee691185b751acd5
--- /dev/null
+++ b/config/feature_flags/development/instance_level_integration_overrides.yml
@@ -0,0 +1,8 @@
+---
+name: instance_level_integration_overrides
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66723
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336750
+milestone: '14.2'
+type: development
+group: group::ecosystem
+default_enabled: false
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index de72d782daf86147ec702f539330c099db204527..ca6d44c3274d017ffa8115596c9a9f03c1f6c3c8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -17656,6 +17656,9 @@ msgstr ""
msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)."
msgstr ""
+msgid "Integrations|Projects using custom settings"
+msgstr ""
+
msgid "Integrations|Projects using custom settings will not be affected."
msgstr ""
diff --git a/spec/controllers/admin/integrations_controller_spec.rb b/spec/controllers/admin/integrations_controller_spec.rb
index 617a43b37850d06108519c855db4cb8c3338e04e..3d37fe7bf79e17389f5ad8e99c0558f6832df9f7 100644
--- a/spec/controllers/admin/integrations_controller_spec.rb
+++ b/spec/controllers/admin/integrations_controller_spec.rb
@@ -105,17 +105,44 @@
let_it_be(:overridden_other_integration) { create(:confluence_integration) }
subject do
- get :overrides, params: { id: instance_integration.class.to_param }, format: :json
+ get :overrides, params: { id: instance_integration.class.to_param }, format: format
end
- include_context 'JSON response'
+ context 'when format is JSON' do
+ let(:format) { :json }
- it 'returns projects with overrides', :aggregate_failures do
- subject
+ include_context 'JSON response'
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to include_pagination_headers
- expect(json_response).to contain_exactly(a_hash_including('full_name' => overridden_integration.project.full_name))
+ it 'returns projects with overrides', :aggregate_failures do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to include_pagination_headers
+ expect(json_response).to contain_exactly(a_hash_including('full_name' => overridden_integration.project.full_name))
+ end
+ end
+
+ context 'when format is HTML' do
+ let(:format) { :html }
+
+ it 'renders template' do
+ subject
+
+ expect(response).to render_template 'shared/integrations/overrides'
+ expect(assigns(:integration)).to eq(instance_integration)
+ end
+
+ context 'when `instance_level_integration_overrides` is not enabled' do
+ before do
+ stub_feature_flags(instance_level_integration_overrides: false)
+ end
+
+ it 'renders a 404' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
end