Commit 5ad0cf26 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent f47c768f
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.default-before_script: .default-before_script:
before_script: before_script:
- date - date
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/' - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- export GOPATH=$CI_PROJECT_DIR/.go - export GOPATH=$CI_PROJECT_DIR/.go
- mkdir -p $GOPATH - mkdir -p $GOPATH
- source scripts/utils.sh - source scripts/utils.sh
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
paths: paths:
- vendor/ruby - vendor/ruby
before_script: before_script:
- '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/' - '[ "$FOSS_ONLY" = "1" ] && rm -rf ee/ qa/spec/ee/ qa/qa/specs/features/ee/ qa/qa/ee/ qa/qa/ee.rb'
- cd qa/ - cd qa/
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet - bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet
- bundle check - bundle check
...@@ -50,6 +50,13 @@ qa:internal: ...@@ -50,6 +50,13 @@ qa:internal:
script: script:
- bundle exec rspec - bundle exec rspec
qa:internal-foss:
extends:
- .qa-job-base
- .only-ee-as-if-foss
script:
- bundle exec rspec
qa:selectors: qa:selectors:
extends: .qa-job-base extends: .qa-job-base
script: script:
......
...@@ -44,7 +44,7 @@ export default { ...@@ -44,7 +44,7 @@ export default {
:action="action" :action="action"
/> />
<li v-if="hasMoreChildren" class="group-row"> <li v-if="hasMoreChildren" class="group-row">
<a :href="parentGroup.relativePath" class="group-row-contents has-more-items"> <a :href="parentGroup.relativePath" class="group-row-contents has-more-items py-2">
<i class="fa fa-external-link" aria-hidden="true"> </i> {{ moreChildrenStats }} <i class="fa fa-external-link" aria-hidden="true"> </i> {{ moreChildrenStats }}
</a> </a>
</li> </li>
......
...@@ -91,7 +91,7 @@ export default { ...@@ -91,7 +91,7 @@ export default {
<li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup"> <li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup">
<div <div
:class="{ 'project-row-contents': !isGroup }" :class="{ 'project-row-contents': !isGroup }"
class="group-row-contents d-flex align-items-center" class="group-row-contents d-flex align-items-center py-2"
> >
<div class="folder-toggle-wrap append-right-4 d-flex align-items-center"> <div class="folder-toggle-wrap append-right-4 d-flex align-items-center">
<item-caret :is-group-open="group.isOpen" /> <item-caret :is-group-open="group.isOpen" />
...@@ -104,7 +104,7 @@ export default { ...@@ -104,7 +104,7 @@ export default {
/> />
<div <div
:class="{ 'd-sm-flex': !group.isChildrenLoading }" :class="{ 'd-sm-flex': !group.isChildrenLoading }"
class="avatar-container rect-avatar s40 d-none flex-grow-0 flex-shrink-0 " class="avatar-container rect-avatar s32 d-none flex-grow-0 flex-shrink-0 "
> >
<a :href="group.relativePath" class="no-expand"> <a :href="group.relativePath" class="no-expand">
<img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s40" /> <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s40" />
......
...@@ -47,7 +47,7 @@ export default { ...@@ -47,7 +47,7 @@ export default {
<template> <template>
<li :id="envId" :class="isOpenClass" class="group-row has-children"> <li :id="envId" :class="isOpenClass" class="group-row has-children">
<div <div
class="group-row-contents d-flex justify-content-end align-items-center" class="group-row-contents d-flex justify-content-end align-items-center py-2"
role="button" role="button"
@click.stop="toggleOpen" @click.stop="toggleOpen"
> >
......
...@@ -63,7 +63,7 @@ export default { ...@@ -63,7 +63,7 @@ export default {
<template> <template>
<li :id="name" class="group-row"> <li :id="name" class="group-row">
<div class="group-row-contents" role="button" @click="openDetails"> <div class="group-row-contents py-2" role="button" @click="openDetails">
<p class="float-right text-right"> <p class="float-right text-right">
<span>{{ image }}</span <span>{{ image }}</span
><br /> ><br />
......
...@@ -382,8 +382,6 @@ table.pipeline-project-metrics tr td { ...@@ -382,8 +382,6 @@ table.pipeline-project-metrics tr td {
} }
.group-row-contents { .group-row-contents {
padding: $gl-padding;
&:hover { &:hover {
border-color: $blue-200; border-color: $blue-200;
background-color: $blue-50; background-color: $blue-50;
...@@ -410,13 +408,7 @@ table.pipeline-project-metrics tr td { ...@@ -410,13 +408,7 @@ table.pipeline-project-metrics tr td {
.title { .title {
margin-top: -$gl-padding-8; // negative margin required for flex-wrap margin-top: -$gl-padding-8; // negative margin required for flex-wrap
font-size: $gl-font-size-large; font-size: $gl-font-size;
}
@include media-breakpoint-down(md) {
.title {
font-size: $gl-font-size;
}
} }
&.has-more-items { &.has-more-items {
...@@ -483,7 +475,6 @@ table.pipeline-project-metrics tr td { ...@@ -483,7 +475,6 @@ table.pipeline-project-metrics tr td {
.last-updated { .last-updated {
position: relative; position: relative;
right: 12px;
min-width: 250px; min-width: 250px;
text-align: right; text-align: right;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
......
# frozen_string_literal: true
# Include this in your controller and call `limit_pages` in order
# to configure the limiter.
#
# Examples:
# class MyController < ApplicationController
# include PageLimiter
#
# before_action only: [:index] do
# limit_pages(500)
# end
#
# # You can override the default response
# rescue_from PageOutOfBoundsError, with: :page_out_of_bounds
#
# def page_out_of_bounds(error)
# # Page limit number is available as error.message
# head :ok
# end
#
module PageLimiter
extend ActiveSupport::Concern
PageLimiterError = Class.new(StandardError)
PageLimitNotANumberError = Class.new(PageLimiterError)
PageLimitNotSensibleError = Class.new(PageLimiterError)
PageOutOfBoundsError = Class.new(PageLimiterError)
included do
rescue_from PageOutOfBoundsError, with: :default_page_out_of_bounds_response
end
def limit_pages(max_page_number)
check_page_number!(max_page_number)
end
private
# If the page exceeds the defined maximum, raise a PageOutOfBoundsError
# If the page doesn't exceed the limit, it does nothing.
def check_page_number!(max_page_number)
raise PageLimitNotANumberError unless max_page_number.is_a?(Integer)
raise PageLimitNotSensibleError unless max_page_number > 0
if params[:page].present? && params[:page].to_i > max_page_number
record_page_limit_interception
raise PageOutOfBoundsError.new(max_page_number)
end
end
# By default just return a HTTP status code and an empty response
def default_page_out_of_bounds_response
head :bad_request
end
# Record the page limit being hit in Prometheus
def record_page_limit_interception
dd = DeviceDetector.new(request.user_agent)
Gitlab::Metrics.counter(:gitlab_page_out_of_bounds,
controller: params[:controller],
action: params[:action],
bot: dd.bot?
)
end
end
# frozen_string_literal: true # frozen_string_literal: true
class Explore::ProjectsController < Explore::ApplicationController class Explore::ProjectsController < Explore::ApplicationController
include PageLimiter
include ParamsBackwardCompatibility include ParamsBackwardCompatibility
include RendersMemberAccess include RendersMemberAccess
include SortingHelper include SortingHelper
...@@ -9,6 +10,13 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -9,6 +10,13 @@ class Explore::ProjectsController < Explore::ApplicationController
before_action :set_non_archived_param before_action :set_non_archived_param
before_action :set_sorting before_action :set_sorting
# Limit taken from https://gitlab.com/gitlab-org/gitlab/issues/38357
before_action only: [:index, :trending, :starred] do
limit_pages(200)
end
rescue_from PageOutOfBoundsError, with: :page_out_of_bounds
def index def index
@projects = load_projects @projects = load_projects
...@@ -53,10 +61,14 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -53,10 +61,14 @@ class Explore::ProjectsController < Explore::ApplicationController
private private
# rubocop: disable CodeReuse/ActiveRecord def load_project_counts
def load_projects
@total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
@total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
end
# rubocop: disable CodeReuse/ActiveRecord
def load_projects
load_project_counts
projects = ProjectsFinder.new(current_user: current_user, params: params) projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute .execute
...@@ -80,4 +92,21 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -80,4 +92,21 @@ class Explore::ProjectsController < Explore::ApplicationController
def sorting_field def sorting_field
Project::SORTING_PREFERENCE_FIELD Project::SORTING_PREFERENCE_FIELD
end end
def page_out_of_bounds(error)
load_project_counts
@max_page_number = error.message
respond_to do |format|
format.html do
render "page_out_of_bounds", status: :bad_request
end
format.json do
render json: {
html: view_to_html_string("explore/projects/page_out_of_bounds")
}, status: :bad_request
end
end
end
end end
...@@ -15,17 +15,17 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -15,17 +15,17 @@ class Projects::SnippetsController < Projects::ApplicationController
before_action :check_snippets_available! before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow read any snippet # Allow create snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index] before_action :authorize_create_snippet!, only: [:new, :create]
# Allow write(create) snippet # Allow read any snippet
before_action :authorize_create_project_snippet!, only: [:new, :create] before_action :authorize_read_snippet!, except: [:new, :create, :index]
# Allow modify snippet # Allow modify snippet
before_action :authorize_update_project_snippet!, only: [:edit, :update] before_action :authorize_update_snippet!, only: [:edit, :update]
# Allow destroy snippet # Allow destroy snippet
before_action :authorize_admin_project_snippet!, only: [:destroy] before_action :authorize_admin_snippet!, only: [:destroy]
respond_to :html respond_to :html
...@@ -115,16 +115,16 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -115,16 +115,16 @@ class Projects::SnippetsController < Projects::ApplicationController
project_snippet_path(@project, @snippet) project_snippet_path(@project, @snippet)
end end
def authorize_read_project_snippet! def authorize_read_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet) return render_404 unless can?(current_user, :read_snippet, @snippet)
end end
def authorize_update_project_snippet! def authorize_update_snippet!
return render_404 unless can?(current_user, :update_project_snippet, @snippet) return render_404 unless can?(current_user, :update_snippet, @snippet)
end end
def authorize_admin_project_snippet! def authorize_admin_snippet!
return render_404 unless can?(current_user, :admin_project_snippet, @snippet) return render_404 unless can?(current_user, :admin_snippet, @snippet)
end end
def snippet_params def snippet_params
......
...@@ -33,7 +33,7 @@ class Snippets::NotesController < ApplicationController ...@@ -33,7 +33,7 @@ class Snippets::NotesController < ApplicationController
end end
def authorize_read_snippet! def authorize_read_snippet!
return render_404 unless can?(current_user, :read_personal_snippet, snippet) return render_404 unless can?(current_user, :read_snippet, snippet)
end end
def authorize_create_note! def authorize_create_note!
......
...@@ -126,7 +126,7 @@ class SnippetsController < ApplicationController ...@@ -126,7 +126,7 @@ class SnippetsController < ApplicationController
end end
def authorize_read_snippet! def authorize_read_snippet!
return if can?(current_user, :read_personal_snippet, @snippet) return if can?(current_user, :read_snippet, @snippet)
if current_user if current_user
render_404 render_404
...@@ -136,15 +136,15 @@ class SnippetsController < ApplicationController ...@@ -136,15 +136,15 @@ class SnippetsController < ApplicationController
end end
def authorize_update_snippet! def authorize_update_snippet!
return render_404 unless can?(current_user, :update_personal_snippet, @snippet) return render_404 unless can?(current_user, :update_snippet, @snippet)
end end
def authorize_admin_snippet! def authorize_admin_snippet!
return render_404 unless can?(current_user, :admin_personal_snippet, @snippet) return render_404 unless can?(current_user, :admin_snippet, @snippet)
end end
def authorize_create_snippet! def authorize_create_snippet!
return render_404 unless can?(current_user, :create_personal_snippet) return render_404 unless can?(current_user, :create_snippet)
end end
def snippet_params def snippet_params
......
...@@ -41,6 +41,8 @@ class UploadsController < ApplicationController ...@@ -41,6 +41,8 @@ class UploadsController < ApplicationController
case model case model
when Note when Note
can?(current_user, :read_project, model.project) can?(current_user, :read_project, model.project)
when Snippet, ProjectSnippet
can?(current_user, :read_snippet, model)
when User when User
# We validate the current user has enough (writing) # We validate the current user has enough (writing)
# access to itself when a secret is given. # access to itself when a secret is given.
......
...@@ -67,11 +67,11 @@ module Mutations ...@@ -67,11 +67,11 @@ module Mutations
end end
def authorized_resource?(project) def authorized_resource?(project)
Ability.allowed?(context[:current_user], :create_project_snippet, project) Ability.allowed?(context[:current_user], :create_snippet, project)
end end
def can_create_personal_snippet? def can_create_personal_snippet?
Ability.allowed?(context[:current_user], :create_personal_snippet) Ability.allowed?(context[:current_user], :create_snippet)
end end
end end
end end
......
...@@ -21,7 +21,7 @@ module Types ...@@ -21,7 +21,7 @@ module Types
permission_field :create_snippet permission_field :create_snippet
def create_snippet def create_snippet
Ability.allowed?(context[:current_user], :create_project_snippet, object) Ability.allowed?(context[:current_user], :create_snippet, object)
end end
end end
end end
......
...@@ -8,7 +8,7 @@ module Types ...@@ -8,7 +8,7 @@ module Types
permission_field :create_snippet permission_field :create_snippet
def create_snippet def create_snippet
Ability.allowed?(context[:current_user], :create_personal_snippet) Ability.allowed?(context[:current_user], :create_snippet)
end end
end end
end end
......
...@@ -76,13 +76,14 @@ module MarkupHelper ...@@ -76,13 +76,14 @@ module MarkupHelper
# +max_chars+ limit. If the length limit falls within a tag's contents, then # +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag. # the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(object, attribute, max_chars = nil, options = {}) def first_line_in_markdown(object, attribute, max_chars = nil, options = {})
md = markdown_field(object, attribute, options) md = markdown_field(object, attribute, options.merge(post_process: false))
return unless md.present? return unless md.present?
tags = %w(a gl-emoji b pre code p span) tags = %w(a gl-emoji b pre code p span)
tags << 'img' if options[:allow_images] tags << 'img' if options[:allow_images]
text = truncate_visible(md, max_chars || md.length) text = truncate_visible(md, max_chars || md.length)
text = prepare_for_rendering(text, markdown_field_render_context(object, attribute, options))
text = sanitize( text = sanitize(
text, text,
tags: tags, tags: tags,
...@@ -107,15 +108,12 @@ module MarkupHelper ...@@ -107,15 +108,12 @@ module MarkupHelper
def markdown_field(object, field, context = {}) def markdown_field(object, field, context = {})
object = object.for_display if object.respond_to?(:for_display) object = object.for_display if object.respond_to?(:for_display)
redacted_field_html = object.try(:"redacted_#{field}_html")
return '' unless object.present? return '' unless object.present?
return redacted_field_html if redacted_field_html
html = Banzai.render_field(object, field, context) redacted_field_html = object.try(:"redacted_#{field}_html")
context.reverse_merge!(object.banzai_render_context(field)) if object.respond_to?(:banzai_render_context) return redacted_field_html if redacted_field_html
prepare_for_rendering(html, context) render_markdown_field(object, field, context)
end end
def markup(file_name, text, context = {}) def markup(file_name, text, context = {})
...@@ -277,6 +275,23 @@ module MarkupHelper ...@@ -277,6 +275,23 @@ module MarkupHelper
Gitlab::OtherMarkup.render(file_name, text, context) Gitlab::OtherMarkup.render(file_name, text, context)
end end
def render_markdown_field(object, field, context = {})
post_process = context.delete(:post_process)
post_process = true if post_process.nil?
html = Banzai.render_field(object, field, context)
return html unless post_process
prepare_for_rendering(html, markdown_field_render_context(object, field, context))
end
def markdown_field_render_context(object, field, base_context = {})
return base_context unless object.respond_to?(:banzai_render_context)
base_context.reverse_merge(object.banzai_render_context(field))
end
def prepare_for_rendering(html, context = {}) def prepare_for_rendering(html, context = {})
return '' unless html.present? return '' unless html.present?
......
...@@ -425,7 +425,7 @@ module ProjectsHelper ...@@ -425,7 +425,7 @@ module ProjectsHelper
{ {
environments: :read_environment, environments: :read_environment,
milestones: :read_milestone, milestones: :read_milestone,
snippets: :read_project_snippet, snippets: :read_snippet,
settings: :admin_project, settings: :admin_project,
builds: :read_build, builds: :read_build,
clusters: :read_cluster, clusters: :read_cluster,
...@@ -443,7 +443,7 @@ module ProjectsHelper ...@@ -443,7 +443,7 @@ module ProjectsHelper
blobs: :download_code, blobs: :download_code,
commits: :download_code, commits: :download_code,
merge_requests: :read_merge_request, merge_requests: :read_merge_request,
notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet], notes: [:read_merge_request, :download_code, :read_issue, :read_snippet],
members: :read_project_member members: :read_project_member
) )
end end
......
...@@ -26,19 +26,17 @@ module Emails ...@@ -26,19 +26,17 @@ module Emails
mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id, reason)) mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id, reason))
end end
def note_project_snippet_email(recipient_id, note_id, reason = nil) def note_snippet_email(recipient_id, note_id, reason = nil)
setup_note_mail(note_id, recipient_id) setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable @snippet = @note.noteable
@target_url = project_snippet_url(*note_target_url_options)
mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason))
end
def note_personal_snippet_email(recipient_id, note_id, reason = nil) case @snippet
setup_note_mail(note_id, recipient_id) when ProjectSnippet
@target_url = project_snippet_url(*note_target_url_options)
when Snippet
@target_url = gitlab_snippet_url(@note.noteable)
end
@snippet = @note.noteable
@target_url = gitlab_snippet_url(@note.noteable)
mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason)) mail_answer_note_thread(@snippet, @note, note_thread_options(recipient_id, reason))
end end
......
...@@ -24,7 +24,7 @@ class Ability ...@@ -24,7 +24,7 @@ class Ability
# read the given snippet. # read the given snippet.
def users_that_can_read_personal_snippet(users, snippet) def users_that_can_read_personal_snippet(users, snippet)
DeclarativePolicy.subject_scope do DeclarativePolicy.subject_scope do
users.select { |u| allowed?(u, :read_personal_snippet, snippet) } users.select { |u| allowed?(u, :read_snippet, snippet) }
end end
end end
......
...@@ -62,6 +62,10 @@ module ProjectFeaturesCompatibility ...@@ -62,6 +62,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:snippets_access_level, value) write_feature_attribute_string(:snippets_access_level, value)
end end
def pages_access_level=(value)
write_feature_attribute_string(:pages_access_level, value)
end
private private
def write_feature_attribute_boolean(field, value) def write_feature_attribute_boolean(field, value)
......
...@@ -145,10 +145,8 @@ class Event < ApplicationRecord ...@@ -145,10 +145,8 @@ class Event < ApplicationRecord
Ability.allowed?(user, :read_issue, note? ? note_target : target) Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note? elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target) Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
elsif personal_snippet_note? elsif personal_snippet_note? || project_snippet_note?
Ability.allowed?(user, :read_personal_snippet, note_target) Ability.allowed?(user, :read_snippet, note_target)
elsif project_snippet_note?
Ability.allowed?(user, :read_project_snippet, note_target)
elsif milestone? elsif milestone?
Ability.allowed?(user, :read_milestone, project) Ability.allowed?(user, :read_milestone, project)
else else
......
...@@ -367,7 +367,7 @@ class Note < ApplicationRecord ...@@ -367,7 +367,7 @@ class Note < ApplicationRecord
end end
def noteable_ability_name def noteable_ability_name
for_snippet? ? noteable.class.name.underscore : noteable_type.demodulize.underscore for_snippet? ? 'snippet' : noteable_type.demodulize.underscore
end end
def can_be_discussion_note? def can_be_discussion_note?
......
...@@ -322,7 +322,7 @@ class Project < ApplicationRecord ...@@ -322,7 +322,7 @@ class Project < ApplicationRecord
:pages_enabled?, :public_pages?, :private_pages?, :pages_enabled?, :public_pages?, :private_pages?,
:merge_requests_access_level, :forking_access_level, :issues_access_level, :merge_requests_access_level, :forking_access_level, :issues_access_level,
:wiki_access_level, :snippets_access_level, :builds_access_level, :wiki_access_level, :snippets_access_level, :builds_access_level,
:repository_access_level, :repository_access_level, :pages_access_level,
to: :project_feature, allow_nil: true to: :project_feature, allow_nil: true
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?, delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true prefix: :import, to: :import_state, allow_nil: true
...@@ -2274,7 +2274,7 @@ class Project < ApplicationRecord ...@@ -2274,7 +2274,7 @@ class Project < ApplicationRecord
end end
def snippets_visible?(user = nil) def snippets_visible?(user = nil)
Ability.allowed?(user, :read_project_snippet, self) Ability.allowed?(user, :read_snippet, self)
end end
def max_attachment_size def max_attachment_size
......
...@@ -215,9 +215,7 @@ class Snippet < ApplicationRecord ...@@ -215,9 +215,7 @@ class Snippet < ApplicationRecord
end end
def embeddable? def embeddable?
ability = project_id? ? :read_project_snippet : :read_personal_snippet Ability.allowed?(nil, :read_snippet, self)
Ability.allowed?(nil, ability, self)
end end
def notes_with_associations def notes_with_associations
...@@ -240,7 +238,7 @@ class Snippet < ApplicationRecord ...@@ -240,7 +238,7 @@ class Snippet < ApplicationRecord
end end
def to_ability_name def to_ability_name
model_name.singular 'snippet'
end end
def valid_secret_token?(token) def valid_secret_token?(token)
......
...@@ -75,7 +75,7 @@ class GlobalPolicy < BasePolicy ...@@ -75,7 +75,7 @@ class GlobalPolicy < BasePolicy
rule { ~anonymous }.policy do rule { ~anonymous }.policy do
enable :read_instance_metadata enable :read_instance_metadata
enable :create_personal_snippet enable :create_snippet
end end
rule { admin }.policy do rule { admin }.policy do
...@@ -83,7 +83,7 @@ class GlobalPolicy < BasePolicy ...@@ -83,7 +83,7 @@ class GlobalPolicy < BasePolicy
enable :update_custom_attribute enable :update_custom_attribute
end end
rule { external_user }.prevent :create_personal_snippet rule { external_user }.prevent :create_snippet
end end
GlobalPolicy.prepend_if_ee('EE::GlobalPolicy') GlobalPolicy.prepend_if_ee('EE::GlobalPolicy')
...@@ -6,19 +6,19 @@ class PersonalSnippetPolicy < BasePolicy ...@@ -6,19 +6,19 @@ class PersonalSnippetPolicy < BasePolicy
condition(:internal_snippet, scope: :subject) { @subject.internal? } condition(:internal_snippet, scope: :subject) { @subject.internal? }
rule { public_snippet }.policy do rule { public_snippet }.policy do
enable :read_personal_snippet enable :read_snippet
enable :create_note enable :create_note
end end
rule { is_author | admin }.policy do rule { is_author | admin }.policy do
enable :read_personal_snippet enable :read_snippet
enable :update_personal_snippet enable :update_snippet
enable :admin_personal_snippet enable :admin_snippet
enable :create_note enable :create_note
end end
rule { internal_snippet & ~external_user }.policy do rule { internal_snippet & ~external_user }.policy do
enable :read_personal_snippet enable :read_snippet
enable :create_note enable :create_note
end end
...@@ -26,8 +26,5 @@ class PersonalSnippetPolicy < BasePolicy ...@@ -26,8 +26,5 @@ class PersonalSnippetPolicy < BasePolicy
rule { can?(:create_note) }.enable :award_emoji rule { can?(:create_note) }.enable :award_emoji
rule { can?(:read_all_resources) }.enable :read_personal_snippet rule { can?(:read_all_resources) }.enable :read_snippet
# Aliasing the ability to ease GraphQL permissions check
rule { can?(:read_personal_snippet) }.enable :read_snippet
end end
...@@ -9,7 +9,7 @@ class ProjectPolicy < BasePolicy ...@@ -9,7 +9,7 @@ class ProjectPolicy < BasePolicy
merge_request merge_request
label label
milestone milestone
project_snippet snippet
wiki wiki
note note
pipeline pipeline
...@@ -185,7 +185,7 @@ class ProjectPolicy < BasePolicy ...@@ -185,7 +185,7 @@ class ProjectPolicy < BasePolicy
enable :read_issue enable :read_issue
enable :read_label enable :read_label
enable :read_milestone enable :read_milestone
enable :read_project_snippet enable :read_snippet
enable :read_project_member enable :read_project_member
enable :read_note enable :read_note
enable :create_project enable :create_project
...@@ -208,7 +208,7 @@ class ProjectPolicy < BasePolicy ...@@ -208,7 +208,7 @@ class ProjectPolicy < BasePolicy
enable :download_code enable :download_code
enable :read_statistics enable :read_statistics
enable :download_wiki_code enable :download_wiki_code
enable :create_project_snippet enable :create_snippet
enable :update_issue enable :update_issue
enable :reopen_issue enable :reopen_issue
enable :admin_issue enable :admin_issue
...@@ -286,8 +286,8 @@ class ProjectPolicy < BasePolicy ...@@ -286,8 +286,8 @@ class ProjectPolicy < BasePolicy
rule { can?(:maintainer_access) }.policy do rule { can?(:maintainer_access) }.policy do
enable :admin_board enable :admin_board
enable :push_to_delete_protected_branch enable :push_to_delete_protected_branch
enable :update_project_snippet enable :update_snippet
enable :admin_project_snippet enable :admin_snippet
enable :admin_project_member enable :admin_project_member
enable :admin_note enable :admin_note
enable :admin_wiki enable :admin_wiki
...@@ -352,7 +352,7 @@ class ProjectPolicy < BasePolicy ...@@ -352,7 +352,7 @@ class ProjectPolicy < BasePolicy
end end
rule { snippets_disabled }.policy do rule { snippets_disabled }.policy do
prevent(*create_read_update_admin_destroy(:project_snippet)) prevent(*create_read_update_admin_destroy(:snippet))
end end
rule { wiki_disabled }.policy do rule { wiki_disabled }.policy do
...@@ -405,7 +405,7 @@ class ProjectPolicy < BasePolicy ...@@ -405,7 +405,7 @@ class ProjectPolicy < BasePolicy
enable :read_wiki enable :read_wiki
enable :read_label enable :read_label
enable :read_milestone enable :read_milestone
enable :read_project_snippet enable :read_snippet
enable :read_project_member enable :read_project_member
enable :read_merge_request enable :read_merge_request
enable :read_note enable :read_note
......
...@@ -14,44 +14,41 @@ class ProjectSnippetPolicy < BasePolicy ...@@ -14,44 +14,41 @@ class ProjectSnippetPolicy < BasePolicy
# We have to check both project feature visibility and a snippet visibility and take the stricter one # We have to check both project feature visibility and a snippet visibility and take the stricter one
# This will be simplified - check https://gitlab.com/gitlab-org/gitlab-foss/issues/27573 # This will be simplified - check https://gitlab.com/gitlab-org/gitlab-foss/issues/27573
rule { ~can?(:read_project) }.policy do rule { ~can?(:read_project) }.policy do
prevent :read_project_snippet prevent :read_snippet
prevent :update_project_snippet prevent :update_snippet
prevent :admin_project_snippet prevent :admin_snippet
end end
# we have to use this complicated prevent because the delegated project policy # we have to use this complicated prevent because the delegated project
# is overly greedy in allowing :read_project_snippet, since it doesn't have any # policy is overly greedy in allowing :read_snippet, since it doesn't have
# information about the snippet. However, :read_project_snippet on the *project* # any information about the snippet. However, :read_snippet on the *project*
# is used to hide/show various snippet-related controls, so we can't just move # is used to hide/show various snippet-related controls, so we can't just
# all of the handling here. # move all of the handling here.
rule do rule do
all?(private_snippet | (internal_snippet & external_user), all?(private_snippet | (internal_snippet & external_user),
~project.guest, ~project.guest,
~is_author, ~is_author,
~can?(:read_all_resources)) ~can?(:read_all_resources))
end.prevent :read_project_snippet end.prevent :read_snippet
rule { internal_snippet & ~is_author & ~admin }.policy do rule { internal_snippet & ~is_author & ~admin }.policy do
prevent :update_project_snippet prevent :update_snippet
prevent :admin_project_snippet prevent :admin_snippet
end end
rule { public_snippet }.enable :read_project_snippet rule { public_snippet }.enable :read_snippet
rule { is_author & ~project.reporter & ~admin }.policy do rule { is_author & ~project.reporter & ~admin }.policy do
prevent :admin_project_snippet prevent :admin_snippet
end end
rule { is_author | admin }.policy do rule { is_author | admin }.policy do
enable :read_project_snippet enable :read_snippet
enable :update_project_snippet enable :update_snippet
enable :admin_project_snippet enable :admin_snippet
end end
rule { ~can?(:read_project_snippet) }.prevent :create_note rule { ~can?(:read_snippet) }.prevent :create_note
# Aliasing the ability to ease GraphQL permissions check
rule { can?(:read_project_snippet) }.enable :read_snippet
end end
ProjectSnippetPolicy.prepend_if_ee('EE::ProjectSnippetPolicy') ProjectSnippetPolicy.prepend_if_ee('EE::ProjectSnippetPolicy')
...@@ -36,9 +36,7 @@ module Snippets ...@@ -36,9 +36,7 @@ module Snippets
attr_reader :snippet attr_reader :snippet
def user_can_delete_snippet? def user_can_delete_snippet?
return can?(current_user, :admin_project_snippet, snippet) if project can?(current_user, :admin_snippet, snippet)
can?(current_user, :admin_personal_snippet, snippet)
end end
def service_response_error(message, http_status) def service_response_error(message, http_status)
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
.form-group .form-group
= f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light' = f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light'
= f.number_field :session_expire_delay, class: 'form-control' = f.number_field :session_expire_delay, class: 'form-control'
%span.form-text.text-muted#session_expire_delay_help_block= _('GitLab restart is required to apply changes') %span.form-text.text-muted#session_expire_delay_help_block= _('GitLab restart is required to apply changes.')
= render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f = render_if_exists 'admin/application_settings/personal_access_token_expiration_policy', form: f
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- if current_user && current_user.snippets.any? || @snippets.any? - if current_user && current_user.snippets.any? || @snippets.any?
.page-title-controls .page-title-controls
- if can?(current_user, :create_personal_snippet) - if can?(current_user, :create_snippet)
= link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet") = link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet")
.top-area .top-area
......
- @hide_top_links = true - @hide_top_links = true
- page_title "Snippets" - page_title "Snippets"
- header_title "Snippets", dashboard_snippets_path - header_title "Snippets", dashboard_snippets_path
- button_path = new_snippet_path if can?(current_user, :create_personal_snippet) - button_path = new_snippet_path if can?(current_user, :create_snippet)
= render 'dashboard/snippets_head' = render 'dashboard/snippets_head'
- if current_user.snippets.exists? - if current_user.snippets.exists?
......
- @hide_top_links = true
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
= render_dashboard_gold_trial(current_user)
- if current_user
= render 'dashboard/projects_head', project_tab_filter: :explore
- else
= render 'explore/head'
= render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
.nothing-here-block
.svg-content
= image_tag 'illustrations/profile-page/personal-project.svg', size: '75'
.text-content
%h5= _("Maximum page reached")
%p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.")
= link_to _("Back to page %{number}") % { number: @max_page_number }, request.params.merge(page: @max_page_number), class: 'btn btn-inverted'
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
- if @project&.persisted? - if @project&.persisted?
- create_project_issue = show_new_issue_link?(@project) - create_project_issue = show_new_issue_link?(@project)
- merge_project = merge_request_source_project_for_project(@project) - merge_project = merge_request_source_project_for_project(@project)
- create_project_snippet = can?(current_user, :create_project_snippet, @project) - create_project_snippet = can?(current_user, :create_snippet, @project)
- if create_project_issue || merge_project || create_project_snippet - if create_project_issue || merge_project || create_project_snippet
%li.dropdown-bold-header %li.dropdown-bold-header
...@@ -38,5 +38,5 @@ ...@@ -38,5 +38,5 @@
%li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link' %li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
- if current_user.can_create_group? - if current_user.can_create_group?
%li= link_to _('New group'), new_group_path %li= link_to _('New group'), new_group_path
- if current_user.can?(:create_personal_snippet) - if current_user.can?(:create_snippet)
%li= link_to _('New snippet'), new_snippet_path, class: 'qa-global-new-snippet-link' %li= link_to _('New snippet'), new_snippet_path, class: 'qa-global-new-snippet-link'
- can_create_issue = show_new_issue_link?(@project) - can_create_issue = show_new_issue_link?(@project)
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project) - can_create_project_snippet = can?(current_user, :create_snippet, @project)
- can_push_code = can?(current_user, :push_code, @project) - can_push_code = can?(current_user, :push_code, @project)
- create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project) - create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
- merge_project = merge_request_source_project_for_project(@project) - merge_project = merge_request_source_project_for_project(@project)
......
- return unless current_user - return unless current_user
.d-none.d-sm-block .d-none.d-sm-block
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_snippet, @snippet)
= link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do
= _('Edit') = _('Edit')
- if can?(current_user, :admin_project_snippet, @snippet) - if can?(current_user, :admin_snippet, @snippet)
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
= _('Delete') = _('Delete')
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_snippet, @project)
= link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-success', title: _("New snippet") do = link_to new_project_snippet_path(@project), class: 'btn btn-grouped btn-inverted btn-success', title: _("New snippet") do
= _('New snippet') = _('New snippet')
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
= link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam') = link_to _('Submit as spam'), mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :create_snippet, @project) || can?(current_user, :update_snippet, @snippet)
.d-block.d-sm-none.dropdown .d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
= _('Options') = _('Options')
= icon('caret-down') = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width .dropdown-menu.dropdown-menu-full-width
%ul %ul
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_snippet, @project)
%li %li
= link_to new_project_snippet_path(@project), title: _("New snippet") do = link_to new_project_snippet_path(@project), title: _("New snippet") do
= _('New snippet') = _('New snippet')
- if can?(current_user, :admin_project_snippet, @snippet) - if can?(current_user, :admin_snippet, @snippet)
%li %li
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do = link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _('Delete') = _('Delete')
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_snippet, @snippet)
%li %li
= link_to edit_project_snippet_path(@project, @snippet) do = link_to edit_project_snippet_path(@project, @snippet) do
= _('Edit') = _('Edit')
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
- include_private = @project.team.member?(current_user) || current_user.admin? - include_private = @project.team.member?(current_user) || current_user.admin?
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_snippet, @project)
.nav-controls .nav-controls
= link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet") = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
......
- return unless current_user - return unless current_user
.d-none.d-sm-block .d-none.d-sm-block
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
= _("Edit") = _("Edit")
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_snippet, @snippet)
= link_to gitlab_snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do = link_to gitlab_snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
= _("Delete") = _("Delete")
- if can?(current_user, :create_personal_snippet) - if can?(current_user, :create_snippet)
= link_to new_snippet_path, class: "btn btn-grouped btn-success btn-inverted", title: _("New snippet") do = link_to new_snippet_path, class: "btn btn-grouped btn-success btn-inverted", title: _("New snippet") do
= _("New snippet") = _("New snippet")
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
...@@ -18,15 +18,15 @@ ...@@ -18,15 +18,15 @@
= icon('caret-down') = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width .dropdown-menu.dropdown-menu-full-width
%ul %ul
- if can?(current_user, :create_personal_snippet) - if can?(current_user, :create_snippet)
%li %li
= link_to new_snippet_path, title: _("New snippet") do = link_to new_snippet_path, title: _("New snippet") do
= _("New snippet") = _("New snippet")
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_snippet, @snippet)
%li %li
= link_to gitlab_snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do = link_to gitlab_snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _("Delete") = _("Delete")
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_snippet, @snippet)
%li %li
= link_to edit_snippet_path(@snippet) do = link_to edit_snippet_path(@snippet) do
= _("Edit") = _("Edit")
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
- current_user_empty_message_header = s_('UserProfile|You haven\'t created any snippets.') - current_user_empty_message_header = s_('UserProfile|You haven\'t created any snippets.')
- current_user_empty_message_description = s_('UserProfile|Snippets in GitLab can either be private, internal, or public.') - current_user_empty_message_description = s_('UserProfile|Snippets in GitLab can either be private, internal, or public.')
- primary_button_label = _('New snippet') - primary_button_label = _('New snippet')
- primary_button_link = new_snippet_path if can?(current_user, :create_personal_snippet) - primary_button_link = new_snippet_path if can?(current_user, :create_snippet)
- visitor_empty_message = s_('UserProfile|No snippets found.') - visitor_empty_message = s_('UserProfile|No snippets found.')
.snippets-list-holder .snippets-list-holder
......
---
title: Reducing whitespace in group list to show more on screen and reduce vertical scrolling
merge_request: 21584
author:
type: other
---
title: Limit page number on explore/projects
merge_request: 22876
author:
type: performance
---
title: Prevent unnecessary Gitaly calls when rendering comment excerpts in todos and
activity feed
merge_request: 23100
author:
type: performance
---
title: Stop NoMethodError happening for 1.16+ Kubernetes clusters
merge_request: 23149
author:
type: fixed
---
title: Add pages_access_level to projects API
merge_request: 23176
author: Mathieu Parent
type: added
...@@ -1008,6 +1008,7 @@ POST /projects ...@@ -1008,6 +1008,7 @@ POST /projects
| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `pages_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push | | `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) | | `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
...@@ -1074,6 +1075,7 @@ POST /projects/user/:user_id ...@@ -1074,6 +1075,7 @@ POST /projects/user/:user_id
| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `pages_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push | | `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project | | `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
...@@ -1139,6 +1141,7 @@ PUT /projects/:id ...@@ -1139,6 +1141,7 @@ PUT /projects/:id
| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` | | `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `pages_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push | | `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project | | `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) | | `container_expiration_policy_attributes` | hash | no | Update the container expiration policy for this project. Accepts: `cadence` (string), `keep_n` (string), `older_than` (string), `name_regex` (string), `enabled` (boolean) |
......
...@@ -167,8 +167,15 @@ end ...@@ -167,8 +167,15 @@ end
Normally, GitLab CE/EE tests use a local clone of Gitaly in Normally, GitLab CE/EE tests use a local clone of Gitaly in
`tmp/tests/gitaly` pinned at the version specified in `tmp/tests/gitaly` pinned at the version specified in
`GITALY_SERVER_VERSION`. The `GITALY_SERVER_VERSION` file supports also `GITALY_SERVER_VERSION`. The `GITALY_SERVER_VERSION` file supports also
branches and SHA to use a custom commit in <https://gitlab.com/gitlab-org/gitaly>. If branches and SHA to use a custom commit in <https://gitlab.com/gitlab-org/gitaly>.
you want to run tests locally against a modified version of Gitaly you
NOTE: **Note:**
With the introduction of auto-deploy for Gitaly, the format of
`GITALY_SERVER_VERSION` was aligned with Omnibus syntax.
It no longer supports `=revision`, it will evaluate the file content as a Git
reference (branch or SHA), only if it matches a semver it will prepend a `v`.
If you want to run tests locally against a modified version of Gitaly you
can replace `tmp/tests/gitaly` with a symlink. This is much faster can replace `tmp/tests/gitaly` with a symlink. This is much faster
because if will avoid a Gitaly re-install each time you run `rspec`. because if will avoid a Gitaly re-install each time you run `rspec`.
...@@ -185,8 +192,7 @@ to manually run `make` again. ...@@ -185,8 +192,7 @@ to manually run `make` again.
Note that CI tests will not use your locally modified version of Note that CI tests will not use your locally modified version of
Gitaly. To use a custom Gitaly version in CI you need to update Gitaly. To use a custom Gitaly version in CI you need to update
GITALY_SERVER_VERSION. You can use the format `=revision` to use a GITALY_SERVER_VERSION as described at the beginning of this paragraph.
non-tagged commit from <https://gitlab.com/gitlab-org/gitaly> in CI.
To use a different Gitaly repository, e.g., if your changes are present To use a different Gitaly repository, e.g., if your changes are present
on a fork, you can specify a `GITALY_REPO_URL` environment variable when on a fork, you can specify a `GITALY_REPO_URL` environment variable when
......
...@@ -157,7 +157,7 @@ The plain text title and description of the issue fill the top center of the iss ...@@ -157,7 +157,7 @@ The plain text title and description of the issue fill the top center of the iss
The description fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm), The description fully supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm),
allowing many formatting options. allowing many formatting options.
> [Since GitLab 12.5](https://gitlab.com/gitlab-org/gitlab/issues/10103), changes to an issue's description are listed in the [issue history](#23-issue-history).**(STARTER)** > [Since GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/issues/10103), changes to an issue's description are listed in the [issue history](#23-issue-history).**(STARTER)**
#### 17. Mentions #### 17. Mentions
......
...@@ -127,6 +127,8 @@ module API ...@@ -127,6 +127,8 @@ module API
case awardable case awardable
when Note when Note
read_ability(awardable.noteable) read_ability(awardable.noteable)
when Snippet, ProjectSnippet
:read_snippet
else else
:"read_#{awardable.class.to_s.underscore}" :"read_#{awardable.class.to_s.underscore}"
end end
......
...@@ -311,6 +311,7 @@ module API ...@@ -311,6 +311,7 @@ module API
expose(:wiki_access_level) { |project, options| project.project_feature.string_access_level(:wiki) } expose(:wiki_access_level) { |project, options| project.project_feature.string_access_level(:wiki) }
expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) } expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) }
expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) } expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) }
expose(:pages_access_level) { |project, options| project.project_feature.string_access_level(:pages) }
expose :shared_runners_enabled expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled expose :lfs_enabled?, as: :lfs_enabled
......
...@@ -72,7 +72,15 @@ module API ...@@ -72,7 +72,15 @@ module API
end end
def noteable_read_ability_name(noteable) def noteable_read_ability_name(noteable)
"read_#{noteable.class.to_s.underscore}".to_sym "read_#{ability_name(noteable)}".to_sym
end
def ability_name(noteable)
if noteable.respond_to?(:to_ability_name)
noteable.to_ability_name
else
noteable.class.to_s.underscore
end
end end
def find_noteable(parent_type, parent_id, noteable_type, noteable_id) def find_noteable(parent_type, parent_id, noteable_type, noteable_id)
......
...@@ -27,6 +27,7 @@ module API ...@@ -27,6 +27,7 @@ module API
optional :wiki_access_level, type: String, values: %w(disabled private enabled), desc: 'Wiki access level. One of `disabled`, `private` or `enabled`' optional :wiki_access_level, type: String, values: %w(disabled private enabled), desc: 'Wiki access level. One of `disabled`, `private` or `enabled`'
optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`' optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`'
optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`' optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`'
optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`'
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project' optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push' optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
...@@ -107,6 +108,7 @@ module API ...@@ -107,6 +108,7 @@ module API
:name, :name,
:only_allow_merge_if_all_discussions_are_resolved, :only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds, :only_allow_merge_if_pipeline_succeeds,
:pages_access_level,
:path, :path,
:printing_merge_request_link_enabled, :printing_merge_request_link_enabled,
:public_builds, :public_builds,
......
...@@ -60,7 +60,7 @@ module API ...@@ -60,7 +60,7 @@ module API
mutually_exclusive :code, :content mutually_exclusive :code, :content
end end
post ":id/snippets" do post ":id/snippets" do
authorize! :create_project_snippet, user_project authorize! :create_snippet, user_project
snippet_params = declared_params(include_missing: false).merge(request: request, api: true) snippet_params = declared_params(include_missing: false).merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present? snippet_params[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
...@@ -97,7 +97,7 @@ module API ...@@ -97,7 +97,7 @@ module API
snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id)) snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id))
not_found!('Snippet') unless snippet not_found!('Snippet') unless snippet
authorize! :update_project_snippet, snippet authorize! :update_snippet, snippet
snippet_params = declared_params(include_missing: false) snippet_params = declared_params(include_missing: false)
.merge(request: request, api: true) .merge(request: request, api: true)
...@@ -126,7 +126,7 @@ module API ...@@ -126,7 +126,7 @@ module API
snippet = snippets_for_current_user.find_by(id: params[:snippet_id]) snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
not_found!('Snippet') unless snippet not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet authorize! :admin_snippet, snippet
destroy_conditionally!(snippet) do |snippet| destroy_conditionally!(snippet) do |snippet|
service = ::Snippets::DestroyService.new(current_user, snippet) service = ::Snippets::DestroyService.new(current_user, snippet)
......
...@@ -106,7 +106,7 @@ module API ...@@ -106,7 +106,7 @@ module API
snippet = snippets_for_current_user.find_by_id(params.delete(:id)) snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
authorize! :update_personal_snippet, snippet authorize! :update_snippet, snippet
attrs = declared_params(include_missing: false).merge(request: request, api: true) attrs = declared_params(include_missing: false).merge(request: request, api: true)
service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet) service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet)
...@@ -132,7 +132,7 @@ module API ...@@ -132,7 +132,7 @@ module API
snippet = snippets_for_current_user.find_by_id(params.delete(:id)) snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet break not_found!('Snippet') unless snippet
authorize! :admin_personal_snippet, snippet authorize! :admin_snippet, snippet
destroy_conditionally!(snippet) do |snippet| destroy_conditionally!(snippet) do |snippet|
service = ::Snippets::DestroyService.new(current_user, snippet) service = ::Snippets::DestroyService.new(current_user, snippet)
......
...@@ -12,7 +12,7 @@ module Banzai ...@@ -12,7 +12,7 @@ module Banzai
private private
def can_read_reference?(user, ref_project, node) def can_read_reference?(user, ref_project, node)
can?(user, :read_project_snippet, referenced_by([node]).first) can?(user, :read_snippet, referenced_by([node]).first)
end end
end end
end end
......
...@@ -6,7 +6,7 @@ module Gitlab ...@@ -6,7 +6,7 @@ module Gitlab
CleanupError = Class.new(StandardError) CleanupError = Class.new(StandardError)
BG_CLEANUP_RUNTIME_S = 10 BG_CLEANUP_RUNTIME_S = 10
FG_CLEANUP_RUNTIME_S = 0.5 FG_CLEANUP_RUNTIME_S = 1
MUTEX = Mutex.new MUTEX = Mutex.new
...@@ -127,7 +127,10 @@ module Gitlab ...@@ -127,7 +127,10 @@ module Gitlab
# error. # error.
# Failing to remove the tmp directory could leave the `gpg-agent` process # Failing to remove the tmp directory could leave the `gpg-agent` process
# running forever. # running forever.
Retriable.retriable(max_elapsed_time: cleanup_time, base_interval: 0.1) do #
# 15 tries will never complete within the maximum time with exponential
# backoff. So our limit is the runtime, not the number of tries.
Retriable.retriable(max_elapsed_time: cleanup_time, base_interval: 0.1, tries: 15) do
FileUtils.remove_entry(tmp_dir) if File.exist?(tmp_dir) FileUtils.remove_entry(tmp_dir) if File.exist?(tmp_dir)
end end
rescue => e rescue => e
......
...@@ -16,6 +16,7 @@ module Gitlab ...@@ -16,6 +16,7 @@ module Gitlab
SUPPORTED_API_GROUPS = { SUPPORTED_API_GROUPS = {
core: { group: 'api', version: 'v1' }, core: { group: 'api', version: 'v1' },
rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' }, rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' },
apps: { group: 'apis/apps', version: 'v1' },
extensions: { group: 'apis/extensions', version: 'v1beta1' }, extensions: { group: 'apis/extensions', version: 'v1beta1' },
istio: { group: 'apis/networking.istio.io', version: 'v1alpha3' }, istio: { group: 'apis/networking.istio.io', version: 'v1alpha3' },
knative: { group: 'apis/serving.knative.dev', version: 'v1alpha1' } knative: { group: 'apis/serving.knative.dev', version: 'v1alpha1' }
...@@ -74,10 +75,6 @@ module Gitlab ...@@ -74,10 +75,6 @@ module Gitlab
:update_role_binding, :update_role_binding,
to: :rbac_client to: :rbac_client
# Deployments resource is currently on the apis/extensions api group
delegate :get_deployments,
to: :extensions_client
# non-entity methods that can only work with the core client # non-entity methods that can only work with the core client
# as it uses the pods/log resource # as it uses the pods/log resource
delegate :get_pod_log, delegate :get_pod_log,
...@@ -103,6 +100,21 @@ module Gitlab ...@@ -103,6 +100,21 @@ module Gitlab
validate_url! validate_url!
end end
# Deployments resource is currently on the apis/extensions api group
# until Kubernetes 1.15. Kubernetest 1.16+ has deployments resources in
# the apis/apps api group.
#
# As we still support Kubernetes 1.12+, we will need to support both.
def get_deployments(**args)
extensions_client.discover unless extensions_client.discovered
if extensions_client.respond_to?(:get_deployments)
extensions_client.get_deployments(**args)
else
apps_client.get_deployments(**args)
end
end
def create_or_update_cluster_role_binding(resource) def create_or_update_cluster_role_binding(resource)
if cluster_role_binding_exists?(resource) if cluster_role_binding_exists?(resource)
update_cluster_role_binding(resource) update_cluster_role_binding(resource)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -2528,6 +2528,9 @@ msgstr "" ...@@ -2528,6 +2528,9 @@ msgstr ""
msgid "Average per day: %{average}" msgid "Average per day: %{average}"
msgstr "" msgstr ""
msgid "Back to page %{number}"
msgstr ""
msgid "Background Color" msgid "Background Color"
msgstr "" msgstr ""
...@@ -8951,7 +8954,7 @@ msgstr "" ...@@ -8951,7 +8954,7 @@ msgstr ""
msgid "GitLab project export" msgid "GitLab project export"
msgstr "" msgstr ""
msgid "GitLab restart is required to apply changes" msgid "GitLab restart is required to apply changes."
msgstr "" msgstr ""
msgid "GitLab single sign on URL" msgid "GitLab single sign on URL"
...@@ -10922,7 +10925,7 @@ msgstr "" ...@@ -10922,7 +10925,7 @@ msgstr ""
msgid "Leave Admin Mode" msgid "Leave Admin Mode"
msgstr "" msgstr ""
msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked" msgid "Leave blank for no limit. Once set, existing personal access tokens may be revoked."
msgstr "" msgstr ""
msgid "Leave edit mode? All unsaved changes will be lost." msgid "Leave edit mode? All unsaved changes will be lost."
...@@ -11496,6 +11499,9 @@ msgstr "" ...@@ -11496,6 +11499,9 @@ msgstr ""
msgid "Maximum number of mirrors that can be synchronizing at the same time." msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr "" msgstr ""
msgid "Maximum page reached"
msgstr ""
msgid "Maximum push size (MB)" msgid "Maximum push size (MB)"
msgstr "" msgstr ""
...@@ -17410,6 +17416,9 @@ msgstr "" ...@@ -17410,6 +17416,9 @@ msgstr ""
msgid "Sorry, no projects matched your search" msgid "Sorry, no projects matched your search"
msgstr "" msgstr ""
msgid "Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further."
msgstr ""
msgid "Sorry, your filter produced no results" msgid "Sorry, your filter produced no results"
msgstr "" msgstr ""
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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