Commit 6b833f1e authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 88a08249
......@@ -17,7 +17,7 @@ export default {
diffFilesLength: {
type: Number,
required: false,
default: 0,
default: null,
},
},
computed: {
......
......@@ -2,7 +2,15 @@
import { mapActions, mapGetters, mapState } from 'vuex';
import dateFormat from 'dateformat';
import createFlash from '~/flash';
import { GlButton, GlFormInput, GlLink, GlLoadingIcon, GlBadge } from '@gitlab/ui';
import {
GlButton,
GlFormInput,
GlLink,
GlLoadingIcon,
GlBadge,
GlAlert,
GlSprintf,
} from '@gitlab/ui';
import { __, sprintf, n__ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -26,6 +34,8 @@ export default {
Icon,
Stacktrace,
GlBadge,
GlAlert,
GlSprintf,
},
directives: {
TrackEvent: TrackEventDirective,
......@@ -85,6 +95,8 @@ export default {
return {
GQLerror: null,
issueCreationInProgress: false,
isAlertVisible: false,
closedIssueId: null,
};
},
computed: {
......@@ -184,7 +196,14 @@ export default {
onResolveStatusUpdate() {
const status =
this.errorStatus === errorStatus.RESOLVED ? errorStatus.UNRESOLVED : errorStatus.RESOLVED;
this.updateResolveStatus({ endpoint: this.issueUpdatePath, status });
// eslint-disable-next-line promise/catch-or-return
this.updateResolveStatus({ endpoint: this.issueUpdatePath, status }).then(res => {
this.closedIssueId = res.closed_issue_iid;
if (this.closedIssueId) {
this.isAlertVisible = true;
}
});
},
formatDate(date) {
return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
......@@ -199,6 +218,18 @@ export default {
<gl-loading-icon :size="3" />
</div>
<div v-else-if="showDetails" class="error-details">
<gl-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false">
<gl-sprintf
:message="
__('The associated issue #%{issueId} has been closed as the error is now resolved.')
"
>
<template #issueId>
<span>{{ closedIssueId }}</span>
</template>
</gl-sprintf>
</gl-alert>
<div class="top-area align-items-center justify-content-between py-3">
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
<div class="d-inline-flex">
......
......@@ -11,9 +11,11 @@ export const setStatus = ({ commit }, status) => {
export const updateStatus = ({ commit }, { endpoint, redirectUrl, status }) =>
service
.updateErrorStatus(endpoint, status)
.then(() => {
if (redirectUrl) visitUrl(redirectUrl);
.then(resp => {
commit(types.SET_ERROR_STATUS, status);
if (redirectUrl) visitUrl(redirectUrl);
return resp.data.result;
})
.catch(() => createFlash(__('Failed to update issue status')));
......
......@@ -27,7 +27,7 @@ export default {
<template>
<div class="d-flex">
<gl-sprintf message="by %{user}">
<gl-sprintf :message="__('by %{user}')">
<template #user>
<user-avatar-link
class="prepend-left-4"
......
......@@ -146,9 +146,8 @@ export default {
v-if="commit.description"
:class="{ 'd-block': showDescription }"
class="commit-row-description append-bottom-8"
>{{ commit.description }}</pre
>
{{ commit.description }}
</pre>
</div>
<div class="commit-actions flex-row">
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
......
......@@ -165,7 +165,7 @@ export default {
<gl-icon :name="visibilityLevelIcon" :size="14" />
</div>
<div class="creator">
<gl-sprintf message="Authored %{timeago} by %{author}">
<gl-sprintf :message="__('Authored %{timeago} by %{author}')">
<template #timeago>
<time-ago-tooltip
:time="snippet.createdAt"
......@@ -218,7 +218,7 @@ export default {
errorMessage
}}</gl-alert>
<gl-sprintf message="Are you sure you want to delete %{name}?">
<gl-sprintf :message="__('Are you sure you want to delete %{name}?')">
<template #name
><strong>{{ snippet.title }}</strong></template
>
......
......@@ -25,7 +25,7 @@ export default {
</div>
<small v-if="snippet.updatedAt !== snippet.createdAt" class="edited-text">
<gl-sprintf message="Edited %{timeago}">
<gl-sprintf :message="__('Edited %{timeago}')">
<template #timeago>
<time-ago-tooltip :time="snippet.updatedAt" tooltip-placement="bottom" />
</template>
......
......@@ -65,7 +65,10 @@ class Blob < SimpleDelegator
BlobViewer::YarnLock
].freeze
attr_reader :project
attr_reader :container
delegate :repository, to: :container, allow_nil: true
delegate :project, to: :repository, allow_nil: true
# Wrap a Gitlab::Git::Blob object, or return nil when given nil
#
......@@ -77,22 +80,22 @@ class Blob < SimpleDelegator
#
# blob = Blob.decorate(nil)
# puts "truthy" if blob # No output
def self.decorate(blob, project = nil)
def self.decorate(blob, container = nil)
return if blob.nil?
new(blob, project)
new(blob, container)
end
def self.lazy(project, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
BatchLoader.for([commit_id, path]).batch(key: project.repository) do |items, loader, args|
def self.lazy(container, commit_id, path, blob_size_limit: Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
BatchLoader.for([commit_id, path]).batch(key: container.repository) do |items, loader, args|
args[:key].blobs_at(items, blob_size_limit: blob_size_limit).each do |blob|
loader.call([blob.commit_id, blob.path], blob) if blob
end
end
end
def initialize(blob, project = nil)
@project = project
def initialize(blob, container = nil)
@container = container
super(blob)
end
......@@ -116,7 +119,7 @@ class Blob < SimpleDelegator
def load_all_data!
# Endpoint needed: https://gitlab.com/gitlab-org/gitaly/issues/756
Gitlab::GitalyClient.allow_n_plus_1_calls do
super(project.repository) if project
super(repository) if container
end
end
......
......@@ -21,11 +21,14 @@ class Commit
participant :committer
participant :notes_with_associations
attr_accessor :project, :author
attr_accessor :author
attr_accessor :redacted_description_html
attr_accessor :redacted_title_html
attr_accessor :redacted_full_title_html
attr_reader :gpg_commit
attr_reader :gpg_commit, :container
delegate :repository, to: :container
delegate :project, to: :repository, allow_nil: true
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
......@@ -44,12 +47,12 @@ class Commit
cache_markdown_field :description, pipeline: :commit_description
class << self
def decorate(commits, project)
def decorate(commits, container)
commits.map do |commit|
if commit.is_a?(Commit)
commit
else
self.new(commit, project)
self.new(commit, container)
end
end
end
......@@ -85,24 +88,24 @@ class Commit
}
end
def from_hash(hash, project)
raw_commit = Gitlab::Git::Commit.new(project.repository.raw, hash)
new(raw_commit, project)
def from_hash(hash, container)
raw_commit = Gitlab::Git::Commit.new(container.repository.raw, hash)
new(raw_commit, container)
end
def valid_hash?(key)
!!(EXACT_COMMIT_SHA_PATTERN =~ key)
end
def lazy(project, oid)
BatchLoader.for({ project: project, oid: oid }).batch(replace_methods: false) do |items, loader|
items_by_project = items.group_by { |i| i[:project] }
def lazy(container, oid)
BatchLoader.for({ container: container, oid: oid }).batch(replace_methods: false) do |items, loader|
items_by_container = items.group_by { |i| i[:container] }
items_by_project.each do |project, commit_ids|
items_by_container.each do |container, commit_ids|
oids = commit_ids.map { |i| i[:oid] }
project.repository.commits_by(oids: oids).each do |commit|
loader.call({ project: commit.project, oid: commit.id }, commit) if commit
container.repository.commits_by(oids: oids).each do |commit|
loader.call({ container: commit.container, oid: commit.id }, commit) if commit
end
end
end
......@@ -115,12 +118,12 @@ class Commit
attr_accessor :raw
def initialize(raw_commit, project)
def initialize(raw_commit, container)
raise "Nil as raw commit passed" unless raw_commit
@raw = raw_commit
@project = project
@gpg_commit = Gitlab::Gpg::Commit.new(self) if project
@container = container
@gpg_commit = Gitlab::Gpg::Commit.new(self) if container
end
delegate \
......@@ -141,7 +144,7 @@ class Commit
end
def project_id
project.id
project&.id
end
def ==(other)
......@@ -269,17 +272,17 @@ class Commit
end
def parents
@parents ||= parent_ids.map { |oid| Commit.lazy(project, oid) }
@parents ||= parent_ids.map { |oid| Commit.lazy(container, oid) }
end
def parent
strong_memoize(:parent) do
project.commit_by(oid: self.parent_id) if self.parent_id
container.commit_by(oid: self.parent_id) if self.parent_id
end
end
def notes
project.notes.for_commit_id(self.id)
container.notes.for_commit_id(self.id)
end
def user_mentions
......@@ -295,7 +298,7 @@ class Commit
end
def merge_requests
@merge_requests ||= project.merge_requests.by_commit_sha(sha)
@merge_requests ||= project&.merge_requests&.by_commit_sha(sha)
end
def method_missing(method, *args, &block)
......@@ -330,7 +333,7 @@ class Commit
end
def cherry_pick_branch_name
project.repository.next_branch("cherry-pick-#{short_id}", mild: true)
repository.next_branch("cherry-pick-#{short_id}", mild: true)
end
def cherry_pick_description(user)
......@@ -418,7 +421,7 @@ class Commit
return unless entry
if entry[:type] == :blob
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project)
blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), container)
blob.image? || blob.video? || blob.audio? ? :raw : :blob
else
entry[:type]
......@@ -484,7 +487,7 @@ class Commit
end
def commit_reference(from, referable_commit_id, full: false)
base = project.to_reference_base(from, full: full)
base = project&.to_reference_base(from, full: full)
if base.present?
"#{base}#{self.class.reference_prefix}#{referable_commit_id}"
......@@ -510,6 +513,6 @@ class Commit
end
def merged_merge_request_no_cache(user)
MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit?
MergeRequestsFinder.new(user, project_id: project_id).find_by(merge_commit_sha: id) if merge_commit?
end
end
# frozen_string_literal: true
# A collection of Commit instances for a specific project and Git reference.
# A collection of Commit instances for a specific container and Git reference.
class CommitCollection
include Enumerable
include Gitlab::Utils::StrongMemoize
attr_reader :project, :ref, :commits
attr_reader :container, :ref, :commits
# project - The project the commits belong to.
delegate :repository, to: :container, allow_nil: true
delegate :project, to: :repository, allow_nil: true
# container - The object the commits belong to.
# commits - The Commit instances to store.
# ref - The name of the ref (e.g. "master").
def initialize(project, commits, ref = nil)
@project = project
def initialize(container, commits, ref = nil)
@container = container
@commits = commits
@ref = ref
end
......@@ -39,6 +42,8 @@ class CommitCollection
# Setting the pipeline for each commit ahead of time removes the need for running
# a query for every commit we're displaying.
def with_latest_pipeline(ref = nil)
return self unless project
pipelines = project.ci_pipelines.latest_pipeline_per_commit(map(&:id), ref)
each do |commit|
......@@ -59,16 +64,16 @@ class CommitCollection
# Batch load any commits that are not backed by full gitaly data, and
# replace them in the collection.
def enrich!
# A project is needed in order to fetch data from gitaly. Projects
# A container is needed in order to fetch data from gitaly. Containers
# can be absent from commits in certain rare situations (like when
# viewing a MR of a deleted fork). In these cases, assume that the
# enriched data is not needed.
return self if project.blank? || fully_enriched?
return self if container.blank? || fully_enriched?
# Batch load full Commits from the repository
# and map to a Hash of id => Commit
replacements = Hash[unenriched.map do |c|
[c.id, Commit.lazy(project, c.id)]
[c.id, Commit.lazy(container, c.id)]
end.compact]
# Replace the commits, keeping the same order
......
# frozen_string_literal: true
module HasRepository
extend ActiveSupport::Concern
include Gitlab::ShellAdapter
include AfterCommitQueue
include Gitlab::Utils::StrongMemoize
delegate :base_dir, :disk_path, to: :storage
def valid_repo?
repository.exists?
rescue
errors.add(:path, _('Invalid repository path'))
false
end
def repo_exists?
strong_memoize(:repo_exists) do
repository.exists?
rescue
false
end
end
def repository_exists?
!!repository.exists?
end
def root_ref?(branch)
repository.root_ref == branch
end
def commit(ref = 'HEAD')
repository.commit(ref)
end
def commit_by(oid:)
repository.commit_by(oid: oid)
end
def commits_by(oids:)
repository.commits_by(oids: oids)
end
def repository
raise NotImplementedError
end
def storage
raise NotImplementedError
end
def full_path
raise NotImplementedError
end
def empty_repo?
repository.empty?
end
def default_branch
@default_branch ||= repository.root_ref
end
def reload_default_branch
@default_branch = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
default_branch
end
def url_to_repo
gitlab_shell.url_to_repo(full_path)
end
def ssh_url_to_repo
url_to_repo
end
def http_url_to_repo
custom_root = Gitlab::CurrentSettings.custom_http_clone_url_root
url = if custom_root.present?
Gitlab::Utils.append_path(
custom_root,
web_url(only_path: true)
)
else
web_url
end
"#{url}.git"
end
def web_url(only_path: nil)
raise NotImplementedError
end
end
......@@ -19,6 +19,7 @@ class Project < ApplicationRecord
include ProjectFeaturesCompatibility
include SelectForProjectAuthorization
include Presentable
include HasRepository
include Routable
include GroupDescendant
include Gitlab::SQL::Pattern
......@@ -326,7 +327,6 @@ class Project < ApplicationRecord
to: :project_feature, allow_nil: true
delegate :scheduled?, :started?, :in_progress?, :failed?, :finished?,
prefix: :import, to: :import_state, allow_nil: true
delegate :base_dir, :disk_path, to: :storage
delegate :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
......@@ -767,10 +767,6 @@ class Project < ApplicationRecord
Feature.enabled?(:context_commits, default_enabled: true)
end
def empty_repo?
repository.empty?
end
def team
@team ||= ProjectTeam.new(self)
end
......@@ -798,18 +794,6 @@ class Project < ApplicationRecord
has_root_container_repository_tags?
end
def commit(ref = 'HEAD')
repository.commit(ref)
end
def commit_by(oid:)
repository.commit_by(oid: oid)
end
def commits_by(oids:)
repository.commits_by(oids: oids)
end
# ref can't be HEAD, can only be branch/tag name
def latest_successful_build_for_ref(job_name, ref = default_branch)
return unless ref
......@@ -1357,48 +1341,6 @@ class Project < ApplicationRecord
services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
end
def valid_repo?
repository.exists?
rescue
errors.add(:path, _('Invalid repository path'))
false
end
def url_to_repo
gitlab_shell.url_to_repo(full_path)
end
def repo_exists?
strong_memoize(:repo_exists) do
repository.exists?
rescue
false
end
end
def root_ref?(branch)
repository.root_ref == branch
end
def ssh_url_to_repo
url_to_repo
end
def http_url_to_repo
custom_root = Gitlab::CurrentSettings.custom_http_clone_url_root
project_url = if custom_root.present?
Gitlab::Utils.append_path(
custom_root,
web_url(only_path: true)
)
else
web_url
end
"#{project_url}.git"
end
# Is overridden in EE
def lfs_http_url_to_repo(_)
http_url_to_repo
......@@ -1538,15 +1480,6 @@ class Project < ApplicationRecord
end
end
def default_branch
@default_branch ||= repository.root_ref
end
def reload_default_branch
@default_branch = nil
default_branch
end
def visibility_level_field
:visibility_level
end
......@@ -1583,10 +1516,6 @@ class Project < ApplicationRecord
create_repository(force: true) unless repository_exists?
end
def repository_exists?
!!repository.exists?
end
def wiki_repository_exists?
wiki.repository_exists?
end
......
......@@ -22,7 +22,7 @@ class Repository
include Gitlab::RepositoryCacheAdapter
attr_accessor :full_path, :disk_path, :project, :repo_type
attr_accessor :full_path, :disk_path, :container, :repo_type
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
......@@ -67,10 +67,10 @@ class Repository
MERGED_BRANCH_NAMES_CACHE_DURATION = 10.minutes
def initialize(full_path, project, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
def initialize(full_path, container, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
@full_path = full_path
@disk_path = disk_path || full_path
@project = project
@container = container
@commit_cache = {}
@repo_type = repo_type
end
......@@ -97,7 +97,7 @@ class Repository
def path_to_repo
@path_to_repo ||=
begin
storage = Gitlab.config.repositories.storages[project.repository_storage]
storage = Gitlab.config.repositories.storages[container.repository_storage]
File.expand_path(
File.join(storage.legacy_disk_path, disk_path + '.git')
......@@ -130,7 +130,7 @@ class Repository
commits = Gitlab::Git::Commit.batch_by_oid(raw_repository, oids)
if commits.present?
Commit.decorate(commits, project)
Commit.decorate(commits, container)
else
[]
end
......@@ -161,14 +161,14 @@ class Repository
}
commits = Gitlab::Git::Commit.where(options)
commits = Commit.decorate(commits, project) if commits.present?
commits = Commit.decorate(commits, container) if commits.present?
CommitCollection.new(project, commits, ref)
CommitCollection.new(container, commits, ref)
end
def commits_between(from, to)
commits = Gitlab::Git::Commit.between(raw_repository, from, to)
commits = Commit.decorate(commits, project) if commits.present?
commits = Commit.decorate(commits, container) if commits.present?
commits
end
......@@ -176,7 +176,7 @@ class Repository
def new_commits(newrev)
commits = raw.new_commits(newrev)
::Commit.decorate(commits, project)
::Commit.decorate(commits, container)
end
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/384
......@@ -188,7 +188,7 @@ class Repository
commits = raw_repository.find_commits_by_message(query, ref, path, limit, offset).map do |c|
commit(c)
end
CommitCollection.new(project, commits, ref)
CommitCollection.new(container, commits, ref)
end
def find_branch(name)
......@@ -281,7 +281,7 @@ class Repository
raw_repository.archive_metadata(
ref,
storage_path,
project.path,
project&.path,
format,
append_sha: append_sha,
path: path
......@@ -499,7 +499,7 @@ class Repository
end
def blob_at(sha, path)
blob = Blob.decorate(raw_repository.blob_at(sha, path), project)
blob = Blob.decorate(raw_repository.blob_at(sha, path), container)
# Don't attempt to return a special result if there is no blob at all
return unless blob
......@@ -522,7 +522,7 @@ class Repository
return [] unless exists?
raw_repository.batch_blobs(items, blob_size_limit: blob_size_limit).map do |blob|
Blob.decorate(blob, project)
Blob.decorate(blob, container)
end
end
......@@ -701,13 +701,13 @@ class Repository
commits = raw_repository.list_last_commits_for_tree(sha, path, offset: offset, limit: limit)
commits.each do |path, commit|
commits[path] = ::Commit.new(commit, project)
commits[path] = ::Commit.new(commit, container)
end
end
def last_commit_for_path(sha, path)
commit = raw_repository.last_commit_for_path(sha, path)
::Commit.new(commit, project) if commit
::Commit.new(commit, container) if commit
end
def last_commit_id_for_path(sha, path)
......@@ -986,6 +986,7 @@ class Repository
# rubocop:disable Gitlab/RailsLogger
def async_remove_remote(remote_name)
return unless remote_name
return unless project
job_id = RepositoryRemoveRemoteWorker.perform_async(project.id, remote_name)
......@@ -1157,6 +1158,10 @@ class Repository
Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) }
end
def project
container
end
private
# TODO Genericize finder, later split this on finders by Ref or Oid
......@@ -1203,10 +1208,10 @@ class Repository
end
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage,
Gitlab::Git::Repository.new(container.repository_storage,
disk_path + '.git',
repo_type.identifier_for_container(project),
project.full_path)
repo_type.identifier_for_container(container),
container.full_path)
end
end
......
......@@ -1527,6 +1527,13 @@ class User < ApplicationRecord
end
def read_only_attribute?(attribute)
if Feature.enabled?(:ldap_readonly_attributes, default_enabled: true)
enabled = Gitlab::Auth::LDAP::Config.enabled?
read_only = attribute.to_sym.in?(UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES)
return true if enabled && read_only
end
user_synced_attributes_metadata&.read_only?(attribute)
end
......
......@@ -13,7 +13,7 @@ module MergeRequests
diffs.each_batch(of: BATCH_SIZE) do |relation, index|
ids = relation.pluck_primary_key.map { |id| [id] }
DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids)
DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids) # rubocop:disable Scalability/BulkPerformWithContext
end
end
end
......
......@@ -9,7 +9,7 @@ module MergeRequests
def self.enqueue!
ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS)
MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] })
MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] }) # rubocop:disable Scalability/BulkPerformWithContext
end
def initialize(merge_request_diff)
......
......@@ -11,7 +11,7 @@ class UserProjectAccessChangedService
if blocking
AuthorizedProjectsWorker.bulk_perform_and_wait(bulk_args)
else
AuthorizedProjectsWorker.bulk_perform_async(bulk_args)
AuthorizedProjectsWorker.bulk_perform_async(bulk_args) # rubocop:disable Scalability/BulkPerformWithContext
end
end
end
......
......@@ -53,7 +53,11 @@ module Users
end
def discard_read_only_attributes
discard_synced_attributes
if Feature.enabled?(:ldap_readonly_attributes, default_enabled: true)
params.reject! { |key, _| @user.read_only_attribute?(key.to_sym) }
else
discard_synced_attributes
end
end
def discard_synced_attributes
......
......@@ -2,7 +2,7 @@
class AdminEmailWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category_not_owned!
......
......@@ -3,7 +3,7 @@
module Ci
class ArchiveTracesCronWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :continuous_integration
......
......@@ -2,7 +2,7 @@
class ContainerExpirationPolicyWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :container_registry
......
......@@ -2,7 +2,7 @@
class ExpireBuildArtifactsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :continuous_integration
......
......@@ -4,7 +4,7 @@ class GitlabUsagePingWorker
LEASE_TIMEOUT = 86400
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category_not_owned!
......
......@@ -2,7 +2,7 @@
class ImportExportProjectCleanupWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :importers
......
......@@ -2,7 +2,7 @@
class IssueDueSchedulerWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :issue_tracking
......@@ -10,7 +10,7 @@ class IssueDueSchedulerWorker
def perform
project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
MailScheduler::IssueDueWorker.bulk_perform_async(project_ids)
MailScheduler::IssueDueWorker.bulk_perform_async(project_ids) # rubocop:disable Scalability/BulkPerformWithContext
end
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -3,7 +3,7 @@
module Namespaces
class PruneAggregationSchedulesWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
worker_resource_boundary :cpu
......
......@@ -2,7 +2,7 @@
class PagesDomainRemovalCronWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :pages
worker_resource_boundary :cpu
......
......@@ -2,7 +2,7 @@
class PagesDomainSslRenewalCronWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :pages
......
......@@ -2,7 +2,7 @@
class PagesDomainVerificationCronWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :pages
......
......@@ -3,7 +3,7 @@
module PersonalAccessTokens
class ExpiringWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :authentication_and_authorization
......
......@@ -2,7 +2,7 @@
class PipelineScheduleWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :continuous_integration
worker_resource_boundary :cpu
......
......@@ -2,7 +2,7 @@
class PruneOldEventsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category_not_owned!
......
......@@ -4,7 +4,7 @@
# table.
class PruneWebHookLogsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :integrations
......
......@@ -2,7 +2,7 @@
class RemoveExpiredGroupLinksWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :authentication_and_authorization
......
......@@ -2,7 +2,7 @@
class RemoveExpiredMembersWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :authentication_and_authorization
worker_resource_boundary :cpu
......
......@@ -2,7 +2,7 @@
class RemoveUnreferencedLfsObjectsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
......
......@@ -2,7 +2,7 @@
class RepositoryArchiveCacheWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
......
......@@ -3,7 +3,7 @@
module RepositoryCheck
class DispatchWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
include ::EachShardWorker
include ExclusiveLeaseGuard
......
......@@ -2,7 +2,7 @@
class RequestsProfilesWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
......
......@@ -2,7 +2,7 @@
class ScheduleMigrateExternalDiffsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
include Gitlab::ExclusiveLeaseHelpers
feature_category :source_code_management
......
......@@ -2,7 +2,7 @@
class StuckCiJobsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :continuous_integration
worker_resource_boundary :cpu
......
......@@ -2,7 +2,7 @@
class StuckImportJobsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :importers
worker_resource_boundary :cpu
......
......@@ -2,7 +2,7 @@
class StuckMergeJobsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
......
......@@ -2,7 +2,7 @@
class TrendingProjectsWorker
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
feature_category :source_code_management
......
---
title: Introduce license_scanning CI template
merge_request: 22773
author:
type: added
---
title: Close related GitLab issue on Sentry error resolve
merge_request: 23610
author:
type: added
---
title: Updated package details page header to begin updating the page design.
merge_request: 24055
author:
type: added
---
title: Make name, email, and location attributes readonly for LDAP enabled instances
merge_request: 24049
author:
type: changed
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DefaultLockVersionToZeroForMergeRequests < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
with_lock_retries do
change_column_default :merge_requests, :lock_version, from: nil, to: 0
end
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DefaultLockVersionToZeroForIssues < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
with_lock_retries do
change_column_default :issues, :lock_version, from: nil, to: 0
end
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DefaultLockVersionToZeroForEpics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
with_lock_retries do
change_column_default :epics, :lock_version, from: nil, to: 0
end
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DefaultLockVersionToZeroForCiBuilds < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
with_lock_retries do
change_column_default :ci_builds, :lock_version, from: nil, to: 0
end
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DefaultLockVersionToZeroForCiStages < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
with_lock_retries do
change_column_default :ci_stages, :lock_version, from: nil, to: 0
end
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DefaultLockVersionToZeroForCiPipelines < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
with_lock_retries do
change_column_default :ci_pipelines, :lock_version, from: nil, to: 0
end
end
end
......@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_01_30_161817) do
ActiveRecord::Schema.define(version: 2020_02_03_025821) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
......@@ -659,7 +659,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do
t.text "yaml_variables"
t.datetime "queued_at"
t.string "token"
t.integer "lock_version"
t.integer "lock_version", default: 0
t.string "coverage_regex"
t.integer "auto_canceled_by_id"
t.boolean "retried"
......@@ -835,7 +835,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do
t.datetime "finished_at"
t.integer "duration"
t.integer "user_id"
t.integer "lock_version"
t.integer "lock_version", default: 0
t.integer "auto_canceled_by_id"
t.integer "pipeline_schedule_id"
t.integer "source"
......@@ -949,7 +949,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do
t.datetime "updated_at"
t.string "name"
t.integer "status"
t.integer "lock_version"
t.integer "lock_version", default: 0
t.integer "position"
t.index ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true
t.index ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position"
......@@ -1523,7 +1523,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do
t.integer "cached_markdown_version"
t.integer "updated_by_id"
t.integer "last_edited_by_id"
t.integer "lock_version"
t.integer "lock_version", default: 0
t.date "start_date"
t.date "end_date"
t.datetime "last_edited_at"
......@@ -2140,7 +2140,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do
t.boolean "confidential", default: false, null: false
t.date "due_date"
t.integer "moved_to_id"
t.integer "lock_version"
t.integer "lock_version", default: 0
t.text "title_html"
t.text "description_html"
t.integer "time_estimate"
......@@ -2562,7 +2562,7 @@ ActiveRecord::Schema.define(version: 2020_01_30_161817) do
t.integer "approvals_before_merge"
t.string "rebase_commit_sha"
t.string "in_progress_merge_commit_sha"
t.integer "lock_version"
t.integer "lock_version", default: 0
t.text "title_html"
t.text "description_html"
t.integer "time_estimate"
......
......@@ -276,6 +276,125 @@ class SomeCrossCuttingConcernWorker
end
```
## Worker context
To have some more information about workers in the logs, we add
[metadata to the jobs in the form of an
`ApplicationContext`](logging.md#logging-context-metadata-through-rails-or-grape-requests).
In most cases, when scheduling a job from a request, this context will
already be deducted from the request and added to the scheduled
job.
When a job runs, the context that was active when it was scheduled
will be restored. This causes the context to be propagated to any job
scheduled from within the running job.
All this means that in most cases, to add context to jobs, we don't
need to do anything.
There are however some instances when there would be no context
present when the job is scheduled, or the context that is present is
likely to be incorrect. For these instances we've added rubocop-rules
to draw attention and avoid incorrect metadata in our logs.
As with most our cops, there are perfectly valid reasons for disabling
them. In this case it could be that the context from the request is
correct. Or maybe you've specified a context already in a way that
isn't picked up by the cops. In any case, please leave a code-comment
pointing to which context will be used when disabling the cops.
When you do provide objects to the context, please make sure that the
route for namespaces and projects is preloaded. This can be done using
the `.with_route` scope defined on all `Routable`s.
### Cron-Workers
The context is automatically cleared for workers in the cronjob-queue
(which `include CronjobQueue`), even when scheduling them from
requests. We do this to avoid incorrect metadata when other jobs are
scheduled from the cron-worker.
Cron-Workers themselves run instance wide, so they aren't scoped to
users, namespaces, projects or other resources that should be added to
the context.
However, they often schedule other jobs that _do_ require context.
That is why there needs to be an indication of context somewhere in
the worker. This can be done by using one of the following methods
somewhere within the worker:
1. Wrap the code that schedules jobs in the `with_context` helper:
```ruby
def perform
deletion_cutoff = Gitlab::CurrentSettings
.deletion_adjourned_period.days.ago.to_date
projects = Project.with_route.with_namespace
.aimed_for_deletion(deletion_cutoff)
projects.find_each(batch_size: 100).with_index do |project, index|
delay = index * INTERVAL
with_context(project: project) do
AdjournedProjectDeletionWorker.perform_in(delay, project.id)
end
end
end
```
1. Use the a batch scheduling method that provides context:
```ruby
def schedule_projects_in_batch(projects)
ProjectImportScheduleWorker.bulk_perform_async_with_contexts(
projects,
arguments_proc: -> (project) { project.id },
context_proc: -> (project) { { project: project } }
)
end
```
or when scheduling with delays:
```ruby
diffs.each_batch(of: BATCH_SIZE) do |diffs, index|
DeleteDiffFilesWorker
.bulk_perform_in_with_contexts(index * 5.minutes,
diffs,
arguments_proc: -> (diff) { diff.id },
context_proc: -> (diff) { { project: diff.merge_request.target_project } })
end
```
### Jobs scheduled in bulk
Often, when scheduling jobs in bulk, these jobs should have a separate
context rather than the overarching context.
If that is the case, `bulk_perform_async` can be replaced by the
`bulk_perform_async_with_context` helper, and instead of
`bulk_perform_in` use `bulk_perform_in_with_context`.
For example:
```ruby
ProjectImportScheduleWorker.bulk_perform_async_with_contexts(
projects,
arguments_proc: -> (project) { project.id },
context_proc: -> (project) { { project: project } }
)
```
Each object from the enumerable in the first argument is yielded into 2
blocks:
The `arguments_proc` which needs to return the list of arguments the
job needs to be scheduled with.
The `context_proc` which needs to return a hash with the context
information for the job.
## Tests
Each Sidekiq worker must be tested using RSpec, just like any other class. These
......
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/license_management/
#
# Configure the scanning tool through the environment variables.
# List of the variables: https://gitlab.com/gitlab-org/security-products/license-management#settings
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Deprecated: https://gitlab.com/gitlab-org/gitlab/issues/14624
# Please, use License-Scanning.gitlab-ci.yml template instead
variables:
LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
......@@ -16,6 +13,7 @@ license_management:
SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD
allow_failure: true
script:
- echo "This template is deprecated, please use License-Scanning.gitlab-ci.yml template instead."
- /run.sh analyze .
artifacts:
reports:
......
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/license_compliance/
#
# Configure the scanning tool through the environment variables.
# List of the variables: https://gitlab.com/gitlab-org/security-products/license-management#settings
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
variables:
LICENSE_MANAGEMENT_SETUP_CMD: '' # If needed, specify a command to setup your environment with a custom package manager.
license_scanning:
stage: test
image:
name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
entrypoint: [""]
variables:
SETUP_CMD: $LICENSE_MANAGEMENT_SETUP_CMD
allow_failure: true
script:
- /run.sh analyze .
after_script:
- mv gl-license-management-report.json gl-license-scanning-report.json
artifacts:
reports:
license_scanning: gl-license-scanning-report.json
dependencies: []
only:
refs:
- branches
variables:
- $GITLAB_FEATURES =~ /\blicense_management\b/
except:
variables:
- $LICENSE_MANAGEMENT_DISABLED
......@@ -37,7 +37,9 @@ module Gitlab
def ==(other)
other.is_a?(self.class) &&
x == other.x &&
y == other.y
y == other.y &&
width == other.width &&
height == other.height
end
end
end
......
......@@ -17,7 +17,7 @@ module Gitlab
def self.execute_all_async(data)
args = files.map { |file| [file, data] }
FileHookWorker.bulk_perform_async(args)
FileHookWorker.bulk_perform_async(args) # rubocop:disable Scalability/BulkPerformWithContext
end
def self.execute(file, data)
......
......@@ -8,7 +8,7 @@ module Gitlab
def initialize(commit)
@commit = commit
repo = commit.project.repository.raw_repository
repo = commit.container.repository.raw_repository
@signature_data = Gitlab::Git::Commit.extract_signature_lazily(repo, commit.sha || commit.id)
lazy_signature
......
......@@ -340,6 +340,9 @@ msgstr ""
msgid "%{name}'s avatar"
msgstr ""
msgid "%{numberOfDays} days"
msgstr ""
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
......@@ -537,6 +540,9 @@ msgstr ""
msgid "+%{extraOptionCount} more"
msgstr ""
msgid "+%{tags} more"
msgstr ""
msgid ", or "
msgstr ""
......@@ -2146,6 +2152,9 @@ msgstr ""
msgid "Are you sure you want to cancel editing this comment?"
msgstr ""
msgid "Are you sure you want to delete %{name}?"
msgstr ""
msgid "Are you sure you want to delete these artifacts?"
msgstr ""
......@@ -2411,6 +2420,9 @@ msgstr ""
msgid "Author"
msgstr ""
msgid "Authored %{timeago} by %{author}"
msgstr ""
msgid "Authorization code:"
msgstr ""
......@@ -5903,6 +5915,9 @@ msgstr ""
msgid "CycleAnalytics|Tasks by type"
msgstr ""
msgid "CycleAnalytics|The given date range is larger than 180 days"
msgstr ""
msgid "CycleAnalytics|Total days to completion"
msgstr ""
......@@ -6918,6 +6933,9 @@ msgstr ""
msgid "Edit your most recent comment in a thread (from an empty textarea)"
msgstr ""
msgid "Edited %{timeago}"
msgstr ""
msgid "Editing"
msgstr ""
......@@ -13266,6 +13284,18 @@ msgstr ""
msgid "PackageRegistry|yarn"
msgstr ""
msgid "PackageType|Conan"
msgstr ""
msgid "PackageType|Maven"
msgstr ""
msgid "PackageType|NPM"
msgstr ""
msgid "PackageType|NuGet"
msgstr ""
msgid "Packages"
msgstr ""
......@@ -18669,6 +18699,9 @@ msgstr ""
msgid "The application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential."
msgstr ""
msgid "The associated issue #%{issueId} has been closed as the error is now resolved."
msgstr ""
msgid "The branch for this project has no active pipeline configuration."
msgstr ""
......@@ -22155,6 +22188,9 @@ msgstr ""
msgid "by"
msgstr ""
msgid "by %{user}"
msgstr ""
msgid "cannot be changed if a personal project has container registry tags."
msgstr ""
......
# frozen_string_literal: true
require_relative '../../migration_helpers'
require_relative '../../code_reuse_helpers'
module RuboCop
module Cop
module Scalability
class BulkPerformWithContext < RuboCop::Cop::Cop
include RuboCop::MigrationHelpers
include RuboCop::CodeReuseHelpers
MSG = <<~MSG
Prefer using `Worker.bulk_perform_async_with_contexts` and
`Worker.bulk_perform_in_with_context` over the methods without a context
if your worker deals with specific projects or namespaces
The context is required to add metadata to our logs.
If there is already a parent context that will apply to the jobs
being scheduled, please disable this cop with a comment explaing which
context will be applied.
Read more about it https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#worker-context
MSG
def_node_matcher :schedules_in_batch_without_context?, <<~PATTERN
(send (...) {:bulk_perform_async :bulk_perform_in} _*)
PATTERN
def on_send(node)
return if in_migration?(node) || in_spec?(node)
return unless schedules_in_batch_without_context?(node)
return if name_of_receiver(node) == "BackgroundMigrationWorker"
add_offense(node, location: :expression)
end
private
def in_spec?(node)
file_path_for_node(node).end_with?("_spec.rb")
end
end
end
end
end
# frozen_string_literal: true
module RuboCop
module Cop
module Scalability
class CronWorkerContext < RuboCop::Cop::Cop
MSG = <<~MSG
Manually define an ApplicationContext for cronjob-workers. The context
is required to add metadata to our logs.
If there is no relevant metadata, please disable the cop with a comment
explaining this.
Read more about it https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#worker-context
MSG
def_node_matcher :includes_cronjob_queue?, <<~PATTERN
(send nil? :include (const nil? :CronjobQueue))
PATTERN
def_node_search :defines_contexts?, <<~PATTERN
(send nil? :with_context _)
PATTERN
def_node_search :schedules_with_batch_context?, <<~PATTERN
(send (...) {:bulk_perform_async_with_contexts :bulk_perform_in_with_contexts} (...))
PATTERN
def on_send(node)
return unless includes_cronjob_queue?(node)
return if defines_contexts?(node.parent)
return if schedules_with_batch_context?(node.parent)
add_offense(node.arguments.first, location: :expression)
end
end
end
end
end
......@@ -44,6 +44,8 @@ require_relative 'cop/qa/element_with_pattern'
require_relative 'cop/qa/ambiguous_page_object_name'
require_relative 'cop/sidekiq_options_queue'
require_relative 'cop/scalability/file_uploads'
require_relative 'cop/scalability/bulk_perform_with_context'
require_relative 'cop/scalability/cron_worker_context'
require_relative 'cop/destroy_all'
require_relative 'cop/ruby_interpolation_in_translation'
require_relative 'code_reuse_helpers'
......
......@@ -3,6 +3,18 @@ import Icon from '~/vue_shared/components/icon.vue';
import DiffStats from '~/diffs/components/diff_stats.vue';
describe('diff_stats', () => {
it('does not render a group if diffFileLengths is empty', () => {
const wrapper = shallowMount(DiffStats, {
propsData: {
addedLines: 1,
removedLines: 2,
},
});
const groups = wrapper.findAll('.diff-stats-group');
expect(groups.length).toBe(2);
});
it('does not render a group if diffFileLengths is not a number', () => {
const wrapper = shallowMount(DiffStats, {
propsData: {
......
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { __ } from '~/locale';
import { GlLoadingIcon, GlLink, GlBadge, GlFormInput } from '@gitlab/ui';
import { GlLoadingIcon, GlLink, GlBadge, GlFormInput, GlAlert, GlSprintf } from '@gitlab/ui';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
import ErrorDetails from '~/error_tracking/components/error_details.vue';
......@@ -28,7 +28,7 @@ describe('ErrorDetails', () => {
function mountComponent() {
wrapper = shallowMount(ErrorDetails, {
stubs: { LoadingButton },
stubs: { LoadingButton, GlSprintf },
localVue,
store,
mocks,
......@@ -62,7 +62,7 @@ describe('ErrorDetails', () => {
startPollingDetails: () => {},
startPollingStacktrace: () => {},
updateIgnoreStatus: jest.fn(),
updateResolveStatus: jest.fn(),
updateResolveStatus: jest.fn().mockResolvedValue({ closed_issue_iid: 1 }),
};
getters = {
......@@ -313,6 +313,20 @@ describe('ErrorDetails', () => {
expect.objectContaining({ status: errorStatus.UNRESOLVED }),
);
});
it('should show alert with closed issueId', () => {
const findAlert = () => wrapper.find(GlAlert);
const closedIssueId = 123;
wrapper.setData({
isAlertVisible: true,
closedIssueId,
});
return wrapper.vm.$nextTick().then(() => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toContain(`#${closedIssueId}`);
});
});
});
});
......
......@@ -6,6 +6,7 @@ exports[`User Operation confirmation modal renders modal with form included 1`]
modalid="user-operation-modal"
ok-title="action"
ok-variant="warning"
size="md"
title="title"
titletag="h4"
>
......
......@@ -61,6 +61,7 @@ exports[`self monitor component When the self monitor project has not been creat
modalid="delete-self-monitor-modal"
ok-title="Delete project"
ok-variant="danger"
size="md"
title="Disable self monitoring?"
titletag="h4"
>
......
......@@ -3,20 +3,34 @@
require 'spec_helper'
describe Gitlab::Diff::Formatters::ImageFormatter do
it_behaves_like "position formatter" do
let(:base_attrs) do
{
base_sha: 123,
start_sha: 456,
head_sha: 789,
old_path: 'old_image.png',
new_path: 'new_image.png',
position_type: 'image'
}
end
let(:base_attrs) do
{
base_sha: 123,
start_sha: 456,
head_sha: 789,
old_path: 'old_image.png',
new_path: 'new_image.png',
position_type: 'image'
}
end
let(:attrs) do
base_attrs.merge(width: 100, height: 100, x: 1, y: 2)
end
it_behaves_like 'position formatter'
describe '#==' do
subject { described_class.new(attrs) }
it { is_expected.to eq(subject) }
[:width, :height, :x, :y].each do |attr|
let(:other_formatter) do
described_class.new(attrs.merge(attr => 9))
end
let(:attrs) do
base_attrs.merge(width: 100, height: 100, x: 1, y: 2)
it { is_expected.not_to eq(other_formatter) }
end
end
end
......@@ -106,6 +106,14 @@ describe Project do
it { is_expected.to have_many(:sourced_pipelines) }
it { is_expected.to have_many(:source_pipelines) }
it_behaves_like 'model with repository' do
let_it_be(:container) { create(:project, :repository, path: 'somewhere') }
let(:stubbed_container) { build_stubbed(:project) }
let(:expected_full_path) { "#{container.namespace.full_path}/somewhere" }
let(:expected_repository_klass) { Repository }
let(:expected_storage_klass) { Storage::Hashed }
end
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
end
......@@ -510,7 +518,6 @@ describe Project do
describe 'Respond to' do
it { is_expected.to respond_to(:url_to_repo) }
it { is_expected.to respond_to(:repo_exists?) }
it { is_expected.to respond_to(:execute_hooks) }
it { is_expected.to respond_to(:owner) }
it { is_expected.to respond_to(:path_with_namespace) }
......@@ -664,44 +671,6 @@ describe Project do
expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git')
end
describe "#web_url" do
let(:project) { create(:project, path: "somewhere") }
context 'when given the only_path option' do
subject { project.web_url(only_path: only_path) }
context 'when only_path is false' do
let(:only_path) { false }
it 'returns the full web URL for this repo' do
expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
end
end
context 'when only_path is true' do
let(:only_path) { true }
it 'returns the relative web URL for this repo' do
expect(subject).to eq("/#{project.namespace.full_path}/somewhere")
end
end
context 'when only_path is nil' do
let(:only_path) { nil }
it 'returns the full web URL for this repo' do
expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
end
end
end
context 'when not given the only_path option' do
it 'returns the full web URL for this repo' do
expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere")
end
end
end
describe "#readme_url" do
context 'with a non-existing repository' do
let(:project) { create(:project) }
......@@ -931,14 +900,6 @@ describe Project do
end
end
describe '#repository' do
let(:project) { create(:project, :repository) }
it 'returns valid repo' do
expect(project.repository).to be_kind_of(Repository)
end
end
describe '#default_issues_tracker?' do
it "is true if used internal tracker" do
project = build(:project)
......@@ -954,24 +915,6 @@ describe Project do
end
end
describe '#empty_repo?' do
context 'when the repo does not exist' do
let(:project) { build_stubbed(:project) }
it 'returns true' do
expect(project.empty_repo?).to be(true)
end
end
context 'when the repo exists' do
let(:project) { create(:project, :repository) }
let(:empty_project) { create(:project, :empty_repo) }
it { expect(empty_project.empty_repo?).to be(true) }
it { expect(project.empty_repo?).to be(false) }
end
end
describe '#external_issue_tracker' do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
......@@ -3406,59 +3349,6 @@ describe Project do
end
end
describe '#http_url_to_repo' do
let(:project) { create(:project) }
context 'when a custom HTTP clone URL root is not set' do
it 'returns the url to the repo without a username' do
expect(project.http_url_to_repo).to eq("#{project.web_url}.git")
expect(project.http_url_to_repo).not_to include('@')
end
end
context 'when a custom HTTP clone URL root is set' do
before do
stub_application_setting(custom_http_clone_url_root: custom_http_clone_url_root)
end
context 'when custom HTTP clone URL root has a relative URL root' do
context 'when custom HTTP clone URL root ends with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab/' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(project.http_url_to_repo).to eq("https://git.example.com:51234/mygitlab/#{project.full_path}.git")
end
end
context 'when custom HTTP clone URL root does not end with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(project.http_url_to_repo).to eq("https://git.example.com:51234/mygitlab/#{project.full_path}.git")
end
end
end
context 'when custom HTTP clone URL root does not have a relative URL root' do
context 'when custom HTTP clone URL root ends with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234/' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(project.http_url_to_repo).to eq("https://git.example.com:51234/#{project.full_path}.git")
end
end
context 'when custom HTTP clone URL root does not end with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(project.http_url_to_repo).to eq("https://git.example.com:51234/#{project.full_path}.git")
end
end
end
end
end
describe '#lfs_http_url_to_repo' do
let(:project) { create(:project) }
......@@ -5054,16 +4944,6 @@ describe Project do
end
end
context '#commits_by' do
let(:project) { create(:project, :repository) }
let(:commits) { project.repository.commits('HEAD', limit: 3).commits }
let(:commit_shas) { commits.map(&:id) }
it 'retrieves several commits from the repository by oid' do
expect(project.commits_by(oids: commit_shas)).to eq commits
end
end
context '#members_among' do
let(:users) { create_list(:user, 3) }
......
......@@ -4084,4 +4084,46 @@ describe User, :do_not_mock_admin_mode do
end
end
end
describe '#read_only_attribute?' do
context 'when LDAP server is enabled' do
before do
allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
end
%i[name email location].each do |attribute|
it "is true for #{attribute}" do
expect(subject.read_only_attribute?(attribute)).to be_truthy
end
end
context 'and ldap_readonly_attributes feature is disabled' do
before do
stub_feature_flags(ldap_readonly_attributes: false)
end
%i[name email location].each do |attribute|
it "is false" do
expect(subject.read_only_attribute?(attribute)).to be_falsey
end
end
end
end
context 'when synced attributes metadata is present' do
it 'delegates to synced_attributes_metadata' do
subject.build_user_synced_attributes_metadata
expect(subject.build_user_synced_attributes_metadata)
.to receive(:read_only?).with(:email).and_return('return-value')
expect(subject.read_only_attribute?(:email)).to eq('return-value')
end
end
context 'when synced attributes metadata is present' do
it 'is false for any attribute' do
expect(subject.read_only_attribute?(:email)).to be_falsey
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../support/helpers/expect_offense'
require_relative '../../../../rubocop/cop/scalability/bulk_perform_with_context'
describe RuboCop::Cop::Scalability::BulkPerformWithContext do
include CopHelper
include ExpectOffense
subject(:cop) { described_class.new }
it "adds an offense when calling bulk_perform_async" do
inspect_source(<<~CODE.strip_indent)
Worker.bulk_perform_async(args)
CODE
expect(cop.offenses.size).to eq(1)
end
it "adds an offense when calling bulk_perform_in" do
inspect_source(<<~CODE.strip_indent)
diffs.each_batch(of: BATCH_SIZE) do |relation, index|
ids = relation.pluck_primary_key.map { |id| [id] }
DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids)
end
CODE
expect(cop.offenses.size).to eq(1)
end
it "does not add an offense for migrations" do
allow(cop).to receive(:in_migration?).and_return(true)
inspect_source(<<~CODE.strip_indent)
Worker.bulk_perform_in(args)
CODE
expect(cop.offenses.size).to eq(0)
end
it "does not add an offence for specs" do
allow(cop).to receive(:in_spec?).and_return(true)
inspect_source(<<~CODE.strip_indent)
Worker.bulk_perform_in(args)
CODE
expect(cop.offenses.size).to eq(0)
end
it "does not add an offense for scheduling BackgroundMigrations" do
inspect_source(<<~CODE.strip_indent)
BackgroundMigrationWorker.bulk_perform_in(args)
CODE
expect(cop.offenses.size).to eq(0)
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../support/helpers/expect_offense'
require_relative '../../../../rubocop/cop/scalability/cron_worker_context'
describe RuboCop::Cop::Scalability::CronWorkerContext do
include CopHelper
include ExpectOffense
subject(:cop) { described_class.new }
it 'adds an offense when including CronjobQueue' do
inspect_source(<<~CODE.strip_indent)
class SomeWorker
include CronjobQueue
end
CODE
expect(cop.offenses.size).to eq(1)
end
it 'does not add offenses for other workers' do
expect_no_offenses(<<~CODE.strip_indent)
class SomeWorker
end
CODE
end
it 'does not add an offense when the class defines a context' do
expect_no_offenses(<<~CODE.strip_indent)
class SomeWorker
include CronjobQueue
with_context user: 'bla'
end
CODE
end
it 'does not add an offense when the worker calls `with_context`' do
expect_no_offenses(<<~CODE.strip_indent)
class SomeWorker
include CronjobQueue
def perform
with_context(user: 'bla') do
# more work
end
end
end
CODE
end
it 'does not add an offense when the worker calls `bulk_perform_async_with_contexts`' do
expect_no_offenses(<<~CODE.strip_indent)
class SomeWorker
include CronjobQueue
def perform
SomeOtherWorker.bulk_perform_async_with_contexts(contexts_for_arguments)
end
end
CODE
end
it 'does not add an offense when the worker calls `bulk_perform_in_with_contexts`' do
expect_no_offenses(<<~CODE.strip_indent)
class SomeWorker
include CronjobQueue
def perform
SomeOtherWorker.bulk_perform_in_with_contexts(contexts_for_arguments)
end
end
CODE
end
end
......@@ -55,6 +55,15 @@ describe Users::UpdateService do
expect(result[:message]).to eq("Emoji is not included in the list")
end
it 'ignores read-only attributes' do
allow(user).to receive(:read_only_attribute?).with(:name).and_return(true)
expect do
update_user(user, name: 'changed' + user.name)
user.reload
end.not_to change { user.name }
end
def update_user(user, opts)
described_class.new(user, opts.merge(user: user)).execute
end
......
# frozen_string_literal: true
RSpec.shared_examples 'model with repository' do
describe '#commits_by' do
let(:commits) { container.repository.commits('HEAD', limit: 3).commits }
let(:commit_shas) { commits.map(&:id) }
it 'retrieves several commits from the repository by oid' do
expect(container.commits_by(oids: commit_shas)).to eq commits
end
end
describe "#web_url" do
context 'when given the only_path option' do
subject { container.web_url(only_path: only_path) }
context 'when only_path is false' do
let(:only_path) { false }
it 'returns the full web URL for this repo' do
expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}")
end
end
context 'when only_path is true' do
let(:only_path) { true }
it 'returns the relative web URL for this repo' do
expect(subject).to eq("/#{expected_full_path}")
end
end
context 'when only_path is nil' do
let(:only_path) { nil }
it 'returns the full web URL for this repo' do
expect(subject).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}")
end
end
end
context 'when not given the only_path option' do
it 'returns the full web URL for this repo' do
expect(container.web_url).to eq("#{Gitlab.config.gitlab.url}/#{expected_full_path}")
end
end
end
describe '#ssh_url_to_repo' do
it 'returns container ssh address' do
expect(container.ssh_url_to_repo).to eq container.url_to_repo
end
end
describe '#http_url_to_repo' do
subject { container.http_url_to_repo }
context 'when a custom HTTP clone URL root is not set' do
it 'returns the url to the repo without a username' do
expect(subject).to eq("#{container.web_url}.git")
expect(subject).not_to include('@')
end
end
context 'when a custom HTTP clone URL root is set' do
before do
stub_application_setting(custom_http_clone_url_root: custom_http_clone_url_root)
end
context 'when custom HTTP clone URL root has a relative URL root' do
context 'when custom HTTP clone URL root ends with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab/' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(subject).to eq("#{custom_http_clone_url_root}#{expected_full_path}.git")
end
end
context 'when custom HTTP clone URL root does not end with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234/mygitlab' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_full_path}.git")
end
end
end
context 'when custom HTTP clone URL root does not have a relative URL root' do
context 'when custom HTTP clone URL root ends with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234/' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(subject).to eq("#{custom_http_clone_url_root}#{expected_full_path}.git")
end
end
context 'when custom HTTP clone URL root does not end with a slash' do
let(:custom_http_clone_url_root) { 'https://git.example.com:51234' }
it 'returns the url to the repo, with the root replaced with the custom one' do
expect(subject).to eq("#{custom_http_clone_url_root}/#{expected_full_path}.git")
end
end
end
end
end
describe '#repository' do
it 'returns valid repo' do
expect(container.repository).to be_kind_of(expected_repository_klass)
end
end
describe '#storage' do
it 'returns valid storage' do
expect(container.storage).to be_kind_of(expected_storage_klass)
end
end
describe '#full_path' do
it 'returns valid full_path' do
expect(container.full_path).to eq(expected_full_path)
end
end
describe '#empty_repo?' do
context 'when the repo does not exist' do
it 'returns true' do
expect(stubbed_container.empty_repo?).to be(true)
end
end
context 'when the repo exists' do
it { expect(container.empty_repo?).to be(false) }
it 'returns true when repository is empty' do
allow(container.repository).to receive(:empty?).and_return(true)
expect(container.empty_repo?).to be(true)
end
end
end
describe '#valid_repo?' do
it { expect(stubbed_container.valid_repo?).to be(false)}
it { expect(container.valid_repo?).to be(true) }
end
describe '#repository_exists?' do
it { expect(stubbed_container.repository_exists?).to be(false)}
it { expect(container.repository_exists?).to be(true) }
end
describe '#repo_exists?' do
it { expect(stubbed_container.repo_exists?).to be(false)}
it { expect(container.repo_exists?).to be(true) }
end
describe '#root_ref' do
let(:root_ref) { container.repository.root_ref }
it { expect(container.root_ref?(root_ref)).to be(true) }
it { expect(container.root_ref?('HEAD')).to be(false) }
it { expect(container.root_ref?('foo')).to be(false) }
end
describe 'Respond to' do
it { is_expected.to respond_to(:base_dir) }
it { is_expected.to respond_to(:disk_path) }
end
end
......@@ -10,7 +10,7 @@ describe CronjobQueue do
end
include ApplicationWorker
include CronjobQueue
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
end
end
......
......@@ -736,15 +736,15 @@
dependencies:
vue-eslint-parser "^6.0.4"
"@gitlab/svgs@^1.91.0":
version "1.91.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.91.0.tgz#cf7b28e43779a929a438dcc0c51f39f92551e58e"
integrity sha512-Sz8aaaNnUUtlrk/FPf6FNceu4XsDLBh6g/c6nmzDVGF9kHrfiXfWL0tYAax8vhkrld5vewGHE0bRpq2ZILq0pw==
"@gitlab/ui@^9.0.0":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.0.0.tgz#16d637f47ba0537100fd1c6d452b56174b50171b"
integrity sha512-OfP8UAticpqKkqbPBZ+7bbCBsd9Fxq3eL55Uq5nEwxJ8gGXm+nSc+HFnbzX/ryv0iz5+7nCI4DfIfgy9E4QAeQ==
"@gitlab/svgs@^1.94.0":
version "1.94.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.94.0.tgz#d6a39f982811f82d942692a91bf2678961752eba"
integrity sha512-lB7HTVsNPBLUEgNUXLLC4V/XJsWg7aSO7RBp6cuuL3n6fUS9VGfELH9aBnuPJglTHddktcnElkZ3S54XI8kYHw==
"@gitlab/ui@^9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-9.3.0.tgz#851b4246c2e661a5a343184f74e0448c597ce28c"
integrity sha512-DB9Q8XDLfn3Ui6EfYTVnmHVYPwbukocYTWL+uD6zN3leiamYQqaoYGmtcrXk9oSiAyuJYwaJlCzlblG1GPwnfw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment