提交 2c2dd5e3 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 2ff184ad
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)
......
......@@ -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
......
# 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
......
- 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)
......
- 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
# 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:
- 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) |
The response will be `202 Accepted` if the user has authorization.
## Restore group marked for deletion **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/33257) in GitLab 12.8.
Restores a group marked for deletion.
```plaintext
POST /groups/:id/restore
```
Parameters:
This will queue a background job to delete all projects in the group. The
response will be a 202 Accepted if the user has authorization.
| Attribute | Type | Required | Description |
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
## Search for group
......
......@@ -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:
![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)
......
......@@ -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
......
# 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
# 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
......@@ -9,3 +9,18 @@ RSpec.shared_examples 'has nav sidebar' do
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
......@@ -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
# 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% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册