diff --git a/.rubocop_todo/gitlab/json.yml b/.rubocop_todo/gitlab/json.yml index 799d90f0048..727ccda4983 100644 --- a/.rubocop_todo/gitlab/json.yml +++ b/.rubocop_todo/gitlab/json.yml @@ -18,31 +18,6 @@ Gitlab/Json: - 'app/controllers/projects/templates_controller.rb' - 'app/controllers/projects_controller.rb' - 'app/controllers/search_controller.rb' - - 'app/helpers/access_tokens_helper.rb' - - 'app/helpers/application_settings_helper.rb' - - 'app/helpers/breadcrumbs_helper.rb' - - 'app/helpers/ci/builds_helper.rb' - - 'app/helpers/ci/pipelines_helper.rb' - - 'app/helpers/compare_helper.rb' - - 'app/helpers/emails_helper.rb' - - 'app/helpers/environment_helper.rb' - - 'app/helpers/groups_helper.rb' - - 'app/helpers/ide_helper.rb' - - 'app/helpers/integrations_helper.rb' - - 'app/helpers/invite_members_helper.rb' - - 'app/helpers/issuables_description_templates_helper.rb' - - 'app/helpers/issuables_helper.rb' - - 'app/helpers/jira_connect_helper.rb' - - 'app/helpers/learn_gitlab_helper.rb' - - 'app/helpers/namespaces_helper.rb' - - 'app/helpers/notes_helper.rb' - - 'app/helpers/operations_helper.rb' - - 'app/helpers/packages_helper.rb' - - 'app/helpers/projects/project_members_helper.rb' - - 'app/helpers/projects_helper.rb' - - 'app/helpers/search_helper.rb' - - 'app/helpers/terms_helper.rb' - - 'app/helpers/users_helper.rb' - 'app/mailers/emails/members.rb' - 'app/models/concerns/redis_cacheable.rb' - 'app/models/diff_discussion.rb' diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue index 0127df730b8..27186281c42 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue @@ -194,6 +194,7 @@ export default { ref="dropdown" :text="buttonText" class="gl-w-full" + block data-testid="labels-select-dropdown-contents" data-qa-selector="labels_dropdown_content" @hide="handleDropdownHide" diff --git a/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb b/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb new file mode 100644 index 00000000000..2e4312f0045 --- /dev/null +++ b/app/graphql/mutations/ci/pipeline_schedule/take_ownership.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module PipelineSchedule + class TakeOwnership < Base + graphql_name 'PipelineScheduleTakeOwnership' + + authorize :take_ownership_pipeline_schedule + + field :pipeline_schedule, + Types::Ci::PipelineScheduleType, + description: 'Updated pipeline schedule ownership.' + + def resolve(id:) + schedule = authorized_find!(id: id) + + service_response = ::Ci::PipelineSchedules::TakeOwnershipService.new(schedule, current_user).execute + { + pipeline_schedule: schedule, + errors: service_response.errors + } + end + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 5ffc1aeacad..e4b705245ed 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -115,6 +115,7 @@ module Types mount_mutation Mutations::Ci::Pipeline::Destroy mount_mutation Mutations::Ci::Pipeline::Retry mount_mutation Mutations::Ci::PipelineSchedule::Delete + mount_mutation Mutations::Ci::PipelineSchedule::TakeOwnership mount_mutation Mutations::Ci::CiCdSettingsUpdate, deprecated: { reason: :renamed, replacement: 'ProjectCiCdSettingsUpdate', diff --git a/app/helpers/access_tokens_helper.rb b/app/helpers/access_tokens_helper.rb index 44200e84afb..07e61b7f552 100644 --- a/app/helpers/access_tokens_helper.rb +++ b/app/helpers/access_tokens_helper.rb @@ -9,7 +9,7 @@ module AccessTokensHelper end def tokens_app_data - { + data = { feed_token: { enabled: !Gitlab::CurrentSettings.disable_feed_token, token: current_user.feed_token, @@ -25,7 +25,9 @@ module AccessTokensHelper token: current_user.enabled_static_object_token, reset_path: reset_static_object_token_profile_path } - }.to_json + } + + Gitlab::Json.dump(data) end def expires_at_field_data diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index b48f843135d..c57f355b487 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -137,7 +137,7 @@ module ApplicationSettingsHelper } end - options.to_json + Gitlab::Json.dump(options) end def external_authorization_description diff --git a/app/helpers/breadcrumbs_helper.rb b/app/helpers/breadcrumbs_helper.rb index 38ed6e95a44..a14bc8e00bf 100644 --- a/app/helpers/breadcrumbs_helper.rb +++ b/app/helpers/breadcrumbs_helper.rb @@ -40,11 +40,11 @@ module BreadcrumbsHelper end def schema_breadcrumb_json - { + Gitlab::Json.dump({ '@context': 'https://schema.org', '@type': 'BreadcrumbList', 'itemListElement': build_item_list_elements - }.to_json + }) end private diff --git a/app/helpers/ci/builds_helper.rb b/app/helpers/ci/builds_helper.rb index afd0af18ba7..265969a6370 100644 --- a/app/helpers/ci/builds_helper.rb +++ b/app/helpers/ci/builds_helper.rb @@ -38,13 +38,13 @@ module Ci end def prepare_failed_jobs_summary_data(failed_builds) - failed_builds.map do |build| + Gitlab::Json.dump(failed_builds.map do |build| { id: build.id, failure: build.present.callout_failure_message, failure_summary: build_summary(build) } - end.to_json + end) end end end diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb index c93c8dd8d76..80632f2ea10 100644 --- a/app/helpers/ci/pipelines_helper.rb +++ b/app/helpers/ci/pipelines_helper.rb @@ -87,7 +87,7 @@ module Ci endpoint: list_url, project_id: project.id, default_branch_name: project.default_branch, - params: params.to_json, + params: Gitlab::Json.dump(params), artifacts_endpoint: downloadable_artifacts_project_pipeline_path(project, artifacts_endpoint_placeholder, format: :json), artifacts_endpoint_placeholder: artifacts_endpoint_placeholder, pipeline_schedule_url: pipeline_schedules_path(project), @@ -100,7 +100,7 @@ module Ci reset_cache_path: can?(current_user, :admin_pipeline, project) && reset_cache_project_settings_ci_cd_path(project), has_gitlab_ci: has_gitlab_ci?(project).to_s, pipeline_editor_path: can?(current_user, :create_pipeline, project) && project_ci_pipeline_editor_path(project), - suggested_ci_templates: suggested_ci_templates.to_json, + suggested_ci_templates: Gitlab::Json.dump(suggested_ci_templates), ci_runner_settings_path: project_settings_ci_cd_path(project, ci_runner_templates: true, anchor: 'js-runners-settings') } diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index 9ecf780f55b..91f8567aa2d 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -37,17 +37,17 @@ module CompareHelper def project_compare_selector_data(project, merge_request, params) { project_compare_index_path: project_compare_index_path(project), - source_project: { id: project.id, name: project.full_path }.to_json, - target_project: { id: @target_project.id, name: @target_project.full_path }.to_json, + source_project: Gitlab::Json.dump({ id: project.id, name: project.full_path }), + target_project: Gitlab::Json.dump({ id: @target_project.id, name: @target_project.full_path }), source_project_refs_path: refs_project_path(project), target_project_refs_path: refs_project_path(@target_project), params_from: params[:from], params_to: params[:to], straight: params[:straight] }.tap do |data| - data[:projects_from] = target_projects(project).map do |target_project| + data[:projects_from] = Gitlab::Json.dump(target_projects(project).map do |target_project| { id: target_project.id, name: target_project.full_path } - end.to_json + end) data[:project_merge_request_path] = if merge_request.present? diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 54733fa9101..fe3324120eb 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -36,7 +36,7 @@ module EmailsHelper } content_tag :script, type: 'application/ld+json' do - data.to_json.html_safe + Gitlab::Json.dump(data).html_safe end end diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index b6997b6fb70..677892e7d32 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -91,6 +91,6 @@ module EnvironmentHelper end def environments_detail_data_json(user, project, environment) - environments_detail_data(user, project, environment).to_json + Gitlab::Json.dump(environments_detail_data(user, project, environment)) end end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 6b00c213875..1612c161f01 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -119,7 +119,7 @@ module GroupsHelper { id: group.id, text: group.human_name } end - groups.to_json + Gitlab::Json.dump(groups) end def render_setting_to_allow_project_access_token_creation?(group) diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 5b3ca25b5af..cdc3804d192 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -56,7 +56,7 @@ module IdeHelper def convert_to_project_entity_json(project) return unless project - API::Entities::Project.represent(project, current_user: current_user).to_json + Gitlab::Json.dump(API::Entities::Project.represent(project, current_user: current_user)) end def enable_environments_guidance? diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index abfa55cff24..14eec407d15 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -114,7 +114,7 @@ module IntegrationsHelper learn_more_path: integrations_help_page_path, about_pricing_url: Gitlab::Saas.about_pricing_url, trigger_events: trigger_events_for_integration(integration), - sections: integration.sections.to_json, + sections: Gitlab::Json.dump(integration.sections), fields: fields_for_integration(integration), inherit_from_id: integration.inherit_from_id, integration_level: integration_level(integration), @@ -144,7 +144,7 @@ module IntegrationsHelper def integration_list_data(integrations, group: nil, project: nil) { - integrations: integrations.map { |i| serialize_integration(i, group: group, project: project) }.to_json + integrations: Gitlab::Json.dump(integrations.map { |i| serialize_integration(i, group: group, project: project) }) } end @@ -237,11 +237,15 @@ module IntegrationsHelper end def trigger_events_for_integration(integration) - Integrations::EventSerializer.new(integration: integration).represent(integration.configurable_events).to_json + serializer = Integrations::EventSerializer.new(integration: integration).represent(integration.configurable_events) + + Gitlab::Json.dump(serializer) end def fields_for_integration(integration) - Integrations::FieldSerializer.new(integration: integration).represent(integration.form_fields).to_json + serializer = Integrations::FieldSerializer.new(integration: integration).represent(integration.form_fields) + + Gitlab::Json.dump(serializer) end def integration_level(integration) diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb index 5d537767eaf..8672dfb39dc 100644 --- a/app/helpers/invite_members_helper.rb +++ b/app/helpers/invite_members_helper.rb @@ -29,7 +29,7 @@ module InviteMembersHelper invalid_groups: source.related_group_ids, help_link: help_page_url('user/permissions'), is_project: is_project, - access_levels: member_class.permissible_access_level_roles(current_user, source).to_json + access_levels: Gitlab::Json.dump(member_class.permissible_access_level_roles(current_user, source)) }.merge(group_select_data(source)) end @@ -44,8 +44,8 @@ module InviteMembersHelper if show_invite_members_for_task?(source) dataset.merge!( - tasks_to_be_done_options: tasks_to_be_done_options.to_json, - projects: projects_for_source(source).to_json, + tasks_to_be_done_options: Gitlab::Json.dump(tasks_to_be_done_options), + projects: Gitlab::Json.dump(projects_for_source(source)), new_project_path: source.is_a?(Group) ? new_project_path(namespace_id: source.id) : '' ) end diff --git a/app/helpers/issuables_description_templates_helper.rb b/app/helpers/issuables_description_templates_helper.rb index 58b86dca1e0..075b2710722 100644 --- a/app/helpers/issuables_description_templates_helper.rb +++ b/app/helpers/issuables_description_templates_helper.rb @@ -53,7 +53,7 @@ module IssuablesDescriptionTemplatesHelper end def available_service_desk_templates_for(project) - issuable_templates(project, 'issue').flatten.to_json + Gitlab::Json.dump(issuable_templates(project, 'issue').flatten) end def template_names_path(parent, issuable) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index e9e6241a6a7..0a5d02cbcc7 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -73,10 +73,9 @@ module IssuablesHelper MergeRequestSerializer end - serializer_klass + Gitlab::Json.dump(serializer_klass .new(current_user: current_user, project: issuable.project) - .represent(issuable, opts) - .to_json + .represent(issuable, opts)) end def users_dropdown_label(selected_users) @@ -441,7 +440,7 @@ module IssuablesHelper labels_manage_path: project_labels_path(project), project_issues_path: issuable_sidebar[:project_issuables_path], project_path: project.full_path, - selected_labels: issuable_sidebar[:labels].to_json + selected_labels: Gitlab::Json.dump(issuable_sidebar[:labels]) } end diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb index 0971fdae8dd..70e1f7ca4f6 100644 --- a/app/helpers/jira_connect_helper.rb +++ b/app/helpers/jira_connect_helper.rb @@ -6,12 +6,12 @@ module JiraConnectHelper { groups_path: api_v4_groups_path(params: { min_access_level: Gitlab::Access::MAINTAINER, skip_groups: skip_groups }), - subscriptions: subscriptions.map { |s| serialize_subscription(s) }.to_json, + subscriptions: Gitlab::Json.dump(subscriptions.map { |s| serialize_subscription(s) }), add_subscriptions_path: jira_connect_subscriptions_path, subscriptions_path: jira_connect_subscriptions_path(format: :json), users_path: current_user ? nil : jira_connect_users_path, # users_path is used to determine if user is signed in gitlab_user_path: current_user ? user_path(current_user) : nil, - oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data(installation).to_json : nil + oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? Gitlab::Json.dump(jira_connect_oauth_data(installation)) : nil } end diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index a07922e451a..485a85cc73f 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -14,9 +14,9 @@ module LearnGitlabHelper def learn_gitlab_data(project) { - actions: onboarding_actions_data(project).to_json, - sections: onboarding_sections_data.to_json, - project: onboarding_project_data(project).to_json + actions: Gitlab::Json.dump(onboarding_actions_data(project)), + sections: Gitlab::Json.dump(onboarding_sections_data), + project: Gitlab::Json.dump(onboarding_project_data(project)) } end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 60796e628a3..3d4f7254c18 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -67,7 +67,7 @@ module NamespacesHelper end { - popover_data: popover_data.to_json, + popover_data: Gitlab::Json.dump(popover_data), testid: 'cascading-settings-lock-icon' } end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index b47f4633348..c0d26d4759d 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -22,12 +22,14 @@ module NotesHelper end def noteable_json(noteable) - { + data = { id: noteable.id, class: noteable.class.name, resources: noteable.class.table_name, project_id: noteable.project.id - }.to_json + } + + Gitlab::Json.dump(data) end def diff_view_data diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb index baeb9a477c3..542b8260d20 100644 --- a/app/helpers/operations_helper.rb +++ b/app/helpers/operations_helper.rb @@ -26,7 +26,7 @@ module OperationsHelper 'disabled' => disabled.to_s, 'project_path' => @project.full_path, 'multi_integrations' => 'false', - 'templates' => templates.to_json, + 'templates' => Gitlab::Json.dump(templates), 'create_issue' => setting.create_issue.to_s, 'issue_template_key' => setting.issue_template_key.to_s, 'send_email' => setting.send_email.to_s, diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb index f9ec20bdd01..9f72acf40b5 100644 --- a/app/helpers/packages_helper.rb +++ b/app/helpers/packages_helper.rb @@ -24,7 +24,7 @@ module PackagesHelper def package_from_presenter(package) presenter = ::Packages::Detail::PackagePresenter.new(package) - presenter.detail_view.to_json + Gitlab::Json.dump(presenter.detail_view) end def pypi_registry_url(project_id) @@ -68,9 +68,9 @@ module PackagesHelper { project_id: @project.id, project_path: @project.full_path, - cadence_options: cadence_options.to_json, - keep_n_options: keep_n_options.to_json, - older_than_options: older_than_options.to_json, + cadence_options: Gitlab::Json.dump(cadence_options), + keep_n_options: Gitlab::Json.dump(keep_n_options), + older_than_options: Gitlab::Json.dump(older_than_options), is_admin: current_user&.admin.to_s, admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), project_settings_path: project_settings_packages_and_registries_path(@project), diff --git a/app/helpers/projects/project_members_helper.rb b/app/helpers/projects/project_members_helper.rb index 51a7d3e35d0..e35277eabaa 100644 --- a/app/helpers/projects/project_members_helper.rb +++ b/app/helpers/projects/project_members_helper.rb @@ -2,14 +2,14 @@ module Projects::ProjectMembersHelper def project_members_app_data_json(project, members:, invited:, access_requests:, include_relations:, search:) - { + Gitlab::Json.dump({ user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }), group: project_group_links_list_data(project, include_relations, search), invite: project_members_list_data(project, invited.nil? ? [] : invited), access_request: project_members_list_data(project, access_requests.nil? ? [] : access_requests), source_id: project.id, can_manage_members: Ability.allowed?(current_user, :admin_project_member, project) - }.to_json + }) end def project_member_header_subtext(project) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 122a0ae5277..53c29a3c8b4 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -300,13 +300,15 @@ module ProjectsHelper return if setting.blank? || setting.project_slug.blank? || setting.organization_slug.blank? - { + data = { sentry_project_id: setting.sentry_project_id, name: setting.project_name, organization_name: setting.organization_name, organization_slug: setting.organization_slug, slug: setting.project_slug - }.to_json + } + + Gitlab::Json.dump(data) end def directory? diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 7741f714134..b3f82e89dfa 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -440,9 +440,9 @@ module SearchHelper end def search_navigation_json - search_navigation.each_with_object({}) do |(key, value), hash| + Gitlab::Json.dump(search_navigation.each_with_object({}) do |(key, value), hash| hash[key] = search_filter_link_json(key, value[:label], value[:data], value[:search]) if value[:condition] - end.to_json + end) end def search_filter_input_options(type, placeholder = _('Search or filter results...')) diff --git a/app/helpers/terms_helper.rb b/app/helpers/terms_helper.rb index 5f321551413..1dc5e4f1974 100644 --- a/app/helpers/terms_helper.rb +++ b/app/helpers/terms_helper.rb @@ -4,7 +4,7 @@ module TermsHelper def terms_data(terms, redirect) redirect_params = { redirect: redirect } if redirect - { + data = { terms: markdown_field(terms, :terms), permissions: { can_accept: can?(current_user, :accept_terms, terms), @@ -15,6 +15,8 @@ module TermsHelper decline: decline_term_path(terms, redirect_params), root: root_path } - }.to_json + } + + Gitlab::Json.dump(data) end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 4f345fdeb9c..d66942c2884 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -3,8 +3,8 @@ module UsersHelper def admin_users_data_attributes(users) { - users: Admin::UserSerializer.new.represent(users, { current_user: current_user }).to_json, - paths: admin_users_paths.to_json + users: Gitlab::Json.dump(Admin::UserSerializer.new.represent(users, { current_user: current_user })), + paths: Gitlab::Json.dump(admin_users_paths) } end @@ -163,8 +163,8 @@ module UsersHelper def admin_user_actions_data_attributes(user) { - user: Admin::UserEntity.represent(user, { current_user: current_user }).to_json, - paths: admin_users_paths.to_json + user: Gitlab::Json.dump(Admin::UserEntity.represent(user, { current_user: current_user })), + paths: Gitlab::Json.dump(admin_users_paths) } end diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb index 14520b2da26..c8b8fe5c951 100644 --- a/app/models/container_repository.rb +++ b/app/models/container_repository.rb @@ -224,6 +224,13 @@ class ContainerRepository < ApplicationRecord end end + # Container Repository model and the code that makes API calls + # are tied. Sometimes (mainly in Geo) we need to work with Registry + # when Container Repository record doesn't even exist. + # The ability to create a not-persisted record with a certain "path" parameter + # is very useful + attr_writer :path + def self.exists_by_path?(path) where( project: path.repository_project, diff --git a/app/services/ci/pipeline_schedules/take_ownership_service.rb b/app/services/ci/pipeline_schedules/take_ownership_service.rb new file mode 100644 index 00000000000..9b4001c74bd --- /dev/null +++ b/app/services/ci/pipeline_schedules/take_ownership_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Ci + module PipelineSchedules + class TakeOwnershipService + def initialize(schedule, user) + @schedule = schedule + @user = user + end + + def execute + return forbidden unless allowed? + + if schedule.update(owner: user) + ServiceResponse.success(payload: schedule) + else + ServiceResponse.error(message: schedule.errors.full_messages) + end + end + + private + + attr_reader :schedule, :user + + def allowed? + user.can?(:take_ownership_pipeline_schedule, schedule) + end + + def forbidden + ServiceResponse.error(message: _('Failed to change the owner'), reason: :access_denied) + end + end + end +end diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb deleted file mode 100644 index 2a626a402e4..00000000000 --- a/app/services/clusters/applications/create_service.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Applications - class CreateService < Clusters::Applications::BaseService - private - - def worker_class(application) - application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker - end - - def builder - cluster.public_send(application_class.association_name) || # rubocop:disable GitlabSecurity/PublicSend - cluster.public_send(:"build_application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend - end - end - end -end diff --git a/app/views/admin/application_settings/_user_restrictions.html.haml b/app/views/admin/application_settings/_user_restrictions.html.haml index de8faa6705f..82f5e6def9f 100644 --- a/app/views/admin/application_settings/_user_restrictions.html.haml +++ b/app/views/admin/application_settings/_user_restrictions.html.haml @@ -3,4 +3,4 @@ .form-group = label_tag _('User restrictions') = render_if_exists 'admin/application_settings/updating_name_disabled_for_users', form: form - = form.gitlab_ui_checkbox_component :can_create_group, _("Allow users to create top-level groups") + = form.gitlab_ui_checkbox_component :can_create_group, _("Allow new users to create top-level groups") diff --git a/app/workers/cluster_install_app_worker.rb b/app/workers/cluster_install_app_worker.rb index e16e6e9ca71..0c94f8cad6a 100644 --- a/app/workers/cluster_install_app_worker.rb +++ b/app/workers/cluster_install_app_worker.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# DEPRECATED +# +# To be removed by https://gitlab.com/gitlab-org/gitlab/-/issues/366573 class ClusterInstallAppWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker @@ -12,9 +15,5 @@ class ClusterInstallAppWorker # rubocop:disable Scalability/IdempotentWorker worker_has_external_dependencies! loggable_arguments 0 - def perform(app_name, app_id) - find_application(app_name, app_id) do |app| - Clusters::Applications::InstallService.new(app).execute - end - end + def perform(app_name, app_id); end end diff --git a/app/workers/cluster_upgrade_app_worker.rb b/app/workers/cluster_upgrade_app_worker.rb index bbe0cb7f0c2..40feee9374d 100644 --- a/app/workers/cluster_upgrade_app_worker.rb +++ b/app/workers/cluster_upgrade_app_worker.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# DEPRECATED +# +# To be removed by https://gitlab.com/gitlab-org/gitlab/-/issues/366573 class ClusterUpgradeAppWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker @@ -12,9 +15,5 @@ class ClusterUpgradeAppWorker # rubocop:disable Scalability/IdempotentWorker worker_has_external_dependencies! loggable_arguments 0 - def perform(app_name, app_id) - find_application(app_name, app_id) do |app| - Clusters::Applications::UpgradeService.new(app).execute - end - end + def perform(app_name, app_id); end end diff --git a/config/feature_flags/development/split_background_migration_on_query_canceled.yml b/config/feature_flags/development/split_background_migration_on_query_canceled.yml new file mode 100644 index 00000000000..48409041f1a --- /dev/null +++ b/config/feature_flags/development/split_background_migration_on_query_canceled.yml @@ -0,0 +1,8 @@ +--- +name: split_background_migration_on_query_canceled +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101825 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/378846 +milestone: '15.6' +type: development +group: group::database +default_enabled: false diff --git a/db/post_migrate/20221025220607_add_index_id_on_scan_finding_approval_merge_request_rules.rb b/db/post_migrate/20221025220607_add_index_id_on_scan_finding_approval_merge_request_rules.rb new file mode 100644 index 00000000000..4e72e7f95ec --- /dev/null +++ b/db/post_migrate/20221025220607_add_index_id_on_scan_finding_approval_merge_request_rules.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexIdOnScanFindingApprovalMergeRequestRules < Gitlab::Database::Migration[2.0] + INDEX_NAME = 'scan_finding_approval_mr_rule_index_id' + SCAN_FINDING_REPORT_TYPE = 4 + + disable_ddl_transaction! + + def up + add_concurrent_index :approval_merge_request_rules, :id, + where: "report_type = #{SCAN_FINDING_REPORT_TYPE}", name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :approval_merge_request_rules, INDEX_NAME + end +end diff --git a/db/schema_migrations/20221025220607 b/db/schema_migrations/20221025220607 new file mode 100644 index 00000000000..30322b1ab3f --- /dev/null +++ b/db/schema_migrations/20221025220607 @@ -0,0 +1 @@ +d6eb5bb918f12c08f23c228916b7e21432e1e2958832c10be4e46dfa2079103d \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 570417ccbb6..7f9d177a3ea 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -31078,6 +31078,8 @@ CREATE UNIQUE INDEX partial_index_sop_configs_on_project_id ON security_orchestr CREATE INDEX partial_index_user_id_app_id_created_at_token_not_revoked ON oauth_access_tokens USING btree (resource_owner_id, application_id, created_at) WHERE (revoked_at IS NULL); +CREATE INDEX scan_finding_approval_mr_rule_index_id ON approval_merge_request_rules USING btree (id) WHERE (report_type = 4); + CREATE INDEX scan_finding_approval_mr_rule_index_merge_request_id ON approval_merge_request_rules USING btree (merge_request_id) WHERE (report_type = 4); CREATE INDEX scan_finding_approval_project_rule_index_created_at_project_id ON approval_project_rules USING btree (created_at, project_id) WHERE (report_type = 4); diff --git a/doc/administration/user_settings.md b/doc/administration/user_settings.md index a767132db0f..c96a6311208 100644 --- a/doc/administration/user_settings.md +++ b/doc/administration/user_settings.md @@ -14,7 +14,7 @@ By default, new users can create top-level groups. To disable new users' ability to create top-level groups (does not affect existing users' setting), GitLab administrators can modify this setting: - In GitLab 15.5 and later, using either: - - The [GitLab UI](../user/admin_area/settings/account_and_limit_settings.md#prevent-users-from-creating-top-level-groups). + - The [GitLab UI](../user/admin_area/settings/account_and_limit_settings.md#prevent-new-users-from-creating-top-level-groups). - The [application setting API](../api/settings.md#change-application-settings). - In GitLab 15.4 and earlier, in a configuration file by following the steps in this section. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index a68ae262686..1a2ddc6eea8 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4198,6 +4198,25 @@ Input type: `PipelineScheduleDeleteInput` | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +### `Mutation.pipelineScheduleTakeOwnership` + +Input type: `PipelineScheduleTakeOwnershipInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `id` | [`CiPipelineScheduleID!`](#cipipelinescheduleid) | ID of the pipeline schedule to mutate. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| `pipelineSchedule` | [`PipelineSchedule`](#pipelineschedule) | Updated pipeline schedule ownership. | + ### `Mutation.projectCiCdSettingsUpdate` Input type: `ProjectCiCdSettingsUpdateInput` diff --git a/doc/ci/testing/code_quality.md b/doc/ci/testing/code_quality.md index 7345c7ca5eb..d1ed28b79c0 100644 --- a/doc/ci/testing/code_quality.md +++ b/doc/ci/testing/code_quality.md @@ -339,6 +339,20 @@ This example is specific to GitLab Code Quality. For more general instructions on how to configure DinD with a registry mirror, see the relevant [documentation](../docker/using_docker_build.md#enable-registry-mirror-for-dockerdind-service). +#### List of images to be stored in the private container registry + +The following images are needed for the [default `.codeclimate.yml`](https://gitlab.com/gitlab-org/ci-cd/codequality/-/blob/master/codeclimate_defaults/.codeclimate.yml.template): + +- `codeclimate/codeclimate-structure:latest` +- `codeclimate/codeclimate-csslint:latest` +- `codeclimate/codeclimate-coffeelint:latest` +- `codeclimate/codeclimate-duplication:latest` +- `codeclimate/codeclimate-eslint:latest` +- `codeclimate/codeclimate-fixme:latest` +- `codeclimate/codeclimate-rubocop:rubocop-0-92` + +If you are using a custom `.codeclimate.yml` configuration file, you must add the specified plugins in your private container registry. + #### Configure Code Quality to use the Dependency Proxy Prerequisite: diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md index 3d05aa120cf..44a6bbb9d8e 100644 --- a/doc/user/admin_area/settings/account_and_limit_settings.md +++ b/doc/user/admin_area/settings/account_and_limit_settings.md @@ -276,11 +276,11 @@ When this ability is disabled, GitLab administrators can still use the [Admin Area](../index.md#administering-users) or the [API](../../../api/users.md#user-modification) to update usernames. -## Prevent users from creating top-level groups +## Prevent new users from creating top-level groups > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367754) in GitLab 15.5. -By default, new users can create top-level groups. GitLab administrators can prevent users from creating top-level groups: +By default, new users can create top-level groups. GitLab administrators can prevent new users from creating top-level groups: - In GitLab 15.5 and later, using either: - The GitLab UI using the steps in this section. @@ -289,7 +289,7 @@ By default, new users can create top-level groups. GitLab administrators can pre 1. On the top bar, select **Main menu > Admin**. 1. On the left sidebar, select **Settings > General**, then expand **Account and limit**. -1. Clear the **Allow users to create top-level groups** checkbox. +1. Clear the **Allow new users to create top-level groups** checkbox. ## Troubleshooting diff --git a/lib/api/api.rb b/lib/api/api.rb index 37ed8f8659c..7a3631b2036 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -170,6 +170,7 @@ module API # Mount endpoints to include in the OpenAPI V2 documentation here namespace do mount ::API::AccessRequests + mount ::API::Appearance mount ::API::Deployments mount ::API::Metadata mount ::API::UserCounts @@ -186,7 +187,6 @@ module API mount ::API::Admin::PlanLimits mount ::API::Admin::Sidekiq mount ::API::AlertManagementAlerts - mount ::API::Appearance mount ::API::Applications mount ::API::Avatar mount ::API::AwardEmoji diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb index e599abf4aaf..69f1521ef2a 100644 --- a/lib/api/appearance.rb +++ b/lib/api/appearance.rb @@ -22,6 +22,7 @@ module API desc 'Modify appearance' do success Entities::Appearance + consumes ['multipart/form-data'] end params do optional :title, type: String, desc: 'Instance title on the sign in / sign up page' diff --git a/lib/gitlab/database/background_migration/batched_job.rb b/lib/gitlab/database/background_migration/batched_job.rb index 81898a59da7..9ac6d1442a2 100644 --- a/lib/gitlab/database/background_migration/batched_job.rb +++ b/lib/gitlab/database/background_migration/batched_job.rb @@ -112,7 +112,7 @@ module Gitlab end def can_split?(exception) - attempts >= MAX_ATTEMPTS && TIMEOUT_EXCEPTIONS.include?(exception&.class) && batch_size > sub_batch_size && batch_size > 1 + attempts >= MAX_ATTEMPTS && timeout_exception?(exception&.class) && batch_size > sub_batch_size && batch_size > 1 end def split_and_retry! @@ -161,6 +161,15 @@ module Gitlab end end end + + private + + def timeout_exception?(exception_class) + return false unless exception_class + + TIMEOUT_EXCEPTIONS.include?(exception_class) || + (Feature.enabled?(:split_background_migration_on_query_canceled) && exception_class == ActiveRecord::QueryCanceled) + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index efbbf7d49ca..a353ed4e544 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3976,6 +3976,9 @@ msgstr "" msgid "Allow group owners to manage LDAP-related settings" msgstr "" +msgid "Allow new users to create top-level groups" +msgstr "" + msgid "Allow non-administrators access to the performance bar" msgstr "" @@ -4012,9 +4015,6 @@ msgstr "" msgid "Allow use of licensed EE features" msgstr "" -msgid "Allow users to create top-level groups" -msgstr "" - msgid "Allow users to dismiss the broadcast message" msgstr "" diff --git a/qa/Gemfile b/qa/Gemfile index bd02aaa9dbf..77507e2b0b0 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -26,7 +26,7 @@ gem 'octokit', '~> 6.0.0' gem "faraday-retry", "~> 2.0" gem 'webdrivers', '~> 5.2' gem 'zeitwerk', '~> 2.4' -gem 'influxdb-client', '~> 2.7' +gem 'influxdb-client', '~> 2.8' gem 'terminal-table', '~> 3.0.2', require: false gem 'slack-notifier', '~> 2.4', require: false gem 'fog-google', '~> 1.19', require: false diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 8e64739df58..f8a857e67ec 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -156,7 +156,7 @@ GEM httpclient (2.8.3) i18n (1.12.0) concurrent-ruby (~> 1.0) - influxdb-client (2.7.0) + influxdb-client (2.8.0) jwt (2.5.0) knapsack (4.0.0) rake @@ -315,7 +315,7 @@ DEPENDENCIES fog-core (= 2.1.0) fog-google (~> 1.19) gitlab-qa (~> 8, >= 8.9.0) - influxdb-client (~> 2.7) + influxdb-client (~> 2.8) knapsack (~> 4.0) nokogiri (~> 1.13, >= 1.13.9) octokit (~> 6.0.0) diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb index c2c78be6a0f..6971ec83ce2 100644 --- a/spec/helpers/users_helper_spec.rb +++ b/spec/helpers/users_helper_spec.rb @@ -391,7 +391,7 @@ RSpec.describe UsersHelper do expect_next_instance_of(Admin::UserSerializer) do |instance| expect(instance).to receive(:represent).with([user], { current_user: user }).and_return(entity) end - expect(entity).to receive(:to_json).and_return("{\"username\":\"admin\"}") + expect(entity).to receive(:as_json).and_return({ "username" => "admin" }) expect(data[:users]).to eq "{\"username\":\"admin\"}" end diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index 32746a46308..332c9618cb5 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -272,7 +272,21 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d context 'when is a timeout exception' do let(:exception) { ActiveRecord::StatementTimeout.new } - it { expect(subject).to be_truthy } + it { expect(subject).to be_truthy } + end + + context 'when is a QueryCanceled exception' do + let(:exception) { ActiveRecord::QueryCanceled.new } + + it { expect(subject).to be_truthy } + + context 'when split_background_migration_on_query_canceled feature flag is disabled' do + before do + stub_feature_flags(split_background_migration_on_query_canceled: false) + end + + it { expect(subject).to be_falsey } + end end context 'when is not a timeout exception' do diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb new file mode 100644 index 00000000000..8dfbf20d00b --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'PipelineScheduleTakeOwnership' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: owner) } + + let(:mutation) do + graphql_mutation( + :pipeline_schedule_take_ownership, + { id: pipeline_schedule_id }, + <<-QL + errors + QL + ) + end + + let(:pipeline_schedule_id) { pipeline_schedule.to_global_id.to_s } + + before_all do + project.add_maintainer(user) + end + + it 'returns an error if the user is not allowed to take ownership of the schedule' do + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).not_to be_empty + end + + it 'takes ownership of the schedule' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_nil + end +end diff --git a/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb b/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb new file mode 100644 index 00000000000..9a3aad20d89 --- /dev/null +++ b/spec/services/ci/pipeline_schedules/take_ownership_service_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PipelineSchedules::TakeOwnershipService do + let_it_be(:user) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: owner) } + + before_all do + project.add_maintainer(user) + project.add_maintainer(owner) + project.add_reporter(reporter) + end + + describe '#execute' do + context 'when user does not have permission' do + subject(:service) { described_class.new(pipeline_schedule, reporter) } + + it 'returns ServiceResponse.error' do + result = service.execute + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq(_('Failed to change the owner')) + end + end + + context 'when user has permission' do + subject(:service) { described_class.new(pipeline_schedule, user) } + + it 'returns ServiceResponse.success' do + result = service.execute + + expect(result).to be_a(ServiceResponse) + expect(result.success?).to be(true) + expect(result.payload).to eq(pipeline_schedule) + end + + context 'when schedule update fails' do + subject(:service) { described_class.new(pipeline_schedule, owner) } + + before do + allow(pipeline_schedule).to receive(:update).and_return(false) + + errors = ActiveModel::Errors.new(pipeline_schedule) + errors.add(:base, 'An error occurred') + allow(pipeline_schedule).to receive(:errors).and_return(errors) + end + + it 'returns ServiceResponse.error' do + result = service.execute + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq(['An error occurred']) + end + end + end + end +end diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb deleted file mode 100644 index 00a67a9b2ef..00000000000 --- a/spec/services/clusters/applications/create_service_spec.rb +++ /dev/null @@ -1,279 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Clusters::Applications::CreateService do - include TestRequestHelpers - - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:user) { create(:user) } - let(:params) { { application: 'ingress' } } - let(:service) { described_class.new(cluster, user, params) } - - describe '#execute' do - before do - allow(ClusterInstallAppWorker).to receive(:perform_async) - allow(ClusterUpgradeAppWorker).to receive(:perform_async) - end - - subject { service.execute(test_request) } - - it 'creates an application' do - expect do - subject - - cluster.reload - end.to change(cluster, :application_ingress) - end - - context 'application already installed' do - let!(:application) { create(:clusters_applications_ingress, :installed, cluster: cluster) } - - it 'does not create a new application' do - expect do - subject - end.not_to change(Clusters::Applications::Ingress, :count) - end - - it 'schedules an upgrade for the application' do - expect(ClusterUpgradeAppWorker).to receive(:perform_async) - - subject - end - end - - context 'known applications' do - context 'ingress application' do - let(:params) do - { - application: 'ingress' - } - end - - before do - expect_any_instance_of(Clusters::Applications::Ingress) - .to receive(:make_scheduled!) - .and_call_original - end - - it 'creates the application' do - expect do - subject - - cluster.reload - end.to change(cluster, :application_ingress) - end - end - - context 'cert manager application' do - let(:params) do - { - application: 'cert_manager', - email: 'test@example.com' - } - end - - before do - expect_any_instance_of(Clusters::Applications::CertManager) - .to receive(:make_scheduled!) - .and_call_original - end - - it 'creates the application' do - expect do - subject - - cluster.reload - end.to change(cluster, :application_cert_manager) - end - - it 'sets the email' do - expect(subject.email).to eq('test@example.com') - end - end - - context 'jupyter application' do - let(:params) do - { - application: 'jupyter', - hostname: 'example.com' - } - end - - before do - create(:clusters_applications_ingress, :installed, external_ip: "127.0.0.0", cluster: cluster) - expect_any_instance_of(Clusters::Applications::Jupyter) - .to receive(:make_scheduled!) - .and_call_original - end - - it 'creates the application' do - expect do - subject - - cluster.reload - end.to change(cluster, :application_jupyter) - end - - it 'sets the hostname' do - expect(subject.hostname).to eq('example.com') - end - - it 'sets the oauth_application' do - expect(subject.oauth_application).to be_present - end - end - - context 'knative application' do - let(:params) do - { - application: 'knative', - hostname: 'example.com', - pages_domain_id: domain.id - } - end - - let(:domain) { create(:pages_domain, :instance_serverless) } - let(:associate_domain_service) { double('AssociateDomainService') } - - before do - expect_any_instance_of(Clusters::Applications::Knative) - .to receive(:make_scheduled!) - .and_call_original - end - - it 'creates the application' do - expect do - subject - - cluster.reload - end.to change(cluster, :application_knative) - end - - it 'sets the hostname' do - expect(subject.hostname).to eq('example.com') - end - - it 'executes AssociateDomainService' do - expect(Serverless::AssociateDomainService).to receive(:new) do |knative, args| - expect(knative).to be_a(Clusters::Applications::Knative) - expect(args[:pages_domain_id]).to eq(params[:pages_domain_id]) - expect(args[:creator]).to eq(user) - - associate_domain_service - end - - expect(associate_domain_service).to receive(:execute) - - subject - end - end - end - - context 'invalid application' do - let(:params) { { application: 'non-existent' } } - - it 'raises an error' do - expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError) - end - end - - context 'group cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, :group) } - - using RSpec::Parameterized::TableSyntax - - where(:application, :association, :allowed, :pre_create_ingress) do - 'ingress' | :application_ingress | true | false - 'runner' | :application_runner | true | false - 'prometheus' | :application_prometheus | true | false - 'jupyter' | :application_jupyter | true | true - end - - with_them do - before do - klass = "Clusters::Applications::#{application.titleize}" - allow_any_instance_of(klass.constantize).to receive(:make_scheduled!).and_call_original - create(:clusters_applications_ingress, :installed, cluster: cluster, external_hostname: 'example.com') if pre_create_ingress - end - - let(:params) { { application: application } } - - it 'executes for each application' do - if allowed - expect do - subject - - cluster.reload - end.to change(cluster, association) - else - expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError) - end - end - end - end - - context 'when application is installable' do - shared_examples 'installable applications' do - it 'makes the application scheduled' do - expect do - subject - end.to change { Clusters::Applications::Ingress.with_status(:scheduled).count }.by(1) - end - - it 'schedules an install via worker' do - expect(ClusterInstallAppWorker) - .to receive(:perform_async) - .with(*worker_arguments) - .once - - subject - end - end - - context 'when application is associated with a cluster' do - let(:application) { create(:clusters_applications_ingress, :installable, cluster: cluster) } - let(:worker_arguments) { [application.name, application.id] } - - it_behaves_like 'installable applications' - end - - context 'when application is not associated with a cluster' do - let(:worker_arguments) { [params[:application], kind_of(Numeric)] } - - it_behaves_like 'installable applications' - end - end - - context 'when installation is already in progress' do - let!(:application) { create(:clusters_applications_ingress, :installing, cluster: cluster) } - - it 'raises an exception' do - expect { subject } - .to raise_exception(StateMachines::InvalidTransition) - .and not_change(application.class.with_status(:scheduled), :count) - end - - it 'does not schedule a cluster worker' do - expect(ClusterInstallAppWorker).not_to receive(:perform_async) - end - end - - context 'when application is installed' do - %i(installed updated).each do |status| - let(:application) { create(:clusters_applications_ingress, status, cluster: cluster) } - - it 'schedules an upgrade via worker' do - expect(ClusterUpgradeAppWorker) - .to receive(:perform_async) - .with(application.name, application.id) - .once - - subject - - expect(application.reload).to be_scheduled - end - end - end - end -end