diff --git a/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js b/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2d77f2686f77a478a89de268ecbb243180d32d51 --- /dev/null +++ b/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js @@ -0,0 +1,16 @@ +import IntegrationSettingsForm from '~/integrations/integration_settings_form'; +import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; +import initAlertsSettings from '~/alerts_service_settings'; + +document.addEventListener('DOMContentLoaded', () => { + const prometheusSettingsWrapper = document.querySelector('.js-prometheus-metrics-monitoring'); + const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form'); + integrationSettingsForm.init(); + + if (prometheusSettingsWrapper) { + const prometheusMetrics = new PrometheusMetrics('.js-prometheus-metrics-monitoring'); + prometheusMetrics.loadActiveMetrics(); + } + + initAlertsSettings(document.querySelector('.js-alerts-service-settings')); +}); diff --git a/app/controllers/admin/integrations_controller.rb b/app/controllers/admin/integrations_controller.rb index 715aa882bda2e750a1ee74c65095dedc4620c64d..0d79032233f9e63f58642e9d7fe39e972305c4ed 100644 --- a/app/controllers/admin/integrations_controller.rb +++ b/app/controllers/admin/integrations_controller.rb @@ -1,67 +1,15 @@ # frozen_string_literal: true class Admin::IntegrationsController < Admin::ApplicationController - include ServiceParams - - before_action :not_found, unless: :instance_level_integrations_enabled? - before_action :service, only: [:edit, :update, :test] - - def edit - end - - def update - @service.attributes = service_params[:service] - - if @service.save(context: :manual_change) - redirect_to edit_admin_application_settings_integration_path(@service), notice: success_message - else - render :edit - end - end - - def test - if @service.can_test? - render json: service_test_response, status: :ok - else - render json: {}, status: :not_found - end - end + include IntegrationsActions private - def instance_level_integrations_enabled? + def integrations_enabled? Feature.enabled?(:instance_level_integrations) end - def project - # TODO: Change to something more meaningful - Project.first - end - - def service - @service ||= project.find_or_initialize_service(params[:id]) - end - - def success_message - message = @service.active? ? _('activated') : _('settings saved, but not activated') - - _('%{service_title} %{message}.') % { service_title: @service.title, message: message } - end - - def service_test_response - unless @service.update(service_params[:service]) - return { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false } - end - - data = @service.test_data(project, current_user) - outcome = @service.test(data) - - unless outcome[:success] - return { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } - end - - {} - rescue Gitlab::HTTP::BlockedUrlError => e - { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true } + def scoped_edit_integration_path(integration) + edit_admin_application_settings_integration_path(integration) end end diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb new file mode 100644 index 0000000000000000000000000000000000000000..ffb5d7a8086972fb7bc6978ce195ba81c6dff1ff --- /dev/null +++ b/app/controllers/concerns/integrations_actions.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +module IntegrationsActions + extend ActiveSupport::Concern + + included do + include ServiceParams + + before_action :not_found, unless: :integrations_enabled? + before_action :integration, only: [:edit, :update, :test] + end + + def edit + render 'shared/integrations/edit' + end + + def update + integration.attributes = service_params[:service] + + saved = integration.save(context: :manual_change) + + respond_to do |format| + format.html do + if saved + redirect_to scoped_edit_integration_path(integration), notice: success_message + else + render 'shared/integrations/edit' + end + end + + format.json do + status = saved ? :ok : :unprocessable_entity + + render json: serialize_as_json, status: status + end + end + end + + def test + if integration.can_test? + render json: service_test_response, status: :ok + else + render json: {}, status: :not_found + end + end + + private + + def integrations_enabled? + false + end + + # TODO: Use actual integrations on the group / instance level + # To be completed in https://gitlab.com/groups/gitlab-org/-/epics/2430 + def project + Project.first + end + + def integration + # Using instance variable `@service` still required as it's used in ServiceParams + # and app/views/shared/_service_settings.html.haml. Should be removed once + # those 2 are refactored to use `@integration`. + @integration = @service ||= project.find_or_initialize_service(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables + end + + def success_message + message = integration.active? ? _('activated') : _('settings saved, but not activated') + + _('%{service_title} %{message}.') % { service_title: integration.title, message: message } + end + + def serialize_as_json + integration + .as_json(only: integration.json_fields) + .merge(errors: integration.errors.as_json) + end + + def service_test_response + unless integration.update(service_params[:service]) + return { error: true, message: _('Validations failed.'), service_response: integration.errors.full_messages.join(','), test_failed: false } + end + + data = integration.test_data(project, current_user) + outcome = integration.test(data) + + unless outcome[:success] + return { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true } + end + + {} + rescue Gitlab::HTTP::BlockedUrlError => e + { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true } + end +end diff --git a/app/controllers/groups/settings/integrations_controller.rb b/app/controllers/groups/settings/integrations_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..43f8a7118d4a7445a04567e548b1cafa5f7edf65 --- /dev/null +++ b/app/controllers/groups/settings/integrations_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Groups + module Settings + class IntegrationsController < Groups::ApplicationController + include IntegrationsActions + + before_action :authorize_admin_group! + + private + + def integrations_enabled? + Feature.enabled?(:group_level_integrations, group) + end + + def scoped_edit_integration_path(integration) + edit_group_settings_integration_path(group, integration) + end + end + end +end diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index d2336de7193f1b51c352b40c1e2cc461b039eb92..fe2df9188196004a88bf6dd99a7e3567fec49540 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -66,6 +66,36 @@ def edit_integration_path(integration) edit_admin_application_settings_integration_path(integration) end + def scoped_integrations_path + if @project.present? + project_settings_integrations_path(@project) + elsif @group.present? + group_settings_integrations_path(@group) + else + integrations_admin_application_settings_path + end + end + + def scoped_integration_path(integration) + if @project.present? + project_settings_integration_path(@project, integration) + elsif @group.present? + group_settings_integration_path(@group, integration) + else + admin_application_settings_integration_path(integration) + end + end + + def scoped_test_integration_path(integration) + if @project.present? + test_project_settings_integration_path(@project, integration) + elsif @group.present? + test_group_settings_integration_path(@group, integration) + else + test_admin_application_settings_integration_path(integration) + end + end + extend self end diff --git a/app/views/admin/integrations/_form.html.haml b/app/views/admin/integrations/_form.html.haml deleted file mode 100644 index aa865c3b052dfaa792edf5bacd5d823e01fa55e2..0000000000000000000000000000000000000000 --- a/app/views/admin/integrations/_form.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%h3.page-title - = @service.title - -%p= @service.description - -= form_for @service, as: :service, url: admin_application_settings_integration_path, method: :put, html: { class: 'gl-show-field-errors fieldset-form integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_admin_application_settings_integration_path(@service) } } do |form| - = render 'shared/service_settings', form: form, service: @service - - - if @service.editable? - .footer-block.row-content-block - = service_save_button(@service) - = link_to _('Cancel'), admin_application_settings_integration_path, class: 'btn btn-cancel' diff --git a/app/views/admin/integrations/edit.html.haml b/app/views/admin/integrations/edit.html.haml deleted file mode 100644 index b19d00d7a16d4e764d2dd4ec9f0fc1b98e383b77..0000000000000000000000000000000000000000 --- a/app/views/admin/integrations/edit.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -- add_to_breadcrumbs _('Integrations'), integrations_admin_application_settings_path -- breadcrumb_title @service.title -- page_title @service.title, _('Integrations') - -= render 'form' diff --git a/app/views/shared/integrations/_form.html.haml b/app/views/shared/integrations/_form.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..0ddab1368c2f70723c19e0b1a44fe2439410d215 --- /dev/null +++ b/app/views/shared/integrations/_form.html.haml @@ -0,0 +1,14 @@ +- integration = local_assigns.fetch(:integration) + +%h3.page-title + = integration.title + +%p= integration.description + += form_for integration, as: :service, url: scoped_integration_path(integration), method: :put, html: { class: 'gl-show-field-errors fieldset-form integration-settings-form js-integration-settings-form', data: { 'can-test' => integration.can_test?, 'test-url' => scoped_test_integration_path(integration) } } do |form| + = render 'shared/service_settings', form: form, integration: integration + + - if integration.editable? + .footer-block.row-content-block + = service_save_button(integration) + = link_to _('Cancel'), scoped_integration_path(integration), class: 'btn btn-cancel' diff --git a/app/views/shared/integrations/edit.html.haml b/app/views/shared/integrations/edit.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..927d2410132a2d1f8c81b06ef6dc7aa3eac2cbc6 --- /dev/null +++ b/app/views/shared/integrations/edit.html.haml @@ -0,0 +1,5 @@ +- add_to_breadcrumbs _('Integrations'), scoped_integrations_path +- breadcrumb_title @integration.title +- page_title @integration.title, _('Integrations') + += render 'shared/integrations/form', integration: @integration diff --git a/config/routes/admin.rb b/config/routes/admin.rb index c92484316e469950c3449e89f37498bf90d1aa45..116c607c2cbcbc379da1a1fb2b0cfb584cffae6c 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -121,7 +121,7 @@ get '/', to: redirect('admin/application_settings/general'), as: nil resources :services, only: [:index, :edit, :update] - resources :integrations, only: [:edit, :update, :test] do + resources :integrations, only: [:edit, :update] do member do put :test end diff --git a/config/routes/group.rb b/config/routes/group.rb index 1d51b3fb6fe65dc7381f56f23303523781cdd2fe..97d339fea98db30c41283fe5ff5bc8e618aca76b 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -31,6 +31,12 @@ patch :update_auto_devops post :create_deploy_token, path: 'deploy_token/create' end + + resources :integrations, only: [:index, :edit, :update] do + member do + put :test + end + end end resource :variables, only: [:show, :update] diff --git a/spec/controllers/admin/integrations_controller_spec.rb b/spec/controllers/admin/integrations_controller_spec.rb index 0641f64b0e3980f997226808c2ca5b72b75d70d0..507489188938a14da6c355cadef2e378e7dcca56 100644 --- a/spec/controllers/admin/integrations_controller_spec.rb +++ b/spec/controllers/admin/integrations_controller_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe Admin::IntegrationsController do + let_it_be(:project) { create(:project) } let(:admin) { create(:admin) } - let!(:project) { create(:project) } before do sign_in(admin) @@ -13,7 +13,7 @@ describe '#edit' do context 'when instance_level_integrations not enabled' do it 'returns not_found' do - allow(Feature).to receive(:enabled?).with(:instance_level_integrations) { false } + stub_feature_flags(instance_level_integrations: false) get :edit, params: { id: Service.available_services_names.sample } diff --git a/spec/controllers/groups/settings/integrations_controller_spec.rb b/spec/controllers/groups/settings/integrations_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..bbf215a4bb902855cead9f32ab5ed57a396ea36d --- /dev/null +++ b/spec/controllers/groups/settings/integrations_controller_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Groups::Settings::IntegrationsController do + let_it_be(:project) { create(:project) } + let(:user) { create(:user) } + let(:group) { create(:group) } + + before do + sign_in(user) + end + + describe '#edit' do + context 'when group_level_integrations not enabled' do + it 'returns not_found' do + stub_feature_flags(group_level_integrations: { enabled: false, thing: group }) + + get :edit, params: { group_id: group, id: Service.available_services_names.sample } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is not owner' do + it 'renders not_found' do + get :edit, params: { group_id: group, id: Service.available_services_names.sample } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when user is owner' do + before do + group.add_owner(user) + end + + Service.available_services_names.each do |integration_name| + context "#{integration_name}" do + it 'successfully displays the template' do + get :edit, params: { group_id: group, id: integration_name } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:edit) + end + end + end + end + end + + describe '#update' do + let(:integration) { create(:jira_service, project: project) } + + before do + group.add_owner(user) + + put :update, params: { group_id: group, id: integration.class.to_param, service: { url: url } } + end + + context 'valid params' do + let(:url) { 'https://jira.gitlab-example.com' } + + it 'updates the integration' do + expect(response).to have_gitlab_http_status(:found) + expect(integration.reload.url).to eq(url) + end + end + + context 'invalid params' do + let(:url) { 'ftp://jira.localhost' } + + it 'does not update the integration' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:edit) + expect(integration.reload.url).not_to eq(url) + end + end + end + + describe '#test' do + context 'testable' do + let(:integration) { create(:jira_service, project: project) } + + before do + group.add_owner(user) + end + + it 'returns ok' do + allow_any_instance_of(integration.class).to receive(:test) { { success: true } } + + put :test, params: { group_id: group, id: integration.class.to_param } + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'not testable' do + let(:integration) { create(:alerts_service, project: project) } + + it 'returns not found' do + put :test, params: { group_id: group, id: integration.class.to_param } + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end +end