Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
dc889678d1
commit
ce8a0b9084
63 changed files with 671 additions and 389 deletions
|
@ -1,4 +1,4 @@
|
|||
query mergeRequest($projectPath: ID!, $mergeRequestIID: String!) {
|
||||
query mergeRequest($projectPath: ID!, $mergeRequestIID: ID!) {
|
||||
project(fullPath: $projectPath) {
|
||||
mergeRequest(iid: $mergeRequestIID) {
|
||||
createdAt
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
query ($fullPath: ID!, $iid: String!) {
|
||||
query ($fullPath: ID!, $iid: ID!) {
|
||||
project (fullPath: $fullPath) {
|
||||
issue (iid: $iid) {
|
||||
iid
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
query ($fullPath: ID!, $iid: String!) {
|
||||
query ($fullPath: ID!, $iid: ID!) {
|
||||
project (fullPath: $fullPath) {
|
||||
issue (iid: $iid) {
|
||||
iid
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
required: true,
|
||||
description: "The project the issue to mutate is in"
|
||||
|
||||
argument :iid, GraphQL::STRING_TYPE,
|
||||
argument :iid, GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: "The iid of the issue to mutate"
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
required: true,
|
||||
description: "The project the merge request to mutate is in"
|
||||
|
||||
argument :iid, GraphQL::STRING_TYPE,
|
||||
argument :iid, GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: "The iid of the merge request to mutate"
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
module Resolvers
|
||||
class IssuesResolver < BaseResolver
|
||||
argument :iid, GraphQL::STRING_TYPE,
|
||||
argument :iid, GraphQL::ID_TYPE,
|
||||
required: false,
|
||||
description: 'IID of the issue. For example, "1"'
|
||||
|
||||
argument :iids, [GraphQL::STRING_TYPE],
|
||||
argument :iids, [GraphQL::ID_TYPE],
|
||||
required: false,
|
||||
description: 'List of IIDs of issues. For example, [1, 2]'
|
||||
argument :state, Types::IssuableStateEnum,
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
module Resolvers
|
||||
class MergeRequestsResolver < BaseResolver
|
||||
argument :iid, GraphQL::STRING_TYPE,
|
||||
argument :iid, GraphQL::ID_TYPE,
|
||||
required: false,
|
||||
description: 'The IID of the merge request, e.g., "1"'
|
||||
|
||||
argument :iids, [GraphQL::STRING_TYPE],
|
||||
argument :iids, [GraphQL::ID_TYPE],
|
||||
required: false,
|
||||
description: 'The list of IIDs of issues, e.g., [1, 2]'
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ module Types
|
|||
|
||||
field :id, GraphQL::ID_TYPE, null: false,
|
||||
description: 'ID of the pipeline'
|
||||
field :iid, GraphQL::STRING_TYPE, null: false,
|
||||
field :iid, GraphQL::ID_TYPE, null: false,
|
||||
description: 'Internal ID of the pipeline'
|
||||
|
||||
field :sha, GraphQL::STRING_TYPE, null: false,
|
||||
|
|
|
@ -14,7 +14,7 @@ module Types
|
|||
|
||||
field :id, GraphQL::ID_TYPE, null: false,
|
||||
description: 'ID of the merge request'
|
||||
field :iid, GraphQL::STRING_TYPE, null: false,
|
||||
field :iid, GraphQL::ID_TYPE, null: false,
|
||||
description: 'Internal ID of the merge request'
|
||||
field :title, GraphQL::STRING_TYPE, null: false,
|
||||
description: 'Title of the merge request'
|
||||
|
|
|
@ -31,7 +31,6 @@ module AnalyticsNavbarHelper
|
|||
end
|
||||
|
||||
def cycle_analytics_navbar_link(project, current_user)
|
||||
return unless Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project, default_enabled: true)
|
||||
return unless project_nav_tab?(:cycle_analytics)
|
||||
|
||||
navbar_sub_item(
|
||||
|
@ -43,7 +42,6 @@ module AnalyticsNavbarHelper
|
|||
end
|
||||
|
||||
def repository_analytics_navbar_link(project, current_user)
|
||||
return if Feature.disabled?(:analytics_pages_under_project_analytics_sidebar, project, default_enabled: true)
|
||||
return if project.empty_repo?
|
||||
|
||||
navbar_sub_item(
|
||||
|
@ -55,7 +53,6 @@ module AnalyticsNavbarHelper
|
|||
end
|
||||
|
||||
def ci_cd_analytics_navbar_link(project, current_user)
|
||||
return unless Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, project, default_enabled: true)
|
||||
return unless project_nav_tab?(:pipelines)
|
||||
return unless project.feature_available?(:builds, current_user) || !project.empty_repo?
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ class Namespace < ApplicationRecord
|
|||
return unless host.ends_with?(gitlab_host)
|
||||
|
||||
name = host.delete_suffix(gitlab_host)
|
||||
Namespace.find_by_path(name)
|
||||
Namespace.where(parent_id: nil).find_by_path(name)
|
||||
end
|
||||
|
||||
# overridden in ee
|
||||
|
|
|
@ -164,6 +164,7 @@ class User < ApplicationRecord
|
|||
has_one :status, class_name: 'UserStatus'
|
||||
has_one :user_preference
|
||||
has_one :user_detail
|
||||
has_one :user_highest_role
|
||||
|
||||
#
|
||||
# Validations
|
||||
|
|
7
app/models/user_highest_role.rb
Normal file
7
app/models/user_highest_role.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UserHighestRole < ApplicationRecord
|
||||
belongs_to :user, optional: false
|
||||
|
||||
validates :highest_access_level, allow_nil: true, inclusion: { in: Gitlab::Access.all_values }
|
||||
end
|
|
@ -5,9 +5,8 @@ class AvatarUploader < GitlabUploader
|
|||
include RecordsUploads::Concern
|
||||
include ObjectStorage::Concern
|
||||
prepend ObjectStorage::Extension::RecordsUploads
|
||||
include UploadTypeCheck::Concern
|
||||
|
||||
check_upload_type extensions: AvatarUploader::SAFE_IMAGE_EXT
|
||||
MIME_WHITELIST = %w[image/png image/jpeg image/gif image/bmp image/tiff image/vnd.microsoft.icon].freeze
|
||||
|
||||
def exists?
|
||||
model.avatar.file && model.avatar.file.present?
|
||||
|
@ -29,6 +28,10 @@ class AvatarUploader < GitlabUploader
|
|||
super || 'avatar'
|
||||
end
|
||||
|
||||
def content_type_whitelist
|
||||
MIME_WHITELIST
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dynamic_segment
|
||||
|
|
53
app/uploaders/content_type_whitelist.rb
Normal file
53
app/uploaders/content_type_whitelist.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Currently we run CarrierWave 1.3.1 which means we can not whitelist files
|
||||
# by their content type through magic header parsing.
|
||||
#
|
||||
# This is a patch to hold us over until we get to CarrierWave 2 :) It's a mashup of
|
||||
# CarrierWave's lib/carrierwave/uploader/content_type_whitelist.rb and
|
||||
# lib/carrierwave/sanitized_file.rb
|
||||
#
|
||||
# Include this concern and add a content_type_whitelist method to get the same
|
||||
# behavior as you would with CarrierWave 2.
|
||||
#
|
||||
# This is not an exact replacement as we don't override
|
||||
# SanitizedFile#content_type but we do set the content_type attribute when we
|
||||
# check the whitelist.
|
||||
#
|
||||
# Remove this after moving to CarrierWave 2, though on practical terms it shouldn't
|
||||
# break anything if left for a while.
|
||||
module ContentTypeWhitelist
|
||||
module Concern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
# CarrierWave calls this method as part of it's before :cache callbacks.
|
||||
# Here we override and extend CarrierWave's method that does not parse the
|
||||
# magic headers.
|
||||
def check_content_type_whitelist!(new_file)
|
||||
new_file.content_type = mime_magic_content_type(new_file.path)
|
||||
|
||||
if content_type_whitelist && !whitelisted_content_type?(new_file.content_type)
|
||||
message = I18n.translate(:"errors.messages.content_type_whitelist_error", allowed_types: Array(content_type_whitelist).join(", "))
|
||||
raise CarrierWave::IntegrityError, message
|
||||
end
|
||||
|
||||
super(new_file)
|
||||
end
|
||||
|
||||
def whitelisted_content_type?(content_type)
|
||||
Array(content_type_whitelist).any? { |item| content_type =~ /#{item}/ }
|
||||
end
|
||||
|
||||
def mime_magic_content_type(path)
|
||||
if path
|
||||
File.open(path) do |file|
|
||||
MimeMagic.by_magic(file).try(:type) || 'invalid/invalid'
|
||||
end
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FaviconUploader < AttachmentUploader
|
||||
include UploadTypeCheck::Concern
|
||||
|
||||
EXTENSION_WHITELIST = %w[png ico].freeze
|
||||
|
||||
check_upload_type extensions: EXTENSION_WHITELIST
|
||||
MIME_WHITELIST = %w[image/png image/vnd.microsoft.icon].freeze
|
||||
|
||||
def extension_whitelist
|
||||
EXTENSION_WHITELIST
|
||||
end
|
||||
|
||||
def content_type_whitelist
|
||||
MIME_WHITELIST
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filename_for_different_format(filename, format)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GitlabUploader < CarrierWave::Uploader::Base
|
||||
include ContentTypeWhitelist::Concern
|
||||
|
||||
class_attribute :options
|
||||
|
||||
class << self
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
- should_display_analytics_pages_in_sidebar = Feature.enabled?(:analytics_pages_under_group_analytics_sidebar, @group, default_enabled: true)
|
||||
- issues_count = group_issues_count(state: 'opened')
|
||||
- merge_requests_count = group_merge_requests_count(state: 'opened')
|
||||
|
||||
|
@ -13,8 +12,7 @@
|
|||
%ul.sidebar-top-level-items.qa-group-sidebar
|
||||
- if group_sidebar_link?(:overview)
|
||||
- paths = group_overview_nav_link_paths
|
||||
- paths << 'contribution_analytics#show' unless should_display_analytics_pages_in_sidebar
|
||||
= nav_link(path: paths, unless: -> { should_display_analytics_pages_in_sidebar && current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do
|
||||
= nav_link(path: paths, unless: -> { current_path?('groups/contribution_analytics#show') }, html_options: { class: 'home' }) do
|
||||
= link_to group_path(@group) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('home')
|
||||
|
@ -45,19 +43,10 @@
|
|||
%span
|
||||
= _('Activity')
|
||||
|
||||
- unless should_display_analytics_pages_in_sidebar
|
||||
- if group_sidebar_link?(:contribution_analytics)
|
||||
= nav_link(path: 'contribution_analytics#show') do
|
||||
= link_to group_contribution_analytics_path(@group), title: _('Contribution'), data: { placement: 'right', qa_selector: 'contribution_analytics_link' } do
|
||||
%span
|
||||
= _('Contribution')
|
||||
|
||||
= render_if_exists 'layouts/nav/group_insights_link'
|
||||
|
||||
= render_if_exists "layouts/nav/ee/epic_link", group: @group
|
||||
|
||||
- if group_sidebar_link?(:issues)
|
||||
= nav_link(path: group_issues_sub_menu_items, unless: -> { should_display_analytics_pages_in_sidebar && current_path?('issues_analytics#show') }) do
|
||||
= nav_link(path: group_issues_sub_menu_items, unless: -> { current_path?('issues_analytics#show') }) do
|
||||
= link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('issues')
|
||||
|
@ -84,9 +73,6 @@
|
|||
%span
|
||||
= boards_link_text
|
||||
|
||||
- unless should_display_analytics_pages_in_sidebar
|
||||
= render_if_exists 'layouts/nav/issues_analytics_link'
|
||||
|
||||
- if group_sidebar_link?(:labels)
|
||||
= nav_link(path: 'labels#index') do
|
||||
= link_to group_labels_path(@group), title: _('Labels') do
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
- should_display_analytics_pages_in_sidebar = Feature.enabled?(:analytics_pages_under_project_analytics_sidebar, @project, default_enabled: true)
|
||||
|
||||
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?) }
|
||||
.nav-sidebar-inner-scroll
|
||||
- can_edit = can?(current_user, :admin_project, @project)
|
||||
|
@ -10,9 +8,7 @@
|
|||
.sidebar-context-title
|
||||
= @project.name
|
||||
%ul.sidebar-top-level-items.qa-project-sidebar
|
||||
- paths = sidebar_projects_paths
|
||||
- paths << 'cycle_analytics#show' unless should_display_analytics_pages_in_sidebar
|
||||
= nav_link(path: paths, html_options: { class: 'home' }) do
|
||||
= nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
|
||||
= link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('home')
|
||||
|
@ -39,17 +35,8 @@
|
|||
%span= _('Releases')
|
||||
|
||||
|
||||
- unless should_display_analytics_pages_in_sidebar
|
||||
- if can?(current_user, :read_cycle_analytics, @project)
|
||||
= nav_link(path: 'cycle_analytics#show') do
|
||||
= link_to project_cycle_analytics_path(@project), title: _('Value Stream'), class: 'shortcuts-project-cycle-analytics' do
|
||||
%span= _('Value Stream')
|
||||
|
||||
= render_if_exists 'layouts/nav/project_insights_link'
|
||||
|
||||
|
||||
- if project_nav_tab? :files
|
||||
= nav_link(controller: sidebar_repository_paths, unless: -> { should_display_analytics_pages_in_sidebar && current_path?('projects/graphs#charts') }) do
|
||||
= nav_link(controller: sidebar_repository_paths, unless: -> { current_path?('projects/graphs#charts') }) do
|
||||
= link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
|
||||
.nav-icon-container
|
||||
= sprite_icon('doc-text')
|
||||
|
@ -90,11 +77,6 @@
|
|||
= link_to project_compare_index_path(@project, from: @repository.root_ref, to: current_ref) do
|
||||
= _('Compare')
|
||||
|
||||
- unless should_display_analytics_pages_in_sidebar
|
||||
= nav_link(path: 'graphs#charts') do
|
||||
= link_to charts_project_graph_path(@project, current_ref) do
|
||||
= _('Charts')
|
||||
|
||||
= render_if_exists 'projects/sidebar/repository_locked_files'
|
||||
|
||||
- if project_nav_tab? :issues
|
||||
|
@ -178,7 +160,7 @@
|
|||
= number_with_delimiter(@project.open_merge_requests_count)
|
||||
|
||||
- if project_nav_tab? :pipelines
|
||||
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], unless: -> { should_display_analytics_pages_in_sidebar && current_path?('projects/pipelines#charts') }) do
|
||||
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts], unless: -> { current_path?('projects/pipelines#charts') }) do
|
||||
= link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines', data: { qa_selector: 'ci_cd_link' } do
|
||||
.nav-icon-container
|
||||
= sprite_icon('rocket')
|
||||
|
@ -215,12 +197,6 @@
|
|||
%span
|
||||
= _('Schedules')
|
||||
|
||||
- if !should_display_analytics_pages_in_sidebar && @project.feature_available?(:builds, current_user) && !@project.empty_repo?
|
||||
= nav_link(path: 'pipelines#charts') do
|
||||
= link_to charts_project_pipelines_path(@project), title: _('Charts'), class: 'shortcuts-pipelines-charts' do
|
||||
%span
|
||||
= _('Charts')
|
||||
|
||||
= render_if_exists 'layouts/nav/sidebar/project_security_link' # EE-specific
|
||||
|
||||
- if project_nav_tab? :operations
|
||||
|
@ -426,13 +402,6 @@
|
|||
= link_to project_network_path(@project, current_ref), title: _('Network'), class: 'shortcuts-network' do
|
||||
= _('Graph')
|
||||
|
||||
- unless should_display_analytics_pages_in_sidebar
|
||||
-# Shortcut to Repository > Charts (formerly, top-nav item "Graphs")
|
||||
- unless @project.empty_repo?
|
||||
%li.hidden
|
||||
= link_to charts_project_graph_path(@project, current_ref), title: _('Charts'), class: 'shortcuts-repository-charts' do
|
||||
= _('Charts')
|
||||
|
||||
-# Shortcut to Issues > New Issue
|
||||
- if project_nav_tab?(:issues)
|
||||
%li.hidden
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,7 @@ module WorkerAttributes
|
|||
VALID_RESOURCE_BOUNDARIES = [:memory, :cpu, :unknown].freeze
|
||||
|
||||
# Urgencies that workers can declare through the `urgencies` attribute
|
||||
VALID_URGENCIES = [:high, :default, :none].freeze
|
||||
VALID_URGENCIES = [:high, :low, :throttled].freeze
|
||||
|
||||
NAMESPACE_WEIGHTS = {
|
||||
auto_devops: 2,
|
||||
|
@ -65,7 +65,7 @@ module WorkerAttributes
|
|||
end
|
||||
|
||||
def get_urgency
|
||||
worker_attributes[:urgency] || :default
|
||||
worker_attributes[:urgency] || :low
|
||||
end
|
||||
|
||||
# Set this attribute on a job when it will call to services outside of the
|
||||
|
|
|
@ -26,8 +26,8 @@ class ErrorTrackingIssueLinkWorker # rubocop:disable Scalability/IdempotentWorke
|
|||
logger.info("Linking Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id}")
|
||||
|
||||
sentry_client.create_issue_link(integration_id, sentry_issue_id, issue)
|
||||
rescue Sentry::Client::Error
|
||||
logger.info("Failed to link Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id}")
|
||||
rescue Sentry::Client::Error => e
|
||||
logger.info("Failed to link Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id} with error: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -18,21 +18,7 @@ class UpdateMergeRequestsWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
user = User.find_by(id: user_id)
|
||||
return unless user
|
||||
|
||||
# TODO: remove this benchmarking when we have rich logging
|
||||
time = Benchmark.measure do
|
||||
MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref)
|
||||
end
|
||||
|
||||
args_log = [
|
||||
"elapsed=#{time.real}",
|
||||
"project_id=#{project_id}",
|
||||
"user_id=#{user_id}",
|
||||
"oldrev=#{oldrev}",
|
||||
"newrev=#{newrev}",
|
||||
"ref=#{ref}"
|
||||
].join(',')
|
||||
|
||||
Rails.logger.info("UpdateMergeRequestsWorker#perform #{args_log}") if time.real > LOG_TIME_THRESHOLD # rubocop:disable Gitlab/RailsLogger
|
||||
MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace avatar and favicon upload type consistency validation with content whitelist validation
|
||||
merge_request: 25401
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Introduce database table for user highest roles
|
||||
merge_request: 26987
|
||||
author:
|
||||
type: added
|
|
@ -6,8 +6,8 @@ en:
|
|||
carrierwave_download_error: could not be downloaded
|
||||
extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
|
||||
extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
|
||||
content_type_whitelist_error: "You are not allowed to upload %{content_type} files"
|
||||
content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
|
||||
content_type_whitelist_error: "file format is not supported. Please try one of the following supported formats: %{allowed_types}"
|
||||
content_type_blacklist_error: "You are not allowed to upload %{content_type} files, prohibited types: %{allowed_types}"
|
||||
rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
|
||||
mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
|
||||
min_size_error: "File size should be greater than %{min_size}"
|
||||
|
|
25
db/migrate/20200311093210_create_user_highest_roles.rb
Normal file
25
db/migrate/20200311093210_create_user_highest_roles.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateUserHighestRoles < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
create_table :user_highest_roles, id: false do |t|
|
||||
t.datetime_with_timezone :updated_at, null: false
|
||||
t.references :user, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade }
|
||||
t.integer :highest_access_level
|
||||
|
||||
t.index [:user_id, :highest_access_level]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
drop_table :user_highest_roles
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4251,6 +4251,12 @@ ActiveRecord::Schema.define(version: 2020_03_11_165635) do
|
|||
t.index ["user_id"], name: "index_user_details_on_user_id", unique: true
|
||||
end
|
||||
|
||||
create_table "user_highest_roles", primary_key: "user_id", id: :bigint, default: nil, force: :cascade do |t|
|
||||
t.datetime_with_timezone "updated_at", null: false
|
||||
t.integer "highest_access_level"
|
||||
t.index ["user_id", "highest_access_level"], name: "index_user_highest_roles_on_user_id_and_highest_access_level"
|
||||
end
|
||||
|
||||
create_table "user_interacted_projects", id: false, force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "project_id", null: false
|
||||
|
@ -5135,6 +5141,7 @@ ActiveRecord::Schema.define(version: 2020_03_11_165635) do
|
|||
add_foreign_key "user_callouts", "users", on_delete: :cascade
|
||||
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
|
||||
add_foreign_key "user_details", "users", on_delete: :cascade
|
||||
add_foreign_key "user_highest_roles", "users", on_delete: :cascade
|
||||
add_foreign_key "user_interacted_projects", "projects", name: "fk_722ceba4f7", on_delete: :cascade
|
||||
add_foreign_key "user_interacted_projects", "users", name: "fk_0894651f08", on_delete: :cascade
|
||||
add_foreign_key "user_preferences", "users", on_delete: :cascade
|
||||
|
|
|
@ -30,7 +30,7 @@ This configuration is supported in [GitLab Starter, Premium and Ultimate](https:
|
|||
|
||||
References:
|
||||
|
||||
- [Installation Docs](../../install/README.html)
|
||||
- [Installation Docs](../../install/README.md)
|
||||
- [Backup/Restore Docs](https://docs.gitlab.com/omnibus/settings/backups.html#backup-and-restore-omnibus-gitlab-configuration)
|
||||
|
||||
### Level 2: Multiple application servers
|
||||
|
@ -68,7 +68,7 @@ This configuration is supported in [GitLab Premium and Ultimate](https://about.g
|
|||
References:
|
||||
|
||||
- [Geo Documentation](../../gitlab-geo/README.html)
|
||||
- [GitLab Geo with a highly available configuration](../geo/replication/high_availability.html)
|
||||
- [GitLab Geo with a highly available configuration](../geo/replication/high_availability.md)
|
||||
|
||||
## Recommended setups based on number of users
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ following attributes:
|
|||
- `has_external_dependencies` - whether or not the queue connects to external
|
||||
services. For example, all importers have this set to `true`.
|
||||
- `urgency` - how important it is that this queue's jobs run
|
||||
quickly. Can be `high`, `default`, or `none`. For example, the
|
||||
quickly. Can be `high`, `low`, or `throttled`. For example, the
|
||||
`authorized_projects` queue is used to refresh user permissions, and
|
||||
is high urgency.
|
||||
- `name` - the queue name. The other attributes are typically more useful as
|
||||
|
|
|
@ -3757,7 +3757,7 @@ input IssueSetConfidentialInput {
|
|||
"""
|
||||
The iid of the issue to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The project the issue to mutate is in
|
||||
|
@ -3802,7 +3802,7 @@ input IssueSetDueDateInput {
|
|||
"""
|
||||
The iid of the issue to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The project the issue to mutate is in
|
||||
|
@ -3842,7 +3842,7 @@ input IssueSetWeightInput {
|
|||
"""
|
||||
The iid of the issue to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The project the issue to mutate is in
|
||||
|
@ -4150,7 +4150,7 @@ type MergeRequest implements Noteable {
|
|||
"""
|
||||
Internal ID of the merge request
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
Commit SHA of the merge request if merge is in progress
|
||||
|
@ -4550,7 +4550,7 @@ input MergeRequestSetAssigneesInput {
|
|||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The operation to perform. Defaults to REPLACE.
|
||||
|
@ -4595,7 +4595,7 @@ input MergeRequestSetLabelsInput {
|
|||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The Label IDs to set. Replaces existing labels by default.
|
||||
|
@ -4645,7 +4645,7 @@ input MergeRequestSetLockedInput {
|
|||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
Whether or not to lock the merge request.
|
||||
|
@ -4690,7 +4690,7 @@ input MergeRequestSetMilestoneInput {
|
|||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The milestone to assign to the merge request.
|
||||
|
@ -4735,7 +4735,7 @@ input MergeRequestSetSubscriptionInput {
|
|||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The project the merge request to mutate is in
|
||||
|
@ -4780,7 +4780,7 @@ input MergeRequestSetWipInput {
|
|||
"""
|
||||
The iid of the merge request to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The project the merge request to mutate is in
|
||||
|
@ -5351,7 +5351,7 @@ type Pipeline {
|
|||
"""
|
||||
Internal ID of the pipeline
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
SHA of the pipeline's commit
|
||||
|
@ -5629,12 +5629,12 @@ type Project {
|
|||
"""
|
||||
IID of the issue. For example, "1"
|
||||
"""
|
||||
iid: String
|
||||
iid: ID
|
||||
|
||||
"""
|
||||
List of IIDs of issues. For example, [1, 2]
|
||||
"""
|
||||
iids: [String!]
|
||||
iids: [ID!]
|
||||
|
||||
"""
|
||||
Labels applied to this issue
|
||||
|
@ -5724,12 +5724,12 @@ type Project {
|
|||
"""
|
||||
IID of the issue. For example, "1"
|
||||
"""
|
||||
iid: String
|
||||
iid: ID
|
||||
|
||||
"""
|
||||
List of IIDs of issues. For example, [1, 2]
|
||||
"""
|
||||
iids: [String!]
|
||||
iids: [ID!]
|
||||
|
||||
"""
|
||||
Labels applied to this issue
|
||||
|
@ -5799,12 +5799,12 @@ type Project {
|
|||
"""
|
||||
The IID of the merge request, e.g., "1"
|
||||
"""
|
||||
iid: String
|
||||
iid: ID
|
||||
|
||||
"""
|
||||
The list of IIDs of issues, e.g., [1, 2]
|
||||
"""
|
||||
iids: [String!]
|
||||
iids: [ID!]
|
||||
): MergeRequest
|
||||
|
||||
"""
|
||||
|
@ -5829,12 +5829,12 @@ type Project {
|
|||
"""
|
||||
The IID of the merge request, e.g., "1"
|
||||
"""
|
||||
iid: String
|
||||
iid: ID
|
||||
|
||||
"""
|
||||
The list of IIDs of issues, e.g., [1, 2]
|
||||
"""
|
||||
iids: [String!]
|
||||
iids: [ID!]
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
|
@ -8112,7 +8112,7 @@ input UpdateIssueInput {
|
|||
"""
|
||||
The iid of the issue to mutate
|
||||
"""
|
||||
iid: String!
|
||||
iid: ID!
|
||||
|
||||
"""
|
||||
The project the issue to mutate is in
|
||||
|
|
|
@ -702,7 +702,7 @@
|
|||
"description": "IID of the issue. For example, \"1\"",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
|
@ -718,7 +718,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
|
@ -881,7 +881,7 @@
|
|||
"description": "IID of the issue. For example, \"1\"",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
|
@ -897,7 +897,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
|
@ -1156,7 +1156,7 @@
|
|||
"description": "The IID of the merge request, e.g., \"1\"",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
|
@ -1172,7 +1172,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
|
@ -1197,7 +1197,7 @@
|
|||
"description": "The IID of the merge request, e.g., \"1\"",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
|
@ -1213,7 +1213,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
|
@ -14425,7 +14425,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -16204,7 +16204,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -20999,7 +20999,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21129,7 +21129,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21259,7 +21259,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21425,7 +21425,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21602,7 +21602,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21732,7 +21732,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21858,7 +21858,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -21988,7 +21988,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -22118,7 +22118,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
@ -24554,7 +24554,7 @@
|
|||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
|
|
|
@ -630,7 +630,7 @@ Autogenerated return type of MarkAsSpamSnippet
|
|||
| `forceRemoveSourceBranch` | Boolean | Indicates if the project settings will lead to source branch deletion after merge |
|
||||
| `headPipeline` | Pipeline | The pipeline running on the branch HEAD of the merge request |
|
||||
| `id` | ID! | ID of the merge request |
|
||||
| `iid` | String! | Internal ID of the merge request |
|
||||
| `iid` | ID! | Internal ID of the merge request |
|
||||
| `inProgressMergeCommitSha` | String | Commit SHA of the merge request if merge is in progress |
|
||||
| `mergeCommitMessage` **{warning-solid}** | String | **Deprecated:** Renamed to defaultMergeCommitMessage |
|
||||
| `mergeCommitSha` | String | SHA of the merge request commit (set once merged) |
|
||||
|
@ -834,7 +834,7 @@ Information about pagination in a connection.
|
|||
| `duration` | Int | Duration of the pipeline in seconds |
|
||||
| `finishedAt` | Time | Timestamp of the pipeline's completion |
|
||||
| `id` | ID! | ID of the pipeline |
|
||||
| `iid` | String! | Internal ID of the pipeline |
|
||||
| `iid` | ID! | Internal ID of the pipeline |
|
||||
| `sha` | String! | SHA of the pipeline's commit |
|
||||
| `startedAt` | Time | Timestamp when the pipeline was started |
|
||||
| `status` | PipelineStatusEnum! | Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED) |
|
||||
|
|
|
@ -541,7 +541,7 @@ argument :project_path, GraphQL::ID_TYPE,
|
|||
required: true,
|
||||
description: "The project the merge request to mutate is in"
|
||||
|
||||
argument :iid, GraphQL::STRING_TYPE,
|
||||
argument :iid, GraphQL::ID_TYPE,
|
||||
required: true,
|
||||
description: "The iid of the merge request to mutate"
|
||||
|
||||
|
|
|
@ -124,13 +124,13 @@ Consider skipping the cop if you're not confident your job can safely run multip
|
|||
## Job urgency
|
||||
|
||||
Jobs can have an `urgency` attribute set, which can be `:high`,
|
||||
`:default`, or `:none`. These have the below targets:
|
||||
`:low`, or `:throttled`. These have the below targets:
|
||||
|
||||
| **Urgency** | **Queue Scheduling Target** | **Execution Latency Requirement** |
|
||||
|-------------|-----------------------------|------------------------------------|
|
||||
| `:high` | 100 milliseconds | p50 of 1 second, p99 of 10 seconds |
|
||||
| `:default` | 1 minute | Maximum run time of 1 hour |
|
||||
| `:none` | None | Maximum run time of 1 hour |
|
||||
| **Urgency** | **Queue Scheduling Target** | **Execution Latency Requirement** |
|
||||
|--------------|-----------------------------|------------------------------------|
|
||||
| `:high` | 100 milliseconds | p50 of 1 second, p99 of 10 seconds |
|
||||
| `:low` | 1 minute | Maximum run time of 1 hour |
|
||||
| `:throttled` | None | Maximum run time of 1 hour |
|
||||
|
||||
To set a job's urgency, use the `urgency` class method:
|
||||
|
||||
|
@ -175,7 +175,7 @@ these jobs also have very strict execution duration requirements:
|
|||
If a worker cannot meet these expectations, then it cannot be treated as a
|
||||
`urgency :high` worker: consider redesigning the worker, or splitting the
|
||||
work between two different workers, one with `urgency :high` code that
|
||||
executes quickly, and the other with `urgency :default`, which has no
|
||||
executes quickly, and the other with `urgency :low`, which has no
|
||||
execution latency requirements (but also has lower scheduling targets).
|
||||
|
||||
## Jobs with External Dependencies
|
||||
|
|
|
@ -6,6 +6,7 @@ module Gitlab
|
|||
module Test
|
||||
class Junit
|
||||
JunitParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
|
||||
ATTACHMENT_TAG_REGEX = /\[\[ATTACHMENT\|(?<path>.+?)\]\]/.freeze
|
||||
|
||||
def parse!(xml_data, test_suite)
|
||||
root = Hash.from_xml(xml_data)
|
||||
|
@ -49,6 +50,7 @@ module Gitlab
|
|||
if data['failure']
|
||||
status = ::Gitlab::Ci::Reports::TestCase::STATUS_FAILED
|
||||
system_output = data['failure']
|
||||
attachment = attachment_path(data['system_out'])
|
||||
elsif data['error']
|
||||
status = ::Gitlab::Ci::Reports::TestCase::STATUS_ERROR
|
||||
system_output = data['error']
|
||||
|
@ -63,9 +65,17 @@ module Gitlab
|
|||
file: data['file'],
|
||||
execution_time: data['time'],
|
||||
status: status,
|
||||
system_output: system_output
|
||||
system_output: system_output,
|
||||
attachment: attachment
|
||||
)
|
||||
end
|
||||
|
||||
def attachment_path(data)
|
||||
return unless data
|
||||
|
||||
matches = data.match(ATTACHMENT_TAG_REGEX)
|
||||
matches[:path] if matches
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,9 +10,9 @@ module Gitlab
|
|||
STATUS_ERROR = 'error'
|
||||
STATUS_TYPES = [STATUS_SUCCESS, STATUS_FAILED, STATUS_SKIPPED, STATUS_ERROR].freeze
|
||||
|
||||
attr_reader :name, :classname, :execution_time, :status, :file, :system_output, :stack_trace, :key
|
||||
attr_reader :name, :classname, :execution_time, :status, :file, :system_output, :stack_trace, :key, :attachment
|
||||
|
||||
def initialize(name:, classname:, execution_time:, status:, file: nil, system_output: nil, stack_trace: nil)
|
||||
def initialize(name:, classname:, execution_time:, status:, file: nil, system_output: nil, stack_trace: nil, attachment: nil)
|
||||
@name = name
|
||||
@classname = classname
|
||||
@file = file
|
||||
|
@ -21,6 +21,11 @@ module Gitlab
|
|||
@system_output = system_output
|
||||
@stack_trace = stack_trace
|
||||
@key = sanitize_key_name("#{classname}_#{name}")
|
||||
@attachment = attachment
|
||||
end
|
||||
|
||||
def has_attachment?
|
||||
attachment.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -3406,9 +3406,6 @@ msgstr ""
|
|||
msgid "Changing group path can have unintended side effects."
|
||||
msgstr ""
|
||||
|
||||
msgid "Charts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Charts can't be displayed as the request for data has timed out. %{documentationLink}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19917,10 +19914,10 @@ msgstr ""
|
|||
msgid "There was an error fetching median data for stages"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the Designs"
|
||||
msgid "There was an error fetching the Node's Groups"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the Node's Groups"
|
||||
msgid "There was an error fetching the designs"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the environments information."
|
||||
|
@ -19965,7 +19962,7 @@ msgstr ""
|
|||
msgid "There was an error subscribing to this label."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error syncing the Design Repositories."
|
||||
msgid "There was an error syncing the designs."
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error trying to validate your query"
|
||||
|
|
|
@ -10,7 +10,6 @@ module QA
|
|||
element :group_settings_item
|
||||
element :group_members_item
|
||||
element :general_settings_link
|
||||
element :contribution_analytics_link
|
||||
end
|
||||
|
||||
view 'app/views/layouts/nav/sidebar/_analytics_links.html.haml' do
|
||||
|
|
31
spec/factories/ci/test_case.rb
Normal file
31
spec/factories/ci/test_case.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :test_case, class: 'Gitlab::Ci::Reports::TestCase' do
|
||||
name { "test-1" }
|
||||
classname { "trace" }
|
||||
file { "spec/trace_spec.rb" }
|
||||
execution_time { 1.23 }
|
||||
status { "success" }
|
||||
system_output { nil }
|
||||
attachment { nil }
|
||||
|
||||
trait :with_attachment do
|
||||
attachment { "some/path.png" }
|
||||
end
|
||||
|
||||
skip_create
|
||||
|
||||
initialize_with do
|
||||
new(
|
||||
name: name,
|
||||
classname: classname,
|
||||
file: file,
|
||||
execution_time: execution_time,
|
||||
status: status,
|
||||
system_output: system_output,
|
||||
attachment: attachment
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
7
spec/factories/user_highest_roles.rb
Normal file
7
spec/factories/user_highest_roles.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :user_highest_role do
|
||||
user
|
||||
end
|
||||
end
|
|
@ -230,7 +230,7 @@ describe 'Merge request > User creates image diff notes', :js do
|
|||
it_behaves_like 'onion skin'
|
||||
end
|
||||
|
||||
describe 'swipe view' do
|
||||
describe 'swipe view', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/209999' do
|
||||
before do
|
||||
switch_to_swipe_view
|
||||
end
|
||||
|
|
|
@ -7,8 +7,6 @@ describe 'Project active tab' do
|
|||
let(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
|
||||
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
@ -45,7 +43,7 @@ describe 'Project active tab' do
|
|||
|
||||
it_behaves_like 'page has active tab', 'Repository'
|
||||
|
||||
%w(Files Commits Graph Compare Charts Branches Tags).each do |sub_menu|
|
||||
%w(Files Commits Graph Compare Branches Tags).each do |sub_menu|
|
||||
context "on project Repository/#{sub_menu}" do
|
||||
before do
|
||||
click_tab(sub_menu)
|
||||
|
@ -124,29 +122,23 @@ describe 'Project active tab' do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do
|
||||
context 'on project Analytics' do
|
||||
before do
|
||||
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project })
|
||||
visit charts_project_graph_path(project, 'master')
|
||||
end
|
||||
|
||||
context 'on project Analytics' do
|
||||
context 'on project Analytics/Repository Analytics' do
|
||||
it_behaves_like 'page has active tab', _('Analytics')
|
||||
it_behaves_like 'page has active sub tab', _('Repository')
|
||||
end
|
||||
|
||||
context 'on project Analytics/Cycle Analytics' do
|
||||
before do
|
||||
visit charts_project_graph_path(project, 'master')
|
||||
click_tab(_('CI / CD'))
|
||||
end
|
||||
|
||||
context 'on project Analytics/Repository Analytics' do
|
||||
it_behaves_like 'page has active tab', _('Analytics')
|
||||
it_behaves_like 'page has active sub tab', _('Repository')
|
||||
end
|
||||
|
||||
context 'on project Analytics/Cycle Analytics' do
|
||||
before do
|
||||
click_tab(_('CI / CD'))
|
||||
end
|
||||
|
||||
it_behaves_like 'page has active tab', _('Analytics')
|
||||
it_behaves_like 'page has active sub tab', _('CI / CD')
|
||||
end
|
||||
it_behaves_like 'page has active tab', _('Analytics')
|
||||
it_behaves_like 'page has active sub tab', _('CI / CD')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,8 +7,6 @@ describe 'User uses shortcuts', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
|
||||
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
|
||||
|
@ -119,8 +117,8 @@ describe 'User uses shortcuts', :js do
|
|||
find('body').native.send_key('g')
|
||||
find('body').native.send_key('d')
|
||||
|
||||
expect(page).to have_active_navigation('Repository')
|
||||
expect(page).to have_active_sub_navigation('Charts')
|
||||
expect(page).to have_active_navigation(_('Analytics'))
|
||||
expect(page).to have_active_sub_navigation(_('Repository'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -211,18 +209,4 @@ describe 'User uses shortcuts', :js do
|
|||
expect(page).to have_active_navigation('Wiki')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when `analytics_pages_under_project_analytics_sidebar` feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: true, thing: project })
|
||||
end
|
||||
|
||||
it 'redirects to the repository charts page' do
|
||||
find('body').native.send_key('g')
|
||||
find('body').native.send_key('d')
|
||||
|
||||
expect(page).to have_active_navigation(_('Analytics'))
|
||||
expect(page).to have_active_sub_navigation(_('Repository'))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -205,6 +205,75 @@ describe Gitlab::Ci::Parsers::Test::Junit do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when data contains an attachment tag' do
|
||||
let(:junit) do
|
||||
<<~EOF
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<testcase classname='Calculator' name='sumTest1' time='0.01'>
|
||||
<failure>Some failure</failure>
|
||||
<system-out>[[ATTACHMENT|some/path.png]]</system-out>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'add attachment to a test case' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(test_cases[0].has_attachment?).to be_truthy
|
||||
expect(test_cases[0].attachment).to eq("some/path.png")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when data contains multiple attachments tag' do
|
||||
let(:junit) do
|
||||
<<~EOF
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<testcase classname='Calculator' name='sumTest1' time='0.01'>
|
||||
<failure>Some failure</failure>
|
||||
<system-out>
|
||||
[[ATTACHMENT|some/path.png]]
|
||||
[[ATTACHMENT|some/path.html]]
|
||||
</system-out>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'adds the first match attachment to a test case' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(test_cases[0].has_attachment?).to be_truthy
|
||||
expect(test_cases[0].attachment).to eq("some/path.png")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when data does not match attachment tag regex' do
|
||||
let(:junit) do
|
||||
<<~EOF
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<testcase classname='Calculator' name='sumTest1' time='0.01'>
|
||||
<failure>Some failure</failure>
|
||||
<system-out>[[attachment]some/path.png]]</system-out>
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
EOF
|
||||
end
|
||||
|
||||
it 'does not add attachment to a test case' do
|
||||
expect { subject }.not_to raise_error
|
||||
|
||||
expect(test_cases[0].has_attachment?).to be_falsy
|
||||
expect(test_cases[0].attachment).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def flattened_test_cases(test_suite)
|
||||
|
|
|
@ -88,5 +88,17 @@ describe Gitlab::Ci::Reports::TestCase do
|
|||
expect { test_case }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when attachment is present' do
|
||||
let(:attachment_test_case) { build(:test_case, :with_attachment) }
|
||||
|
||||
it "initializes the attachment if present" do
|
||||
expect(attachment_test_case.attachment).to eq("some/path.png")
|
||||
end
|
||||
|
||||
it '#has_attachment?' do
|
||||
expect(attachment_test_case.has_attachment?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -124,7 +124,7 @@ describe Gitlab::SidekiqConfig::CliMethods do
|
|||
name: 'a',
|
||||
feature_category: :category_a,
|
||||
has_external_dependencies: false,
|
||||
urgency: :default,
|
||||
urgency: :low,
|
||||
resource_boundary: :cpu
|
||||
},
|
||||
{
|
||||
|
@ -145,7 +145,7 @@ describe Gitlab::SidekiqConfig::CliMethods do
|
|||
name: 'c',
|
||||
feature_category: :category_c,
|
||||
has_external_dependencies: false,
|
||||
urgency: :none,
|
||||
urgency: :throttled,
|
||||
resource_boundary: :memory
|
||||
}
|
||||
]
|
||||
|
@ -168,9 +168,9 @@ describe Gitlab::SidekiqConfig::CliMethods do
|
|||
|
||||
# urgency
|
||||
'urgency=high' | %w(a:2 b)
|
||||
'urgency=default' | %w(a)
|
||||
'urgency=high,default,none' | %w(a a:2 b c)
|
||||
'urgency=default|urgency=none' | %w(a c)
|
||||
'urgency=low' | %w(a)
|
||||
'urgency=high,low,throttled' | %w(a a:2 b c)
|
||||
'urgency=low|urgency=throttled' | %w(a c)
|
||||
'urgency!=high' | %w(a c)
|
||||
|
||||
# name
|
||||
|
|
|
@ -88,7 +88,7 @@ describe Gitlab::SidekiqConfig::Worker do
|
|||
attributes_a = {
|
||||
feature_category: :source_code_management,
|
||||
has_external_dependencies: false,
|
||||
urgency: :default,
|
||||
urgency: :low,
|
||||
resource_boundary: :memory,
|
||||
weight: 2,
|
||||
idempotent: true
|
||||
|
|
|
@ -9,7 +9,7 @@ describe Gitlab::SidekiqMiddleware::ClientMetrics do
|
|||
let(:queue) { :test }
|
||||
let(:worker_class) { worker.class }
|
||||
let(:job) { {} }
|
||||
let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", urgency: "default" } }
|
||||
let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", urgency: "low" } }
|
||||
|
||||
shared_examples "a metrics client middleware" do
|
||||
context "with mocked prometheus" do
|
||||
|
@ -80,8 +80,8 @@ describe Gitlab::SidekiqMiddleware::ClientMetrics do
|
|||
|
||||
context "no urgency" do
|
||||
it_behaves_like "a metrics client middleware" do
|
||||
let(:urgency) { :none }
|
||||
let(:labels) { default_labels.merge(urgency: "none") }
|
||||
let(:urgency) { :throttled }
|
||||
let(:labels) { default_labels.merge(urgency: "throttled") }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
|
|||
let(:job) { {} }
|
||||
let(:job_status) { :done }
|
||||
let(:labels_with_job_status) { labels.merge(job_status: job_status.to_s) }
|
||||
let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", urgency: "default" } }
|
||||
let(:default_labels) { { queue: queue.to_s, boundary: "", external_dependencies: "no", feature_category: "", urgency: "low" } }
|
||||
|
||||
shared_examples "a metrics middleware" do
|
||||
context "with mocked prometheus" do
|
||||
|
@ -202,11 +202,11 @@ describe Gitlab::SidekiqMiddleware::ServerMetrics do
|
|||
end
|
||||
|
||||
context "combined" do
|
||||
let(:urgency) { :none }
|
||||
let(:urgency) { :throttled }
|
||||
let(:external_dependencies) { true }
|
||||
let(:resource_boundary) { :cpu }
|
||||
let(:feature_category) { :authentication }
|
||||
let(:labels) { default_labels.merge(urgency: "none", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
|
||||
let(:labels) { default_labels.merge(urgency: "throttled", external_dependencies: "yes", boundary: "cpu", feature_category: "authentication") }
|
||||
|
||||
it_behaves_like "a metrics middleware"
|
||||
end
|
||||
|
|
|
@ -201,6 +201,26 @@ describe Namespace do
|
|||
expect(described_class.find_by_pages_host(host)).to eq(namespace)
|
||||
end
|
||||
|
||||
context 'when there is non-top-level group with searched name' do
|
||||
before do
|
||||
create(:group, :nested, path: 'pages')
|
||||
end
|
||||
|
||||
it 'ignores this group' do
|
||||
host = "pages.#{Settings.pages.host.upcase}"
|
||||
|
||||
expect(described_class.find_by_pages_host(host)).to be_nil
|
||||
end
|
||||
|
||||
it 'finds right top level group' do
|
||||
group = create(:group, path: 'pages')
|
||||
|
||||
host = "pages.#{Settings.pages.host.upcase}"
|
||||
|
||||
expect(described_class.find_by_pages_host(host)).to eq(group)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns no result if the provided host is not subdomain of the Pages host" do
|
||||
create(:namespace, name: 'namespace.io')
|
||||
host = "namespace.io"
|
||||
|
|
13
spec/models/user_highest_role_spec.rb
Normal file
13
spec/models/user_highest_role_spec.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe UserHighestRole do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:user).required }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_inclusion_of(:highest_access_level).in_array([nil, *Gitlab::Access.all_values]) }
|
||||
end
|
||||
end
|
|
@ -30,6 +30,7 @@ describe User, :do_not_mock_admin_mode do
|
|||
it { is_expected.to have_one(:status) }
|
||||
it { is_expected.to have_one(:max_access_level_membership) }
|
||||
it { is_expected.to have_one(:user_detail) }
|
||||
it { is_expected.to have_one(:user_highest_role) }
|
||||
it { is_expected.to have_many(:snippets).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:members) }
|
||||
it { is_expected.to have_many(:project_members) }
|
||||
|
|
|
@ -48,7 +48,7 @@ describe API::Groups do
|
|||
|
||||
context 'when file format is not supported' do
|
||||
let(:file_path) { 'spec/fixtures/doc_sample.txt' }
|
||||
let(:message) { 'file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff, ico' }
|
||||
let(:message) { 'file format is not supported. Please try one of the following supported formats: image/png, image/jpeg, image/gif, image/bmp, image/tiff, image/vnd.microsoft.icon' }
|
||||
|
||||
it_behaves_like 'invalid file upload request'
|
||||
end
|
||||
|
|
|
@ -20,6 +20,7 @@ RSpec.shared_context 'uploader with type check' do
|
|||
end
|
||||
end
|
||||
|
||||
# This works with the UploadTypeCheck::Concern
|
||||
RSpec.shared_context 'stubbed MimeMagic mime type detection' do
|
||||
let(:mime_type) { '' }
|
||||
let(:magic_mime) { mime_type }
|
||||
|
@ -31,3 +32,19 @@ RSpec.shared_context 'stubbed MimeMagic mime type detection' do
|
|||
allow(MimeMagic).to receive(:by_path).with(anything).and_return(ext_mime_obj)
|
||||
end
|
||||
end
|
||||
|
||||
# @param uploader [CarrierWave::Uploader::Base] uploader with extension_whitelist method.
|
||||
RSpec.shared_context 'ignore extension whitelist check' do
|
||||
before do
|
||||
allow(uploader).to receive(:extension_whitelist).and_return(nil)
|
||||
end
|
||||
end
|
||||
|
||||
# This works with a content_type_whitelist and content_type_blacklist type check.
|
||||
# @param mime_type [String] mime type to forcibly detect.
|
||||
RSpec.shared_context 'force content type detection to mime_type' do
|
||||
before do
|
||||
magic_mime_obj = MimeMagic.new(mime_type)
|
||||
allow(MimeMagic).to receive(:by_magic).with(anything).and_return(magic_mime_obj)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
|
||||
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
|
||||
shared_examples 'denied carrierwave upload' do
|
||||
it 'will deny upload' do
|
||||
fixture_file = fixture_file_upload(path)
|
||||
expect { uploader.cache!(fixture_file) }.to raise_exception(CarrierWave::IntegrityError)
|
||||
end
|
||||
end
|
||||
|
||||
# @param path [String] the path to file to upload. E.g. File.join('spec', 'fixtures', 'sanitized.svg')
|
||||
# @param uploader [CarrierWave::Uploader::Base] uploader to handle the upload.
|
||||
shared_examples 'accepted carrierwave upload' do
|
||||
let(:fixture_file) { fixture_file_upload(path) }
|
||||
|
||||
before do
|
||||
uploader.remove!
|
||||
end
|
||||
|
||||
it 'will accept upload' do
|
||||
expect { uploader.cache!(fixture_file) }.not_to raise_exception
|
||||
end
|
||||
|
||||
it 'will cache uploaded file' do
|
||||
expect { uploader.cache!(fixture_file) }.to change { uploader.file }.from(nil).to(kind_of(CarrierWave::SanitizedFile))
|
||||
end
|
||||
end
|
||||
|
||||
def check_content_matches_extension!(file = double(read: nil, path: ''))
|
||||
magic_file = UploadTypeCheck::MagicFile.new(file)
|
||||
uploader.check_content_matches_extension!(magic_file)
|
||||
|
|
|
@ -47,15 +47,29 @@ describe AvatarUploader do
|
|||
end
|
||||
end
|
||||
|
||||
context 'upload type check' do
|
||||
AvatarUploader::SAFE_IMAGE_EXT.each do |ext|
|
||||
context "#{ext} extension" do
|
||||
it_behaves_like 'type checked uploads', filenames: "image.#{ext}"
|
||||
end
|
||||
end
|
||||
context 'accept whitelist file content type' do
|
||||
# We need to feed through a valid path, but we force the parsed mime type
|
||||
# in a stub below so we can set any path.
|
||||
let_it_be(:path) { File.join('spec', 'fixtures', 'video_sample.mp4') }
|
||||
|
||||
context 'skip image/svg+xml integrity check' do
|
||||
it_behaves_like 'skipped type checked uploads', filenames: 'image.svg'
|
||||
where(:mime_type) { described_class::MIME_WHITELIST }
|
||||
|
||||
with_them do
|
||||
include_context 'force content type detection to mime_type'
|
||||
|
||||
it_behaves_like 'accepted carrierwave upload'
|
||||
end
|
||||
end
|
||||
|
||||
context 'upload non-whitelisted file content type' do
|
||||
let_it_be(:path) { File.join('spec', 'fixtures', 'sanitized.svg') }
|
||||
|
||||
it_behaves_like 'denied carrierwave upload'
|
||||
end
|
||||
|
||||
context 'upload misnamed non-whitelisted file content type' do
|
||||
let_it_be(:path) { File.join('spec', 'fixtures', 'not_a_png.png') }
|
||||
|
||||
it_behaves_like 'denied carrierwave upload'
|
||||
end
|
||||
end
|
||||
|
|
34
spec/uploaders/content_type_whitelist_spec.rb
Normal file
34
spec/uploaders/content_type_whitelist_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ContentTypeWhitelist do
|
||||
class DummyUploader < CarrierWave::Uploader::Base
|
||||
include ContentTypeWhitelist::Concern
|
||||
|
||||
def content_type_whitelist
|
||||
%w[image/png image/jpeg]
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:model) { build_stubbed(:user) }
|
||||
let_it_be(:uploader) { DummyUploader.new(model, :dummy) }
|
||||
|
||||
context 'upload whitelisted file content type' do
|
||||
let(:path) { File.join('spec', 'fixtures', 'rails_sample.jpg') }
|
||||
|
||||
it_behaves_like 'accepted carrierwave upload'
|
||||
end
|
||||
|
||||
context 'upload non-whitelisted file content type' do
|
||||
let(:path) { File.join('spec', 'fixtures', 'sanitized.svg') }
|
||||
|
||||
it_behaves_like 'denied carrierwave upload'
|
||||
end
|
||||
|
||||
context 'upload misnamed non-whitelisted file content type' do
|
||||
let(:path) { File.join('spec', 'fixtures', 'not_a_png.png') }
|
||||
|
||||
it_behaves_like 'denied carrierwave upload'
|
||||
end
|
||||
end
|
|
@ -6,19 +6,35 @@ describe FaviconUploader do
|
|||
let_it_be(:model) { build_stubbed(:user) }
|
||||
let_it_be(:uploader) { described_class.new(model, :favicon) }
|
||||
|
||||
context 'upload type check' do
|
||||
FaviconUploader::EXTENSION_WHITELIST.each do |ext|
|
||||
context "#{ext} extension" do
|
||||
it_behaves_like 'type checked uploads', filenames: "image.#{ext}"
|
||||
end
|
||||
context 'accept whitelist file content type' do
|
||||
include_context 'ignore extension whitelist check'
|
||||
|
||||
# We need to feed through a valid path, but we force the parsed mime type
|
||||
# in a stub below so we can set any path.
|
||||
let_it_be(:path) { File.join('spec', 'fixtures', 'video_sample.mp4') }
|
||||
|
||||
where(:mime_type) { described_class::MIME_WHITELIST }
|
||||
|
||||
with_them do
|
||||
include_context 'force content type detection to mime_type'
|
||||
|
||||
it_behaves_like 'accepted carrierwave upload'
|
||||
end
|
||||
end
|
||||
|
||||
context 'upload non-whitelisted file extensions' do
|
||||
it 'will deny upload' do
|
||||
path = File.join('spec', 'fixtures', 'banana_sample.gif')
|
||||
fixture_file = fixture_file_upload(path)
|
||||
expect { uploader.cache!(fixture_file) }.to raise_exception(CarrierWave::IntegrityError)
|
||||
end
|
||||
context 'upload non-whitelisted file content type' do
|
||||
include_context 'ignore extension whitelist check'
|
||||
|
||||
let_it_be(:path) { File.join('spec', 'fixtures', 'sanitized.svg') }
|
||||
|
||||
it_behaves_like 'denied carrierwave upload'
|
||||
end
|
||||
|
||||
context 'upload misnamed non-whitelisted file content type' do
|
||||
include_context 'ignore extension whitelist check'
|
||||
|
||||
let_it_be(:path) { File.join('spec', 'fixtures', 'not_a_png.png') }
|
||||
|
||||
it_behaves_like 'denied carrierwave upload'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -166,7 +166,6 @@ describe 'layouts/nav/sidebar/_project' do
|
|||
|
||||
before do
|
||||
allow(view).to receive(:can?).with(nil, :read_cycle_analytics, project).and_return(read_cycle_analytics)
|
||||
stub_feature_flags(analytics_pages_under_project_analytics_sidebar: { enabled: false, thing: project })
|
||||
end
|
||||
|
||||
describe 'when value stream analytics is enabled' do
|
||||
|
|
|
@ -26,17 +26,5 @@ describe UpdateMergeRequestsWorker do
|
|||
|
||||
perform
|
||||
end
|
||||
|
||||
context 'when slow' do
|
||||
before do
|
||||
stub_const("UpdateMergeRequestsWorker::LOG_TIME_THRESHOLD", -1)
|
||||
end
|
||||
|
||||
it 'logs debug info' do
|
||||
expect(Rails.logger).to receive(:info).with(a_string_matching(/\AUpdateMergeRequestsWorker#perform.*project_id=#{project.id},user_id=#{user.id},oldrev=#{oldrev},newrev=#{newrev},ref=#{ref}/))
|
||||
|
||||
perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue