diff options
57 files changed, 574 insertions, 151 deletions
diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index 7bea16da4e6..4ec5e9c98fd 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -1,5 +1,12 @@ Please view this file on the master branch, on stable branches it's out of date. +## 12.7.1 + +### Fixed (1 change) + +- Fix create/delete API calls for approval rules. !23107 + + ## 12.7.0 ### Removed (2 changes) diff --git a/CHANGELOG.md b/CHANGELOG.md index 896b3a68bad..a147bd438b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,19 @@ entry. ## 12.7.1 -- No changes. +### Fixed (6 changes) + +- Fix loading of sub-epics caused by wrong subscription check. !23184 +- Fix Bitbucket Server importer error handler. !23310 +- Fixes random passwords generated not conforming to minimum_password_length setting. !23387 +- Reverts MR diff redesign which fixes Web IDE visual bugs including file dropdown not showing up. !23428 +- Allow users to sign out on a read-only instance. !23545 +- Remove invalid data from jira_tracker_data table. !23621 + +### Added (1 change) + +- Close Issue when resolving corresponding Sentry error. !22744 + ## 12.7.0 diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index f6b9ea5d30d..5917c96990e 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -34,7 +34,10 @@ export default { projectPath: this.projectPath, }; }, - update: data => data.project.userPermissions, + update: data => data.project?.userPermissions, + error(error) { + throw error; + }, }, }, mixins: [getRefMixin], @@ -172,7 +175,7 @@ export default { ); } - if (this.userPermissions.pushCode) { + if (this.userPermissions?.pushCode) { items.push( { type: ROW_TYPES.divider, diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue index fe1724acf89..573b0c4963e 100644 --- a/app/assets/javascripts/repository/components/last_commit.vue +++ b/app/assets/javascripts/repository/components/last_commit.vue @@ -40,16 +40,19 @@ export default { }; }, update: data => { - const pipelines = data.project.repository.tree.lastCommit.pipelines.edges; + const pipelines = data.project?.repository?.tree?.lastCommit?.pipelines?.edges; return { - ...data.project.repository.tree.lastCommit, - pipeline: pipelines.length && pipelines[0].node, + ...data.project?.repository?.tree?.lastCommit, + pipeline: pipelines?.length && pipelines[0].node, }; }, context: { isSingleRequest: true, }, + error(error) { + throw error; + }, }, }, props: { @@ -62,7 +65,7 @@ export default { data() { return { projectPath: '', - commit: {}, + commit: null, showDescription: false, }; }, @@ -79,6 +82,11 @@ export default { return this.commit.sha.substr(0, 8); }, }, + watch: { + currentPath() { + this.commit = null; + }, + }, methods: { toggleShowDescription() { this.showDescription = !this.showDescription; @@ -91,7 +99,7 @@ export default { <template> <div class="info-well d-none d-sm-flex project-last-commit commit p-3"> <gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" /> - <template v-else> + <template v-else-if="commit"> <user-avatar-link v-if="commit.author" :link-href="commit.author.webUrl" diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue index 92e33b013c3..7b34e9ef60d 100644 --- a/app/assets/javascripts/repository/components/tree_content.vue +++ b/app/assets/javascripts/repository/components/tree_content.vue @@ -86,7 +86,8 @@ export default { }, }) .then(({ data }) => { - if (!data) return; + if (data.errors) throw data.errors; + if (!data?.project?.repository) return; const pageInfo = this.hasNextPage(data.project.repository.tree); @@ -99,12 +100,15 @@ export default { {}, ); - if (pageInfo && pageInfo.hasNextPage) { + if (pageInfo?.hasNextPage) { this.nextPageCursor = pageInfo.endCursor; this.fetchFiles(); } }) - .catch(() => createFlash(__('An error occurred while fetching folder content.'))); + .catch(error => { + createFlash(__('An error occurred while fetching folder content.')); + throw error; + }); }, normalizeData(key, data) { return this.entries[key].concat(data.map(({ node }) => node)); diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index 2ef0c078f13..a26acbbe301 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -23,7 +23,7 @@ export default function setupVueRepositoryList() { projectPath, projectShortPath, ref, - vueFileListLfsBadge: gon?.features?.vueFileListLfsBadge, + vueFileListLfsBadge: gon.features?.vueFileListLfsBadge || false, commits: [], }, }); diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 783c59822f1..9eaa55039c8 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -66,7 +66,7 @@ class Admin::RunnersController < Admin::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def assign_builds_and_projects - @builds = runner.builds.order('id DESC').first(30) + @builds = runner.builds.order('id DESC').preload_project_and_pipeline_project.first(30) @projects = if params[:search].present? ::Project.search(params[:search]) @@ -75,7 +75,8 @@ class Admin::RunnersController < Admin::ApplicationController end @projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any? - @projects = @projects.page(params[:page]).per(30) + @projects = @projects.inc_routes + @projects = @projects.page(params[:page]).per(30).without_count end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb index f16f752f85b..04919a4b9d0 100644 --- a/app/controllers/import/base_controller.rb +++ b/app/controllers/import/base_controller.rb @@ -7,14 +7,14 @@ class Import::BaseController < ApplicationController # rubocop: disable CodeReuse/ActiveRecord def find_already_added_projects(import_type) - current_user.created_projects.where(import_type: import_type).includes(:import_state) + current_user.created_projects.where(import_type: import_type).with_import_state end # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord def find_jobs(import_type) current_user.created_projects - .includes(:import_state) + .with_import_state .where(import_type: import_type) .to_json(only: [:id], methods: [:import_status]) end diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb index dc72a4e4fd9..5fb7b5dccc5 100644 --- a/app/controllers/import/bitbucket_server_controller.rb +++ b/app/controllers/import/bitbucket_server_controller.rb @@ -82,7 +82,7 @@ class Import::BitbucketServerController < Import::BaseController # rubocop: disable CodeReuse/ActiveRecord def filter_added_projects(import_type, import_sources) - current_user.created_projects.where(import_type: import_type, import_source: import_sources).includes(:import_state) + current_user.created_projects.where(import_type: import_type, import_source: import_sources).with_import_state end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/import/manifest_controller.rb b/app/controllers/import/manifest_controller.rb index 7ba8b3ce938..9aec870c6ea 100644 --- a/app/controllers/import/manifest_controller.rb +++ b/app/controllers/import/manifest_controller.rb @@ -87,7 +87,7 @@ class Import::ManifestController < Import::BaseController group.all_projects .where(import_type: 'manifest') .where(creator_id: current_user) - .includes(:import_state) + .with_import_state end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/contributed_projects_finder.rb b/app/finders/contributed_projects_finder.rb index f8c7f0c3167..a351d30229e 100644 --- a/app/finders/contributed_projects_finder.rb +++ b/app/finders/contributed_projects_finder.rb @@ -12,16 +12,14 @@ class ContributedProjectsFinder < UnionFinder # visible by this user. # # Returns an ActiveRecord::Relation. - # rubocop: disable CodeReuse/ActiveRecord def execute(current_user = nil) # Do not show contributed projects if the user profile is private. return Project.none unless can_read_profile?(current_user) segments = all_projects(current_user) - find_union(segments, Project).includes(:namespace).order_id_desc + find_union(segments, Project).with_namespace.order_id_desc end - # rubocop: enable CodeReuse/ActiveRecord private diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb index 20f5b221a89..e7094d73905 100644 --- a/app/finders/personal_projects_finder.rb +++ b/app/finders/personal_projects_finder.rb @@ -17,15 +17,13 @@ class PersonalProjectsFinder < UnionFinder # min_access_level: integer # # Returns an ActiveRecord::Relation. - # rubocop: disable CodeReuse/ActiveRecord def execute(current_user = nil) return Project.none unless can?(current_user, :read_user_profile, @user) segments = all_projects(current_user) - find_union(segments, Project).includes(:namespace).order_updated_desc + find_union(segments, Project).with_namespace.order_updated_desc end - # rubocop: enable CodeReuse/ActiveRecord private diff --git a/app/helpers/analytics_navbar_helper.rb b/app/helpers/analytics_navbar_helper.rb new file mode 100644 index 00000000000..d9aadfaeb15 --- /dev/null +++ b/app/helpers/analytics_navbar_helper.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module AnalyticsNavbarHelper + class NavbarSubItem + attr_reader :title, :path, :link, :link_to_options + + def initialize(title:, path:, link:, link_to_options: {}) + @title = title + @path = path + @link = link + @link_to_options = link_to_options.merge(title: title) + end + end + + def project_analytics_navbar_links(project, current_user) + [ + cycle_analytics_navbar_link(project, current_user), + repository_analytics_navbar_link(project, current_user), + ci_cd_analytics_navbar_link(project, current_user) + ].compact + end + + private + + def navbar_sub_item(args) + NavbarSubItem.new(args) + end + + def cycle_analytics_navbar_link(project, current_user) + return unless Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project) + return unless project_nav_tab?(:cycle_analytics) + + navbar_sub_item( + title: _('Cycle Analytics'), + path: 'cycle_analytics#show', + link: project_cycle_analytics_path(project), + link_to_options: { class: 'shortcuts-project-cycle-analytics' } + ) + end + + def repository_analytics_navbar_link(project, current_user) + return if Feature.disabled?(:analytics_pages_under_project_analytics_sidebar, project) + return if project.empty_repo? + + navbar_sub_item( + title: _('Repository Analytics'), + path: 'graphs#charts', + link: charts_project_graph_path(project, current_ref), + link_to_options: { class: 'shortcuts-repository-charts' } + ) + end + + def ci_cd_analytics_navbar_link(project, current_user) + return unless Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project) + return unless project_nav_tab?(:pipelines) + return unless project.feature_available?(:builds, current_user) || !project.empty_repo? + + navbar_sub_item( + title: _('CI / CD Analytics'), + path: 'pipelines#charts', + link: charts_project_pipelines_path(project) + ) + end +end + +AnalyticsNavbarHelper.prepend_if_ee('EE::AnalyticsNavbarHelper') diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 339d68871ae..3000cfb678e 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -403,6 +403,10 @@ module ProjectsHelper nav_tabs << :operations end + if can?(current_user, :read_cycle_analytics, project) + nav_tabs << :cycle_analytics + end + tab_ability_map.each do |tab, ability| if can?(current_user, ability, project) nav_tabs << tab @@ -643,7 +647,6 @@ module ProjectsHelper projects#show projects#activity releases#index - cycle_analytics#show ] end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 58edb327be0..0f156003a01 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -12,6 +12,7 @@ module TabHelper # :action - One or more action names to check (optional). # :path - A shorthand path, such as 'dashboard#index', to check (optional). # :html_options - Extra options to be passed to the list element (optional). + # :unless - Callable object to skip rendering the 'active' class on `li` element (optional). # block - An optional block that will become the contents of the returned # `li` element. # @@ -56,6 +57,14 @@ module TabHelper # nav_link(path: 'admin/appearances#show') { "Hello"} # # => '<li class="active">Hello</li>' # + # # Shorthand path + unless + # # Add `active` class when TreeController is requested, except the `index` action. + # nav_link(controller: 'tree', unless: -> { action_name?('index') }) { "Hello" } + # # => '<li class="active">Hello</li>' + # + # # When `TreeController#index` is requested + # # => '<li>Hello</li>' + # # Returns a list item element String def nav_link(options = {}, &block) klass = active_nav_link?(options) ? 'active' : '' @@ -73,6 +82,8 @@ module TabHelper end def active_nav_link?(options) + return false if options[:unless]&.call + if path = options.delete(:path) unless path.respond_to?(:each) path = [path] diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 369a793f3d5..d1edf3e9c03 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -172,6 +172,9 @@ module Ci scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) } scope :order_id_desc, -> { order('ci_builds.id DESC') } + PROJECT_ROUTE_AND_NAMESPACE_ROUTE = { project: [:project_feature, :route, { namespace: :route }] }.freeze + scope :preload_project_and_pipeline_project, -> { preload(PROJECT_ROUTE_AND_NAMESPACE_ROUTE, pipeline: PROJECT_ROUTE_AND_NAMESPACE_ROUTE) } + acts_as_taggable add_authentication_token_field :token, encrypted: :optional diff --git a/app/models/project.rb b/app/models/project.rb index 236111cba94..cd191589351 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -411,6 +411,8 @@ class Project < ApplicationRecord scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :inc_routes, -> { includes(:route, namespace: :route) } scope :with_statistics, -> { includes(:statistics) } + scope :with_namespace, -> { includes(:namespace) } + scope :with_import_state, -> { includes(:import_state) } scope :with_service, ->(service) { joins(service).eager_load(service) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) } scope :with_container_registry, -> { where(container_registry_enabled: true) } diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 033c80fd8ed..8a0f44b4e93 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -100,9 +100,7 @@ module SystemNoteService end def close_after_error_tracking_resolve(issue, project, author) - body = _('resolved the corresponding error and closed the issue.') - - create_note(NoteSummary.new(issue, project, author, body, action: 'closed')) + ::SystemNotes::IssuablesService.new(noteable: issue, project: project, author: author).close_after_error_tracking_resolve end def change_status(noteable, project, author, status, source = nil) @@ -243,23 +241,6 @@ module SystemNoteService def zoom_link_removed(issue, project, author) ::SystemNotes::ZoomService.new(noteable: issue, project: project, author: author).zoom_link_removed end - - private - - def create_note(note_summary) - note = Note.create(note_summary.note.merge(system: true)) - note.system_note_metadata = SystemNoteMetadata.new(note_summary.metadata) if note_summary.metadata? - - note - end - - def url_helpers - @url_helpers ||= Gitlab::Routing.url_helpers - end - - def content_tag(*args) - ActionController::Base.helpers.content_tag(*args) - end end SystemNoteService.prepend_if_ee('EE::SystemNoteService') diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb index 6fffd2ed4bf..d7787dac4b8 100644 --- a/app/services/system_notes/issuables_service.rb +++ b/app/services/system_notes/issuables_service.rb @@ -282,6 +282,12 @@ module SystemNotes create_note(NoteSummary.new(noteable, project, author, body, action: action)) end + def close_after_error_tracking_resolve + body = _('resolved the corresponding error and closed the issue.') + + create_note(NoteSummary.new(noteable, project, author, body, action: 'closed')) + end + private def cross_reference_note_content(gfm_reference) diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 62be38e9dd2..f860b7a61a2 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -48,7 +48,7 @@ = project.full_name %td .float-right - = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-sm' + = link_to 'Disable', admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete, class: 'btn btn-danger btn-sm' %table.table.unassigned-projects %thead @@ -70,10 +70,10 @@ = project.full_name %td .float-right - = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f| + = form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post do |f| = f.hidden_field :runner_id, value: @runner.id = f.submit 'Enable', class: 'btn btn-sm' - = paginate @projects, theme: "gitlab" + = paginate_without_count @projects .col-md-6 %h4 Recent jobs served by this Runner diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 3464cc1ea07..3096b2e43fe 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -1,3 +1,5 @@ +- should_display_analytics_pages_in_sidebar = Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, @project) + .nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) } .nav-sidebar-inner-scroll - can_edit = can?(current_user, :admin_project, @project) @@ -8,7 +10,9 @@ .sidebar-context-title = @project.name %ul.sidebar-top-level-items - = nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do + - paths = sidebar_projects_paths + - paths << 'cycle_analytics#show' unless should_display_analytics_pages_in_sidebar + = nav_link(path: paths, html_options: { class: 'home' }) do = link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do .nav-icon-container = sprite_icon('home') @@ -34,15 +38,18 @@ = link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do %span= _('Releases') - - if can?(current_user, :read_cycle_analytics, @project) - = nav_link(path: 'cycle_analytics#show') do - = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do - %span= _('Cycle Analytics') - = render_if_exists 'layouts/nav/project_insights_link' + - unless should_display_analytics_pages_in_sidebar + - if can?(current_user, :read_cycle_analytics, @project) + = nav_link(path: 'cycle_analytics#show') do + = link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do + %span= _('Cycle Analytics') + + = render_if_exists 'layouts/nav/project_insights_link' + - if project_nav_tab? :files - = nav_link(controller: sidebar_repository_paths) do + = nav_link(controller: sidebar_repository_paths, unless: -> { should_display_analytics_pages_in_sidebar && current_path?('projects/graphs#charts') }) do = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do .nav-icon-container = sprite_icon('doc-text') @@ -83,9 +90,10 @@ = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do = _('Compare') - = nav_link(path: 'graphs#charts') do - = link_to charts_project_graph_path(@project, current_ref) do - = _('Charts') + - unless should_display_analytics_pages_in_sidebar + = nav_link(path: 'graphs#charts') do + = link_to charts_project_graph_path(@project, current_ref) do + = _('Charts') = render_if_exists 'projects/sidebar/repository_locked_files' @@ -170,7 +178,7 @@ = number_with_delimiter(@project.open_merge_requests_count) - if project_nav_tab? :pipelines - = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do + = nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], unless: -> { should_display_analytics_pages_in_sidebar && current_path?('projects/pipelines#charts') }) do = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines', data: { qa_selector: 'ci_cd_link' } do .nav-icon-container = sprite_icon('rocket') @@ -201,13 +209,13 @@ %span = _('Artifacts') - - if project_nav_tab? :pipelines + - if !should_display_analytics_pages_in_sidebar && project_nav_tab?(:pipelines) = nav_link(controller: :pipeline_schedules) do = link_to pipeline_schedules_path(@project), title: _('Schedules'), class: 'shortcuts-builds' do %span = _('Schedules') - - if @project.feature_available?(:builds, current_user) && !@project.empty_repo? + - if !should_display_analytics_pages_in_sidebar && @project.feature_available?(:builds, current_user) && !@project.empty_repo? = nav_link(path: 'pipelines#charts') do = link_to charts_project_pipelines_path(@project), title: _('Charts'), class: 'shortcuts-pipelines-charts' do %span @@ -290,7 +298,7 @@ = render_if_exists 'layouts/nav/sidebar/project_packages_link' - = render_if_exists 'layouts/nav/sidebar/project_analytics_link' # EE-specific + = render 'layouts/nav/sidebar/project_analytics_link' - if project_nav_tab? :wiki - wiki_url = project_wiki_path(@project, :home) @@ -410,11 +418,12 @@ = link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do = _('Graph') - -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") - - unless @project.empty_repo? - %li.hidden - = link_to charts_project_graph_path(@project, current_ref), title: _('Charts'), class: 'shortcuts-repository-charts' do - = _('Charts') + - unless should_display_analytics_pages_in_sidebar + -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") + - unless @project.empty_repo? + %li.hidden + = link_to charts_project_graph_path(@project, current_ref), title: _('Charts'), class: 'shortcuts-repository-charts' do + = _('Charts') -# Shortcut to Issues > New Issue - if project_nav_tab?(:issues) diff --git a/app/views/layouts/nav/sidebar/_project_analytics_link.html.haml b/app/views/layouts/nav/sidebar/_project_analytics_link.html.haml new file mode 100644 index 00000000000..3eb8c024588 --- /dev/null +++ b/app/views/layouts/nav/sidebar/_project_analytics_link.html.haml @@ -0,0 +1,16 @@ +- navbar_sub_item = project_analytics_navbar_links(@project, current_user).sort_by(&:title) +- all_paths = navbar_sub_item.map(&:path) + +- if navbar_sub_item.any? + = nav_link(path: all_paths) do + = link_to navbar_sub_item.first.link, data: { qa_selector: 'project_analytics_link' } do + .nav-icon-container + = sprite_icon('chart') + %span.nav-item-name + = _('Analytics') + + %ul.sidebar-sub-level-items + - navbar_sub_item.each do |menu_item| + = nav_link(path: menu_item.path) do + = link_to(menu_item.link, menu_item.link_to_options) do + %span= menu_item.title diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb index 62748808ff1..733156ab758 100644 --- a/app/workers/concerns/application_worker.rb +++ b/app/workers/concerns/application_worker.rb @@ -9,6 +9,7 @@ module ApplicationWorker include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker include WorkerAttributes + include WorkerContext included do set_queue diff --git a/app/workers/concerns/cronjob_queue.rb b/app/workers/concerns/cronjob_queue.rb index 0683b229381..25ee4539cab 100644 --- a/app/workers/concerns/cronjob_queue.rb +++ b/app/workers/concerns/cronjob_queue.rb @@ -8,5 +8,6 @@ module CronjobQueue included do queue_namespace :cronjob sidekiq_options retry: false + worker_context project: nil, namespace: nil, user: nil end end diff --git a/app/workers/concerns/worker_context.rb b/app/workers/concerns/worker_context.rb new file mode 100644 index 00000000000..d85565e3446 --- /dev/null +++ b/app/workers/concerns/worker_context.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module WorkerContext + extend ActiveSupport::Concern + + class_methods do + def worker_context(attributes) + @worker_context = Gitlab::ApplicationContext.new(attributes) + end + + def get_worker_context + @worker_context || superclass_context + end + + private + + def superclass_context + return unless superclass.include?(WorkerContext) + + superclass.get_worker_context + end + end + + def with_context(context, &block) + Gitlab::ApplicationContext.new(context).use(&block) + end +end diff --git a/changelogs/unreleased/197925-setting-minimum-password-length-breaks-sign-up-with-saml.yml b/changelogs/unreleased/197925-setting-minimum-password-length-breaks-sign-up-with-saml.yml deleted file mode 100644 index 0530fd88749..00000000000 --- a/changelogs/unreleased/197925-setting-minimum-password-length-breaks-sign-up-with-saml.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes random passwords generated not conforming to minimum_password_length setting -merge_request: 23387 -author: -type: fixed diff --git a/changelogs/unreleased/198030-fix-wrong-data.yml b/changelogs/unreleased/198030-fix-wrong-data.yml deleted file mode 100644 index d18203cdfef..00000000000 --- a/changelogs/unreleased/198030-fix-wrong-data.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove invalid data from jira_tracker_data table -merge_request: 23621 -author: -type: fixed diff --git a/changelogs/unreleased/198289-unable-to-sign-out-from-secondary-geo-node.yml b/changelogs/unreleased/198289-unable-to-sign-out-from-secondary-geo-node.yml deleted file mode 100644 index ac8db19b2c4..00000000000 --- a/changelogs/unreleased/198289-unable-to-sign-out-from-secondary-geo-node.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow users to sign out on a read-only instance -merge_request: 23545 -author: -type: fixed diff --git a/changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-api.yml b/changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-api.yml new file mode 100644 index 00000000000..769d1192f6d --- /dev/null +++ b/changelogs/unreleased/33257-prevent-accidental-deletions-via-soft-delete-for-groups-api.yml @@ -0,0 +1,5 @@ +--- +title: Add API endpoints for 'soft-delete for groups' feature +merge_request: 19430 +author: +type: added diff --git a/changelogs/unreleased/39825-close-issue-after-error-resolve.yml b/changelogs/unreleased/39825-close-issue-after-error-resolve.yml deleted file mode 100644 index 9a59c812290..00000000000 --- a/changelogs/unreleased/39825-close-issue-after-error-resolve.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Close Issue when resolving corresponding Sentry error -merge_request: 22744 -author: -type: added diff --git a/changelogs/unreleased/jprovazn-fix-group-preloader.yml b/changelogs/unreleased/jprovazn-fix-group-preloader.yml deleted file mode 100644 index b2211fb81af..00000000000 --- a/changelogs/unreleased/jprovazn-fix-group-preloader.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix loading of sub-epics caused by wrong subscription check. -merge_request: 23184 -author: -type: fixed diff --git a/changelogs/unreleased/revert-22364.yml b/changelogs/unreleased/revert-22364.yml deleted file mode 100644 index 2b2d066adab..00000000000 --- a/changelogs/unreleased/revert-22364.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Reverts MR diff redesign which fixes Web IDE visual bugs including file dropdown - not showing up -merge_request: 23428 -author: -type: fixed diff --git a/changelogs/unreleased/sh-add-missing-backtrace-define.yml b/changelogs/unreleased/sh-add-missing-backtrace-define.yml deleted file mode 100644 index fd07321d012..00000000000 --- a/changelogs/unreleased/sh-add-missing-backtrace-define.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Bitbucket Server importer error handler -merge_request: 23310 -author: -type: fixed diff --git a/changelogs/unreleased/sh-optimize-runners-admin-show.yml b/changelogs/unreleased/sh-optimize-runners-admin-show.yml new file mode 100644 index 00000000000..2222f622a6f --- /dev/null +++ b/changelogs/unreleased/sh-optimize-runners-admin-show.yml @@ -0,0 +1,5 @@ +--- +title: Optimize page loading of Admin::RunnersController#show +merge_request: 23309 +author: +type: performance diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index 7d3be9e1bd3..6366ed21865 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -105,6 +105,7 @@ recorded: - Ask for password reset - Grant OAuth access - Started/stopped user impersonation +- Changed username It is possible to filter particular actions by choosing an audit data type from the filter dropdown box. You can further filter by specific group, project or user diff --git a/doc/api/groups.md b/doc/api/groups.md index de8490fa1f4..156dda52ab0 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -628,7 +628,12 @@ Feature.disable(:limit_projects_in_groups_api) ## Remove group -Removes group with all projects inside. Only available to group owners and administrators. +Only available to group owners and administrators. + +This endpoint either: + +- Removes group, and queues a background job to delete all projects in the group as well. +- Since GitLab 12.8, on [Premium](https://about.gitlab.com/pricing/premium/) or higher tiers, marks a group for deletion. The deletion will happen 7 days later by default, but this can be changed in the [instance settings](../user/admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only). ``` DELETE /groups/:id @@ -636,10 +641,27 @@ DELETE /groups/:id Parameters: -- `id` (required) - The ID or path of a user group +| Attribute | Type | Required | Description | +| --------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) | + +The response will be `202 Accepted` if the user has authorization. + +## Restore group marked for deletion **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/33257) in GitLab 12.8. + +Restores a group marked for deletion. + +```plaintext +POST /groups/:id/restore +``` + +Parameters: -This will queue a background job to delete all projects in the group. The -response will be a 202 Accepted if the user has authorization. +| Attribute | Type | Required | Description | +| --------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) | ## Search for group diff --git a/doc/api/projects.md b/doc/api/projects.md index c950ec9384d..6467f2626de 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1769,7 +1769,7 @@ This endpoint either: - Removes a project including all associated resources (issues, merge requests etc). - From GitLab 12.6 on Premium or higher tiers, marks a project for deletion. Actual deletion happens after number of days specified in - [instance settings](../user/admin_area/settings/visibility_and_access_controls.md#project-deletion-adjourned-period-premium-only). + [instance settings](../user/admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only). ``` DELETE /projects/:id @@ -1781,6 +1781,8 @@ DELETE /projects/:id ## Restore project marked for deletion **(PREMIUM)** +> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32935) in GitLab 12.6. + Restores project marked for deletion. ``` diff --git a/doc/api/settings.md b/doc/api/settings.md index 316e5bb0109..ed07bbc575d 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -294,7 +294,7 @@ are listed in the descriptions of the relevant settings. | `plantuml_enabled` | boolean | no | (**If enabled, requires:** `plantuml_url`) Enable PlantUML integration. Default is `false`. | | `plantuml_url` | string | required by: `plantuml_enabled` | The PlantUML instance URL for integration. | | `polling_interval_multiplier` | decimal | no | Interval multiplier used by endpoints that perform polling. Set to `0` to disable polling. | -| `deletion_adjourned_period` | integer | no | **(PREMIUM ONLY)** How many days after marking project for deletion it is actually removed. Value between 0 and 90. +| `deletion_adjourned_period` | integer | no | **(PREMIUM ONLY)** The number of days to wait before removing a project or group that is marked for deletion. Value must be between 0 and 90. | `project_export_enabled` | boolean | no | Enable project export. | | `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. | | `protected_ci_variables` | boolean | no | Environment variables are protected by default. | diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index b5d708b5e04..6a02f15ff43 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -47,11 +47,13 @@ To ensure only admin users can delete projects: 1. Check the **Default project deletion protection** checkbox. 1. Click **Save changes**. -## Project deletion adjourned period **(PREMIUM ONLY)** +## Default deletion adjourned period **(PREMIUM ONLY)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32935) in GitLab 12.6. -By default, project marked for deletion will be permanently removed after 7 days. This period may be changed. +By default, a project or group marked for removal will be permanently removed after 7 days. +This period may be changed, and setting this period to 0 will enable immediate removal +of projects or groups. To change this period: diff --git a/doc/user/project/operations/error_tracking.md b/doc/user/project/operations/error_tracking.md index 3fcf6e39ee0..47b153ddd7e 100644 --- a/doc/user/project/operations/error_tracking.md +++ b/doc/user/project/operations/error_tracking.md @@ -57,11 +57,11 @@ This page has: - Other details about the issue, including a full stack trace. - In [GitLab 12.7 and newer](https://gitlab.com/gitlab-org/gitlab/issues/36246), language and urgency are displayed. -By default, a **Create issue** button is displayed. Once you have used it to create an issue, the button is hidden. +By default, a **Create issue** button is displayed: ![Error Details without Issue Link](img/error_details_v12_7.png) -If a link does exist, it will be shown in the details and the 'Create issue' button will change to a 'View issue' button: +If you create a GitLab issue from the error, the **Create issue** button will change to a **View issue** button: ![Error Details with Issue Link](img/error_details_with_issue_v12_7.png) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 52fa3f8a68e..d375c35e8c0 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -92,6 +92,15 @@ module API present paginate(groups), options end + + def delete_group(group) + Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/46285') + destroy_conditionally!(group) do |group| + ::Groups::DestroyService.new(group, current_user).async_execute + end + + accepted! + end end resource :groups do @@ -187,12 +196,7 @@ module API group = find_group!(params[:id]) authorize! :admin_group, group - Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/46285') - destroy_conditionally!(group) do |group| - ::Groups::DestroyService.new(group, current_user).async_execute - end - - accepted! + delete_group(group) end desc 'Get a list of projects in this group.' do diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb index 5a9a99423c4..b950bfb0f3a 100644 --- a/lib/gitlab/application_context.rb +++ b/lib/gitlab/application_context.rb @@ -16,7 +16,7 @@ module Gitlab def self.with_context(args, &block) application_context = new(**args) - Labkit::Context.with_context(application_context.to_lazy_hash, &block) + application_context.use(&block) end def self.push(args) @@ -42,6 +42,10 @@ module Gitlab end end + def use + Labkit::Context.with_context(to_lazy_hash) { yield } + end + private attr_reader :set_values diff --git a/lib/gitlab/sidekiq_middleware.rb b/lib/gitlab/sidekiq_middleware.rb index 3dda244233f..b19853a1702 100644 --- a/lib/gitlab/sidekiq_middleware.rb +++ b/lib/gitlab/sidekiq_middleware.rb @@ -18,6 +18,7 @@ module Gitlab chain.add Labkit::Middleware::Sidekiq::Server chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger chain.add Gitlab::SidekiqStatus::ServerMiddleware + chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server end end diff --git a/lib/gitlab/sidekiq_middleware/worker_context/server.rb b/lib/gitlab/sidekiq_middleware/worker_context/server.rb new file mode 100644 index 00000000000..29d98ad16a9 --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/worker_context/server.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module SidekiqMiddleware + module WorkerContext + class Server + def call(worker, job, _queue, &block) + worker_class = worker.class + + # This is not a worker we know about, perhaps from a gem + return yield unless worker_class.respond_to?(:get_worker_context) + + # Use the context defined on the class level as a base context + wrap_in_optional_context(worker_class.get_worker_context, &block) + end + + private + + def wrap_in_optional_context(context, &block) + return yield unless context + + context.use(&block) + end + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9a840aa11bc..c51b29d24dc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3014,6 +3014,9 @@ msgstr "" msgid "CI / CD" msgstr "" +msgid "CI / CD Analytics" +msgstr "" + msgid "CI / CD Charts" msgstr "" @@ -14310,9 +14313,6 @@ msgstr "" msgid "Project '%{project_name}' will be deleted on %{date}" msgstr "" -msgid "Project Analytics" -msgstr "" - msgid "Project Badges" msgstr "" @@ -15769,6 +15769,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository Analytics" +msgstr "" + msgid "Repository Graph" msgstr "" diff --git a/spec/controllers/admin/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb index bbeda7dae0f..a1d346d088d 100644 --- a/spec/controllers/admin/runners_controller_spec.rb +++ b/spec/controllers/admin/runners_controller_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Admin::RunnersController do - let!(:runner) { create(:ci_runner) } + let_it_be(:runner) { create(:ci_runner) } before do sign_in(create(:admin)) @@ -36,6 +36,16 @@ describe Admin::RunnersController do end describe '#show' do + render_views + + let_it_be(:project) { create(:project) } + let_it_be(:project_two) { create(:project) } + + before_all do + create(:ci_build, runner: runner, project: project) + create(:ci_build, runner: runner, project: project_two) + end + it 'shows a particular runner' do get :show, params: { id: runner.id } @@ -47,6 +57,21 @@ describe Admin::RunnersController do expect(response).to have_gitlab_http_status(404) end + + it 'avoids N+1 queries', :request_store do + get :show, params: { id: runner.id } + + control_count = ActiveRecord::QueryRecorder.new { get :show, params: { id: runner.id } }.count + + new_project = create(:project) + create(:ci_build, runner: runner, project: new_project) + + # There is one additional query looking up subject.group in ProjectPolicy for the + # needs_new_sso_session permission + expect { get :show, params: { id: runner.id } }.not_to exceed_query_limit(control_count + 1) + + expect(response).to have_gitlab_http_status(200) + end end describe '#update' do diff --git a/spec/features/projects/actve_tabs_spec.rb b/spec/features/projects/active_tabs_spec.rb index 56f587f23ee..41c0e583815 100644 --- a/spec/features/projects/actve_tabs_spec.rb +++ b/spec/features/projects/active_tabs_spec.rb @@ -7,6 +7,8 @@ describe 'Project active tab' do let(:project) { create(:project, :repository) } before do + stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project }) + project.add_maintainer(user) sign_in(user) end @@ -17,21 +19,6 @@ describe 'Project active tab' do end end - shared_examples 'page has active tab' do |title| - it "activates #{title} tab" do - expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) - expect(find('.sidebar-top-level-items > li.active')).to have_content(title) - end - end - - shared_examples 'page has active sub tab' do |title| - it "activates #{title} sub tab" do - expect(page).to have_selector('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)', count: 1) - expect(find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)')) - .to have_content(title) - end - end - context 'on project Home' do before do visit project_path(project) @@ -136,4 +123,35 @@ describe 'Project active tab' do it_behaves_like 'page has active sub tab', 'Repository' end end + + context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do + before do + stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project }) + end + + context 'on project Analytics' do + before do + visit charts_project_graph_path(project, 'master') + end + + context 'on project Analytics/Repository Analytics' do + it_behaves_like 'page has active tab', _('Analytics') + it_behaves_like 'page has active sub tab', _('Repository Analytics') + end + + context 'on project Analytics/Repository Analytics' do + it_behaves_like 'page has active tab', _('Analytics') + it_behaves_like 'page has active sub tab', _('Repository Analytics') + end + + context 'on project Analytics/Cycle Analytics' do + before do + click_tab(_('CI / CD Analytics')) + end + + it_behaves_like 'page has active tab', _('Analytics') + it_behaves_like 'page has active sub tab', _('CI / CD Analytics') + end + end + end end diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb index ff24730acef..c6efe1f1896 100644 --- a/spec/features/projects/user_uses_shortcuts_spec.rb +++ b/spec/features/projects/user_uses_shortcuts_spec.rb @@ -7,6 +7,8 @@ describe 'User uses shortcuts', :js do let(:user) { create(:user) } before do + stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project }) + project.add_maintainer(user) sign_in(user) @@ -156,4 +158,18 @@ describe 'User uses shortcuts', :js do expect(page).to have_active_navigation('Wiki') end end + + context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do + before do + stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project }) + end + + it 'redirects to the repository charts page' do + find('body').native.send_key('g') + find('body').native.send_key('d') + + expect(page).to have_active_navigation(_('Analytics')) + expect(page).to have_active_sub_navigation(_('Repository Analytics')) + end + end end diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb index 1fd400294b7..c6bc3c945a8 100644 --- a/spec/lib/gitlab/application_context_spec.rb +++ b/spec/lib/gitlab/application_context_spec.rb @@ -79,4 +79,18 @@ describe Gitlab::ApplicationContext do .to include(project: project.full_path, root_namespace: project.full_path_components.first) end end + + describe '#use' do + let(:context) { described_class.new(user: build(:user)) } + + it 'yields control' do + expect { |b| context.use(&b) }.to yield_control + end + + it 'passes the expected context on to labkit' do + expect(Labkit::Context).to receive(:with_context).with(a_hash_including(user: duck_type(:call))) + + context.use {} + end + end end diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb new file mode 100644 index 00000000000..f64ebece930 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::WorkerContext::Server do + let(:worker_class) do + Class.new do + def self.name + "TestWorker" + end + + # To keep track of the context that was active for certain arguments + cattr_accessor(:contexts) { {} } + + include ApplicationWorker + + worker_context user: nil + + def perform(identifier, *args) + self.class.contexts.merge!(identifier => Labkit::Context.current.to_h) + end + end + end + + let(:other_worker) do + Class.new do + def self.name + "OtherWorker" + end + + include Sidekiq::Worker + + def perform + end + end + end + + before do + stub_const("TestWorker", worker_class) + stub_const("OtherWorker", other_worker) + end + + around do |example| + Sidekiq::Testing.inline! { example.run } + end + + before(:context) do + Sidekiq::Testing.server_middleware do |chain| + chain.add described_class + end + end + + after(:context) do + Sidekiq::Testing.server_middleware do |chain| + chain.remove described_class + end + end + + describe "#call" do + it 'applies a class context' do + Gitlab::ApplicationContext.with_context(user: build_stubbed(:user)) do + TestWorker.perform_async("identifier", 1) + end + + expect(TestWorker.contexts['identifier'].keys).not_to include('meta.user') + end + + it "doesn't fail for unknown workers" do + expect { OtherWorker.perform_async }.not_to raise_error + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index 473d85c0143..b3c0a5b04f0 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -44,7 +44,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::ServerMetrics, Gitlab::SidekiqMiddleware::ArgumentsLogger, Gitlab::SidekiqMiddleware::MemoryKiller, - Gitlab::SidekiqMiddleware::RequestStoreMiddleware + Gitlab::SidekiqMiddleware::RequestStoreMiddleware, + Gitlab::SidekiqMiddleware::WorkerContext::Server ] end let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 40772297cce..9ef8b390b92 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -63,6 +63,16 @@ describe SystemNoteService do end end + describe '.close_after_error_tracking_resolve' do + it 'calls IssuableService' do + expect_next_instance_of(::SystemNotes::IssuablesService) do |service| + expect(service).to receive(:close_after_error_tracking_resolve) + end + + described_class.close_after_error_tracking_resolve(noteable, project, author) + end + end + describe '.change_milestone' do let(:milestone) { double } diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb index 56ef0039b63..228d69fda4e 100644 --- a/spec/services/system_notes/issuables_service_spec.rb +++ b/spec/services/system_notes/issuables_service_spec.rb @@ -630,4 +630,17 @@ describe ::SystemNotes::IssuablesService do end end end + + describe '#close_after_error_tracking_resolve' do + subject { service.close_after_error_tracking_resolve } + + it_behaves_like 'a system note' do + let(:action) { 'closed' } + end + + it 'creates the expected system note' do + expect(subject.note) + .to eq('resolved the corresponding error and closed the issue.') + end + end end diff --git a/spec/support/shared_examples/nav_sidebar_shared_examples.rb b/spec/support/shared_examples/nav_sidebar_shared_examples.rb new file mode 100644 index 00000000000..e084a957785 --- /dev/null +++ b/spec/support/shared_examples/nav_sidebar_shared_examples.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'has nav sidebar' do + it 'has collapsed nav sidebar on mobile' do + render + + expect(rendered).to have_selector('.nav-sidebar') + expect(rendered).not_to have_selector('.sidebar-collapsed-desktop') + expect(rendered).not_to have_selector('.sidebar-expanded-mobile') + end +end + +RSpec.shared_examples 'page has active tab' do |title| + it "activates #{title} tab" do + expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1) + expect(find('.sidebar-top-level-items > li.active')).to have_content(title) + end +end + +RSpec.shared_examples 'page has active sub tab' do |title| + it "activates #{title} sub tab" do + expect(page).to have_selector('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)', count: 1) + expect(find('.sidebar-sub-level-items > li.active:not(.fly-out-top-item)')) + .to have_content(title) + end +end diff --git a/spec/support/shared_examples/views/nav_sidebar_shared_examples.rb b/spec/support/shared_examples/views/nav_sidebar_shared_examples.rb deleted file mode 100644 index fe9a681377c..00000000000 --- a/spec/support/shared_examples/views/nav_sidebar_shared_examples.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -RSpec.shared_examples 'has nav sidebar' do - it 'has collapsed nav sidebar on mobile' do - render - - expect(rendered).to have_selector('.nav-sidebar') - expect(rendered).not_to have_selector('.sidebar-collapsed-desktop') - expect(rendered).not_to have_selector('.sidebar-expanded-mobile') - end -end diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb index cf4d47b7500..21483d0e4e3 100644 --- a/spec/workers/concerns/cronjob_queue_spec.rb +++ b/spec/workers/concerns/cronjob_queue_spec.rb @@ -21,4 +21,12 @@ describe CronjobQueue do it 'disables retrying of failed jobs' do expect(worker.sidekiq_options['retry']).to eq(false) end + + it 'automatically clears project, user and namespace from the context', :aggregate_failues do + worker_context = worker.get_worker_context.to_lazy_hash.transform_values(&:call) + + expect(worker_context[:user]).to be_nil + expect(worker_context[:root_namespace]).to be_nil + expect(worker_context[:project]).to be_nil + end end diff --git a/spec/workers/concerns/worker_context_spec.rb b/spec/workers/concerns/worker_context_spec.rb new file mode 100644 index 00000000000..a7d0ba2b8bd --- /dev/null +++ b/spec/workers/concerns/worker_context_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe WorkerContext do + let(:worker) do + Class.new do + include WorkerContext + end + end + + describe '.worker_context' do + it 'allows modifying the context for the entire worker' do + worker.worker_context(user: build_stubbed(:user)) + + expect(worker.get_worker_context).to be_a(Gitlab::ApplicationContext) + end + + it 'allows fetches the context from a superclass if none was defined' do + worker.worker_context(user: build_stubbed(:user)) + subclass = Class.new(worker) + + expect(subclass.get_worker_context).to eq(worker.get_worker_context) + end + end + + describe '#with_context' do + it 'allows modifying context when the job is running' do + worker.new.with_context(user: build_stubbed(:user, username: 'jane-doe')) do + expect(Labkit::Context.current.to_h).to include('meta.user' => 'jane-doe') + end + end + end +end |