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

Add latest changes from gitlab-org/gitlab@master

parent f47c768f
......@@ -13,7 +13,7 @@
.default-before_script:
before_script:
- 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
- mkdir -p $GOPATH
- source scripts/utils.sh
......
......@@ -40,7 +40,7 @@
paths:
- vendor/ruby
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/
- bundle install --clean --jobs=$(nproc) --path=vendor --retry=3 --quiet
- bundle check
......@@ -50,6 +50,13 @@ qa:internal:
script:
- bundle exec rspec
qa:internal-foss:
extends:
- .qa-job-base
- .only-ee-as-if-foss
script:
- bundle exec rspec
qa:selectors:
extends: .qa-job-base
script:
......
......@@ -44,7 +44,7 @@ export default {
:action="action"
/>
<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 }}
</a>
</li>
......
......@@ -91,7 +91,7 @@ export default {
<li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup">
<div
: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">
<item-caret :is-group-open="group.isOpen" />
......@@ -104,7 +104,7 @@ export default {
/>
<div
: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">
<img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s40" />
......
......@@ -47,7 +47,7 @@ export default {
<template>
<li :id="envId" :class="isOpenClass" class="group-row has-children">
<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"
@click.stop="toggleOpen"
>
......
......@@ -63,7 +63,7 @@ export default {
<template>
<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">
<span>{{ image }}</span
><br />
......
......@@ -382,8 +382,6 @@ table.pipeline-project-metrics tr td {
}
.group-row-contents {
padding: $gl-padding;
&:hover {
border-color: $blue-200;
background-color: $blue-50;
......@@ -410,13 +408,7 @@ table.pipeline-project-metrics tr td {
.title {
margin-top: -$gl-padding-8; // negative margin required for flex-wrap
font-size: $gl-font-size-large;
}
@include media-breakpoint-down(md) {
.title {
font-size: $gl-font-size;
}
font-size: $gl-font-size;
}
&.has-more-items {
......@@ -483,7 +475,6 @@ table.pipeline-project-metrics tr td {
.last-updated {
position: relative;
right: 12px;
min-width: 250px;
text-align: right;
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
class Explore::ProjectsController < Explore::ApplicationController
include PageLimiter
include ParamsBackwardCompatibility
include RendersMemberAccess
include SortingHelper
......@@ -9,6 +10,13 @@ class Explore::ProjectsController < Explore::ApplicationController
before_action :set_non_archived_param
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
@projects = load_projects
......@@ -53,10 +61,14 @@ class Explore::ProjectsController < Explore::ApplicationController
private
# rubocop: disable CodeReuse/ActiveRecord
def load_projects
def load_project_counts
@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
end
# rubocop: disable CodeReuse/ActiveRecord
def load_projects
load_project_counts
projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute
......@@ -80,4 +92,21 @@ class Explore::ProjectsController < Explore::ApplicationController
def sorting_field
Project::SORTING_PREFERENCE_FIELD
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
......@@ -15,17 +15,17 @@ class Projects::SnippetsController < Projects::ApplicationController
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
# Allow create snippet
before_action :authorize_create_snippet!, only: [:new, :create]
# Allow write(create) snippet
before_action :authorize_create_project_snippet!, only: [:new, :create]
# Allow read any snippet
before_action :authorize_read_snippet!, except: [:new, :create, :index]
# Allow modify snippet
before_action :authorize_update_project_snippet!, only: [:edit, :update]
before_action :authorize_update_snippet!, only: [:edit, :update]
# Allow destroy snippet
before_action :authorize_admin_project_snippet!, only: [:destroy]
before_action :authorize_admin_snippet!, only: [:destroy]
respond_to :html
......@@ -115,16 +115,16 @@ class Projects::SnippetsController < Projects::ApplicationController
project_snippet_path(@project, @snippet)
end
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
def authorize_read_snippet!
return render_404 unless can?(current_user, :read_snippet, @snippet)
end
def authorize_update_project_snippet!
return render_404 unless can?(current_user, :update_project_snippet, @snippet)
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_snippet, @snippet)
end
def authorize_admin_project_snippet!
return render_404 unless can?(current_user, :admin_project_snippet, @snippet)
def authorize_admin_snippet!
return render_404 unless can?(current_user, :admin_snippet, @snippet)
end
def snippet_params
......
......@@ -33,7 +33,7 @@ class Snippets::NotesController < ApplicationController
end
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
def authorize_create_note!
......
......@@ -126,7 +126,7 @@ class SnippetsController < ApplicationController
end
def authorize_read_snippet!
return if can?(current_user, :read_personal_snippet, @snippet)
return if can?(current_user, :read_snippet, @snippet)
if current_user
render_404
......@@ -136,15 +136,15 @@ class SnippetsController < ApplicationController
end
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
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
def authorize_create_snippet!
return render_404 unless can?(current_user, :create_personal_snippet)
return render_404 unless can?(current_user, :create_snippet)
end
def snippet_params
......
......@@ -41,6 +41,8 @@ class UploadsController < ApplicationController
case model
when Note
can?(current_user, :read_project, model.project)
when Snippet, ProjectSnippet
can?(current_user, :read_snippet, model)
when User
# We validate the current user has enough (writing)
# access to itself when a secret is given.
......
......@@ -67,11 +67,11 @@ module Mutations
end
def authorized_resource?(project)
Ability.allowed?(context[:current_user], :create_project_snippet, project)
Ability.allowed?(context[:current_user], :create_snippet, project)
end
def can_create_personal_snippet?
Ability.allowed?(context[:current_user], :create_personal_snippet)
Ability.allowed?(context[:current_user], :create_snippet)
end
end
end
......
......@@ -21,7 +21,7 @@ module Types
permission_field :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
......
......@@ -8,7 +8,7 @@ module Types
permission_field :create_snippet
def create_snippet
Ability.allowed?(context[:current_user], :create_personal_snippet)
Ability.allowed?(context[:current_user], :create_snippet)
end
end
end
......
......@@ -76,13 +76,14 @@ module MarkupHelper
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
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?
tags = %w(a gl-emoji b pre code p span)
tags << 'img' if options[:allow_images]
text = truncate_visible(md, max_chars || md.length)
text = prepare_for_rendering(text, markdown_field_render_context(object, attribute, options))
text = sanitize(
text,
tags: tags,
......@@ -107,15 +108,12 @@ module MarkupHelper
def markdown_field(object, field, context = {})
object = object.for_display if object.respond_to?(:for_display)
redacted_field_html = object.try(:"redacted_#{field}_html")
return '' unless object.present?
return redacted_field_html if redacted_field_html
html = Banzai.render_field(object, field, context)
context.reverse_merge!(object.banzai_render_context(field)) if object.respond_to?(:banzai_render_context)
redacted_field_html = object.try(:"redacted_#{field}_html")
return redacted_field_html if redacted_field_html
prepare_for_rendering(html, context)
render_markdown_field(object, field, context)
end
def markup(file_name, text, context = {})
......@@ -277,6 +275,23 @@ module MarkupHelper
Gitlab::OtherMarkup.render(file_name, text, context)
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 = {})
return '' unless html.present?
......
......@@ -425,7 +425,7 @@ module ProjectsHelper
{
environments: :read_environment,
milestones: :read_milestone,
snippets: :read_project_snippet,
snippets: :read_snippet,
settings: :admin_project,
builds: :read_build,
clusters: :read_cluster,
......@@ -443,7 +443,7 @@ module ProjectsHelper
blobs: :download_code,
commits: :download_code,
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
)
end
......
......@@ -26,19 +26,17 @@ module Emails
mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id, reason))
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)
@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)
setup_note_mail(note_id, recipient_id)
case @snippet
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))
end
......
......@@ -24,7 +24,7 @@ class Ability
# read the given snippet.
def users_that_can_read_personal_snippet(users, snippet)
DeclarativePolicy.subject_scope do
users.select { |u| allowed?(u, :read_personal_snippet, snippet) }
users.select { |u| allowed?(u, :read_snippet, snippet) }
end
end
......
......@@ -62,6 +62,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:snippets_access_level, value)
end
def pages_access_level=(value)
write_feature_attribute_string(:pages_access_level, value)
end
private
def write_feature_attribute_boolean(field, value)
......
......@@ -145,10 +145,8 @@ class Event < ApplicationRecord
Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
elsif personal_snippet_note?
Ability.allowed?(user, :read_personal_snippet, note_target)
elsif project_snippet_note?
Ability.allowed?(user, :read_project_snippet, note_target)
elsif personal_snippet_note? || project_snippet_note?
Ability.allowed?(user, :read_snippet, note_target)
elsif milestone?
Ability.allowed?(user, :read_milestone, project)
else
......
......@@ -367,7 +367,7 @@ class Note < ApplicationRecord
end
def noteable_ability_name
for_snippet? ? noteable.class.name.underscore : noteable_type.demodulize.underscore
for_snippet? ? 'snippet' : noteable_type.demodulize.underscore
end
def can_be_discussion_note?
......
......@@ -322,7 +322,7 @@ class Project < ApplicationRecord
:pages_enabled?, :public_pages?, :private_pages?,
:merge_requests_access_level, :forking_access_level, :issues_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
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true
......@@ -2274,7 +2274,7 @@ class Project < ApplicationRecord
end
def snippets_visible?(user = nil)
Ability.allowed?(user, :read_project_snippet, self)
Ability.allowed?(user, :read_snippet, self)
end
def max_attachment_size
......
......@@ -215,9 +215,7 @@ class Snippet < ApplicationRecord
end
def embeddable?
ability = project_id? ? :read_project_snippet : :read_personal_snippet
Ability.allowed?(nil, ability, self)
Ability.allowed?(nil, :read_snippet, self)
end
def notes_with_associations
......@@ -240,7 +238,7 @@ class Snippet < ApplicationRecord
end
def to_ability_name
model_name.singular
'snippet'
end
def valid_secret_token?(token)
......
......@@ -75,7 +75,7 @@ class GlobalPolicy < BasePolicy
rule { ~anonymous }.policy do
enable :read_instance_metadata
enable :create_personal_snippet
enable :create_snippet
end
rule { admin }.policy do
......@@ -83,7 +83,7 @@ class GlobalPolicy < BasePolicy
enable :update_custom_attribute
end
rule { external_user }.prevent :create_personal_snippet
rule { external_user }.prevent :create_snippet
end
GlobalPolicy.prepend_if_ee('EE::GlobalPolicy')
......@@ -6,19 +6,19 @@ class PersonalSnippetPolicy < BasePolicy
condition(:internal_snippet, scope: :subject) { @subject.internal? }
rule { public_snippet }.policy do
enable :read_personal_snippet
enable :read_snippet
enable :create_note
end
rule { is_author | admin }.policy do
enable :read_personal_snippet
enable :update_personal_snippet
enable :admin_personal_snippet
enable :read_snippet
enable :update_snippet
enable :admin_snippet
enable :create_note
end
rule { internal_snippet & ~external_user }.policy do
enable :read_personal_snippet
enable :read_snippet
enable :create_note
end
......@@ -26,8 +26,5 @@ class PersonalSnippetPolicy < BasePolicy
rule { can?(:create_note) }.enable :award_emoji
rule { can?(:read_all_resources) }.enable :read_personal_snippet
# Aliasing the ability to ease GraphQL permissions check
rule { can?(:read_personal_snippet) }.enable :read_snippet
rule { can?(:read_all_resources) }.enable :read_snippet
end
......@@ -9,7 +9,7 @@ class ProjectPolicy < BasePolicy
merge_request
label
milestone
project_snippet
snippet
wiki
note
pipeline
......@@ -185,7 +185,7 @@ class ProjectPolicy < BasePolicy
enable :read_issue
enable :read_label
enable :read_milestone
enable :read_project_snippet
enable :read_snippet
enable :read_project_member
enable :read_note
enable :create_project
......@@ -208,7 +208,7 @@ class ProjectPolicy < BasePolicy
enable :download_code
enable :read_statistics
enable :download_wiki_code
enable :create_project_snippet
enable :create_snippet
enable :update_issue
enable :reopen_issue
enable :admin_issue
......@@ -286,8 +286,8 @@ class ProjectPolicy < BasePolicy
rule { can?(:maintainer_access) }.policy do
enable :admin_board
enable :push_to_delete_protected_branch
enable :update_project_snippet
enable :admin_project_snippet
enable :update_snippet
enable :admin_snippet
enable :admin_project_member
enable :admin_note
enable :admin_wiki
......@@ -352,7 +352,7 @@ class ProjectPolicy < BasePolicy
end
rule { snippets_disabled }.policy do
prevent(*create_read_update_admin_destroy(:project_snippet))
prevent(*create_read_update_admin_destroy(:snippet))
end
rule { wiki_disabled }.policy do
......@@ -405,7 +405,7 @@ class ProjectPolicy < BasePolicy
enable :read_wiki
enable :read_label
enable :read_milestone
enable :read_project_snippet
enable :read_snippet
enable :read_project_member
enable :read_merge_request
enable :read_note
......
......@@ -14,44 +14,41 @@ class ProjectSnippetPolicy < BasePolicy
# 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
rule { ~can?(:read_project) }.policy do
prevent :read_project_snippet
prevent :update_project_snippet
prevent :admin_project_snippet
prevent :read_snippet
prevent :update_snippet
prevent :admin_snippet
end
# we have to use this complicated prevent because the delegated project policy
# is overly greedy in allowing :read_project_snippet, since it doesn't have any
# information about the snippet. However, :read_project_snippet on the *project*
# is used to hide/show various snippet-related controls, so we can't just move
# all of the handling here.
# we have to use this complicated prevent because the delegated project
# policy is overly greedy in allowing :read_snippet, since it doesn't have
# 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 all of the handling here.
rule do
all?(private_snippet | (internal_snippet & external_user),
~project.guest,
~is_author,
~can?(:read_all_resources))
end.prevent :read_project_snippet
end.prevent :read_snippet
rule { internal_snippet & ~is_author & ~admin }.policy do
prevent :update_project_snippet
prevent :admin_project_snippet
prevent :update_snippet
prevent :admin_snippet
end
rule { public_snippet }.enable :read_project_snippet
rule { public_snippet }.enable :read_snippet
rule { is_author & ~project.reporter & ~admin }.policy do
prevent :admin_project_snippet
prevent :admin_snippet
end
rule { is_author | admin }.policy do
enable :read_project_snippet
enable :update_project_snippet
enable :admin_project_snippet
enable :read_snippet
enable :update_snippet
enable :admin_snippet
end
rule { ~can?(:read_project_snippet) }.prevent :create_note
# Aliasing the ability to ease GraphQL permissions check
rule { can?(:read_project_snippet) }.enable :read_snippet
rule { ~can?(:read_snippet) }.prevent :create_note
end
ProjectSnippetPolicy.prepend_if_ee('EE::ProjectSnippetPolicy')
......@@ -36,9 +36,7 @@ module Snippets
attr_reader :snippet
def user_can_delete_snippet?
return can?(current_user, :admin_project_snippet, snippet) if project
can?(current_user, :admin_personal_snippet, snippet)
can?(current_user, :admin_snippet, snippet)
end
def service_response_error(message, http_status)
......
......@@ -22,7 +22,7 @@
.form-group
= f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light'
= 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
......
......@@ -3,7 +3,7 @@
- if current_user && current_user.snippets.any? || @snippets.any?
.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")
.top-area
......
- @hide_top_links = true
- page_title "Snippets"
- 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'
- 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 @@
- if @project&.persisted?
- create_project_issue = show_new_issue_link?(@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
%li.dropdown-bold-header
......@@ -38,5 +38,5 @@
%li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
- if current_user.can_create_group?
%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'
- 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)
- 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)
......
- return unless current_user
.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
= _('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
= _('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
= _('New snippet')
- 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')
- 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
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
= _('Options')
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
- if can?(current_user, :create_project_snippet, @project)
- if can?(current_user, :create_snippet, @project)
%li
= link_to new_project_snippet_path(@project), title: _("New snippet") do
= _('New snippet')
- if can?(current_user, :admin_project_snippet, @snippet)
- if can?(current_user, :admin_snippet, @snippet)
%li
= link_to project_snippet_path(@project, @snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _('Delete')
- if can?(current_user, :update_project_snippet, @snippet)
- if can?(current_user, :update_snippet, @snippet)
%li
= link_to edit_project_snippet_path(@project, @snippet) do
= _('Edit')
......
......@@ -6,7 +6,7 @@
- include_private = @project.team.member?(current_user) || current_user.admin?
= 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
= link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
......
- return unless current_user
.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
= _("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
= _("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
= _("New snippet")
- if @snippet.submittable_as_spam_by?(current_user)
......@@ -18,15 +18,15 @@
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
- if can?(current_user, :create_personal_snippet)
- if can?(current_user, :create_snippet)
%li
= link_to new_snippet_path, title: _("New snippet") do
= _("New snippet")
- if can?(current_user, :admin_personal_snippet, @snippet)
- if can?(current_user, :admin_snippet, @snippet)
%li
= link_to gitlab_snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _("Delete")
- if can?(current_user, :update_personal_snippet, @snippet)
- if can?(current_user, :update_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
= _("Edit")
......
......@@ -3,7 +3,7 @@
- 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.')
- 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.')
.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
| `builds_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` |
| `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 |
| `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) |
......@@ -1074,6 +1075,7 @@ POST /projects/user/:user_id
| `builds_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` |
| `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 |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
......@@ -1139,6 +1141,7 @@ PUT /projects/:id
| `builds_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` |
| `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 |
| `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) |
......
......@@ -167,8 +167,15 @@ end
Normally, GitLab CE/EE tests use a local clone of Gitaly in
`tmp/tests/gitaly` pinned at the version specified in
`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
you want to run tests locally against a modified version of Gitaly you
branches and SHA to use a custom commit in <https://gitlab.com/gitlab-org/gitaly>.
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
because if will avoid a Gitaly re-install each time you run `rspec`.
......@@ -185,8 +192,7 @@ to manually run `make` again.
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_SERVER_VERSION. You can use the format `=revision` to use a
non-tagged commit from <https://gitlab.com/gitlab-org/gitaly> in CI.
GITALY_SERVER_VERSION as described at the beginning of this paragraph.
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
......
......@@ -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),
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
......
......@@ -127,6 +127,8 @@ module API
case awardable
when Note
read_ability(awardable.noteable)
when Snippet, ProjectSnippet
:read_snippet
else
:"read_#{awardable.class.to_s.underscore}"
end
......
......@@ -311,6 +311,7 @@ module API
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(: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 :lfs_enabled?, as: :lfs_enabled
......
......@@ -72,7 +72,15 @@ module API
end
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
def find_noteable(parent_type, parent_id, noteable_type, noteable_id)
......
......@@ -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 :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 :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 :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
:name,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds,
:pages_access_level,
:path,
:printing_merge_request_link_enabled,
:public_builds,
......
......@@ -60,7 +60,7 @@ module API
mutually_exclusive :code, :content
end
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[:content] = snippet_params.delete(:code) if snippet_params[:code].present?
......@@ -97,7 +97,7 @@ module API
snippet = snippets_for_current_user.find_by(id: params.delete(:snippet_id))
not_found!('Snippet') unless snippet
authorize! :update_project_snippet, snippet
authorize! :update_snippet, snippet
snippet_params = declared_params(include_missing: false)
.merge(request: request, api: true)
......@@ -126,7 +126,7 @@ module API
snippet = snippets_for_current_user.find_by(id: params[:snippet_id])
not_found!('Snippet') unless snippet
authorize! :admin_project_snippet, snippet
authorize! :admin_snippet, snippet
destroy_conditionally!(snippet) do |snippet|
service = ::Snippets::DestroyService.new(current_user, snippet)
......
......@@ -106,7 +106,7 @@ module API
snippet = snippets_for_current_user.find_by_id(params.delete(:id))
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)
service_response = ::Snippets::UpdateService.new(nil, current_user, attrs).execute(snippet)
......@@ -132,7 +132,7 @@ module API
snippet = snippets_for_current_user.find_by_id(params.delete(:id))
break not_found!('Snippet') unless snippet
authorize! :admin_personal_snippet, snippet
authorize! :admin_snippet, snippet
destroy_conditionally!(snippet) do |snippet|
service = ::Snippets::DestroyService.new(current_user, snippet)
......
......@@ -12,7 +12,7 @@ module Banzai
private
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
......
......@@ -6,7 +6,7 @@ module Gitlab
CleanupError = Class.new(StandardError)
BG_CLEANUP_RUNTIME_S = 10
FG_CLEANUP_RUNTIME_S = 0.5
FG_CLEANUP_RUNTIME_S = 1
MUTEX = Mutex.new
......@@ -127,7 +127,10 @@ module Gitlab
# error.
# Failing to remove the tmp directory could leave the `gpg-agent` process
# 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)
end
rescue => e
......
......@@ -16,6 +16,7 @@ module Gitlab
SUPPORTED_API_GROUPS = {
core: { group: 'api', version: 'v1' },
rbac: { group: 'apis/rbac.authorization.k8s.io', version: 'v1' },
apps: { group: 'apis/apps', version: 'v1' },
extensions: { group: 'apis/extensions', version: 'v1beta1' },
istio: { group: 'apis/networking.istio.io', version: 'v1alpha3' },
knative: { group: 'apis/serving.knative.dev', version: 'v1alpha1' }
......@@ -74,10 +75,6 @@ module Gitlab
:update_role_binding,
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
# as it uses the pods/log resource
delegate :get_pod_log,
......@@ -103,6 +100,21 @@ module Gitlab
validate_url!
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)
if cluster_role_binding_exists?(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 ""
msgid "Average per day: %{average}"
msgstr ""
msgid "Back to page %{number}"
msgstr ""
msgid "Background Color"
msgstr ""
......@@ -8951,7 +8954,7 @@ msgstr ""
msgid "GitLab project export"
msgstr ""
msgid "GitLab restart is required to apply changes"
msgid "GitLab restart is required to apply changes."
msgstr ""
msgid "GitLab single sign on URL"
......@@ -10922,7 +10925,7 @@ msgstr ""
msgid "Leave Admin Mode"
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 ""
msgid "Leave edit mode? All unsaved changes will be lost."
......@@ -11496,6 +11499,9 @@ msgstr ""
msgid "Maximum number of mirrors that can be synchronizing at the same time."
msgstr ""
msgid "Maximum page reached"
msgstr ""
msgid "Maximum push size (MB)"
msgstr ""
......@@ -17410,6 +17416,9 @@ msgstr ""
msgid "Sorry, no projects matched your search"
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"
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