diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-10 12:07:17 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-08-10 12:07:17 +0300 |
commit | f605b80ff70b395afa345bad11c7f8aa0506a9bf (patch) | |
tree | 9b9d171a24f86cf7f215fc42fdb728acf197840f | |
parent | 76c4dd062c4eeb853866ef8b6451c59f9e24221c (diff) |
Add latest changes from gitlab-org/gitlab@master
55 files changed, 760 insertions, 332 deletions
diff --git a/.rubocop_todo/gitlab/strong_memoize_attr.yml b/.rubocop_todo/gitlab/strong_memoize_attr.yml index b30a18be6ba..cd58daeeab4 100644 --- a/.rubocop_todo/gitlab/strong_memoize_attr.yml +++ b/.rubocop_todo/gitlab/strong_memoize_attr.yml @@ -59,8 +59,8 @@ Gitlab/StrongMemoizeAttr: - 'app/graphql/resolvers/namespace_projects_resolver.rb' - 'app/graphql/resolvers/work_items_resolver.rb' - 'app/graphql/types/board_list_type.rb' + - 'app/helpers/admin/broadcast_messages_helper.rb' - 'app/helpers/appearances_helper.rb' - - 'app/helpers/broadcast_messages_helper.rb' - 'app/helpers/diff_helper.rb' - 'app/helpers/operations_helper.rb' - 'app/helpers/page_layout_helper.rb' diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index 9a561d714b0..014b9a3f551 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -1277,12 +1277,12 @@ Layout/ArgumentAlignment: - 'ee/spec/services/vulnerability_feedback/create_service_spec.rb' - 'ee/spec/services/vulnerability_merge_request_links/create_service_spec.rb' - 'lib/api/access_requests.rb' + - 'lib/api/admin/broadcast_messages.rb' - 'lib/api/admin/plan_limits.rb' - 'lib/api/alert_management_alerts.rb' - 'lib/api/api.rb' - 'lib/api/applications.rb' - 'lib/api/branches.rb' - - 'lib/api/broadcast_messages.rb' - 'lib/api/bulk_imports.rb' - 'lib/api/ci/job_artifacts.rb' - 'lib/api/ci/jobs.rb' diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 65b5d568cfd..162afb96a09 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -2409,13 +2409,13 @@ Layout/LineLength: - 'ee/spec/workers/sync_seat_link_request_worker_spec.rb' - 'ee/spec/workers/update_all_mirrors_worker_spec.rb' - 'ee/spec/workers/vulnerability_exports/export_deletion_worker_spec.rb' + - 'lib/api/admin/broadcast_messages.rb' - 'lib/api/admin/instance_clusters.rb' - 'lib/api/api.rb' - 'lib/api/appearance.rb' - 'lib/api/award_emoji.rb' - 'lib/api/boards_responses.rb' - 'lib/api/branches.rb' - - 'lib/api/broadcast_messages.rb' - 'lib/api/ci/jobs.rb' - 'lib/api/ci/pipeline_schedules.rb' - 'lib/api/ci/pipelines.rb' @@ -2435,10 +2435,10 @@ Layout/LineLength: - 'lib/api/deploy_tokens.rb' - 'lib/api/deployments.rb' - 'lib/api/discussions.rb' + - 'lib/api/entities/system/broadcast_message.rb' - 'lib/api/entities/application_setting.rb' - 'lib/api/entities/basic_project_details.rb' - 'lib/api/entities/branch.rb' - - 'lib/api/entities/broadcast_message.rb' - 'lib/api/entities/container_registry.rb' - 'lib/api/entities/deploy_key.rb' - 'lib/api/entities/issue_basic.rb' @@ -4336,6 +4336,7 @@ Layout/LineLength: - 'spec/rack_servers/puma_spec.rb' - 'spec/requests/admin/background_migrations_controller_spec.rb' - 'spec/requests/api/access_requests_spec.rb' + - 'spec/requests/api/admin/broadcast_messages_spec.rb' - 'spec/requests/api/admin/instance_clusters_spec.rb' - 'spec/requests/api/admin/plan_limits_spec.rb' - 'spec/requests/api/admin/sidekiq_spec.rb' @@ -4344,7 +4345,6 @@ Layout/LineLength: - 'spec/requests/api/award_emoji_spec.rb' - 'spec/requests/api/badges_spec.rb' - 'spec/requests/api/branches_spec.rb' - - 'spec/requests/api/broadcast_messages_spec.rb' - 'spec/requests/api/bulk_imports_spec.rb' - 'spec/requests/api/ci/job_artifacts_spec.rb' - 'spec/requests/api/ci/jobs_spec.rb' diff --git a/.rubocop_todo/rails/helper_instance_variable.yml b/.rubocop_todo/rails/helper_instance_variable.yml index 13c681a634a..1c0541f732a 100644 --- a/.rubocop_todo/rails/helper_instance_variable.yml +++ b/.rubocop_todo/rails/helper_instance_variable.yml @@ -1,6 +1,7 @@ --- Rails/HelperInstanceVariable: Exclude: + - 'app/helpers/admin/broadcast_messages_helper.rb' - 'app/helpers/admin/user_actions_helper.rb' - 'app/helpers/application_helper.rb' - 'app/helpers/application_settings_helper.rb' @@ -9,7 +10,6 @@ Rails/HelperInstanceVariable: - 'app/helpers/boards_helper.rb' - 'app/helpers/branches_helper.rb' - 'app/helpers/breadcrumbs_helper.rb' - - 'app/helpers/broadcast_messages_helper.rb' - 'app/helpers/ci/builds_helper.rb' - 'app/helpers/ci/jobs_helper.rb' - 'app/helpers/commits_helper.rb' diff --git a/.rubocop_todo/rails/output_safety.yml b/.rubocop_todo/rails/output_safety.yml index 53f9381099f..cc2f52a3ddc 100644 --- a/.rubocop_todo/rails/output_safety.yml +++ b/.rubocop_todo/rails/output_safety.yml @@ -13,10 +13,10 @@ Rails/OutputSafety: - 'app/controllers/search_controller.rb' - 'app/graphql/types/ci/job_trace_type.rb' - 'app/graphql/types/project_type.rb' + - 'app/helpers/admin/broadcast_messages_helper.rb' - 'app/helpers/admin/application_settings/settings_helper.rb' - 'app/helpers/appearances_helper.rb' - 'app/helpers/auth_helper.rb' - - 'app/helpers/broadcast_messages_helper.rb' - 'app/helpers/commits_helper.rb' - 'app/helpers/diff_helper.rb' - 'app/helpers/dropdowns_helper.rb' diff --git a/.rubocop_todo/rspec/before_all_role_assignment.yml b/.rubocop_todo/rspec/before_all_role_assignment.yml index 2f59bee0e66..c74c0d93813 100644 --- a/.rubocop_todo/rspec/before_all_role_assignment.yml +++ b/.rubocop_todo/rspec/before_all_role_assignment.yml @@ -1096,8 +1096,8 @@ RSpec/BeforeAllRoleAssignment: - 'spec/graphql/types/todo_type_spec.rb' - 'spec/graphql/types/user_merge_request_interaction_type_spec.rb' - 'spec/graphql/types/user_type_spec.rb' + - 'spec/helpers/admin/broadcast_messages_helper_spec.rb' - 'spec/helpers/admin/user_actions_helper_spec.rb' - - 'spec/helpers/broadcast_messages_helper_spec.rb' - 'spec/helpers/ci/pipelines_helper_spec.rb' - 'spec/helpers/clusters_helper_spec.rb' - 'spec/helpers/groups_helper_spec.rb' diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml index ab2caa7c4d1..fe1f5251bf2 100644 --- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml +++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml @@ -250,6 +250,7 @@ RSpec/FactoryBot/AvoidCreate: - 'spec/components/diffs/overflow_warning_component_spec.rb' - 'spec/components/diffs/stats_component_spec.rb' - 'spec/components/pajamas/avatar_component_spec.rb' + - 'spec/helpers/admin/broadcast_messages_helper_spec.rb' - 'spec/helpers/admin/identities_helper_spec.rb' - 'spec/helpers/admin/user_actions_helper_spec.rb' - 'spec/helpers/appearances_helper_spec.rb' @@ -262,7 +263,6 @@ RSpec/FactoryBot/AvoidCreate: - 'spec/helpers/blob_helper_spec.rb' - 'spec/helpers/boards_helper_spec.rb' - 'spec/helpers/branches_helper_spec.rb' - - 'spec/helpers/broadcast_messages_helper_spec.rb' - 'spec/helpers/button_helper_spec.rb' - 'spec/helpers/calendar_helper_spec.rb' - 'spec/helpers/ci/builds_helper_spec.rb' diff --git a/.rubocop_todo/style/percent_literal_delimiters.yml b/.rubocop_todo/style/percent_literal_delimiters.yml index e6eb7f2a47a..aee5872a343 100644 --- a/.rubocop_todo/style/percent_literal_delimiters.yml +++ b/.rubocop_todo/style/percent_literal_delimiters.yml @@ -930,8 +930,8 @@ Style/PercentLiteralDelimiters: - 'spec/presenters/ci/pipeline_artifacts/code_quality_mr_diff_presenter_spec.rb' - 'spec/presenters/packages/nuget/packages_metadata_presenter_spec.rb' - 'spec/presenters/packages/nuget/search_results_presenter_spec.rb' + - 'spec/requests/api/admin/broadcast_messages_spec.rb' - 'spec/requests/api/badges_spec.rb' - - 'spec/requests/api/broadcast_messages_spec.rb' - 'spec/requests/api/ci/jobs_spec.rb' - 'spec/requests/api/ci/pipelines_spec.rb' - 'spec/requests/api/ci/runner/jobs_request_post_spec.rb' diff --git a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue index 2168685e703..e6813211fe9 100644 --- a/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue +++ b/app/assets/javascripts/ci/runner/admin_runners/admin_runners_app.vue @@ -52,6 +52,8 @@ export default { RunnerTypeTabs, RunnerActionsCell, RunnerJobStatusBadge, + RunnerDashboardLink: () => + import('ee_component/ci/runner/components/runner_dashboard_link.vue'), }, mixins: [glFeatureFlagMixin()], props: { @@ -188,12 +190,12 @@ export default { nav-class="gl-border-none!" /> - <div class="gl-w-full gl-md-w-auto gl-display-flex"> + <div class="gl-w-full gl-md-w-auto gl-display-flex gl-gap-3"> + <runner-dashboard-link /> <gl-button :href="newRunnerPath" variant="confirm"> {{ s__('Runners|New instance runner') }} </gl-button> <registration-dropdown - class="gl-ml-3" :registration-token="registrationToken" :type="$options.INSTANCE_TYPE" placement="right" diff --git a/app/assets/javascripts/ci/runner/admin_runners/index.js b/app/assets/javascripts/ci/runner/admin_runners/index.js index 54eb37f8c90..d4df1393487 100644 --- a/app/assets/javascripts/ci/runner/admin_runners/index.js +++ b/app/assets/javascripts/ci/runner/admin_runners/index.js @@ -1,6 +1,9 @@ import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; + +import { provide } from 'ee_else_ce/ci/runner/admin_runners/provide'; + import { visitUrl } from '~/lib/utils/url_utility'; import { updateOutdatedUrl } from '~/ci/runner/runner_search_utils'; import createDefaultClient from '~/lib/graphql'; @@ -29,14 +32,7 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { return null; } - const { - runnerInstallHelpPage, - newRunnerPath, - registrationToken, - onlineContactTimeoutSecs, - staleTimeoutSecs, - } = el.dataset; - + const { newRunnerPath, registrationToken } = el.dataset; const { cacheConfig, typeDefs, localMutations } = createLocalState(); const apolloProvider = new VueApollo({ @@ -47,10 +43,8 @@ export const initAdminRunners = (selector = '#js-admin-runners') => { el, apolloProvider, provide: { - runnerInstallHelpPage, + ...provide(el.dataset), localMutations, - onlineContactTimeoutSecs, - staleTimeoutSecs, }, render(h) { return h(AdminRunnersApp, { diff --git a/app/assets/javascripts/ci/runner/admin_runners/provide.js b/app/assets/javascripts/ci/runner/admin_runners/provide.js new file mode 100644 index 00000000000..6f2f66ed72e --- /dev/null +++ b/app/assets/javascripts/ci/runner/admin_runners/provide.js @@ -0,0 +1,16 @@ +/** + * Provides global values to the admin runners app. + * + * @param {Object} `data-` HTML attributes of the mounting point + * @returns An object with properties to use provide/inject of the root app. + * See EE version + */ +export const provide = (elDataset) => { + const { runnerInstallHelpPage, onlineContactTimeoutSecs, staleTimeoutSecs } = elDataset; + + return { + runnerInstallHelpPage, + onlineContactTimeoutSecs, + staleTimeoutSecs, + }; +}; diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue index 582aee3c9a3..1d0eaae4c57 100644 --- a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue +++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue @@ -15,21 +15,21 @@ import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { joinPaths } from '~/lib/utils/url_utility'; import { getBulkImportsHistory } from '~/rest_api'; import ImportStatus from '~/import_entities/components/import_status.vue'; +import { StatusPoller } from '~/import_entities/import_groups/services/status_poller'; + import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants'; import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; +import { isImporting } from '../utils'; import { DEFAULT_ERROR } from '../utils/error_messages'; const DEFAULT_PER_PAGE = 20; -const DEFAULT_TH_CLASSES = - 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-200! gl-border-b-1! gl-p-5!'; const HISTORY_PAGINATION_SIZE_PERSIST_KEY = 'gl-bulk-imports-history-per-page'; const tableCell = (config) => ({ - thClass: `${DEFAULT_TH_CLASSES}`, tdClass: (value, key, item) => { return { // eslint-disable-next-line no-underscore-dangle @@ -57,6 +57,8 @@ export default { GlTooltip, }, + inject: ['realtimeChangesPath'], + data() { return { loading: true, @@ -73,12 +75,12 @@ export default { tableCell({ key: 'source_full_path', label: s__('BulkImport|Source'), - thClass: `${DEFAULT_TH_CLASSES} gl-w-30p`, + thClass: `gl-w-30p`, }), tableCell({ key: 'destination_name', label: s__('BulkImport|Destination'), - thClass: `${DEFAULT_TH_CLASSES} gl-w-40p`, + thClass: `gl-w-40p`, }), tableCell({ key: 'created_at', @@ -95,6 +97,12 @@ export default { hasHistoryItems() { return this.historyItems.length > 0; }, + + importingHistoryItemIds() { + return this.historyItems + .filter((item) => isImporting(item.status)) + .map((item) => item.bulk_import_id); + }, }, watch: { @@ -104,10 +112,43 @@ export default { }, deep: true, }, + + importingHistoryItemIds(value) { + if (value.length > 0) { + this.statusPoller.startPolling(); + } else { + this.statusPoller.stopPolling(); + } + }, }, mounted() { this.loadHistoryItems(); + + this.statusPoller = new StatusPoller({ + pollPath: this.realtimeChangesPath, + updateImportStatus: (update) => { + if (!this.importingHistoryItemIds.includes(update.id)) { + return; + } + + const updateItemIndex = this.historyItems.findIndex( + (item) => item.bulk_import_id === update.id, + ); + const updateItem = this.historyItems[updateItemIndex]; + + if (updateItem.status !== update.status_name) { + this.$set(this.historyItems, updateItemIndex, { + ...updateItem, + status: update.status_name, + }); + } + }, + }); + }, + + beforeDestroy() { + this.statusPoller.stopPolling(); }, methods: { diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/index.js b/app/assets/javascripts/pages/import/bulk_imports/history/index.js index 5a67aa99baa..cc12723572d 100644 --- a/app/assets/javascripts/pages/import/bulk_imports/history/index.js +++ b/app/assets/javascripts/pages/import/bulk_imports/history/index.js @@ -4,8 +4,14 @@ import BulkImportHistoryApp from './components/bulk_imports_history_app.vue'; function mountImportHistoryApp(mountElement) { if (!mountElement) return undefined; + const { realtimeChangesPath } = mountElement.dataset; + return new Vue({ el: mountElement, + name: 'BulkImportHistoryRoot', + provide: { + realtimeChangesPath, + }, render(createElement) { return createElement(BulkImportHistoryApp); }, diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/utils/index.js b/app/assets/javascripts/pages/import/bulk_imports/history/utils/index.js new file mode 100644 index 00000000000..09cba40dd36 --- /dev/null +++ b/app/assets/javascripts/pages/import/bulk_imports/history/utils/index.js @@ -0,0 +1,7 @@ +import { STATUSES } from '~/import_entities/constants'; + +export function isImporting(status) { + return [STATUSES.SCHEDULING, STATUSES.SCHEDULED, STATUSES.CREATED, STATUSES.STARTED].includes( + status, + ); +} diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index f8f54567ef2..b953ff3024b 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -91,7 +91,7 @@ } .md-preview-holder { - min-height: 173px; + min-height: 177px; padding: 10px 0; overflow-x: auto; } diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb index 038338e7024..06ee178599d 100644 --- a/app/controllers/admin/broadcast_messages_controller.rb +++ b/app/controllers/admin/broadcast_messages_controller.rb @@ -2,7 +2,7 @@ module Admin class BroadcastMessagesController < ApplicationController - include BroadcastMessagesHelper + include Admin::BroadcastMessagesHelper before_action :find_broadcast_message, only: [:edit, :update, :destroy] before_action :find_broadcast_messages, only: [:index, :create] diff --git a/app/controllers/concerns/google_syndication_csp.rb b/app/controllers/concerns/google_syndication_csp.rb new file mode 100644 index 00000000000..c55debe448b --- /dev/null +++ b/app/controllers/concerns/google_syndication_csp.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module GoogleSyndicationCSP + extend ActiveSupport::Concern + + ALLOWED_SRC = ['*.google.com/pagead/landing', 'pagead2.googlesyndication.com/pagead/landing'].freeze + + included do + content_security_policy do |policy| + next unless helpers.google_tag_manager_enabled? || policy.directives.present? + + connect_src_values = Array.wrap( + policy.directives['connect-src'] || policy.directives['default-src'] + ) + + connect_src_values.concat(ALLOWED_SRC) if helpers.google_tag_manager_enabled? + + policy.connect_src(*connect_src_values.uniq) + end + end +end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index e94138c4d9b..f7c7ee62c1a 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -5,6 +5,7 @@ class ConfirmationsController < Devise::ConfirmationsController include GitlabRecaptcha include OneTrustCSP include GoogleAnalyticsCSP + include GoogleSyndicationCSP skip_before_action :required_signup_info prepend_before_action :check_recaptcha, only: :create diff --git a/app/controllers/registrations/welcome_controller.rb b/app/controllers/registrations/welcome_controller.rb index 870c9780e9d..68f8248d114 100644 --- a/app/controllers/registrations/welcome_controller.rb +++ b/app/controllers/registrations/welcome_controller.rb @@ -4,6 +4,7 @@ module Registrations class WelcomeController < ApplicationController include OneTrustCSP include GoogleAnalyticsCSP + include GoogleSyndicationCSP include ::Gitlab::Utils::StrongMemoize layout 'minimal' diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 76b7d30cd51..d8064bbbe82 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -8,6 +8,7 @@ class RegistrationsController < Devise::RegistrationsController include OneTrustCSP include BizibleCSP include GoogleAnalyticsCSP + include GoogleSyndicationCSP include PreferredLanguageSwitcher include Gitlab::Tracking::Helpers::WeakPasswordErrorEvent include SkipsAlreadySignedInMessage diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index a9972cbd885..66ace16400a 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -13,6 +13,7 @@ class SessionsController < Devise::SessionsController include BizibleCSP include VerifiesWithEmail include GoogleAnalyticsCSP + include GoogleSyndicationCSP include PreferredLanguageSwitcher include SkipsAlreadySignedInMessage diff --git a/app/helpers/admin/broadcast_messages_helper.rb b/app/helpers/admin/broadcast_messages_helper.rb new file mode 100644 index 00000000000..e087361d52e --- /dev/null +++ b/app/helpers/admin/broadcast_messages_helper.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +module Admin + module BroadcastMessagesHelper + include Gitlab::Utils::StrongMemoize + + def current_broadcast_banner_messages + System::BroadcastMessage.current_banner_messages( + current_path: request.path, + user_access_level: current_user_access_level_for_project_or_group + ).select do |message| + cookies["hide_broadcast_message_#{message.id}"].blank? + end + end + + def current_broadcast_notification_message + not_hidden_messages = System::BroadcastMessage.current_notification_messages( + current_path: request.path, + user_access_level: current_user_access_level_for_project_or_group + ).select do |message| + cookies["hide_broadcast_message_#{message.id}"].blank? + end + not_hidden_messages.last + end + + def broadcast_message(message, opts = {}) + return unless message.present? + + render "shared/broadcast_message", { message: message, **opts } + end + + def broadcast_message_status(broadcast_message) + if broadcast_message.active? + 'Active' + elsif broadcast_message.ended? + 'Expired' + else + 'Pending' + end + end + + def render_broadcast_message(broadcast_message) + if broadcast_message.notification? + Banzai.render_field_and_post_process(broadcast_message, :message, { + current_user: current_user, + skip_project_check: true, + broadcast_message_placeholders: true + }).html_safe + else + Banzai.render_field(broadcast_message, :message).html_safe + end + end + + def target_access_level_options + System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS.map do |access_level| + [Gitlab::Access.human_access(access_level), access_level] + end + end + + def target_access_levels_display(access_levels) + access_levels.map do |access_level| + Gitlab::Access.human_access(access_level) + end.join(', ') + end + + def admin_broadcast_messages_data(broadcast_messages) + broadcast_messages.map do |message| + { + id: message.id, + status: broadcast_message_status(message), + message: message.message, + theme: message.theme, + broadcast_type: message.broadcast_type, + dismissable: message.dismissable, + starts_at: message.starts_at.iso8601, + ends_at: message.ends_at.iso8601, + target_roles: target_access_levels_display(message.target_access_levels), + target_path: message.target_path, + type: message.broadcast_type.capitalize, + edit_path: edit_admin_broadcast_message_path(message), + delete_path: "#{admin_broadcast_message_path(message)}.js" + } + end.to_json + end + + def broadcast_message_data(broadcast_message) + { + id: broadcast_message.id, + message: broadcast_message.message, + broadcast_type: broadcast_message.broadcast_type, + theme: broadcast_message.theme, + dismissable: broadcast_message.dismissable.to_s, + target_access_levels: broadcast_message.target_access_levels, + messages_path: admin_broadcast_messages_path, + preview_path: preview_admin_broadcast_messages_path, + target_path: broadcast_message.target_path, + starts_at: broadcast_message.starts_at.iso8601, + ends_at: broadcast_message.ends_at.iso8601, + target_access_level_options: target_access_level_options.to_json, + show_in_cli: broadcast_message.show_in_cli.to_s + } + end + + private + + def current_user_access_level_for_project_or_group + return unless current_user.present? + + strong_memoize(:current_user_access_level_for_project_or_group) do + case controller + when Projects::ApplicationController + next unless @project + + @project.team.max_member_access(current_user.id) + when Groups::ApplicationController + next unless @group + + @group.max_member_access_for_user(current_user) + end + end + end + end +end diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb deleted file mode 100644 index f04694a3aa9..00000000000 --- a/app/helpers/broadcast_messages_helper.rb +++ /dev/null @@ -1,121 +0,0 @@ -# frozen_string_literal: true - -module BroadcastMessagesHelper - include Gitlab::Utils::StrongMemoize - - def current_broadcast_banner_messages - System::BroadcastMessage.current_banner_messages( - current_path: request.path, - user_access_level: current_user_access_level_for_project_or_group - ).select do |message| - cookies["hide_broadcast_message_#{message.id}"].blank? - end - end - - def current_broadcast_notification_message - not_hidden_messages = System::BroadcastMessage.current_notification_messages( - current_path: request.path, - user_access_level: current_user_access_level_for_project_or_group - ).select do |message| - cookies["hide_broadcast_message_#{message.id}"].blank? - end - not_hidden_messages.last - end - - def broadcast_message(message, opts = {}) - return unless message.present? - - render "shared/broadcast_message", { message: message, **opts } - end - - def broadcast_message_status(broadcast_message) - if broadcast_message.active? - 'Active' - elsif broadcast_message.ended? - 'Expired' - else - 'Pending' - end - end - - def render_broadcast_message(broadcast_message) - if broadcast_message.notification? - Banzai.render_field_and_post_process(broadcast_message, :message, { - current_user: current_user, - skip_project_check: true, - broadcast_message_placeholders: true - }).html_safe - else - Banzai.render_field(broadcast_message, :message).html_safe - end - end - - def target_access_level_options - System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS.map do |access_level| - [Gitlab::Access.human_access(access_level), access_level] - end - end - - def target_access_levels_display(access_levels) - access_levels.map do |access_level| - Gitlab::Access.human_access(access_level) - end.join(', ') - end - - def admin_broadcast_messages_data(broadcast_messages) - broadcast_messages.map do |message| - { - id: message.id, - status: broadcast_message_status(message), - message: message.message, - theme: message.theme, - broadcast_type: message.broadcast_type, - dismissable: message.dismissable, - starts_at: message.starts_at.iso8601, - ends_at: message.ends_at.iso8601, - target_roles: target_access_levels_display(message.target_access_levels), - target_path: message.target_path, - type: message.broadcast_type.capitalize, - edit_path: edit_admin_broadcast_message_path(message), - delete_path: "#{admin_broadcast_message_path(message)}.js" - } - end.to_json - end - - def broadcast_message_data(broadcast_message) - { - id: broadcast_message.id, - message: broadcast_message.message, - broadcast_type: broadcast_message.broadcast_type, - theme: broadcast_message.theme, - dismissable: broadcast_message.dismissable.to_s, - target_access_levels: broadcast_message.target_access_levels, - messages_path: admin_broadcast_messages_path, - preview_path: preview_admin_broadcast_messages_path, - target_path: broadcast_message.target_path, - starts_at: broadcast_message.starts_at.iso8601, - ends_at: broadcast_message.ends_at.iso8601, - target_access_level_options: target_access_level_options.to_json, - show_in_cli: broadcast_message.show_in_cli.to_s - } - end - - private - - def current_user_access_level_for_project_or_group - return unless current_user.present? - - strong_memoize(:current_user_access_level_for_project_or_group) do - case controller - when Projects::ApplicationController - next unless @project - - @project.team.max_member_access(current_user.id) - when Groups::ApplicationController - next unless @group - - @group.max_member_access_for_user(current_user) - end - end - end -end diff --git a/app/views/import/bulk_imports/history.html.haml b/app/views/import/bulk_imports/history.html.haml index 80eb0c7a764..38196f97030 100644 --- a/app/views/import/bulk_imports/history.html.haml +++ b/app/views/import/bulk_imports/history.html.haml @@ -3,4 +3,4 @@ - add_page_specific_style 'page_bundles/import' - page_title _('Import history') -#import-history-mount-element +#import-history-mount-element{ data: { realtime_changes_path: realtime_changes_import_bulk_imports_path(format: :json) } } diff --git a/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb b/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb index 772388ffc9e..b40914770b5 100644 --- a/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb +++ b/app/workers/concerns/gitlab/github_import/rescheduling_methods.rb @@ -5,10 +5,15 @@ module Gitlab # Module that provides methods shared by the various workers used for # importing GitHub projects. module ReschedulingMethods + extend ActiveSupport::Concern include JobDelayCalculator ENQUEUED_JOB_COUNT = 'github-importer/enqueued_job_count/%{project}/%{collection}' + included do + loggable_arguments 2 + end + # project_id - The ID of the GitLab project to import the note into. # hash - A Hash containing the details of the GitHub object to import. # notify_key - The Redis key to notify upon completion, if any. diff --git a/db/post_migrate/20230701043315_ensure_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb b/db/post_migrate/20230701043315_ensure_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb index 723b074a898..28c520fea6b 100644 --- a/db/post_migrate/20230701043315_ensure_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb +++ b/db/post_migrate/20230701043315_ensure_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb @@ -9,12 +9,7 @@ class EnsureBackfillForCiPipelineVariablesPipelineIdIsFinished < Gitlab::Databas TABLE_NAME = :ci_pipeline_variables def up - ensure_batched_background_migration_is_finished( - job_class_name: 'CopyColumnUsingBackgroundMigrationJob', - table_name: TABLE_NAME, - column_name: 'pipeline_id', - job_arguments: [['pipeline_id'], ['id_convert_to_bigint']] - ) + # no-op end def down diff --git a/db/post_migrate/20230701053315_ensure_again_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb b/db/post_migrate/20230701053315_ensure_again_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb new file mode 100644 index 00000000000..90e8b4d96b3 --- /dev/null +++ b/db/post_migrate/20230701053315_ensure_again_backfill_for_ci_pipeline_variables_pipeline_id_is_finished.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class EnsureAgainBackfillForCiPipelineVariablesPipelineIdIsFinished < Gitlab::Database::Migration[2.1] + include Gitlab::Database::MigrationHelpers::ConvertToBigint + + restrict_gitlab_migration gitlab_schema: :gitlab_ci + disable_ddl_transaction! + + TABLE_NAME = :ci_pipeline_variables + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'CopyColumnUsingBackgroundMigrationJob', + table_name: TABLE_NAME, + column_name: 'id', + job_arguments: [['pipeline_id'], ['pipeline_id_convert_to_bigint']] + ) + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20230727102936_drop_prepared_at_index.rb b/db/post_migrate/20230727102936_drop_prepared_at_index.rb new file mode 100644 index 00000000000..ee128a9d130 --- /dev/null +++ b/db/post_migrate/20230727102936_drop_prepared_at_index.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class DropPreparedAtIndex < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'index_merge_requests_on_id_and_prepared_at' + + # TODO: Issue for sync deletion: https://gitlab.com/gitlab-org/gitlab/-/issues/419917 + + def up + prepare_async_index_removal :merge_requests, :id, name: INDEX_NAME + end + + def down + unprepare_async_index :merge_requests, :id, name: INDEX_NAME + end +end diff --git a/db/post_migrate/20230727103144_add_prepared_at_created_at_index.rb b/db/post_migrate/20230727103144_add_prepared_at_created_at_index.rb new file mode 100644 index 00000000000..44c293bca2a --- /dev/null +++ b/db/post_migrate/20230727103144_add_prepared_at_created_at_index.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddPreparedAtCreatedAtIndex < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INDEX_NAME = 'index_merge_requests_id_created_at_prepared_at' + + # TODO: Issue for sync deletion: https://gitlab.com/gitlab-org/gitlab/-/issues/419918 + + def up + prepare_async_index(:merge_requests, + [:created_at, :id], + name: INDEX_NAME, + where: "prepared_at IS NULL") + end + + def down + unprepare_async_index(:merge_requests, [:created_at, :id], name: INDEX_NAME) + end +end diff --git a/db/schema_migrations/20230701053315 b/db/schema_migrations/20230701053315 new file mode 100644 index 00000000000..f215dd876ec --- /dev/null +++ b/db/schema_migrations/20230701053315 @@ -0,0 +1 @@ +e171f06f9b1a0824cd11216255b1f99976bdd97069a35a8514934d33e5b3e634
\ No newline at end of file diff --git a/db/schema_migrations/20230727102936 b/db/schema_migrations/20230727102936 new file mode 100644 index 00000000000..a07533f29c8 --- /dev/null +++ b/db/schema_migrations/20230727102936 @@ -0,0 +1 @@ +f71b5244de5b3ed97e38414b304df80fd94ce0855a624efd178ffedfa36dfd31
\ No newline at end of file diff --git a/db/schema_migrations/20230727103144 b/db/schema_migrations/20230727103144 new file mode 100644 index 00000000000..1d7b02fd330 --- /dev/null +++ b/db/schema_migrations/20230727103144 @@ -0,0 +1 @@ +09f9ee7ddf7153dbdb151f15a9de245d722a908b67b799da8a80f1bd9cb93c31
\ No newline at end of file diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md index 8efcd3c44c1..45568c700c7 100644 --- a/doc/development/api_styleguide.md +++ b/doc/development/api_styleguide.md @@ -44,7 +44,7 @@ A good example is as follows: ```ruby desc 'Get all broadcast messages' do detail 'This feature was introduced in GitLab 8.12.' - success Entities::BroadcastMessage + success Entities::System::BroadcastMessage end params do optional :page, type: Integer, desc: 'Current page number' @@ -53,7 +53,7 @@ end get do messages = System::BroadcastMessage.all - present paginate(messages), with: Entities::BroadcastMessage + present paginate(messages), with: Entities::System::BroadcastMessage end ``` diff --git a/doc/topics/manage_code.md b/doc/topics/manage_code.md index 5e9160338a9..e8a4267973c 100644 --- a/doc/topics/manage_code.md +++ b/doc/topics/manage_code.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Manage your code **(FREE)** -Store your source files in a repository, create merge requests, and compile code hosted on GitLab. +Store your source files in a repository and create merge requests. Write, debug, and compile code hosted on GitLab. - [Repositories](../user/project/repository/index.md) - [Merge requests](../user/project/merge_requests/index.md) diff --git a/doc/user/application_security/policies/scan-result-policies.md b/doc/user/application_security/policies/scan-result-policies.md index 0aac36988d4..211ea811d29 100644 --- a/doc/user/application_security/policies/scan-result-policies.md +++ b/doc/user/application_security/policies/scan-result-policies.md @@ -77,6 +77,12 @@ the following sections and tables provide an alternative. ## `scan_finding` rule type +> - The scan result policy field `vulnerability_attributes` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123052) in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `enforce_vulnerability_attributes_rules`. Disabled by default. +> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/418784) in GitLab 16.3. + +FLAG: +On self-managed GitLab, by default the `vulnerability_attributes` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `enforce_vulnerability_attributes_rules`. +On GitLab.com, this feature is available. This rule enforces the defined actions based on security scan findings. | Field | Type | Required | Possible values | Description | @@ -88,6 +94,7 @@ This rule enforces the defined actions based on security scan findings. | `vulnerabilities_allowed` | `integer` | true | Greater than or equal to zero | Number of vulnerabilities allowed before this rule is considered. | | `severity_levels` | `array` of `string` | true | `info`, `unknown`, `low`, `medium`, `high`, `critical` | The severity levels for this rule to consider. | | `vulnerability_states` | `array` of `string` | true | `newly_detected`, `detected`, `confirmed`, `resolved`, `dismissed`, `new_needs_triage`, `new_dismissed` | All vulnerabilities fall into two categories:<br><br>**Newly Detected Vulnerabilities** - the `newly_detected` policy option covers vulnerabilities identified in the merge request branch itself but that do not currently exist on the default branch. This policy option requires a pipeline to complete before the rule is evaluated so that it knows whether vulnerabilities are newly detected or not. Merge requests are blocked until the pipeline and necessary security scans are complete. The `newly_detected` option considers both of the following statuses:<br><br> • Detected<br> • Dismissed<br><br> The `new_needs_triage` option considers the status<br><br> • Detected<br><br> The `new_dismissed` option considers the status<br><br> • Dismissed<br><br>**Pre-Existing Vulnerabilities** - these policy options are evaluated immediately and do not require a pipeline complete as they consider only vulnerabilities previously detected in the default branch.<br><br> • `Detected` - the policy looks for vulnerabilities in the detected state.<br> • `Confirmed` - the policy looks for vulnerabilities in the confirmed state.<br> • `Dismissed` - the policy looks for vulnerabilities in the dismissed state.<br> • `Resolved` - the policy looks for vulnerabilities in the resolved state. | +| `vulnerability_attributes` | `object` | false | `{false_positive: boolean, fix_available: boolean}` | All vulnerability findings are considered by default. But filters can be applied for attributes to consider only vulnerability findings: <br><br> • With a fix available (`fix_available: true`)<br><br> • With no fix available (`fix_available: false`)<br> • That are false positive (`false_positive: true`)<br> • That are not false positive (`false_positive: false`)<br> • Or a combination of both. For example (`fix_available: true, false_positive: false`) | ## `license_finding` rule type @@ -150,6 +157,9 @@ scan_result_policy: - critical vulnerability_states: - newly_detected + vulnerability_attributes: + false_positive: true + fix_available: true actions: - type: require_approval approvals_required: 1 diff --git a/doc/user/group/saml_sso/troubleshooting_scim.md b/doc/user/group/saml_sso/troubleshooting_scim.md index e4531882fc1..7d2aa8faa99 100644 --- a/doc/user/group/saml_sso/troubleshooting_scim.md +++ b/doc/user/group/saml_sso/troubleshooting_scim.md @@ -40,41 +40,43 @@ To check if a user's SAML `NameId` matches their SCIM `externalId`: - Administrators can use the Admin Area to [list SCIM identities for a user](../../../administration/admin_area.md#user-identities). - Group owners can see the list of users and the identifier stored for each user in the group SAML SSO Settings page. -- You can use the [SCIM API](../../../api/scim.md) to manually retrieve the `external_uid` GitLab has stored for users and compare the value for each user from the [SAML API](../../../api/saml.md) . -- Have the user use a [SAML Tracer](troubleshooting.md#saml-debugging-tools) and compare the `external_uid` to +- You can use the [SCIM API](../../../api/scim.md) to manually retrieve the `extern_uid` GitLab has stored for users and compare the value for each user from the [SAML API](../../../api/saml.md) . +- Have the user use a [SAML Tracer](troubleshooting.md#saml-debugging-tools) and compare the `extern_uid` to the value returned as the SAML `NameId`. ## Mismatched SCIM `extern_uid` and SAML `NameId` Whether the value was changed or you need to map to a different field, the following must map to the same field: -- `id` - `externalId` - `NameId` -If the GitLab `extern_uid` doesn't match the SAML `NameId`, it must be updated for the user to sign in. Your identity -provider should be configured to do this update. In some cases the identity provider cannot do the update, for example -when a user lookup fails because of an ID change. +If the GitLab `extern_uid` does not match the SAML `NameId`, you must update the GitLab `extern_uid` to enable the user to sign in. -Be cautious if you revise the fields used by your SCIM identity provider, typically `id` and `externalId`. -GitLab uses these IDs to look up users. If the identity provider does not know the current values for these fields, -that provider may create duplicate users. +Be cautious if you revise the fields used by your SCIM identity provider, typically `externalId`. +Your identity provider should be configured to do this update. +In some cases the identity provider cannot do the update, for example when a user lookup fails. -If the `extern_uid` for a user is not correct, and also doesn't match the SAML `NameID`, either: +GitLab uses these IDs to look up users. +If the identity provider does not know the current values for these fields, +that provider may create duplicate users, or fail to complete expected actions. -- Have users unlink and relink themselves, based on the +To change the identifier values to match: + +1. Have users unlink and relink themselves, based on the [SAML authentication failed: User has already been taken](troubleshooting.md#message-saml-authentication-failed-user-has-already-been-taken) section. -- Unlink all users simultaneously by removing all users from the SCIM app while provisioning is turned on. -- Use the [SCIM API](../../../api/scim.md) to manually correct the `extern_uid` stored for users to match the SAML - `NameId`. To look up a user, you must know the desired value that matches the `NameId` as well as the current - `extern_uid`. +1. Unlink all users simultaneously by removing all users from the SCIM app while provisioning is turned on. +1. Use the [SAML API](../../../api/saml.md) or [SCIM API](../../../api/scim.md) to manually correct the `extern_uid` stored for users to match the SAML + `NameId` or SCIM `externalId`. You must not: - Update these to incorrect values because this causes users to be unable to sign in. - Assign a value to the wrong user because this causes users to be signed in to the wrong account. +Additionally, the user's primary email must match the email in your SCIM identity provider. + ## Change SCIM app When the SCIM app changes: diff --git a/doc/user/project/merge_requests/commit_templates.md b/doc/user/project/merge_requests/commit_templates.md index c930c5c6f7f..0da7bb2537f 100644 --- a/doc/user/project/merge_requests/commit_templates.md +++ b/doc/user/project/merge_requests/commit_templates.md @@ -72,6 +72,7 @@ GitLab creates a squash commit message with this template: > - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/26303) `all_commits` variable in GitLab 14.9. > - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/378352) `reviewed_by` variable in GitLab 15.7. > - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/199823) `local_reference` variable in GitLab 16.1. +> - [Added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128553) `source_project_id` variables in GitLab 16.3. Commit message templates support these variables: @@ -84,6 +85,7 @@ Commit message templates support these variables: | `%{description}` | Description of the merge request. | `Merge request description.`<br>`Can be multiline.` | | `%{reference}` | Reference to the merge request. | `group-name/project-name!72359` | | `%{local_reference}` | Local reference to the merge request. | `!72359` | +| `%{source_project_id}` | ID of the merge request's source project. | `123` | | `%{first_commit}` | Full message of the first commit in merge request diff. | `Update README.md` | | `%{first_multiline_commit}` | Full message of the first commit that's not a merge commit and has more than one line in message body. Merge request title if all commits aren't multiline. | `Update README.md`<br><br>`Improved project description in readme file.` | | `%{url}` | Full URL to the merge request. | `https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1` | diff --git a/doc/user/project/remote_development/index.md b/doc/user/project/remote_development/index.md index ccb1745d490..a0020199766 100644 --- a/doc/user/project/remote_development/index.md +++ b/doc/user/project/remote_development/index.md @@ -34,7 +34,7 @@ With remote development, you can use: For a complete IDE experience, connect the Web IDE to a development environment configured to run as a remote host. You can create this environment [inside](../../workspace/index.md) or [outside](connect_machine.md) of GitLab. -## Workspace +## Workspaces **(PREMIUM)** A [workspace](../../workspace/index.md) is a virtual sandbox environment for your code in GitLab that includes: diff --git a/lib/api/admin/broadcast_messages.rb b/lib/api/admin/broadcast_messages.rb new file mode 100644 index 00000000000..f199f3ce842 --- /dev/null +++ b/lib/api/admin/broadcast_messages.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +module API + module Admin + class BroadcastMessages < ::API::Base + include PaginationParams + + feature_category :onboarding + urgency :low + + resource :broadcast_messages do + helpers do + def find_message + System::BroadcastMessage.find(params[:id]) + end + end + + desc 'Get all broadcast messages' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::System::BroadcastMessage + end + params do + use :pagination + end + get do + messages = System::BroadcastMessage.all.order_id_desc + + present paginate(messages), with: Entities::System::BroadcastMessage + end + + desc 'Create a broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::System::BroadcastMessage + end + params do + requires :message, type: String, desc: 'Message to display' + optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now } + optional :ends_at, type: DateTime, desc: 'Ending time', default: -> { 1.hour.from_now } + optional :color, type: String, desc: 'Background color' + optional :font, type: String, desc: 'Foreground color' + optional :target_access_levels, + type: Array[Integer], + coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce, + values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS, + desc: 'Target user roles' + optional :target_path, type: String, desc: 'Target path' + optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, desc: 'Broadcast type. Defaults to banner', default: -> { + 'banner' + } + optional :dismissable, type: Boolean, desc: 'Is dismissable' + end + post do + authenticated_as_admin! + + message = System::BroadcastMessage.create(declared_params(include_missing: false)) + + if message.persisted? + present message, with: Entities::System::BroadcastMessage + else + render_validation_error!(message) + end + end + + desc 'Get a specific broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::System::BroadcastMessage + end + params do + requires :id, type: Integer, desc: 'Broadcast message ID' + end + get ':id' do + message = find_message + + present message, with: Entities::System::BroadcastMessage + end + + desc 'Update a broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::System::BroadcastMessage + end + params do + requires :id, type: Integer, desc: 'Broadcast message ID' + optional :message, type: String, desc: 'Message to display' + optional :starts_at, type: DateTime, desc: 'Starting time' + optional :ends_at, type: DateTime, desc: 'Ending time' + optional :color, type: String, desc: 'Background color' + optional :font, type: String, desc: 'Foreground color' + optional :target_access_levels, + type: Array[Integer], + coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce, + values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS, + desc: 'Target user roles' + optional :target_path, type: String, desc: 'Target path' + optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, + desc: 'Broadcast Type' + optional :dismissable, type: Boolean, desc: 'Is dismissable' + end + put ':id' do + authenticated_as_admin! + + message = find_message + + if message.update(declared_params(include_missing: false)) + present message, with: Entities::System::BroadcastMessage + else + render_validation_error!(message) + end + end + + desc 'Delete a broadcast message' do + detail 'This feature was introduced in GitLab 8.12.' + success Entities::System::BroadcastMessage + end + params do + requires :id, type: Integer, desc: 'Broadcast message ID' + end + delete ':id' do + authenticated_as_admin! + + message = find_message + + destroy_conditionally!(message) + end + end + end + end +end diff --git a/lib/api/api.rb b/lib/api/api.rb index 2506680fe35..7a384d9cf59 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -188,6 +188,7 @@ module API # Keep in alphabetical order mount ::API::AccessRequests mount ::API::Admin::BatchedBackgroundMigrations + mount ::API::Admin::BroadcastMessages mount ::API::Admin::Ci::Variables mount ::API::Admin::Dictionary mount ::API::Admin::InstanceClusters @@ -199,7 +200,6 @@ module API mount ::API::Avatar mount ::API::Badges mount ::API::Branches - mount ::API::BroadcastMessages mount ::API::BulkImports mount ::API::Ci::JobArtifacts mount ::API::Groups diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb deleted file mode 100644 index 15d64e5337c..00000000000 --- a/lib/api/broadcast_messages.rb +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true - -module API - class BroadcastMessages < ::API::Base - include PaginationParams - - feature_category :onboarding - urgency :low - - resource :broadcast_messages do - helpers do - def find_message - System::BroadcastMessage.find(params[:id]) - end - end - - desc 'Get all broadcast messages' do - detail 'This feature was introduced in GitLab 8.12.' - success Entities::BroadcastMessage - end - params do - use :pagination - end - get do - messages = System::BroadcastMessage.all.order_id_desc - - present paginate(messages), with: Entities::BroadcastMessage - end - - desc 'Create a broadcast message' do - detail 'This feature was introduced in GitLab 8.12.' - success Entities::BroadcastMessage - end - params do - requires :message, type: String, desc: 'Message to display' - optional :starts_at, type: DateTime, desc: 'Starting time', default: -> { Time.zone.now } - optional :ends_at, type: DateTime, desc: 'Ending time', default: -> { 1.hour.from_now } - optional :color, type: String, desc: 'Background color' - optional :font, type: String, desc: 'Foreground color' - optional :target_access_levels, - type: Array[Integer], - coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce, - values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS, - desc: 'Target user roles' - optional :target_path, type: String, desc: 'Target path' - optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, desc: 'Broadcast type. Defaults to banner', default: -> { 'banner' } - optional :dismissable, type: Boolean, desc: 'Is dismissable' - end - post do - authenticated_as_admin! - - message = System::BroadcastMessage.create(declared_params(include_missing: false)) - - if message.persisted? - present message, with: Entities::BroadcastMessage - else - render_validation_error!(message) - end - end - - desc 'Get a specific broadcast message' do - detail 'This feature was introduced in GitLab 8.12.' - success Entities::BroadcastMessage - end - params do - requires :id, type: Integer, desc: 'Broadcast message ID' - end - get ':id' do - message = find_message - - present message, with: Entities::BroadcastMessage - end - - desc 'Update a broadcast message' do - detail 'This feature was introduced in GitLab 8.12.' - success Entities::BroadcastMessage - end - params do - requires :id, type: Integer, desc: 'Broadcast message ID' - optional :message, type: String, desc: 'Message to display' - optional :starts_at, type: DateTime, desc: 'Starting time' - optional :ends_at, type: DateTime, desc: 'Ending time' - optional :color, type: String, desc: 'Background color' - optional :font, type: String, desc: 'Foreground color' - optional :target_access_levels, - type: Array[Integer], - coerce_with: Validations::Types::CommaSeparatedToIntegerArray.coerce, - values: System::BroadcastMessage::ALLOWED_TARGET_ACCESS_LEVELS, - desc: 'Target user roles' - optional :target_path, type: String, desc: 'Target path' - optional :broadcast_type, type: String, values: System::BroadcastMessage.broadcast_types.keys, desc: 'Broadcast Type' - optional :dismissable, type: Boolean, desc: 'Is dismissable' - end - put ':id' do - authenticated_as_admin! - - message = find_message - - if message.update(declared_params(include_missing: false)) - present message, with: Entities::BroadcastMessage - else - render_validation_error!(message) - end - end - - desc 'Delete a broadcast message' do - detail 'This feature was introduced in GitLab 8.12.' - success Entities::BroadcastMessage - end - params do - requires :id, type: Integer, desc: 'Broadcast message ID' - end - delete ':id' do - authenticated_as_admin! - - message = find_message - - destroy_conditionally!(message) - end - end - end -end diff --git a/lib/api/entities/broadcast_message.rb b/lib/api/entities/broadcast_message.rb deleted file mode 100644 index 5a31d64fd86..00000000000 --- a/lib/api/entities/broadcast_message.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class BroadcastMessage < Grape::Entity - expose :id, :message, :starts_at, :ends_at, :color, :font, :target_access_levels, :target_path, :broadcast_type, :dismissable - expose :active?, as: :active - end - end -end diff --git a/lib/api/entities/system/broadcast_message.rb b/lib/api/entities/system/broadcast_message.rb new file mode 100644 index 00000000000..9a31095baf1 --- /dev/null +++ b/lib/api/entities/system/broadcast_message.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + module System + class BroadcastMessage < Grape::Entity + expose :id, :message, :starts_at, :ends_at, :color, :font, :target_access_levels, :target_path, + :broadcast_type, :dismissable + expose :active?, as: :active + end + end + end +end diff --git a/lib/gitlab/merge_requests/message_generator.rb b/lib/gitlab/merge_requests/message_generator.rb index 523e0e665dc..5ca26fdae86 100644 --- a/lib/gitlab/merge_requests/message_generator.rb +++ b/lib/gitlab/merge_requests/message_generator.rb @@ -52,6 +52,7 @@ module Gitlab 'description' => ->(merge_request, _, _) { merge_request.description }, 'reference' => ->(merge_request, _, _) { merge_request.to_reference(full: true) }, 'local_reference' => ->(merge_request, _, _) { merge_request.to_reference(full: false) }, + 'source_project_id' => ->(merge_request, _, _) { merge_request.source_project.id.to_s }, 'first_commit' => -> (merge_request, _, _) { return unless merge_request.persisted? || merge_request.compare_commits.present? diff --git a/spec/features/users/google_syndication_csp_spec.rb b/spec/features/users/google_syndication_csp_spec.rb new file mode 100644 index 00000000000..e71539f87c8 --- /dev/null +++ b/spec/features/users/google_syndication_csp_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Google Syndication content security policy', feature_category: :purchase do + include ContentSecurityPolicyHelpers + + let_it_be(:connect_src) { 'https://other-cdn.test' } + + let_it_be(:google_analytics_src) do + 'localhost https://cdn.cookielaw.org https://*.onetrust.com *.google-analytics.com ' \ + '*.analytics.google.com *.googletagmanager.com' + end + + let_it_be(:allowed_src) do + '*.google.com/pagead/landing pagead2.googlesyndication.com/pagead/landing' + end + + let(:extra) { { google_tag_manager_nonce_id: 'google_tag_manager_nonce_id' } } + + let(:csp) do + ActionDispatch::ContentSecurityPolicy.new do |p| + p.connect_src(*connect_src.split) + end + end + + subject { response_headers['Content-Security-Policy'] } + + before do + setup_csp_for_controller(SessionsController, csp, any_time: true) + stub_config(extra: extra) + visit new_user_session_path + end + + context 'when self-hosted' do + context 'when there is no CSP config' do + let(:extra) { {} } + let(:csp) { ActionDispatch::ContentSecurityPolicy.new } + + it { is_expected.to be_blank } + end + + context 'when connect-src CSP config exists' do + it { is_expected.to include("connect-src #{connect_src} #{google_analytics_src}") } + it { is_expected.not_to include(allowed_src) } + end + end + + context 'when SaaS', :saas do + context 'when connect-src CSP config exists' do + it { is_expected.to include("connect-src #{connect_src} #{google_analytics_src} #{allowed_src}") } + end + end +end diff --git a/spec/frontend/ci/runner/admin_runners/provide_spec.js b/spec/frontend/ci/runner/admin_runners/provide_spec.js new file mode 100644 index 00000000000..b24ddabbb66 --- /dev/null +++ b/spec/frontend/ci/runner/admin_runners/provide_spec.js @@ -0,0 +1,34 @@ +import { provide } from '~/ci/runner/admin_runners/provide'; + +import { + onlineContactTimeoutSecs, + staleTimeoutSecs, + runnerInstallHelpPage, +} from 'jest/ci/runner/mock_data'; + +const mockDataset = { + runnerInstallHelpPage, + onlineContactTimeoutSecs, + staleTimeoutSecs, +}; + +describe('admin runners provide', () => { + it('returns provide values', () => { + expect(provide(mockDataset)).toMatchObject({ + runnerInstallHelpPage, + onlineContactTimeoutSecs, + staleTimeoutSecs, + }); + }); + + it('returns only provide values', () => { + const dataset = { + ...mockDataset, + extraEntry: 'ANOTHER_ENTRY', + }; + + expect(provide(dataset)).not.toMatchObject({ + extraEntry: 'ANOTHER_ENTRY', + }); + }); +}); diff --git a/spec/frontend/ci/runner/mock_data.js b/spec/frontend/ci/runner/mock_data.js index 9ff55cb367d..b8eb9f0ba1b 100644 --- a/spec/frontend/ci/runner/mock_data.js +++ b/spec/frontend/ci/runner/mock_data.js @@ -319,6 +319,7 @@ export const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN'; export const mockAuthenticationToken = 'MOCK_AUTHENTICATION_TOKEN'; export const newRunnerPath = '/runners/new'; +export const runnerInstallHelpPage = 'https://docs.example.com/runner/install/'; export { allRunnersData, diff --git a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js index 8a7fc57c409..0037934cbc5 100644 --- a/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js +++ b/spec/frontend/pages/import/bulk_imports/history/components/bulk_imports_history_app_spec.js @@ -2,6 +2,7 @@ import { GlEmptyState, GlLoadingIcon, GlTableLite } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; +import waitForPromises from 'helpers/wait_for_promises'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; @@ -60,10 +61,13 @@ describe('BulkImportsHistoryApp', () => { let wrapper; let mock; + const mockRealtimeChangesPath = '/import/realtime_changes.json'; function createComponent({ shallow = true } = {}) { const mountFn = shallow ? shallowMount : mount; - wrapper = mountFn(BulkImportsHistoryApp); + wrapper = mountFn(BulkImportsHistoryApp, { + provide: { realtimeChangesPath: mockRealtimeChangesPath }, + }); } const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); @@ -220,4 +224,69 @@ describe('BulkImportsHistoryApp', () => { expect(JSON.parse(detailsRowContent.text())).toStrictEqual(DUMMY_RESPONSE[1].failures); }); }); + + describe('status polling', () => { + describe('when there are no isImporting imports', () => { + it('does not start polling', async () => { + createComponent({ shallow: false }); + await waitForPromises(); + + expect(mock.history.get.map((x) => x.url)).toEqual([API_URL]); + }); + }); + + describe('when there are isImporting imports', () => { + const mockCreatedImport = { + id: 3, + bulk_import_id: 3, + status: 'created', + entity_type: 'group', + source_full_path: 'top-level-group-12', + destination_full_path: 'h5bp/top-level-group-12', + destination_name: 'top-level-group-12', + destination_namespace: 'h5bp', + created_at: '2021-07-08T10:03:44.743Z', + failures: [], + }; + const mockImportChanges = [{ id: 3, status_name: 'finished' }]; + const pollInterval = 1; + + beforeEach(async () => { + const RESPONSE = [mockCreatedImport, ...DUMMY_RESPONSE]; + const POLL_HEADERS = { 'poll-interval': pollInterval }; + + mock.onGet(API_URL).reply(HTTP_STATUS_OK, RESPONSE, DEFAULT_HEADERS); + mock.onGet(mockRealtimeChangesPath).replyOnce(HTTP_STATUS_OK, [], POLL_HEADERS); + mock + .onGet(mockRealtimeChangesPath) + .replyOnce(HTTP_STATUS_OK, mockImportChanges, POLL_HEADERS); + + createComponent({ shallow: false }); + + await waitForPromises(); + }); + + it('starts polling for realtime changes', () => { + jest.advanceTimersByTime(pollInterval); + + expect(mock.history.get.map((x) => x.url)).toEqual([API_URL, mockRealtimeChangesPath]); + expect(wrapper.findAll('tbody tr').at(0).text()).toContain('Pending'); + }); + + it('stops polling when import is finished', async () => { + jest.advanceTimersByTime(pollInterval); + await waitForPromises(); + // Wait an extra interval to make sure we've stopped polling + jest.advanceTimersByTime(pollInterval); + await waitForPromises(); + + expect(mock.history.get.map((x) => x.url)).toEqual([ + API_URL, + mockRealtimeChangesPath, + mockRealtimeChangesPath, + ]); + expect(wrapper.findAll('tbody tr').at(0).text()).toContain('Complete'); + }); + }); + }); }); diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/admin/broadcast_messages_helper_spec.rb index 90c000aea57..434b79d5271 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/admin/broadcast_messages_helper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do +RSpec.describe Admin::BroadcastMessagesHelper, feature_category: :onboarding do include Gitlab::Routing.url_helpers let_it_be(:user) { create(:user) } @@ -133,6 +133,36 @@ RSpec.describe BroadcastMessagesHelper, feature_category: :onboarding do end end + describe '#render_broadcast_message' do + context 'when message is banner' do + let_it_be(:broadcast_message) do + System::BroadcastMessage.new(message: 'Current Message', broadcast_type: :banner) + end.freeze + + it 'renders broadcast message' do + expect(helper.render_broadcast_message(broadcast_message)).to eq("<p>Current Message</p>") + end + end + + context 'when message is notification' do + let_it_be(:broadcast_message) do + System::BroadcastMessage.new(message: 'Current Message', broadcast_type: :notification) + end.freeze + + it 'renders broadcast message' do + expect(helper.render_broadcast_message(broadcast_message)).to eq("<p>Current Message</p>") + end + end + end + + describe '#target_access_levels_display' do + let_it_be(:access_levels) { [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER] }.freeze + + it 'joins access levels' do + expect(helper.target_access_levels_display(access_levels)).to eq("Reporter, Developer") + end + end + describe '#admin_broadcast_messages_data' do let(:starts_at) { 1.day.ago } let(:ends_at) { 1.day.from_now } diff --git a/spec/helpers/ci/runners_helper_spec.rb b/spec/helpers/ci/runners_helper_spec.rb index 4f47d7c936f..1f9063181b8 100644 --- a/spec/helpers/ci/runners_helper_spec.rb +++ b/spec/helpers/ci/runners_helper_spec.rb @@ -83,22 +83,9 @@ RSpec.describe Ci::RunnersHelper, feature_category: :runner_fleet do end describe '#admin_runners_data_attributes' do - let_it_be(:admin) { create(:user, :admin) } - let_it_be(:instance_runner) { create(:ci_runner, :instance) } - let_it_be(:project_runner) { create(:ci_runner, :project) } + subject { helper.admin_runners_data_attributes } - before do - allow(helper).to receive(:current_user).and_return(admin) - end - - it 'returns the data in format' do - expect(helper.admin_runners_data_attributes).to include( - runner_install_help_page: 'https://docs.gitlab.com/runner/install/', - registration_token: Gitlab::CurrentSettings.runners_registration_token, - online_contact_timeout_secs: 7200, - stale_timeout_secs: 7889238 - ) - end + it_behaves_like 'admin_runners_data_attributes contains data' end describe '#group_shared_runners_settings_data' do diff --git a/spec/lib/gitlab/merge_requests/message_generator_spec.rb b/spec/lib/gitlab/merge_requests/message_generator_spec.rb index df8804d38d4..b1a8ff26a86 100644 --- a/spec/lib/gitlab/merge_requests/message_generator_spec.rb +++ b/spec/lib/gitlab/merge_requests/message_generator_spec.rb @@ -96,6 +96,26 @@ RSpec.describe Gitlab::MergeRequests::MessageGenerator, feature_category: :code_ end end + context 'when project has commit template with source project id' do + let(:merge_request) do + double( + :merge_request, + title: 'Fixes', + target_project: project, + source_project: project, + to_reference: '!123', + metrics: nil, + merge_user: nil + ) + end + + let(message_template_name) { '%{source_project_id}' } + + it 'evaluates only necessary variables' do + expect(result_message).to eq project.id.to_s + end + end + context 'when project has commit template with closed issues' do let(message_template_name) { <<~MSG.rstrip } Merge branch '%{source_branch}' into '%{target_branch}' diff --git a/spec/requests/api/broadcast_messages_spec.rb b/spec/requests/api/admin/broadcast_messages_spec.rb index d063f290e8d..58347104a7a 100644 --- a/spec/requests/api/broadcast_messages_spec.rb +++ b/spec/requests/api/admin/broadcast_messages_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::BroadcastMessages, :aggregate_failures, feature_category: :onboarding do +RSpec.describe API::Admin::BroadcastMessages, :aggregate_failures, feature_category: :onboarding do let_it_be(:admin) { create(:admin) } let_it_be(:message) { create(:broadcast_message) } let_it_be(:path) { '/broadcast_messages' } @@ -17,7 +17,8 @@ RSpec.describe API::BroadcastMessages, :aggregate_failures, feature_category: :o expect(response).to include_pagination_headers expect(json_response).to be_kind_of(Array) expect(json_response.first.keys) - .to match_array(%w(id message starts_at ends_at color font active target_access_levels target_path broadcast_type dismissable)) + .to match_array(%w[id message starts_at ends_at color font active target_access_levels target_path + broadcast_type dismissable]) end end @@ -30,7 +31,8 @@ RSpec.describe API::BroadcastMessages, :aggregate_failures, feature_category: :o expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq message.id expect(json_response.keys) - .to match_array(%w(id message starts_at ends_at color font active target_access_levels target_path broadcast_type dismissable)) + .to match_array(%w[id message starts_at ends_at color font active target_access_levels target_path + broadcast_type dismissable]) end end @@ -130,6 +132,22 @@ RSpec.describe API::BroadcastMessages, :aggregate_failures, feature_category: :o expect(response).to have_gitlab_http_status(:created) expect(json_response['dismissable']).to eq true end + + context 'when create does not persist record' do + let_it_be(:message) { build(:broadcast_message) }.freeze + let_it_be(:stubbed_errors) { ActiveModel::Errors.new(double).tap { |e| e.add(:base, 'error') } }.freeze + + before do + allow(System::BroadcastMessage).to receive(:create).and_return(message) + allow(message).to receive(:errors).and_return(stubbed_errors) + end + + it 'calls render_validation_error!' do + post api(path, admin, admin_mode: true), params: { message: 'message' } + + expect(response).to have_gitlab_http_status(:bad_request) + end + end end end @@ -222,6 +240,23 @@ RSpec.describe API::BroadcastMessages, :aggregate_failures, feature_category: :o expect(response).to have_gitlab_http_status(:ok) expect(json_response['dismissable']).to eq true end + + context 'when update fails' do + let_it_be(:message) { build(:broadcast_message) }.freeze + let_it_be(:stubbed_errors) { ActiveModel::Errors.new(double).tap { |e| e.add(:base, 'error') } }.freeze + + before do + allow(System::BroadcastMessage).to receive(:find).and_return(message) + allow(message).to receive(:update).and_return(false) + allow(message).to receive(:errors).and_return(stubbed_errors) + end + + it 'calls render_validation_error!' do + put api(path, admin, admin_mode: true), params: { message: 'message' } + + expect(response).to have_gitlab_http_status(:bad_request) + end + end end end diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index 1c4cc05d6e6..f52f843e56a 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -8150,6 +8150,7 @@ - './spec/requests/admin/version_check_controller_spec.rb' - './spec/requests/api/access_requests_spec.rb' - './spec/requests/api/admin/batched_background_migrations_spec.rb' +- './spec/requests/api/admin/broadcast_messages_spec.rb' - './spec/requests/api/admin/ci/variables_spec.rb' - './spec/requests/api/admin/instance_clusters_spec.rb' - './spec/requests/api/admin/plan_limits_spec.rb' @@ -8165,7 +8166,6 @@ - './spec/requests/api/badges_spec.rb' - './spec/requests/api/boards_spec.rb' - './spec/requests/api/branches_spec.rb' -- './spec/requests/api/broadcast_messages_spec.rb' - './spec/requests/api/bulk_imports_spec.rb' - './spec/requests/api/ci/job_artifacts_spec.rb' - './spec/requests/api/ci/jobs_spec.rb' diff --git a/spec/support/shared_examples/helpers/runners_shared_examples.rb b/spec/support/shared_examples/helpers/runners_shared_examples.rb new file mode 100644 index 00000000000..e509f7a65a5 --- /dev/null +++ b/spec/support/shared_examples/helpers/runners_shared_examples.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'admin_runners_data_attributes contains data' do + it 'returns data' do + expect(subject).to include( + runner_install_help_page: 'https://docs.gitlab.com/runner/install/', + registration_token: Gitlab::CurrentSettings.runners_registration_token, + online_contact_timeout_secs: 7200, + stale_timeout_secs: 7889238 + ) + end +end diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb index cdaff2fc1f4..6475be0243c 100644 --- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb @@ -4,7 +4,14 @@ require 'spec_helper' RSpec.describe Gitlab::GithubImport::ReschedulingMethods, feature_category: :importers do let(:worker) do - Class.new { include(Gitlab::GithubImport::ReschedulingMethods) }.new + Class.new do + def self.name + 'MockImportWorker' + end + + include ApplicationWorker + include Gitlab::GithubImport::ReschedulingMethods + end.new end describe '#perform' do |