Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2ff184ad76
commit
2c2dd5e36c
|
|
@ -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)
|
||||
|
|
|
|||
14
CHANGELOG.md
14
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default function setupVueRepositoryList() {
|
|||
projectPath,
|
||||
projectShortPath,
|
||||
ref,
|
||||
vueFileListLfsBadge: gon?.features?.vueFileListLfsBadge,
|
||||
vueFileListLfsBadge: gon.features?.vueFileListLfsBadge || false,
|
||||
commits: [],
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -9,6 +9,7 @@ module ApplicationWorker
|
|||
|
||||
include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
|
||||
include WorkerAttributes
|
||||
include WorkerContext
|
||||
|
||||
included do
|
||||
set_queue
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fixes random passwords generated not conforming to minimum_password_length setting
|
||||
merge_request: 23387
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Remove invalid data from jira_tracker_data table
|
||||
merge_request: 23621
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Allow users to sign out on a read-only instance
|
||||
merge_request: 23545
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add API endpoints for 'soft-delete for groups' feature
|
||||
merge_request: 19430
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Close Issue when resolving corresponding Sentry error
|
||||
merge_request: 22744
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix loading of sub-epics caused by wrong subscription check.
|
||||
merge_request: 23184
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -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
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix Bitbucket Server importer error handler
|
||||
merge_request: 23310
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Optimize page loading of Admin::RunnersController#show
|
||||
merge_request: 23309
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) |
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
| 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue