Merge tag 'debian/13.4.7-1' into buster-fasttrack
gitlab Debian release 13.4.7-1
This commit is contained in:
commit
0fad823dbb
42 changed files with 693 additions and 63 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -2,6 +2,22 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 13.4.7 (2020-12-07)
|
||||||
|
|
||||||
|
### Security (10 changes)
|
||||||
|
|
||||||
|
- Validate zoom links to start with https only. !1055
|
||||||
|
- Require at least 3 characters when searching for project in the Explore page.
|
||||||
|
- Do not show emails of users in confirmation page.
|
||||||
|
- Forbid setting a gitlabUserList strategy to a list from another project.
|
||||||
|
- Fix mermaid resource consumption in GFM fields.
|
||||||
|
- Ensure group and project memberships are not leaked via API for users with private profiles.
|
||||||
|
- GraphQL User: do not expose email if set to private.
|
||||||
|
- Filter search parameter to prevent data leaks.
|
||||||
|
- Do not expose starred projects of users with private profile via API.
|
||||||
|
- Do not show starred & contributed projects of users with private profile.
|
||||||
|
|
||||||
|
|
||||||
## 13.4.6 (2020-11-03)
|
## 13.4.6 (2020-11-03)
|
||||||
|
|
||||||
### Fixed (1 change)
|
### Fixed (1 change)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
13.4.6
|
13.4.7
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
13.4.6
|
13.4.7
|
|
@ -18,7 +18,13 @@ import { __, sprintf } from '~/locale';
|
||||||
//
|
//
|
||||||
|
|
||||||
// This is an arbitrary number; Can be iterated upon when suitable.
|
// This is an arbitrary number; Can be iterated upon when suitable.
|
||||||
const MAX_CHAR_LIMIT = 5000;
|
const MAX_CHAR_LIMIT = 2000;
|
||||||
|
// Max # of mermaid blocks that can be rendered in a page.
|
||||||
|
const MAX_MERMAID_BLOCK_LIMIT = 50;
|
||||||
|
// Keep a map of mermaid blocks we've already rendered.
|
||||||
|
const elsProcessingMap = new WeakMap();
|
||||||
|
let renderedMermaidBlocks = 0;
|
||||||
|
|
||||||
let mermaidModule = {};
|
let mermaidModule = {};
|
||||||
|
|
||||||
function importMermaidModule() {
|
function importMermaidModule() {
|
||||||
|
@ -110,13 +116,22 @@ function renderMermaids($els) {
|
||||||
let renderedChars = 0;
|
let renderedChars = 0;
|
||||||
|
|
||||||
$els.each((i, el) => {
|
$els.each((i, el) => {
|
||||||
|
// Skipping all the elements which we've already queued in requestIdleCallback
|
||||||
|
if (elsProcessingMap.has(el)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { source } = fixElementSource(el);
|
const { source } = fixElementSource(el);
|
||||||
/**
|
/**
|
||||||
* Restrict the rendering to a certain amount of character to
|
* Restrict the rendering to a certain amount of character
|
||||||
* prevent mermaidjs from hanging up the entire thread and
|
* and mermaid blocks to prevent mermaidjs from hanging
|
||||||
* causing a DoS.
|
* up the entire thread and causing a DoS.
|
||||||
*/
|
*/
|
||||||
if ((source && source.length > MAX_CHAR_LIMIT) || renderedChars > MAX_CHAR_LIMIT) {
|
if (
|
||||||
|
(source && source.length > MAX_CHAR_LIMIT) ||
|
||||||
|
renderedChars > MAX_CHAR_LIMIT ||
|
||||||
|
renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT
|
||||||
|
) {
|
||||||
const html = `
|
const html = `
|
||||||
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
|
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
|
||||||
<div>
|
<div>
|
||||||
|
@ -146,8 +161,13 @@ function renderMermaids($els) {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderedChars += source.length;
|
renderedChars += source.length;
|
||||||
|
renderedMermaidBlocks += 1;
|
||||||
|
|
||||||
renderMermaidEl(el);
|
const requestId = window.requestIdleCallback(() => {
|
||||||
|
renderMermaidEl(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
elsProcessingMap.set(el, requestId);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
|
@ -8,6 +8,8 @@ class Explore::ProjectsController < Explore::ApplicationController
|
||||||
include SortingHelper
|
include SortingHelper
|
||||||
include SortingPreference
|
include SortingPreference
|
||||||
|
|
||||||
|
MIN_SEARCH_LENGTH = 3
|
||||||
|
|
||||||
before_action :set_non_archived_param
|
before_action :set_non_archived_param
|
||||||
before_action :set_sorting
|
before_action :set_sorting
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ class Explore::ProjectsController < Explore::ApplicationController
|
||||||
def load_projects
|
def load_projects
|
||||||
load_project_counts
|
load_project_counts
|
||||||
|
|
||||||
projects = ProjectsFinder.new(current_user: current_user, params: params).execute
|
projects = ProjectsFinder.new(current_user: current_user, params: params.merge(minimum_search_length: MIN_SEARCH_LENGTH)).execute
|
||||||
|
|
||||||
projects = preload_associations(projects)
|
projects = preload_associations(projects)
|
||||||
projects = projects.page(params[:page]).without_count
|
projects = projects.page(params[:page]).without_count
|
||||||
|
|
|
@ -126,7 +126,6 @@ class SearchController < ApplicationController
|
||||||
payload[:metadata] ||= {}
|
payload[:metadata] ||= {}
|
||||||
payload[:metadata]['meta.search.group_id'] = params[:group_id]
|
payload[:metadata]['meta.search.group_id'] = params[:group_id]
|
||||||
payload[:metadata]['meta.search.project_id'] = params[:project_id]
|
payload[:metadata]['meta.search.project_id'] = params[:project_id]
|
||||||
payload[:metadata]['meta.search.search'] = params[:search]
|
|
||||||
payload[:metadata]['meta.search.scope'] = params[:scope]
|
payload[:metadata]['meta.search.scope'] = params[:scope]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ class UsersController < ApplicationController
|
||||||
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
|
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
|
||||||
before_action :user, except: [:exists, :suggests]
|
before_action :user, except: [:exists, :suggests]
|
||||||
before_action :authorize_read_user_profile!,
|
before_action :authorize_read_user_profile!,
|
||||||
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets]
|
only: [:calendar, :calendar_activities, :groups, :projects, :contributed, :starred, :snippets]
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
# personal: boolean
|
# personal: boolean
|
||||||
# search: string
|
# search: string
|
||||||
# search_namespaces: boolean
|
# search_namespaces: boolean
|
||||||
|
# minimum_search_length: int
|
||||||
# non_archived: boolean
|
# non_archived: boolean
|
||||||
# archived: 'only' or boolean
|
# archived: 'only' or boolean
|
||||||
# min_access_level: integer
|
# min_access_level: integer
|
||||||
|
@ -177,6 +178,9 @@ class ProjectsFinder < UnionFinder
|
||||||
|
|
||||||
def by_search(items)
|
def by_search(items)
|
||||||
params[:search] ||= params[:name]
|
params[:search] ||= params[:name]
|
||||||
|
|
||||||
|
return items.none if params[:search].present? && params[:minimum_search_length].present? && params[:search].length < params[:minimum_search_length].to_i
|
||||||
|
|
||||||
items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
|
items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StarredProjectsFinder < ProjectsFinder
|
class StarredProjectsFinder < ProjectsFinder
|
||||||
|
include Gitlab::Allowable
|
||||||
|
|
||||||
def initialize(user, params: {}, current_user: nil)
|
def initialize(user, params: {}, current_user: nil)
|
||||||
|
@user = user
|
||||||
|
|
||||||
super(
|
super(
|
||||||
params: params,
|
params: params,
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
project_ids_relation: user.starred_projects.select(:id)
|
project_ids_relation: user.starred_projects.select(:id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def execute
|
||||||
|
# Do not show starred projects if the user has a private profile.
|
||||||
|
return Project.none unless can?(current_user, :read_user_profile, @user)
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ module Types
|
||||||
field :state, Types::UserStateEnum, null: false,
|
field :state, Types::UserStateEnum, null: false,
|
||||||
description: 'State of the user'
|
description: 'State of the user'
|
||||||
field :email, GraphQL::STRING_TYPE, null: true,
|
field :email, GraphQL::STRING_TYPE, null: true,
|
||||||
description: 'User email'
|
description: 'User email', method: :public_email
|
||||||
field :avatar_url, GraphQL::STRING_TYPE, null: true,
|
field :avatar_url, GraphQL::STRING_TYPE, null: true,
|
||||||
description: "URL of the user's avatar"
|
description: "URL of the user's avatar"
|
||||||
field :web_url, GraphQL::STRING_TYPE, null: false,
|
field :web_url, GraphQL::STRING_TYPE, null: false,
|
||||||
|
@ -30,13 +30,11 @@ module Types
|
||||||
resolver: Resolvers::TodoResolver,
|
resolver: Resolvers::TodoResolver,
|
||||||
description: 'Todos of the user'
|
description: 'Todos of the user'
|
||||||
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
|
field :group_memberships, Types::GroupMemberType.connection_type, null: true,
|
||||||
description: 'Group memberships of the user',
|
description: 'Group memberships of the user'
|
||||||
method: :group_members
|
|
||||||
field :status, Types::UserStatusType, null: true,
|
field :status, Types::UserStatusType, null: true,
|
||||||
description: 'User status'
|
description: 'User status'
|
||||||
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
|
field :project_memberships, Types::ProjectMemberType.connection_type, null: true,
|
||||||
description: 'Project memberships of the user',
|
description: 'Project memberships of the user'
|
||||||
method: :project_members
|
|
||||||
field :starred_projects, Types::ProjectType.connection_type, null: true,
|
field :starred_projects, Types::ProjectType.connection_type, null: true,
|
||||||
description: 'Projects starred by the user',
|
description: 'Projects starred by the user',
|
||||||
resolver: Resolvers::UserStarredProjectsResolver
|
resolver: Resolvers::UserStarredProjectsResolver
|
||||||
|
|
|
@ -23,6 +23,11 @@ module Operations
|
||||||
|
|
||||||
before_destroy :ensure_no_associated_strategies
|
before_destroy :ensure_no_associated_strategies
|
||||||
|
|
||||||
|
def self.belongs_to?(project_id, user_list_ids)
|
||||||
|
uniq_ids = user_list_ids.uniq
|
||||||
|
where(id: uniq_ids, project_id: project_id).count == uniq_ids.count
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ensure_no_associated_strategies
|
def ensure_no_associated_strategies
|
||||||
|
|
|
@ -2,4 +2,18 @@
|
||||||
|
|
||||||
class UserPresenter < Gitlab::View::Presenter::Delegated
|
class UserPresenter < Gitlab::View::Presenter::Delegated
|
||||||
presents :user
|
presents :user
|
||||||
|
|
||||||
|
def group_memberships
|
||||||
|
should_be_private? ? GroupMember.none : user.group_members
|
||||||
|
end
|
||||||
|
|
||||||
|
def project_memberships
|
||||||
|
should_be_private? ? ProjectMember.none : user.project_members
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def should_be_private?
|
||||||
|
!can?(current_user, :read_user_profile, user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,7 +24,7 @@ module Todos
|
||||||
# if at least reporter, all entities including confidential issues can be accessed
|
# if at least reporter, all entities including confidential issues can be accessed
|
||||||
return if user_has_reporter_access?
|
return if user_has_reporter_access?
|
||||||
|
|
||||||
remove_confidential_issue_todos
|
remove_confidential_resource_todos
|
||||||
|
|
||||||
if entity.private?
|
if entity.private?
|
||||||
remove_project_todos
|
remove_project_todos
|
||||||
|
@ -43,7 +43,7 @@ module Todos
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def remove_confidential_issue_todos
|
def remove_confidential_resource_todos
|
||||||
Todo.where(
|
Todo.where(
|
||||||
target_id: confidential_issues.select(:id), target_type: Issue.name, user_id: user.id
|
target_id: confidential_issues.select(:id), target_type: Issue.name, user_id: user.id
|
||||||
).delete_all
|
).delete_all
|
||||||
|
@ -147,3 +147,5 @@ module Todos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Todos::Destroy::EntityLeaveService.prepend_if_ee('EE::Todos::Destroy::EntityLeaveService')
|
||||||
|
|
|
@ -5,8 +5,13 @@
|
||||||
# Custom validator for zoom urls
|
# Custom validator for zoom urls
|
||||||
#
|
#
|
||||||
class ZoomUrlValidator < ActiveModel::EachValidator
|
class ZoomUrlValidator < ActiveModel::EachValidator
|
||||||
|
ALLOWED_SCHEMES = %w(https).freeze
|
||||||
|
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
return if Gitlab::ZoomLinkExtractor.new(value).links.size == 1
|
links_count = Gitlab::ZoomLinkExtractor.new(value).links.size
|
||||||
|
valid = Gitlab::UrlSanitizer.valid?(value, allowed_schemes: ALLOWED_SCHEMES)
|
||||||
|
|
||||||
|
return if links_count == 1 && valid
|
||||||
|
|
||||||
record.errors.add(:url, 'must contain one valid Zoom URL')
|
record.errors.add(:url, 'must contain one valid Zoom URL')
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
= render "devise/shared/error_messages", resource: resource
|
= render "devise/shared/error_messages", resource: resource
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :email
|
= f.label :email
|
||||||
= f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.'
|
= f.email_field :email, class: "form-control", required: true, title: 'Please provide a valid email address.', value: nil
|
||||||
.clearfix
|
.clearfix
|
||||||
= f.submit "Resend", class: 'btn btn-success'
|
= f.submit "Resend", class: 'btn btn-success'
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
- is_explore_page = defined?(explore_page) && explore_page
|
- if params[:name].present? && params[:name].size < Explore::ProjectsController::MIN_SEARCH_LENGTH
|
||||||
= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true)
|
.nothing-here-block
|
||||||
|
%h5= _('Enter at least three characters to search')
|
||||||
|
- else
|
||||||
|
- is_explore_page = defined?(explore_page) && explore_page
|
||||||
|
= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true)
|
||||||
|
|
|
@ -135,6 +135,7 @@ module Gitlab
|
||||||
hook
|
hook
|
||||||
import_url
|
import_url
|
||||||
elasticsearch_url
|
elasticsearch_url
|
||||||
|
search
|
||||||
otp_attempt
|
otp_attempt
|
||||||
sentry_dsn
|
sentry_dsn
|
||||||
trace
|
trace
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
- dynamic_application_security_testing
|
- dynamic_application_security_testing
|
||||||
- editor_extension
|
- editor_extension
|
||||||
- epics
|
- epics
|
||||||
|
- epic_tracking
|
||||||
- error_tracking
|
- error_tracking
|
||||||
- feature_flags
|
- feature_flags
|
||||||
- foundations
|
- foundations
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ScheduleRemoveInaccessibleEpicTodos < ActiveRecord::Migration[6.0]
|
||||||
|
include Gitlab::Database::MigrationHelpers
|
||||||
|
|
||||||
|
DOWNTIME = false
|
||||||
|
INTERVAL = 2.minutes
|
||||||
|
BATCH_SIZE = 10
|
||||||
|
MIGRATION = 'RemoveInaccessibleEpicTodos'
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
class Epic < ActiveRecord::Base
|
||||||
|
include EachBatch
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
return unless Gitlab.ee?
|
||||||
|
|
||||||
|
relation = Epic.where(confidential: true)
|
||||||
|
|
||||||
|
queue_background_migration_jobs_by_range_at_intervals(
|
||||||
|
relation, MIGRATION, INTERVAL, batch_size: BATCH_SIZE)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# no-op
|
||||||
|
end
|
||||||
|
end
|
1
db/schema_migrations/20201109114603
Normal file
1
db/schema_migrations/20201109114603
Normal file
|
@ -0,0 +1 @@
|
||||||
|
ae8034ec52df47ce2ce3397715dd18347e4d297a963c17c7b26321f414dfa632
|
15
debian/changelog
vendored
15
debian/changelog
vendored
|
@ -1,3 +1,18 @@
|
||||||
|
gitlab (13.4.7-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Use packaged version of pdfjs-dist and adapt for worker-loader 3 api
|
||||||
|
* Remove schema-utils 3.0 from package.json (incompatibilities resolved)
|
||||||
|
All modules depending on schema-utils are now provided by debian packages
|
||||||
|
only
|
||||||
|
* Install grpc using gem command (ruby-grpc package has an unfixed
|
||||||
|
regression - see #966653)
|
||||||
|
* Update uuid to ^8.3.1 in package.json
|
||||||
|
* New upstream version 13.4.7 (Fixes: CVE-2020-26407, CVE-2020-26408,
|
||||||
|
CVE-2020-13357, CVE-2020-26411, CVE-2020-26409)
|
||||||
|
* Drop dependency on ruby-grpc (now handled by gem install)
|
||||||
|
|
||||||
|
-- Pirate Praveen <praveen@debian.org> Tue, 08 Dec 2020 15:32:56 +0530
|
||||||
|
|
||||||
gitlab (13.4.6-3~fto10+1) buster-fasttrack; urgency=medium
|
gitlab (13.4.6-3~fto10+1) buster-fasttrack; urgency=medium
|
||||||
|
|
||||||
* Rebuild for buster-fasttrack.
|
* Rebuild for buster-fasttrack.
|
||||||
|
|
4
debian/control
vendored
4
debian/control
vendored
|
@ -322,7 +322,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
|
||||||
ruby-bcrypt-pbkdf (>= 1.0~),
|
ruby-bcrypt-pbkdf (>= 1.0~),
|
||||||
# Gitaly GRPC client
|
# Gitaly GRPC client
|
||||||
ruby-gitaly (>= 13.4.6~),
|
ruby-gitaly (>= 13.4.6~),
|
||||||
ruby-grpc (>= 1.30.2~),
|
# See #966653
|
||||||
|
# ruby-grpc (>= 1.30.2~),
|
||||||
ruby-google-protobuf (>= 3.12~),
|
ruby-google-protobuf (>= 3.12~),
|
||||||
#
|
#
|
||||||
ruby-toml-rb (>= 1.0.0-2~),
|
ruby-toml-rb (>= 1.0.0-2~),
|
||||||
|
@ -392,6 +393,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
|
||||||
node-lodash (>= 4.17.15~),
|
node-lodash (>= 4.17.15~),
|
||||||
node-marked (>= 0.3~),
|
node-marked (>= 0.3~),
|
||||||
node-mousetrap,
|
node-mousetrap,
|
||||||
|
node-pdfjs-dist,
|
||||||
# Include node-pikaday only after @gitlab/ui is accepted
|
# Include node-pikaday only after @gitlab/ui is accepted
|
||||||
# node-pikaday,
|
# node-pikaday,
|
||||||
node-popper.js,
|
node-popper.js,
|
||||||
|
|
19
debian/patches/0740-use-packaged-modules.patch
vendored
19
debian/patches/0740-use-packaged-modules.patch
vendored
|
@ -3,8 +3,8 @@ Use debian packaged node modules when available
|
||||||
- Remove webpack-bundle-analyzer
|
- Remove webpack-bundle-analyzer
|
||||||
- Resolve .json files
|
- Resolve .json files
|
||||||
- Add more plugins to babel.config.js
|
- Add more plugins to babel.config.js
|
||||||
- Add schema-utils 3.0, mkdirp 1.0 and loader-utils 2.0 to ensure compatibility
|
- Add mkdirp 1.0 and loader-utils 2.0 to ensure compatibility for packaged
|
||||||
for packaged modules (See #976310)
|
modules (See #976310)
|
||||||
|
|
||||||
--- a/config/webpack.config.js
|
--- a/config/webpack.config.js
|
||||||
+++ b/config/webpack.config.js
|
+++ b/config/webpack.config.js
|
||||||
|
@ -121,7 +121,7 @@ Use debian packaged node modules when available
|
||||||
"diff": "^3.4.0",
|
"diff": "^3.4.0",
|
||||||
"document-register-element": "1.14.3",
|
"document-register-element": "1.14.3",
|
||||||
"dompurify": "^2.0.11",
|
"dompurify": "^2.0.11",
|
||||||
@@ -85,81 +62,55 @@
|
@@ -85,81 +62,53 @@
|
||||||
"editorconfig": "^0.15.3",
|
"editorconfig": "^0.15.3",
|
||||||
"emoji-regex": "^7.0.3",
|
"emoji-regex": "^7.0.3",
|
||||||
"emoji-unicode-version": "^0.2.1",
|
"emoji-unicode-version": "^0.2.1",
|
||||||
|
@ -160,7 +160,7 @@ Use debian packaged node modules when available
|
||||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||||
"monaco-yaml": "^2.4.1",
|
"monaco-yaml": "^2.4.1",
|
||||||
- "mousetrap": "1.6.5",
|
- "mousetrap": "1.6.5",
|
||||||
"pdfjs-dist": "^2.0.943",
|
- "pdfjs-dist": "^2.0.943",
|
||||||
"pikaday": "^1.8.0",
|
"pikaday": "^1.8.0",
|
||||||
- "popper.js": "^1.16.1",
|
- "popper.js": "^1.16.1",
|
||||||
- "prismjs": "^1.21.0",
|
- "prismjs": "^1.21.0",
|
||||||
|
@ -168,7 +168,6 @@ Use debian packaged node modules when available
|
||||||
- "prosemirror-model": "^1.6.4",
|
- "prosemirror-model": "^1.6.4",
|
||||||
"raphael": "^2.2.7",
|
"raphael": "^2.2.7",
|
||||||
- "raw-loader": "^4.0.0",
|
- "raw-loader": "^4.0.0",
|
||||||
+ "schema-utils": "^3.0.0",
|
|
||||||
"select2": "3.5.2-browserify",
|
"select2": "3.5.2-browserify",
|
||||||
"smooshpack": "^0.0.62",
|
"smooshpack": "^0.0.62",
|
||||||
"sortablejs": "^1.10.2",
|
"sortablejs": "^1.10.2",
|
||||||
|
@ -222,3 +221,13 @@ Use debian packaged node modules when available
|
||||||
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/229146
|
// See: https://gitlab.com/gitlab-org/gitlab/-/issues/229146
|
||||||
'@babel/plugin-transform-arrow-functions',
|
'@babel/plugin-transform-arrow-functions',
|
||||||
'lodash',
|
'lodash',
|
||||||
|
--- a/app/assets/javascripts/pdf/index.vue
|
||||||
|
+++ b/app/assets/javascripts/pdf/index.vue
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
<script>
|
||||||
|
import pdfjsLib from 'pdfjs-dist/build/pdf';
|
||||||
|
-import workerSrc from 'pdfjs-dist/build/pdf.worker.min';
|
||||||
|
+import workerSrc from 'pdfjs-dist/build/pdf.worker';
|
||||||
|
|
||||||
|
import page from './page/index.vue';
|
||||||
|
|
||||||
|
|
24
debian/patches/0790-worker-loader-3.patch
vendored
Normal file
24
debian/patches/0790-worker-loader-3.patch
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
From d074f685027a6a8b03bcaaf1f0cb2ff08c167dc9 Mon Sep 17 00:00:00 2001
|
||||||
|
From: Pirate Praveen <praveen@debian.org>
|
||||||
|
Date: Sun, 6 Dec 2020 18:24:33 +0000
|
||||||
|
Subject: [PATCH] Update worker-loader from 2.x to 3.x
|
||||||
|
|
||||||
|
---
|
||||||
|
config/webpack.config.js | 4 ++--
|
||||||
|
package.json | 2 +-
|
||||||
|
yarn.lock | 46 ++++++++++++++++++++++++++++++++++++++++
|
||||||
|
3 files changed, 49 insertions(+), 3 deletions(-)
|
||||||
|
|
||||||
|
--- a/config/webpack.config.js
|
||||||
|
+++ b/config/webpack.config.js
|
||||||
|
@@ -230,8 +230,8 @@
|
||||||
|
{
|
||||||
|
loader: 'worker-loader',
|
||||||
|
options: {
|
||||||
|
- name: '[name].[contenthash:8].worker.js',
|
||||||
|
- inline: IS_DEV_SERVER,
|
||||||
|
+ filename: '[name].[contenthash:8].worker.js',
|
||||||
|
+ inline: "fallback",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'babel-loader',
|
11
debian/patches/0791-update-uuid.patch
vendored
Normal file
11
debian/patches/0791-update-uuid.patch
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
--- a/package.json
|
||||||
|
+++ b/package.json
|
||||||
|
@@ -99,7 +99,7 @@
|
||||||
|
"tiptap-commands": "^1.4.0",
|
||||||
|
"tiptap-extensions": "^1.8.0",
|
||||||
|
"tributejs": "5.1.3",
|
||||||
|
- "uuid": "8.1.0",
|
||||||
|
+ "uuid": "^8.3.1",
|
||||||
|
"visibilityjs": "^1.2.4",
|
||||||
|
"vue-apollo": "^3.0.3",
|
||||||
|
"vue-loader": "^15.9.0",
|
2
debian/patches/series
vendored
2
debian/patches/series
vendored
|
@ -30,3 +30,5 @@
|
||||||
0770-remove-rubocop-rspec.patch
|
0770-remove-rubocop-rspec.patch
|
||||||
0770-remove-capybara-screenshot-rspec.patch
|
0770-remove-capybara-screenshot-rspec.patch
|
||||||
0780-css-loader-3.patch
|
0780-css-loader-3.patch
|
||||||
|
0790-worker-loader-3.patch
|
||||||
|
0791-update-uuid.patch
|
||||||
|
|
4
debian/rake-tasks.sh
vendored
4
debian/rake-tasks.sh
vendored
|
@ -8,6 +8,10 @@ export DB RAILS_ENV
|
||||||
|
|
||||||
cd /usr/share/gitlab
|
cd /usr/share/gitlab
|
||||||
|
|
||||||
|
# Workaround for #966653
|
||||||
|
/usr/bin/gem install -v 1.30.2 grpc
|
||||||
|
runuser -u ${gitlab_user} -- sh -c '/usr/bin/bundle install --local'
|
||||||
|
|
||||||
# Check if the db is already present
|
# Check if the db is already present
|
||||||
db_relations="$(LANG=C runuser -u postgres -- sh -c "psql gitlab_production -c \"\d\"" 2>&1)"
|
db_relations="$(LANG=C runuser -u postgres -- sh -c "psql gitlab_production -c \"\d\"" 2>&1)"
|
||||||
if [ "$db_relations" = "No relations found." ] || \
|
if [ "$db_relations" = "No relations found." ] || \
|
||||||
|
|
|
@ -129,7 +129,11 @@ Note the following when promoting a secondary:
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Promote the **secondary** node to the **primary** node.
|
1. Promote the **secondary** node to the **primary** node.
|
||||||
|
CAUTION: **Caution:**
|
||||||
|
If the secondary node [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
|
||||||
|
a point-in-time recovery to the last known state.
|
||||||
|
Data that was created on the primary while the secondary was paused will be lost.
|
||||||
|
|
||||||
To promote the secondary node to primary along with preflight checks:
|
To promote the secondary node to primary along with preflight checks:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -159,11 +163,16 @@ conjunction with multiple servers, as it can only
|
||||||
perform changes on a **secondary** with only a single machine. Instead, you must
|
perform changes on a **secondary** with only a single machine. Instead, you must
|
||||||
do this manually.
|
do this manually.
|
||||||
|
|
||||||
|
CAUTION: **Caution:**
|
||||||
|
If the secondary node [has been paused](../../geo/index.md#pausing-and-resuming-replication), this performs
|
||||||
|
a point-in-time recovery to the last known state.
|
||||||
|
Data that was created on the primary while the secondary was paused will be lost.
|
||||||
|
|
||||||
1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
|
1. SSH in to the database node in the **secondary** and trigger PostgreSQL to
|
||||||
promote to read-write:
|
promote to read-write:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo gitlab-pg-ctl promote
|
sudo gitlab-ctl promote-db
|
||||||
```
|
```
|
||||||
|
|
||||||
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
|
In GitLab 12.8 and earlier, see [Message: `sudo: gitlab-pg-ctl: command not found`](../replication/troubleshooting.md#message-sudo-gitlab-pg-ctl-command-not-found).
|
||||||
|
|
|
@ -195,6 +195,10 @@ For information on how to update your Geo nodes to the latest GitLab version, se
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35913) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
|
||||||
|
|
||||||
|
CAUTION: **Caution:**
|
||||||
|
Pausing and resuming of replication is currently only supported for Geo installations using an
|
||||||
|
Omnibus GitLab-managed database. External databases are currently not supported.
|
||||||
|
|
||||||
In some circumstances, like during [upgrades](replication/updating_the_geo_nodes.md) or a [planned failover](disaster_recovery/planned_failover.md), it is desirable to pause replication between the primary and secondary.
|
In some circumstances, like during [upgrades](replication/updating_the_geo_nodes.md) or a [planned failover](disaster_recovery/planned_failover.md), it is desirable to pause replication between the primary and secondary.
|
||||||
|
|
||||||
Pausing and resuming replication is done via a command line tool from the secondary node.
|
Pausing and resuming replication is done via a command line tool from the secondary node.
|
||||||
|
|
|
@ -64,7 +64,7 @@ To-do triggers aren't affected by [GitLab notification email settings](profile/n
|
||||||
|
|
||||||
NOTE: **Note:**
|
NOTE: **Note:**
|
||||||
When a user no longer has access to a resource related to a to-do (such as an
|
When a user no longer has access to a resource related to a to-do (such as an
|
||||||
issue, merge request, project, or group), for security reasons GitLab deletes
|
issue, merge request, epic, project, or group), for security reasons GitLab deletes
|
||||||
any related to-do items within the next hour. Deletion is delayed to prevent
|
any related to-do items within the next hour. Deletion is delayed to prevent
|
||||||
data loss, in the case where a user's access is accidentally revoked.
|
data loss, in the case where a user's access is accidentally revoked.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module BackgroundMigration
|
||||||
|
# rubocop:disable Style/Documentation
|
||||||
|
class RemoveInaccessibleEpicTodos
|
||||||
|
def perform(start_id, stop_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos.prepend_if_ee('EE::Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos')
|
80
spec/controllers/confirmations_controller_spec.rb
Normal file
80
spec/controllers/confirmations_controller_spec.rb
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe ConfirmationsController do
|
||||||
|
include DeviseHelpers
|
||||||
|
|
||||||
|
before do
|
||||||
|
set_devise_mapping(context: @request)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#show' do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
subject { get :show, params: { confirmation_token: confirmation_token } }
|
||||||
|
|
||||||
|
context 'user is already confirmed' do
|
||||||
|
let_it_be_with_reload(:user) { create(:user, :unconfirmed) }
|
||||||
|
let(:confirmation_token) { user.confirmation_token }
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.confirm
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders `new`' do
|
||||||
|
expect(response).to render_template(:new)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an error message' do
|
||||||
|
expect(response.body).to include('Email was already confirmed, please try signing in')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not display the email of the user' do
|
||||||
|
expect(response.body).not_to include(user.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user accesses the link after the expiry of confirmation token has passed' do
|
||||||
|
let_it_be_with_reload(:user) { create(:user, :unconfirmed) }
|
||||||
|
let(:confirmation_token) { user.confirmation_token }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Devise).to receive(:confirm_within).and_return(1.day)
|
||||||
|
|
||||||
|
travel_to(3.days.from_now) do
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders `new`' do
|
||||||
|
expect(response).to render_template(:new)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an error message' do
|
||||||
|
expect(response.body).to include('Email needs to be confirmed within 1 day, please request a new one below')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not display the email of the user' do
|
||||||
|
expect(response.body).not_to include(user.email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an invalid confirmation token' do
|
||||||
|
let(:confirmation_token) { 'invalid_confirmation_token' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders `new`' do
|
||||||
|
expect(response).to render_template(:new)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays an error message' do
|
||||||
|
expect(response.body).to include('Confirmation token is invalid')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -272,7 +272,7 @@ RSpec.describe SearchController do
|
||||||
|
|
||||||
expect(last_payload[:metadata]['meta.search.group_id']).to eq('123')
|
expect(last_payload[:metadata]['meta.search.group_id']).to eq('123')
|
||||||
expect(last_payload[:metadata]['meta.search.project_id']).to eq('456')
|
expect(last_payload[:metadata]['meta.search.project_id']).to eq('456')
|
||||||
expect(last_payload[:metadata]['meta.search.search']).to eq('hello world')
|
expect(last_payload[:metadata]).not_to have_key('meta.search.search')
|
||||||
expect(last_payload[:metadata]['meta.search.scope']).to eq('issues')
|
expect(last_payload[:metadata]['meta.search.scope']).to eq('issues')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -247,32 +247,99 @@ RSpec.describe UsersController do
|
||||||
|
|
||||||
describe 'GET #contributed' do
|
describe 'GET #contributed' do
|
||||||
let(:project) { create(:project, :public) }
|
let(:project) { create(:project, :public) }
|
||||||
let(:current_user) { create(:user) }
|
|
||||||
|
subject do
|
||||||
|
get :contributed, params: { username: author.username }, format: format
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(current_user)
|
sign_in(user)
|
||||||
|
|
||||||
project.add_developer(public_user)
|
project.add_developer(public_user)
|
||||||
project.add_developer(private_user)
|
project.add_developer(private_user)
|
||||||
|
create(:push_event, project: project, author: author)
|
||||||
|
|
||||||
|
subject
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with public profile' do
|
shared_examples_for 'renders contributed projects' do
|
||||||
it 'renders contributed projects' do
|
it 'renders contributed projects' do
|
||||||
create(:push_event, project: project, author: public_user)
|
|
||||||
|
|
||||||
get :contributed, params: { username: public_user.username }
|
|
||||||
|
|
||||||
expect(assigns[:contributed_projects]).not_to be_empty
|
expect(assigns[:contributed_projects]).not_to be_empty
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with private profile' do
|
%i(html json).each do |format|
|
||||||
it 'does not render contributed projects' do
|
context "format: #{format}" do
|
||||||
create(:push_event, project: project, author: private_user)
|
let(:format) { format }
|
||||||
|
|
||||||
get :contributed, params: { username: private_user.username }
|
context 'with public profile' do
|
||||||
|
let(:author) { public_user }
|
||||||
|
|
||||||
expect(assigns[:contributed_projects]).to be_empty
|
it_behaves_like 'renders contributed projects'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with private profile' do
|
||||||
|
let(:author) { private_user }
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a user that has the ability to read private profiles', :enable_admin_mode do
|
||||||
|
let(:user) { create(:admin) }
|
||||||
|
|
||||||
|
it_behaves_like 'renders contributed projects'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET #starred' do
|
||||||
|
let(:project) { create(:project, :public) }
|
||||||
|
|
||||||
|
subject do
|
||||||
|
get :starred, params: { username: author.username }, format: format
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
author.toggle_star(project)
|
||||||
|
|
||||||
|
sign_in(user)
|
||||||
|
subject
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples_for 'renders starred projects' do
|
||||||
|
it 'renders starred projects' do
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(assigns[:starred_projects]).not_to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
%i(html json).each do |format|
|
||||||
|
context "format: #{format}" do
|
||||||
|
let(:format) { format }
|
||||||
|
|
||||||
|
context 'with public profile' do
|
||||||
|
let(:author) { public_user }
|
||||||
|
|
||||||
|
it_behaves_like 'renders starred projects'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with private profile' do
|
||||||
|
let(:author) { private_user }
|
||||||
|
|
||||||
|
it 'returns 404' do
|
||||||
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a user that has the ability to read private profiles', :enable_admin_mode do
|
||||||
|
let(:user) { create(:admin) }
|
||||||
|
|
||||||
|
it_behaves_like 'renders starred projects'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,6 +34,16 @@ RSpec.describe 'User explores projects' do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
||||||
|
stub_feature_flags(project_list_filter_bar: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'minimum search length' do
|
||||||
|
it 'shows a prompt to enter a longer search term', :js do
|
||||||
|
fill_in 'name', with: 'z'
|
||||||
|
|
||||||
|
expect(page).to have_content('Enter at least three characters to search')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when viewing public projects' do
|
context 'when viewing public projects' do
|
||||||
|
@ -42,6 +52,7 @@ RSpec.describe 'User explores projects' do
|
||||||
end
|
end
|
||||||
|
|
||||||
include_examples 'shows public and internal projects'
|
include_examples 'shows public and internal projects'
|
||||||
|
include_examples 'minimum search length'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when viewing most starred projects' do
|
context 'when viewing most starred projects' do
|
||||||
|
@ -50,6 +61,7 @@ RSpec.describe 'User explores projects' do
|
||||||
end
|
end
|
||||||
|
|
||||||
include_examples 'shows public and internal projects'
|
include_examples 'shows public and internal projects'
|
||||||
|
include_examples 'minimum search length'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when viewing trending projects' do
|
context 'when viewing trending projects' do
|
||||||
|
@ -62,6 +74,7 @@ RSpec.describe 'User explores projects' do
|
||||||
end
|
end
|
||||||
|
|
||||||
include_examples 'shows public projects'
|
include_examples 'shows public projects'
|
||||||
|
include_examples 'minimum search length'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,6 +19,9 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
|
|
||||||
visit project_issue_path(project, issue)
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
%w[A B C D].each do |label|
|
%w[A B C D].each do |label|
|
||||||
expect(page).to have_selector('svg text', text: label)
|
expect(page).to have_selector('svg text', text: label)
|
||||||
end
|
end
|
||||||
|
@ -39,6 +42,7 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
visit project_issue_path(project, issue)
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
expected = '<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
|
expected = '<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
|
||||||
expect(page.html.scan(expected).count).to be(4)
|
expect(page.html.scan(expected).count).to be(4)
|
||||||
|
@ -65,6 +69,9 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
|
|
||||||
visit project_issue_path(project, issue)
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
page.within('.description') do
|
page.within('.description') do
|
||||||
expect(page).to have_selector('svg')
|
expect(page).to have_selector('svg')
|
||||||
expect(page).to have_selector('pre.mermaid')
|
expect(page).to have_selector('pre.mermaid')
|
||||||
|
@ -92,6 +99,9 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
|
|
||||||
visit project_issue_path(project, issue)
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
page.within('.description') do
|
page.within('.description') do
|
||||||
page.find('summary').click
|
page.find('summary').click
|
||||||
svg = page.find('svg.mermaid')
|
svg = page.find('svg.mermaid')
|
||||||
|
@ -118,6 +128,9 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
|
|
||||||
visit project_issue_path(project, issue)
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
|
expect(page).to have_css('svg.mermaid[style*="max-width"][width="100%"]')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -147,6 +160,7 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
find('.js-lazy-render-mermaid').click
|
find('.js-lazy-render-mermaid').click
|
||||||
|
|
||||||
|
@ -156,4 +170,55 @@ RSpec.describe 'Mermaid rendering', :js do
|
||||||
expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
|
expect(page).not_to have_selector('.js-lazy-render-mermaid-container')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not render more than 50 mermaid blocks', :js, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/234081' } do
|
||||||
|
graph_edges = "A-->B;B-->A;"
|
||||||
|
|
||||||
|
description = <<~MERMAID
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
#{graph_edges}
|
||||||
|
```
|
||||||
|
MERMAID
|
||||||
|
|
||||||
|
description *= 51
|
||||||
|
|
||||||
|
project = create(:project, :public)
|
||||||
|
|
||||||
|
issue = create(:issue, project: project, description: description)
|
||||||
|
|
||||||
|
visit project_issue_path(project, issue)
|
||||||
|
|
||||||
|
wait_for_requests
|
||||||
|
wait_for_mermaid
|
||||||
|
|
||||||
|
page.within('.description') do
|
||||||
|
expect(page).to have_selector('svg')
|
||||||
|
|
||||||
|
expect(page).to have_selector('.lazy-alert-shown')
|
||||||
|
|
||||||
|
expect(page).to have_selector('.js-lazy-render-mermaid-container')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def wait_for_mermaid
|
||||||
|
run_idle_callback = <<~RUN_IDLE_CALLBACK
|
||||||
|
window.requestIdleCallback(() => {
|
||||||
|
window.__CAPYBARA_IDLE_CALLBACK_EXEC__ = 1;
|
||||||
|
})
|
||||||
|
RUN_IDLE_CALLBACK
|
||||||
|
|
||||||
|
page.evaluate_script(run_idle_callback)
|
||||||
|
|
||||||
|
Timeout.timeout(Capybara.default_max_wait_time) do
|
||||||
|
loop until finished_rendering?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def finished_rendering?
|
||||||
|
check_idle_callback = <<~CHECK_IDLE_CALLBACK
|
||||||
|
window.__CAPYBARA_IDLE_CALLBACK_EXEC__
|
||||||
|
CHECK_IDLE_CALLBACK
|
||||||
|
page.evaluate_script(check_idle_callback) == 1
|
||||||
end
|
end
|
||||||
|
|
|
@ -157,6 +157,29 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
|
||||||
it { is_expected.to eq([public_project]) }
|
it { is_expected.to eq([public_project]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'filter by search with minimum search length' do
|
||||||
|
context 'when search term is shorter than minimum length' do
|
||||||
|
let(:params) { { search: 'C', minimum_search_length: 3 } }
|
||||||
|
|
||||||
|
it { is_expected.to be_empty }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when search term is longer than minimum length' do
|
||||||
|
let(:project) { create(:project, :public, group: group, name: 'test_project') }
|
||||||
|
let(:params) { { search: 'test', minimum_search_length: 3 } }
|
||||||
|
|
||||||
|
it { is_expected.to eq([project]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when minimum length is invalid' do
|
||||||
|
let(:params) { { search: 'C', minimum_search_length: 'x' } }
|
||||||
|
|
||||||
|
it 'ignores the minimum length param' do
|
||||||
|
is_expected.to eq([public_project])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'filter by group name' do
|
describe 'filter by group name' do
|
||||||
let(:params) { { name: group.name, search_namespaces: true } }
|
let(:params) { { name: group.name, search_namespaces: true } }
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ require 'spec_helper'
|
||||||
RSpec.describe StarredProjectsFinder do
|
RSpec.describe StarredProjectsFinder do
|
||||||
let(:project1) { create(:project, :public, :empty_repo) }
|
let(:project1) { create(:project, :public, :empty_repo) }
|
||||||
let(:project2) { create(:project, :public, :empty_repo) }
|
let(:project2) { create(:project, :public, :empty_repo) }
|
||||||
let(:other_project) { create(:project, :public, :empty_repo) }
|
let(:private_project) { create(:project, :private, :empty_repo) }
|
||||||
|
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
let(:other_user) { create(:user) }
|
let(:other_user) { create(:user) }
|
||||||
|
@ -13,6 +13,9 @@ RSpec.describe StarredProjectsFinder do
|
||||||
before do
|
before do
|
||||||
user.toggle_star(project1)
|
user.toggle_star(project1)
|
||||||
user.toggle_star(project2)
|
user.toggle_star(project2)
|
||||||
|
|
||||||
|
private_project.add_maintainer(user)
|
||||||
|
user.toggle_star(private_project)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
|
@ -20,22 +23,56 @@ RSpec.describe StarredProjectsFinder do
|
||||||
|
|
||||||
subject { finder.execute }
|
subject { finder.execute }
|
||||||
|
|
||||||
describe 'as same user' do
|
context 'user has a public profile' do
|
||||||
let(:current_user) { user }
|
describe 'as same user' do
|
||||||
|
let(:current_user) { user }
|
||||||
|
|
||||||
it { is_expected.to contain_exactly(project1, project2) }
|
it { is_expected.to contain_exactly(project1, project2, private_project) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'as other user' do
|
||||||
|
let(:current_user) { other_user }
|
||||||
|
|
||||||
|
it { is_expected.to contain_exactly(project1, project2) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'as no user' do
|
||||||
|
let(:current_user) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to contain_exactly(project1, project2) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'as other user' do
|
context 'user has a private profile' do
|
||||||
let(:current_user) { other_user }
|
before do
|
||||||
|
user.update!(private_profile: true)
|
||||||
|
end
|
||||||
|
|
||||||
it { is_expected.to contain_exactly(project1, project2) }
|
describe 'as same user' do
|
||||||
end
|
let(:current_user) { user }
|
||||||
|
|
||||||
describe 'as no user' do
|
it { is_expected.to contain_exactly(project1, project2, private_project) }
|
||||||
let(:current_user) { nil }
|
end
|
||||||
|
|
||||||
it { is_expected.to contain_exactly(project1, project2) }
|
describe 'as other user' do
|
||||||
|
context 'user does not have access to view the private profile' do
|
||||||
|
let(:current_user) { other_user }
|
||||||
|
|
||||||
|
it { is_expected.to be_empty }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user has access to view the private profile', :enable_admin_mode do
|
||||||
|
let(:current_user) { create(:admin) }
|
||||||
|
|
||||||
|
it { is_expected.to contain_exactly(project1, project2, private_project) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'as no user' do
|
||||||
|
let(:current_user) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_empty }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -70,4 +70,31 @@ RSpec.describe 'Getting starredProjects of the user' do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'the user has a private profile' do
|
||||||
|
before do
|
||||||
|
user.update!(private_profile: true)
|
||||||
|
post_graphql(query, current_user: current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the current user does not have access to view the private profile of the user' do
|
||||||
|
let(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
it 'finds no projects' do
|
||||||
|
expect(starred_projects).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the current user has access to view the private profile of the user' do
|
||||||
|
let(:current_user) { create(:admin) }
|
||||||
|
|
||||||
|
it 'finds all projects starred by the user, which the current user has access to' do
|
||||||
|
expect(starred_projects).to contain_exactly(
|
||||||
|
a_hash_including('id' => global_id_of(project_a)),
|
||||||
|
a_hash_including('id' => global_id_of(project_b)),
|
||||||
|
a_hash_including('id' => global_id_of(project_c))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -77,7 +77,7 @@ RSpec.describe 'getting user information' do
|
||||||
'webUrl' => presenter.web_url,
|
'webUrl' => presenter.web_url,
|
||||||
'avatarUrl' => presenter.avatar_url,
|
'avatarUrl' => presenter.avatar_url,
|
||||||
'status' => presenter.status,
|
'status' => presenter.status,
|
||||||
'email' => presenter.email
|
'email' => presenter.public_email
|
||||||
))
|
))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ RSpec.describe 'getting user information' do
|
||||||
|
|
||||||
context 'the user is private' do
|
context 'the user is private' do
|
||||||
before do
|
before do
|
||||||
user.update(private_profile: true)
|
user.update!(private_profile: true)
|
||||||
post_graphql(query, current_user: current_user)
|
post_graphql(query, current_user: current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -220,6 +220,50 @@ RSpec.describe 'getting user information' do
|
||||||
it_behaves_like 'a working graphql query'
|
it_behaves_like 'a working graphql query'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'we request the groupMemberships' do
|
||||||
|
let_it_be(:membership_a) { create(:group_member, :developer, user: user) }
|
||||||
|
let(:group_memberships) { graphql_data_at(:user, :group_memberships, :nodes) }
|
||||||
|
let(:user_fields) { 'groupMemberships { nodes { id } }' }
|
||||||
|
|
||||||
|
it_behaves_like 'a working graphql query'
|
||||||
|
|
||||||
|
it 'cannot be found' do
|
||||||
|
expect(group_memberships).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the current user is the user' do
|
||||||
|
let(:current_user) { user }
|
||||||
|
|
||||||
|
it 'can be found' do
|
||||||
|
expect(group_memberships).to include(
|
||||||
|
a_hash_including('id' => global_id_of(membership_a))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'we request the projectMemberships' do
|
||||||
|
let_it_be(:membership_a) { create(:project_member, user: user) }
|
||||||
|
let(:project_memberships) { graphql_data_at(:user, :project_memberships, :nodes) }
|
||||||
|
let(:user_fields) { 'projectMemberships { nodes { id } }' }
|
||||||
|
|
||||||
|
it_behaves_like 'a working graphql query'
|
||||||
|
|
||||||
|
it 'cannot be found' do
|
||||||
|
expect(project_memberships).to be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'the current user is the user' do
|
||||||
|
let(:current_user) { user }
|
||||||
|
|
||||||
|
it 'can be found' do
|
||||||
|
expect(project_memberships).to include(
|
||||||
|
a_hash_including('id' => global_id_of(membership_a))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'we request the authoredMergeRequests' do
|
context 'we request the authoredMergeRequests' do
|
||||||
let(:user_fields) { 'authoredMergeRequests { nodes { id } }' }
|
let(:user_fields) { 'authoredMergeRequests { nodes { id } }' }
|
||||||
|
|
||||||
|
|
|
@ -1255,13 +1255,46 @@ RSpec.describe API::Projects do
|
||||||
expect(json_response['message']).to eq('404 User Not Found')
|
expect(json_response['message']).to eq('404 User Not Found')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns projects filtered by user' do
|
context 'with a public profile' do
|
||||||
get api("/users/#{user3.id}/starred_projects/", user)
|
it 'returns projects filtered by user' do
|
||||||
|
get api("/users/#{user3.id}/starred_projects/", user)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(response).to include_pagination_headers
|
expect(response).to include_pagination_headers
|
||||||
expect(json_response).to be_an Array
|
expect(json_response).to be_an Array
|
||||||
expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id)
|
expect(json_response.map { |project| project['id'] })
|
||||||
|
.to contain_exactly(project.id, project2.id, project3.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a private profile' do
|
||||||
|
before do
|
||||||
|
user3.update!(private_profile: true)
|
||||||
|
user3.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user does not have access to view the private profile' do
|
||||||
|
it 'returns no projects' do
|
||||||
|
get api("/users/#{user3.id}/starred_projects/", user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response).to include_pagination_headers
|
||||||
|
expect(json_response).to be_an Array
|
||||||
|
expect(json_response).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'user has access to view the private profile' do
|
||||||
|
it 'returns projects filtered by user' do
|
||||||
|
get api("/users/#{user3.id}/starred_projects/", admin)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
expect(response).to include_pagination_headers
|
||||||
|
expect(json_response).to be_an Array
|
||||||
|
expect(json_response.map { |project| project['id'] })
|
||||||
|
.to contain_exactly(project.id, project2.id, project3.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
36
spec/validators/zoom_url_validator_spec.rb
Normal file
36
spec/validators/zoom_url_validator_spec.rb
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe ZoomUrlValidator do
|
||||||
|
let(:zoom_meeting) { build(:zoom_meeting) }
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
context 'when zoom link starts with https' do
|
||||||
|
it 'passes validation' do
|
||||||
|
zoom_meeting.url = 'https://zoom.us/j/123456789'
|
||||||
|
|
||||||
|
expect(zoom_meeting.valid?).to eq(true)
|
||||||
|
expect(zoom_meeting.errors).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'zoom link does not start with https' do |url|
|
||||||
|
it 'fails validation' do
|
||||||
|
zoom_meeting.url = url
|
||||||
|
expect(zoom_meeting.valid?).to eq(false)
|
||||||
|
|
||||||
|
expect(zoom_meeting.errors).to be_present
|
||||||
|
expect(zoom_meeting.errors.first[1]).to eq 'must contain one valid Zoom URL'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when zoom link does not start with https' do
|
||||||
|
include_examples 'zoom link does not start with https', 'http://zoom.us/j/123456789'
|
||||||
|
|
||||||
|
context 'when zoom link does not start with a scheme' do
|
||||||
|
include_examples 'zoom link does not start with https', 'testinghttp://zoom.us/j/123456789'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue