Commit 2c2dd5e3 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 2ff184ad
Please view this file on the master branch, on stable branches it's out of date. 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 ## 12.7.0
### Removed (2 changes) ### Removed (2 changes)
......
...@@ -4,7 +4,19 @@ entry. ...@@ -4,7 +4,19 @@ entry.
## 12.7.1 ## 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 ## 12.7.0
......
...@@ -34,7 +34,10 @@ export default { ...@@ -34,7 +34,10 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
}; };
}, },
update: data => data.project.userPermissions, update: data => data.project?.userPermissions,
error(error) {
throw error;
},
}, },
}, },
mixins: [getRefMixin], mixins: [getRefMixin],
...@@ -172,7 +175,7 @@ export default { ...@@ -172,7 +175,7 @@ export default {
); );
} }
if (this.userPermissions.pushCode) { if (this.userPermissions?.pushCode) {
items.push( items.push(
{ {
type: ROW_TYPES.divider, type: ROW_TYPES.divider,
......
...@@ -40,16 +40,19 @@ export default { ...@@ -40,16 +40,19 @@ export default {
}; };
}, },
update: data => { update: data => {
const pipelines = data.project.repository.tree.lastCommit.pipelines.edges; const pipelines = data.project?.repository?.tree?.lastCommit?.pipelines?.edges;
return { return {
...data.project.repository.tree.lastCommit, ...data.project?.repository?.tree?.lastCommit,
pipeline: pipelines.length && pipelines[0].node, pipeline: pipelines?.length && pipelines[0].node,
}; };
}, },
context: { context: {
isSingleRequest: true, isSingleRequest: true,
}, },
error(error) {
throw error;
},
}, },
}, },
props: { props: {
...@@ -62,7 +65,7 @@ export default { ...@@ -62,7 +65,7 @@ export default {
data() { data() {
return { return {
projectPath: '', projectPath: '',
commit: {}, commit: null,
showDescription: false, showDescription: false,
}; };
}, },
...@@ -79,6 +82,11 @@ export default { ...@@ -79,6 +82,11 @@ export default {
return this.commit.sha.substr(0, 8); return this.commit.sha.substr(0, 8);
}, },
}, },
watch: {
currentPath() {
this.commit = null;
},
},
methods: { methods: {
toggleShowDescription() { toggleShowDescription() {
this.showDescription = !this.showDescription; this.showDescription = !this.showDescription;
...@@ -91,7 +99,7 @@ export default { ...@@ -91,7 +99,7 @@ export default {
<template> <template>
<div class="info-well d-none d-sm-flex project-last-commit commit p-3"> <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" /> <gl-loading-icon v-if="isLoading" size="md" color="dark" class="m-auto" />
<template v-else> <template v-else-if="commit">
<user-avatar-link <user-avatar-link
v-if="commit.author" v-if="commit.author"
:link-href="commit.author.webUrl" :link-href="commit.author.webUrl"
......
...@@ -86,7 +86,8 @@ export default { ...@@ -86,7 +86,8 @@ export default {
}, },
}) })
.then(({ data }) => { .then(({ data }) => {
if (!data) return; if (data.errors) throw data.errors;
if (!data?.project?.repository) return;
const pageInfo = this.hasNextPage(data.project.repository.tree); const pageInfo = this.hasNextPage(data.project.repository.tree);
...@@ -99,12 +100,15 @@ export default { ...@@ -99,12 +100,15 @@ export default {
{}, {},
); );
if (pageInfo && pageInfo.hasNextPage) { if (pageInfo?.hasNextPage) {
this.nextPageCursor = pageInfo.endCursor; this.nextPageCursor = pageInfo.endCursor;
this.fetchFiles(); 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) { normalizeData(key, data) {
return this.entries[key].concat(data.map(({ node }) => node)); return this.entries[key].concat(data.map(({ node }) => node));
......
...@@ -23,7 +23,7 @@ export default function setupVueRepositoryList() { ...@@ -23,7 +23,7 @@ export default function setupVueRepositoryList() {
projectPath, projectPath,
projectShortPath, projectShortPath,
ref, ref,
vueFileListLfsBadge: gon?.features?.vueFileListLfsBadge, vueFileListLfsBadge: gon.features?.vueFileListLfsBadge || false,
commits: [], commits: [],
}, },
}); });
......
...@@ -66,7 +66,7 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -66,7 +66,7 @@ class Admin::RunnersController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def assign_builds_and_projects 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 = @projects =
if params[:search].present? if params[:search].present?
::Project.search(params[:search]) ::Project.search(params[:search])
...@@ -75,7 +75,8 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -75,7 +75,8 @@ class Admin::RunnersController < Admin::ApplicationController
end end
@projects = @projects.where.not(id: runner.projects.select(:id)) if runner.projects.any? @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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
...@@ -7,14 +7,14 @@ class Import::BaseController < ApplicationController ...@@ -7,14 +7,14 @@ class Import::BaseController < ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_already_added_projects(import_type) 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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_jobs(import_type) def find_jobs(import_type)
current_user.created_projects current_user.created_projects
.includes(:import_state) .with_import_state
.where(import_type: import_type) .where(import_type: import_type)
.to_json(only: [:id], methods: [:import_status]) .to_json(only: [:id], methods: [:import_status])
end end
......
...@@ -82,7 +82,7 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -82,7 +82,7 @@ class Import::BitbucketServerController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def filter_added_projects(import_type, import_sources) 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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -87,7 +87,7 @@ class Import::ManifestController < Import::BaseController ...@@ -87,7 +87,7 @@ class Import::ManifestController < Import::BaseController
group.all_projects group.all_projects
.where(import_type: 'manifest') .where(import_type: 'manifest')
.where(creator_id: current_user) .where(creator_id: current_user)
.includes(:import_state) .with_import_state
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -12,16 +12,14 @@ class ContributedProjectsFinder < UnionFinder ...@@ -12,16 +12,14 @@ class ContributedProjectsFinder < UnionFinder
# visible by this user. # visible by this user.
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
# rubocop: disable CodeReuse/ActiveRecord
def execute(current_user = nil) def execute(current_user = nil)
# Do not show contributed projects if the user profile is private. # Do not show contributed projects if the user profile is private.
return Project.none unless can_read_profile?(current_user) return Project.none unless can_read_profile?(current_user)
segments = all_projects(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 end
# rubocop: enable CodeReuse/ActiveRecord
private private
......
...@@ -17,15 +17,13 @@ class PersonalProjectsFinder < UnionFinder ...@@ -17,15 +17,13 @@ class PersonalProjectsFinder < UnionFinder
# min_access_level: integer # min_access_level: integer
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
# rubocop: disable CodeReuse/ActiveRecord
def execute(current_user = nil) def execute(current_user = nil)
return Project.none unless can?(current_user, :read_user_profile, @user) return Project.none unless can?(current_user, :read_user_profile, @user)
segments = all_projects(current_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 end
# rubocop: enable CodeReuse/ActiveRecord
private private
......
# 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')
...@@ -403,6 +403,10 @@ module ProjectsHelper ...@@ -403,6 +403,10 @@ module ProjectsHelper
nav_tabs << :operations nav_tabs << :operations
end end
if can?(current_user, :read_cycle_analytics, project)
nav_tabs << :cycle_analytics
end
tab_ability_map.each do |tab, ability| tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project) if can?(current_user, ability, project)
nav_tabs << tab nav_tabs << tab
...@@ -643,7 +647,6 @@ module ProjectsHelper ...@@ -643,7 +647,6 @@ module ProjectsHelper
projects#show projects#show
projects#activity projects#activity
releases#index releases#index
cycle_analytics#show
] ]
end end
......
...@@ -12,6 +12,7 @@ module TabHelper ...@@ -12,6 +12,7 @@ module TabHelper
# :action - One or more action names to check (optional). # :action - One or more action names to check (optional).
# :path - A shorthand path, such as 'dashboard#index', 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). # :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 # block - An optional block that will become the contents of the returned
# `li` element. # `li` element.
# #
...@@ -56,6 +57,14 @@ module TabHelper ...@@ -56,6 +57,14 @@ module TabHelper
# nav_link(path: 'admin/appearances#show') { "Hello"} # nav_link(path: 'admin/appearances#show') { "Hello"}
# # => '<li class="active">Hello</li>' # # => '<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 # Returns a list item element String
def nav_link(options = {}, &block) def nav_link(options = {}, &block)
klass = active_nav_link?(options) ? 'active' : '' klass = active_nav_link?(options) ? 'active' : ''
...@@ -73,6 +82,8 @@ module TabHelper ...@@ -73,6 +82,8 @@ module TabHelper
end end
def active_nav_link?(options) def active_nav_link?(options)
return false if options[:unless]&.call
if path = options.delete(:path) if path = options.delete(:path)
unless path.respond_to?(:each) unless path.respond_to?(:each)
path = [path] path = [path]
......
...@@ -172,6 +172,9 @@ module Ci ...@@ -172,6 +172,9 @@ module Ci
scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) } scope :queued_before, ->(time) { where(arel_table[:queued_at].lt(time)) }
scope :order_id_desc, -> { order('ci_builds.id DESC') } 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 acts_as_taggable
add_authentication_token_field :token, encrypted: :optional add_authentication_token_field :token, encrypted: :optional
......
...@@ -411,6 +411,8 @@ class Project < ApplicationRecord ...@@ -411,6 +411,8 @@ class Project < ApplicationRecord
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :inc_routes, -> { includes(:route, namespace: :route) } scope :inc_routes, -> { includes(:route, namespace: :route) }
scope :with_statistics, -> { includes(:statistics) } 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_service, ->(service) { joins(service).eager_load(service) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) } scope :with_container_registry, -> { where(container_registry_enabled: true) }
......
...@@ -100,9 +100,7 @@ module SystemNoteService ...@@ -100,9 +100,7 @@ module SystemNoteService
end end
def close_after_error_tracking_resolve(issue, project, author) def close_after_error_tracking_resolve(issue, project, author)
body = _('resolved the corresponding error and closed the issue.') ::SystemNotes::IssuablesService.new(noteable: issue, project: project, author: author).close_after_error_tracking_resolve
create_note(NoteSummary.new(issue, project, author, body, action: 'closed'))
end end
def change_status(noteable, project, author, status, source = nil) def change_status(noteable, project, author, status, source = nil)
...@@ -243,23 +241,6 @@ module SystemNoteService ...@@ -243,23 +241,6 @@ module SystemNoteService
def zoom_link_removed(issue, project, author) def zoom_link_removed(issue, project, author)
::SystemNotes::ZoomService.new(noteable: issue, project: project, author: author).zoom_link_removed ::SystemNotes::ZoomService.new(noteable: issue, project: project, author: author).zoom_link_removed
end 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 end
SystemNoteService.prepend_if_ee('EE::SystemNoteService') SystemNoteService.prepend_if_ee('EE::SystemNoteService')
...@@ -282,6 +282,12 @@ module SystemNotes ...@@ -282,6 +282,12 @@ module SystemNotes
create_note(NoteSummary.new(noteable, project, author, body, action: action)) create_note(NoteSummary.new(noteable, project, author, body, action: action))
end 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 private
def cross_reference_note_content(gfm_reference) def cross_reference_note_content(gfm_reference)
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
= project.full_name = project.full_name
%td %td
.float-right .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 %table.table.unassigned-projects
%thead %thead
...@@ -70,10 +70,10 @@ ...@@ -70,10 +70,10 @@
= project.full_name = project.full_name
%td %td
.float-right .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.hidden_field :runner_id, value: @runner.id
= f.submit 'Enable', class: 'btn btn-sm' = f.submit 'Enable', class: 'btn btn-sm'
= paginate @projects, theme: "gitlab" = paginate_without_count @projects
.col-md-6 .col-md-6
%h4 Recent jobs served by this Runner %h4 Recent jobs served by this Runner
......
- 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{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
.nav-sidebar-inner-scroll .nav-sidebar-inner-scroll
- can_edit = can?(current_user, :admin_project, @project) - can_edit = can?(current_user, :admin_project, @project)
...@@ -8,7 +10,9 @@ ...@@ -8,7 +10,9 @@
.sidebar-context-title .sidebar-context-title
= @project.name = @project.name
%ul.sidebar-top-level-items %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 = link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('home') = sprite_icon('home')
...@@ -34,15 +38,18 @@ ...@@ -34,15 +38,18 @@
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do = link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
%span= _('Releases') %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 - 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 = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
.nav-icon-container .nav-icon-container
= sprite_icon('doc-text') = sprite_icon('doc-text')
...@@ -83,9 +90,10 @@ ...@@ -83,9 +90,10 @@
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do = link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
= _('Compare') = _('Compare')
= nav_link(path: 'graphs#charts') do - unless should_display_analytics_pages_in_sidebar
= link_to charts_project_graph_path(@project, current_ref) do = nav_link(path: 'graphs#charts') do
= _('Charts') = link_to charts_project_graph_path(@project, current_ref) do
= _('Charts')
= render_if_exists 'projects/sidebar/repository_locked_files' = render_if_exists 'projects/sidebar/repository_locked_files'
...@@ -170,7 +178,7 @@ ...@@ -170,7 +178,7 @@
= number_with_delimiter(@project.open_merge_requests_count) = number_with_delimiter(@project.open_merge_requests_count)
- if project_nav_tab? :pipelines - 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 = 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 .nav-icon-container
= sprite_icon('rocket') = sprite_icon('rocket')
...@@ -201,13 +209,13 @@ ...@@ -201,13 +209,13 @@
%span %span
= _('Artifacts') = _('Artifacts')
- if project_nav_tab? :pipelines - if !should_display_analytics_pages_in_sidebar && project_nav_tab?(:pipelines)
= nav_link(controller: :pipeline_schedules) do = nav_link(controller: :pipeline_schedules) do
= link_to pipeline_schedules_path(@project), title: _('Schedules'), class: 'shortcuts-builds' do = link_to pipeline_schedules_path(@project), title: _('Schedules'), class: 'shortcuts-builds' do
%span %span
= _('Schedules') = _('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 = nav_link(path: 'pipelines#charts') do
= link_to charts_project_pipelines_path(@project), title: _('Charts'), class: 'shortcuts-pipelines-charts' do = link_to charts_project_pipelines_path(@project), title: _('Charts'), class: 'shortcuts-pipelines-charts' do
%span %span
...@@ -290,7 +298,7 @@ ...@@ -290,7 +298,7 @@
= render_if_exists 'layouts/nav/sidebar/project_packages_link' = 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 - if project_nav_tab? :wiki
- wiki_url = project_wiki_path(@project, :home) - wiki_url = project_wiki_path(@project, :home)
...@@ -410,11 +418,12 @@ ...@@ -410,11 +418,12 @@
= link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do = link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do
= _('Graph') = _('Graph')
-# Shortcut to Repository > Charts (formerly, top-nav item "Graphs") - unless should_display_analytics_pages_in_sidebar
- unless @project.empty_repo? -# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
%li.hidden - unless @project.empty_repo?
= link_to charts_project_graph_path(@project, current_ref), title: _('Charts'), class: 'shortcuts-repository-charts' do %li.hidden
= _('Charts') = link_to charts_project_graph_path(@project, current_ref), title: _('Charts'), class: 'shortcuts-repository-charts' do
= _('Charts')
-# Shortcut to Issues > New Issue -# Shortcut to Issues > New Issue
- if project_nav_tab?(:issues) - if project_nav_tab?(:issues)
......
- 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
...@@ -9,6 +9,7 @@ module ApplicationWorker ...@@ -9,6 +9,7 @@ module ApplicationWorker
include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
include WorkerAttributes include WorkerAttributes
include WorkerContext
included do included do
set_queue set_queue
......
...@@ -8,5 +8,6 @@ module CronjobQueue ...@@ -8,5 +8,6 @@ module CronjobQueue
included do included do
queue_namespace :cronjob queue_namespace :cronjob
sidekiq_options retry: false sidekiq_options retry: false
worker_context project: nil, namespace: nil, user: nil
end end
end end
# 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
---
title: Fixes random passwords generated not conforming to minimum_password_length setting
merge_request: 23387
author:
type: fixed
---
title: Remove invalid data from jira_tracker_data table
merge_request: 23621
author:
type: fixed
---
title: Allow users to sign out on a read-only instance
merge_request: 23545
author:
type: fixed
---
title: Add API endpoints for 'soft-delete for groups' feature
merge_request: 19430
author:
type: added
---
title: Close Issue when resolving corresponding Sentry error
merge_request: 22744
author:
type: added
---
title: Fix loading of sub-epics caused by wrong subscription check.
merge_request: 23184
author:
type: fixed
---
title: Reverts MR diff redesign which fixes Web IDE visual bugs including file dropdown
not showing up
merge_request: 23428
author:
type: fixed
---
title: Fix Bitbucket Server importer error handler
merge_request: 23310
author:
type: fixed
---
title: Optimize page loading of Admin::RunnersController#show
merge_request: 23309
author:
type: performance
...@@ -105,6 +105,7 @@ recorded: ...@@ -105,6 +105,7 @@ recorded:
- Ask for password reset - Ask for password reset
- Grant OAuth access - Grant OAuth access
- Started/stopped user impersonation - Started/stopped user impersonation
- Changed username
It is possible to filter particular actions by choosing an audit data type from 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 the filter dropdown box. You can further filter by specific group, project or user
......
...@@ -628,7 +628,12 @@ Feature.disable(:limit_projects_in_groups_api) ...@@ -628,7 +628,12 @@ Feature.disable(:limit_projects_in_groups_api)
## Remove group ## 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 DELETE /groups/:id
...@@ -636,10 +641,27 @@ DELETE /groups/:id ...@@ -636,10 +641,27 @@ DELETE /groups/:id
Parameters: 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 | Attribute | Type | Required | Description |
response will be a 202 Accepted if the user has authorization. | --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
## Search for group ## Search for group
......
...@@ -1769,7 +1769,7 @@ This endpoint either: ...@@ -1769,7 +1769,7 @@ This endpoint either:
- Removes a project including all associated resources (issues, merge requests etc). - 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 - From GitLab 12.6 on Premium or higher tiers, marks a project for deletion. Actual
deletion happens after number of days specified in 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 DELETE /projects/:id
...@@ -1781,6 +1781,8 @@ DELETE /projects/:id ...@@ -1781,6 +1781,8 @@ DELETE /projects/:id
## Restore project marked for deletion **(PREMIUM)** ## Restore project marked for deletion **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32935) in GitLab 12.6.
Restores project marked for deletion. Restores project marked for deletion.
``` ```
......
...@@ -294,7 +294,7 @@ are listed in the descriptions of the relevant settings. ...@@ -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_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. | | `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. | | `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. | | `project_export_enabled` | boolean | no | Enable project export. |
| `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. | | `prometheus_metrics_enabled` | boolean | no | Enable Prometheus metrics. |
| `protected_ci_variables` | boolean | no | Environment variables are protected by default. | | `protected_ci_variables` | boolean | no | Environment variables are protected by default. |
......
...@@ -47,11 +47,13 @@ To ensure only admin users can delete projects: ...@@ -47,11 +47,13 @@ To ensure only admin users can delete projects:
1. Check the **Default project deletion protection** checkbox. 1. Check the **Default project deletion protection** checkbox.
1. Click **Save changes**. 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. > [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: To change this period:
......
...@@ -57,11 +57,11 @@ This page has: ...@@ -57,11 +57,11 @@ This page has:
- Other details about the issue, including a full stack trace. - 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. - 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) ![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) ![Error Details with Issue Link](img/error_details_with_issue_v12_7.png)
......
...@@ -92,6 +92,15 @@ module API ...@@ -92,6 +92,15 @@ module API
present paginate(groups), options present paginate(groups), options
end 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 end
resource :groups do resource :groups do
...@@ -187,12 +196,7 @@ module API ...@@ -187,12 +196,7 @@ module API
group = find_group!(params[:id]) group = find_group!(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/46285') delete_group(group)
destroy_conditionally!(group) do |group|
::Groups::DestroyService.new(group, current_user).async_execute
end
accepted!
end end
desc 'Get a list of projects in this group.' do desc 'Get a list of projects in this group.' do
......
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
def self.with_context(args, &block) def self.with_context(args, &block)
application_context = new(**args) application_context = new(**args)
Labkit::Context.with_context(application_context.to_lazy_hash, &block) application_context.use(&block)
end end
def self.push(args) def self.push(args)
...@@ -42,6 +42,10 @@ module Gitlab ...@@ -42,6 +42,10 @@ module Gitlab
end end
end end
def use
Labkit::Context.with_context(to_lazy_hash) { yield }
end
private private
attr_reader :set_values attr_reader :set_values
......
...@@ -18,6 +18,7 @@ module Gitlab ...@@ -18,6 +18,7 @@ module Gitlab
chain.add Labkit::Middleware::Sidekiq::Server chain.add Labkit::Middleware::Sidekiq::Server
chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger
chain.add Gitlab::SidekiqStatus::ServerMiddleware chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add Gitlab::SidekiqMiddleware::WorkerContext::Server
end end
end end
......
# 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
...@@ -3014,6 +3014,9 @@ msgstr "" ...@@ -3014,6 +3014,9 @@ msgstr ""
msgid "CI / CD" msgid "CI / CD"
msgstr "" msgstr ""
msgid "CI / CD Analytics"
msgstr ""
msgid "CI / CD Charts" msgid "CI / CD Charts"
msgstr "" msgstr ""
...@@ -14310,9 +14313,6 @@ msgstr "" ...@@ -14310,9 +14313,6 @@ msgstr ""
msgid "Project '%{project_name}' will be deleted on %{date}" msgid "Project '%{project_name}' will be deleted on %{date}"
msgstr "" msgstr ""
msgid "Project Analytics"
msgstr ""
msgid "Project Badges" msgid "Project Badges"
msgstr "" msgstr ""
...@@ -15769,6 +15769,9 @@ msgstr "" ...@@ -15769,6 +15769,9 @@ msgstr ""
msgid "Repository" msgid "Repository"
msgstr "" msgstr ""
msgid "Repository Analytics"
msgstr ""
msgid "Repository Graph" msgid "Repository Graph"
msgstr "" msgstr ""
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe Admin::RunnersController do describe Admin::RunnersController do
let!(:runner) { create(:ci_runner) } let_it_be(:runner) { create(:ci_runner) }
before do before do
sign_in(create(:admin)) sign_in(create(:admin))
...@@ -36,6 +36,16 @@ describe Admin::RunnersController do ...@@ -36,6 +36,16 @@ describe Admin::RunnersController do
end end
describe '#show' do 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 it 'shows a particular runner' do
get :show, params: { id: runner.id } get :show, params: { id: runner.id }
...@@ -47,6 +57,21 @@ describe Admin::RunnersController do ...@@ -47,6 +57,21 @@ describe Admin::RunnersController do
expect(response).to have_gitlab_http_status(404) expect(response).to have_gitlab_http_status(404)
end 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 end
describe '#update' do describe '#update' do
......
...@@ -7,6 +7,8 @@ describe 'Project active tab' do ...@@ -7,6 +7,8 @@ describe 'Project active tab' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
before do before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
...@@ -17,21 +19,6 @@ describe 'Project active tab' do ...@@ -17,21 +19,6 @@ describe 'Project active tab' do
end end
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 context 'on project Home' do
before do before do
visit project_path(project) visit project_path(project)
...@@ -136,4 +123,35 @@ describe 'Project active tab' do ...@@ -136,4 +123,35 @@ describe 'Project active tab' do
it_behaves_like 'page has active sub tab', 'Repository' it_behaves_like 'page has active sub tab', 'Repository'
end end
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 end
...@@ -7,6 +7,8 @@ describe 'User uses shortcuts', :js do ...@@ -7,6 +7,8 @@ describe 'User uses shortcuts', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
...@@ -156,4 +158,18 @@ describe 'User uses shortcuts', :js do ...@@ -156,4 +158,18 @@ describe 'User uses shortcuts', :js do
expect(page).to have_active_navigation('Wiki') expect(page).to have_active_navigation('Wiki')
end end
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 end
...@@ -79,4 +79,18 @@ describe Gitlab::ApplicationContext do ...@@ -79,4 +79,18 @@ describe Gitlab::ApplicationContext do
.to include(project: project.full_path, root_namespace: project.full_path_components.first) .to include(project: project.full_path, root_namespace: project.full_path_components.first)
end end
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 end
# 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
...@@ -44,7 +44,8 @@ describe Gitlab::SidekiqMiddleware do ...@@ -44,7 +44,8 @@ describe Gitlab::SidekiqMiddleware do
Gitlab::SidekiqMiddleware::ServerMetrics, Gitlab::SidekiqMiddleware::ServerMetrics,
Gitlab::SidekiqMiddleware::ArgumentsLogger, Gitlab::SidekiqMiddleware::ArgumentsLogger,
Gitlab::SidekiqMiddleware::MemoryKiller, Gitlab::SidekiqMiddleware::MemoryKiller,
Gitlab::SidekiqMiddleware::RequestStoreMiddleware Gitlab::SidekiqMiddleware::RequestStoreMiddleware,
Gitlab::SidekiqMiddleware::WorkerContext::Server
] ]
end end
let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares }
......
...@@ -63,6 +63,16 @@ describe SystemNoteService do ...@@ -63,6 +63,16 @@ describe SystemNoteService do
end end
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 describe '.change_milestone' do
let(:milestone) { double } let(:milestone) { double }
......
...@@ -630,4 +630,17 @@ describe ::SystemNotes::IssuablesService do ...@@ -630,4 +630,17 @@ describe ::SystemNotes::IssuablesService do
end end
end 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 end
...@@ -9,3 +9,18 @@ RSpec.shared_examples 'has nav sidebar' do ...@@ -9,3 +9,18 @@ RSpec.shared_examples 'has nav sidebar' do
expect(rendered).not_to have_selector('.sidebar-expanded-mobile') expect(rendered).not_to have_selector('.sidebar-expanded-mobile')
end end
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
...@@ -21,4 +21,12 @@ describe CronjobQueue do ...@@ -21,4 +21,12 @@ describe CronjobQueue do
it 'disables retrying of failed jobs' do it 'disables retrying of failed jobs' do
expect(worker.sidekiq_options['retry']).to eq(false) expect(worker.sidekiq_options['retry']).to eq(false)
end 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 end
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment