Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
559d99e402
commit
bf217da41b
73 changed files with 1382 additions and 504 deletions
|
@ -2505,26 +2505,6 @@ Style/FrozenStringLiteralComment:
|
|||
- 'qa/qa/fixtures/auto_devops_rack/Rakefile'
|
||||
- 'qa/qa/fixtures/auto_devops_rack/config.ru'
|
||||
- 'qa/qa/page/page_concern.rb'
|
||||
- 'rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb'
|
||||
- 'rubocop/cop/gitlab/finder_with_find_by.rb'
|
||||
- 'rubocop/cop/gitlab/keys-first-and-values-first.rb'
|
||||
- 'rubocop/cop/gitlab/module_with_instance_variables.rb'
|
||||
- 'rubocop/cop/gitlab/predicate_memoization.rb'
|
||||
- 'rubocop/cop/migration/add_concurrent_foreign_key.rb'
|
||||
- 'rubocop/cop/migration/add_concurrent_index.rb'
|
||||
- 'rubocop/cop/migration/add_index.rb'
|
||||
- 'rubocop/cop/migration/add_timestamps.rb'
|
||||
- 'rubocop/cop/migration/datetime.rb'
|
||||
- 'rubocop/cop/migration/hash_index.rb'
|
||||
- 'rubocop/cop/migration/remove_column.rb'
|
||||
- 'rubocop/cop/migration/remove_concurrent_index.rb'
|
||||
- 'rubocop/cop/migration/remove_index.rb'
|
||||
- 'rubocop/cop/migration/safer_boolean_column.rb'
|
||||
- 'rubocop/cop/migration/timestamps.rb'
|
||||
- 'rubocop/cop/migration/update_column_in_batches.rb'
|
||||
- 'rubocop/cop/project_path_helper.rb'
|
||||
- 'rubocop/migration_helpers.rb'
|
||||
- 'rubocop/qa_helpers.rb'
|
||||
- 'scripts/flaky_examples/detect-new-flaky-examples'
|
||||
- 'scripts/flaky_examples/prune-old-flaky-examples'
|
||||
- 'scripts/gather-test-memory-data'
|
||||
|
|
|
@ -1 +1 @@
|
|||
d0a79053ba4fef55b59543b99327fc89aed64876
|
||||
8cdbdb46b4fa31e0c2f1e2646baaf0ffb271b3a0
|
||||
|
|
|
@ -85,7 +85,7 @@ export default {
|
|||
<gl-link
|
||||
:href="action.path"
|
||||
:data-method="action.method"
|
||||
class="btn btn-primary"
|
||||
class="btn gl-button btn-confirm gl-text-decoration-none!"
|
||||
data-testid="job-empty-state-action"
|
||||
>{{ action.button_title }}</gl-link
|
||||
>
|
||||
|
|
|
@ -46,9 +46,9 @@ export default {
|
|||
...mapGetters(['hasForwardDeploymentFailure']),
|
||||
...mapState(['job', 'stages', 'jobs', 'selectedStage']),
|
||||
retryButtonClass() {
|
||||
let className = 'btn btn-retry';
|
||||
let className = 'btn gl-button gl-text-decoration-none!';
|
||||
className +=
|
||||
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
|
||||
this.job.status && this.job.recoverable ? ' btn-confirm' : ' btn-confirm-secondary';
|
||||
return className;
|
||||
},
|
||||
hasArtifact() {
|
||||
|
@ -94,7 +94,7 @@ export default {
|
|||
<gl-link
|
||||
v-if="job.cancel_path"
|
||||
:href="job.cancel_path"
|
||||
class="btn btn-default"
|
||||
class="btn gl-button btn-default gl-text-decoration-none!"
|
||||
data-method="post"
|
||||
data-testid="cancel-button"
|
||||
rel="nofollow"
|
||||
|
@ -115,7 +115,7 @@ export default {
|
|||
<gl-link
|
||||
v-if="job.new_issue_path"
|
||||
:href="job.new_issue_path"
|
||||
class="btn btn-success btn-inverted float-left mr-2"
|
||||
class="btn gl-button btn-success-secondary float-left mr-2 gl-text-decoration-none!"
|
||||
data-testid="job-new-issue"
|
||||
>{{ $options.i18n.newIssue }}
|
||||
</gl-link>
|
||||
|
|
|
@ -30,10 +30,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
statsUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
detailedMetrics: [
|
||||
{
|
||||
|
@ -173,9 +169,6 @@ export default {
|
|||
class="ml-auto"
|
||||
@change-current-request="changeCurrentRequest"
|
||||
/>
|
||||
<div v-if="statsUrl" id="peek-stats" class="view">
|
||||
<a class="gl-text-blue-300" :href="statsUrl">{{ s__('PerformanceBar|Stats') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -30,7 +30,6 @@ const initPerformanceBar = (el) => {
|
|||
requestId: performanceBarData.requestId,
|
||||
peekUrl: performanceBarData.peekUrl,
|
||||
profileUrl: performanceBarData.profileUrl,
|
||||
statsUrl: performanceBarData.statsUrl,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -121,7 +120,6 @@ const initPerformanceBar = (el) => {
|
|||
requestId: this.requestId,
|
||||
peekUrl: this.peekUrl,
|
||||
profileUrl: this.profileUrl,
|
||||
statsUrl: this.statsUrl,
|
||||
},
|
||||
on: {
|
||||
'add-request': this.addRequestManually,
|
||||
|
|
|
@ -12,11 +12,6 @@ import PipelineUrl from './pipeline_url.vue';
|
|||
import PipelineTriggerer from './pipeline_triggerer.vue';
|
||||
import PipelinesTimeago from './time_ago.vue';
|
||||
|
||||
/**
|
||||
* Pipeline table row.
|
||||
*
|
||||
* Given the received object renders a table row in the pipelines' table.
|
||||
*/
|
||||
export default {
|
||||
i18n: {
|
||||
cancelTitle: __('Cancel'),
|
||||
|
@ -127,116 +122,30 @@ export default {
|
|||
|
||||
return commitAuthorInformation;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit tag.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitTag() {
|
||||
if (this.pipeline.ref && this.pipeline.ref.tag) {
|
||||
return this.pipeline.ref.tag;
|
||||
}
|
||||
return undefined;
|
||||
return this.pipeline?.ref?.tag;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit ref.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* Matches `path` prop sent in the API to `ref_url` prop needed
|
||||
* in the commit component.
|
||||
*
|
||||
* @returns {Object|Undefined}
|
||||
*/
|
||||
commitRef() {
|
||||
if (this.pipeline.ref) {
|
||||
return Object.keys(this.pipeline.ref).reduce((accumulator, prop) => {
|
||||
if (prop === 'path') {
|
||||
accumulator.ref_url = this.pipeline.ref[prop];
|
||||
} else {
|
||||
accumulator[prop] = this.pipeline.ref[prop];
|
||||
}
|
||||
return accumulator;
|
||||
}, {});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return this.pipeline?.ref;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit url.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitUrl() {
|
||||
if (this.pipeline.commit && this.pipeline.commit.commit_path) {
|
||||
return this.pipeline.commit.commit_path;
|
||||
}
|
||||
return undefined;
|
||||
return this.pipeline?.commit?.commit_path;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit short sha.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitShortSha() {
|
||||
if (this.pipeline.commit && this.pipeline.commit.short_id) {
|
||||
return this.pipeline.commit.short_id;
|
||||
}
|
||||
return undefined;
|
||||
return this.pipeline?.commit?.short_id;
|
||||
},
|
||||
|
||||
/**
|
||||
* If provided, returns the commit title.
|
||||
* Needed to render the commit component column.
|
||||
*
|
||||
* @returns {String|Undefined}
|
||||
*/
|
||||
commitTitle() {
|
||||
if (this.pipeline.commit && this.pipeline.commit.title) {
|
||||
return this.pipeline.commit.title;
|
||||
}
|
||||
return undefined;
|
||||
return this.pipeline?.commit?.title;
|
||||
},
|
||||
|
||||
/**
|
||||
* Timeago components expects a number
|
||||
*
|
||||
* @return {type} description
|
||||
*/
|
||||
pipelineDuration() {
|
||||
if (this.pipeline.details && this.pipeline.details.duration) {
|
||||
return this.pipeline.details.duration;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return this.pipeline?.details?.duration ?? 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Timeago component expects a String.
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
pipelineFinishedAt() {
|
||||
if (this.pipeline.details && this.pipeline.details.finished_at) {
|
||||
return this.pipeline.details.finished_at;
|
||||
}
|
||||
|
||||
return '';
|
||||
return this.pipeline?.details?.finished_at ?? '';
|
||||
},
|
||||
|
||||
pipelineStatus() {
|
||||
if (this.pipeline.details && this.pipeline.details.status) {
|
||||
return this.pipeline.details.status;
|
||||
}
|
||||
return {};
|
||||
return this.pipeline?.details?.status ?? {};
|
||||
},
|
||||
|
||||
displayPipelineActions() {
|
||||
return (
|
||||
this.pipeline.flags.retryable ||
|
||||
|
@ -245,11 +154,9 @@ export default {
|
|||
this.pipeline.details.artifacts.length
|
||||
);
|
||||
},
|
||||
|
||||
isChildView() {
|
||||
return this.viewType === 'child';
|
||||
},
|
||||
|
||||
isCancelling() {
|
||||
return this.cancelingPipeline === this.pipeline.id;
|
||||
},
|
||||
|
@ -355,7 +262,7 @@ export default {
|
|||
:title="$options.i18n.redeployTitle"
|
||||
:disabled="isRetrying"
|
||||
:loading="isRetrying"
|
||||
class="js-pipelines-retry-button btn-retry"
|
||||
class="js-pipelines-retry-button"
|
||||
data-qa-selector="pipeline_retry_button"
|
||||
icon="repeat"
|
||||
variant="default"
|
||||
|
|
|
@ -133,6 +133,9 @@ export default {
|
|||
? sprintf(__("%{username}'s avatar"), { username: this.author.username })
|
||||
: null;
|
||||
},
|
||||
refUrl() {
|
||||
return this.commitRef.ref_url || this.commitRef.path;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -156,9 +159,10 @@ export default {
|
|||
<gl-link
|
||||
v-else
|
||||
v-gl-tooltip
|
||||
:href="commitRef.ref_url"
|
||||
:href="refUrl"
|
||||
:title="commitRef.name"
|
||||
class="ref-name"
|
||||
data-testid="ref-name"
|
||||
>{{ commitRef.name }}</gl-link
|
||||
>
|
||||
</template>
|
||||
|
|
|
@ -84,7 +84,16 @@ class HelpController < ApplicationController
|
|||
end
|
||||
|
||||
def documentation_base_url
|
||||
@documentation_base_url ||= Gitlab::CurrentSettings.current_application_settings.help_page_documentation_base_url.presence
|
||||
@documentation_base_url ||= documentation_base_url_from_yml_configuration || documentation_base_url_from_db
|
||||
end
|
||||
|
||||
# DEPRECATED
|
||||
def documentation_base_url_from_db
|
||||
Gitlab::CurrentSettings.current_application_settings.help_page_documentation_base_url.presence
|
||||
end
|
||||
|
||||
def documentation_base_url_from_yml_configuration
|
||||
::Gitlab.config.gitlab_docs.host.presence if ::Gitlab.config.gitlab_docs.enabled
|
||||
end
|
||||
|
||||
def documentation_file_path
|
||||
|
|
|
@ -145,9 +145,6 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
format.html do
|
||||
recaptcha_check_with_fallback { render :new }
|
||||
end
|
||||
format.js do
|
||||
@link = @issue.attachment.url.to_js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
12
app/graphql/types/event_action_enum.rb
Normal file
12
app/graphql/types/event_action_enum.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class EventActionEnum < BaseEnum
|
||||
graphql_name 'EventAction'
|
||||
description 'Event action'
|
||||
|
||||
::Event.actions.keys.each do |target_type|
|
||||
value target_type.upcase, value: target_type, description: "#{target_type.titleize} action"
|
||||
end
|
||||
end
|
||||
end
|
36
app/graphql/types/event_type.rb
Normal file
36
app/graphql/types/event_type.rb
Normal file
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
class EventType < BaseObject
|
||||
graphql_name 'Event'
|
||||
description 'Representing an event'
|
||||
|
||||
present_using EventPresenter
|
||||
|
||||
authorize :read_event
|
||||
|
||||
field :id, GraphQL::ID_TYPE,
|
||||
description: 'ID of the event.',
|
||||
null: false
|
||||
|
||||
field :author, Types::UserType,
|
||||
description: 'Author of this event.',
|
||||
null: false
|
||||
|
||||
field :action, Types::EventActionEnum,
|
||||
description: 'Action of the event.',
|
||||
null: false
|
||||
|
||||
field :created_at, Types::TimeType,
|
||||
description: 'When this event was created.',
|
||||
null: false
|
||||
|
||||
field :updated_at, Types::TimeType,
|
||||
description: 'When this event was updated.',
|
||||
null: false
|
||||
|
||||
def author
|
||||
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
|
||||
end
|
||||
end
|
||||
end
|
9
app/graphql/types/eventable_type.rb
Normal file
9
app/graphql/types/eventable_type.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module EventableType
|
||||
include Types::BaseInterface
|
||||
|
||||
field :events, Types::EventType.connection_type, null: true, description: 'A list of events associated with the object.'
|
||||
end
|
||||
end
|
|
@ -10,6 +10,9 @@ module Enums
|
|||
unknown_failure: 0,
|
||||
config_error: 1,
|
||||
external_validation_failure: 2,
|
||||
activity_limit_exceeded: 20,
|
||||
size_limit_exceeded: 21,
|
||||
job_activity_limit_exceeded: 22,
|
||||
deployments_limit_exceeded: 23
|
||||
}
|
||||
end
|
||||
|
@ -77,5 +80,3 @@ module Enums
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Enums::Ci::Pipeline.prepend_if_ee('EE::Enums::Ci::Pipeline')
|
||||
|
|
9
app/policies/event_policy.rb
Normal file
9
app/policies/event_policy.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EventPolicy < BasePolicy # rubocop:disable Gitlab/NamespacedClass
|
||||
condition(:visible_to_user) do
|
||||
subject.visible_to_user?(user)
|
||||
end
|
||||
|
||||
rule { visible_to_user }.enable :read_event
|
||||
end
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
module Pages
|
||||
class MigrateFromLegacyStorageService
|
||||
def initialize(logger, migration_threads, batch_size)
|
||||
def initialize(logger, migration_threads:, batch_size:, ignore_invalid_entries:)
|
||||
@logger = logger
|
||||
@migration_threads = migration_threads
|
||||
@batch_size = batch_size
|
||||
@ignore_invalid_entries = ignore_invalid_entries
|
||||
|
||||
@migrated = 0
|
||||
@errored = 0
|
||||
|
@ -59,19 +60,19 @@ module Pages
|
|||
def migrate_project(project)
|
||||
result = nil
|
||||
time = Benchmark.realtime do
|
||||
result = ::Pages::MigrateLegacyStorageToDeploymentService.new(project).execute
|
||||
result = ::Pages::MigrateLegacyStorageToDeploymentService.new(project, ignore_invalid_entries: @ignore_invalid_entries).execute
|
||||
end
|
||||
|
||||
if result[:status] == :success
|
||||
@logger.info("project_id: #{project.id} #{project.pages_path} has been migrated in #{time} seconds")
|
||||
@logger.info("project_id: #{project.id} #{project.pages_path} has been migrated in #{time.round(2)} seconds")
|
||||
@counters_lock.synchronize { @migrated += 1 }
|
||||
else
|
||||
@logger.error("project_id: #{project.id} #{project.pages_path} failed to be migrated in #{time} seconds: #{result[:message]}")
|
||||
@logger.error("project_id: #{project.id} #{project.pages_path} failed to be migrated in #{time.round(2)} seconds: #{result[:message]}")
|
||||
@counters_lock.synchronize { @errored += 1 }
|
||||
end
|
||||
rescue => e
|
||||
@counters_lock.synchronize { @errored += 1 }
|
||||
@logger.error("#{e.message} project_id: #{project&.id}")
|
||||
@logger.error("project_id: #{project&.id} #{project&.pages_path} failed to be migrated: #{e.message}")
|
||||
Gitlab::ErrorTracking.track_exception(e, project_id: project&.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,8 +9,9 @@ module Pages
|
|||
|
||||
attr_reader :project
|
||||
|
||||
def initialize(project)
|
||||
def initialize(project, ignore_invalid_entries: false)
|
||||
@project = project
|
||||
@ignore_invalid_entries = ignore_invalid_entries
|
||||
end
|
||||
|
||||
def execute
|
||||
|
@ -26,7 +27,7 @@ module Pages
|
|||
private
|
||||
|
||||
def execute_unsafe
|
||||
zip_result = ::Pages::ZipDirectoryService.new(project.pages_path).execute
|
||||
zip_result = ::Pages::ZipDirectoryService.new(project.pages_path, ignore_invalid_entries: @ignore_invalid_entries).execute
|
||||
|
||||
if zip_result[:status] == :error
|
||||
if !project.pages_metadatum&.reload&.pages_deployment &&
|
||||
|
|
|
@ -10,12 +10,17 @@ module Pages
|
|||
|
||||
PUBLIC_DIR = 'public'
|
||||
|
||||
def initialize(input_dir)
|
||||
attr_reader :public_dir, :real_dir
|
||||
|
||||
def initialize(input_dir, ignore_invalid_entries: false)
|
||||
@input_dir = input_dir
|
||||
@ignore_invalid_entries = ignore_invalid_entries
|
||||
end
|
||||
|
||||
def execute
|
||||
return error("Can not find valid public dir in #{@input_dir}") unless valid_path?(public_dir)
|
||||
unless resolve_public_dir
|
||||
return error("Can not find valid public dir in #{@input_dir}")
|
||||
end
|
||||
|
||||
output_file = File.join(real_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects
|
||||
|
||||
|
@ -35,24 +40,36 @@ module Pages
|
|||
|
||||
private
|
||||
|
||||
def resolve_public_dir
|
||||
@real_dir = File.realpath(@input_dir)
|
||||
@public_dir = File.join(real_dir, PUBLIC_DIR)
|
||||
|
||||
valid_path?(public_dir)
|
||||
rescue Errno::ENOENT
|
||||
false
|
||||
end
|
||||
|
||||
def write_entry(zipfile, zipfile_path)
|
||||
disk_file_path = File.join(real_dir, zipfile_path)
|
||||
|
||||
unless valid_path?(disk_file_path)
|
||||
# archive with invalid entry will just have this entry missing
|
||||
raise InvalidEntryError
|
||||
raise InvalidEntryError, "#{disk_file_path} is invalid, input_dir: #{@input_dir}"
|
||||
end
|
||||
|
||||
case File.lstat(disk_file_path).ftype
|
||||
ftype = File.lstat(disk_file_path).ftype
|
||||
case ftype
|
||||
when 'directory'
|
||||
recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
|
||||
when 'file', 'link'
|
||||
zipfile.add(zipfile_path, disk_file_path)
|
||||
else
|
||||
raise InvalidEntryError
|
||||
raise InvalidEntryError, "#{disk_file_path} has invalid ftype: #{ftype}, input_dir: #{@input_dir}"
|
||||
end
|
||||
rescue InvalidEntryError => e
|
||||
rescue Errno::ENOENT, Errno::ELOOP, InvalidEntryError => e
|
||||
Gitlab::ErrorTracking.track_exception(e, input_dir: @input_dir, disk_file_path: disk_file_path)
|
||||
|
||||
raise e unless @ignore_invalid_entries
|
||||
end
|
||||
|
||||
def recursively_zip_directory(zipfile, disk_file_path, zipfile_path)
|
||||
|
@ -70,31 +87,11 @@ module Pages
|
|||
end
|
||||
end
|
||||
|
||||
# that should never happen, but we want to be safer
|
||||
# in theory without this we would allow to use symlinks
|
||||
# to pack any directory on disk
|
||||
# it isn't possible because SafeZip doesn't extract such archives
|
||||
# SafeZip was introduced only recently,
|
||||
# so we have invalid entries on disk
|
||||
def valid_path?(disk_file_path)
|
||||
realpath = File.realpath(disk_file_path)
|
||||
|
||||
realpath == public_dir || realpath.start_with?(public_dir + "/")
|
||||
# happens if target of symlink isn't there
|
||||
rescue => e
|
||||
Gitlab::ErrorTracking.track_exception(e, input_dir: real_dir, disk_file_path: disk_file_path)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
def real_dir
|
||||
strong_memoize(:real_dir) do
|
||||
File.realpath(@input_dir) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
def public_dir
|
||||
strong_memoize(:public_dir) do
|
||||
File.join(real_dir, PUBLIC_DIR) rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,5 @@
|
|||
|
||||
#js-peek{ data: { env: Peek.env,
|
||||
request_id: peek_request_id,
|
||||
stats_url: ENV.fetch('GITLAB_PERFORMANCE_BAR_STATS_URL', ''),
|
||||
peek_url: "#{peek_routes_path}/results" },
|
||||
class: Peek.env }
|
||||
|
|
5
changelogs/unreleased/gl-button-job.yml
Normal file
5
changelogs/unreleased/gl-button-job.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Apply new GitLab UI for buttons in pipeline page
|
||||
merge_request: 53364
|
||||
author: Yogi (@yo)
|
||||
type: other
|
5
changelogs/unreleased/semgrep-docs-n-template.yml
Normal file
5
changelogs/unreleased/semgrep-docs-n-template.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add semgrep SAST analyzer
|
||||
merge_request: 53815
|
||||
author: Daniel Paul Searles
|
||||
type: added
|
|
@ -310,6 +310,13 @@ Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_secret')
|
|||
Settings.pages['storage_path'] = Settings.pages['path']
|
||||
Settings.pages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.pages['object_store'])
|
||||
|
||||
#
|
||||
# GitLab documentation
|
||||
#
|
||||
Settings['gitlab_docs'] ||= Settingslogic.new({})
|
||||
Settings.gitlab_docs['enabled'] ||= false
|
||||
Settings.gitlab_docs['host'] = nil unless Settings.gitlab_docs.enabled
|
||||
|
||||
#
|
||||
# Geo
|
||||
#
|
||||
|
|
|
@ -6,8 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Performance Bar **(FREE SELF)**
|
||||
|
||||
> The **Stats** field [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/271551) in GitLab SaaS 13.9.
|
||||
|
||||
You can display the GitLab Performance Bar to see statistics for the performance
|
||||
of a page. When activated, it looks as follows:
|
||||
|
||||
|
@ -55,8 +53,6 @@ From left to right, it displays:
|
|||
- **Request Selector**: a select box displayed on the right-hand side of the
|
||||
Performance Bar which enables you to view these metrics for any requests made while
|
||||
the current page was open. Only the first two requests per unique URL are captured.
|
||||
- **Stats** (optional): if the `GITLAB_PERFORMANCE_BAR_STATS_URL` environment variable is set,
|
||||
this URL is displayed in the bar. In GitLab 13.9 and later, used only in GitLab SaaS.
|
||||
|
||||
## Request warnings
|
||||
|
||||
|
|
|
@ -1639,7 +1639,7 @@ type BoardEdge {
|
|||
"""
|
||||
Represents an epic on an issue board
|
||||
"""
|
||||
type BoardEpic implements CurrentUserTodos & Noteable {
|
||||
type BoardEpic implements CurrentUserTodos & Eventable & Noteable {
|
||||
"""
|
||||
Author of the epic.
|
||||
"""
|
||||
|
@ -1878,6 +1878,31 @@ type BoardEpic implements CurrentUserTodos & Noteable {
|
|||
"""
|
||||
dueDateIsFixed: Boolean
|
||||
|
||||
"""
|
||||
A list of events associated with the object.
|
||||
"""
|
||||
events(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): EventConnection
|
||||
|
||||
"""
|
||||
Group to which the epic belongs.
|
||||
"""
|
||||
|
@ -8682,7 +8707,7 @@ type EnvironmentsCanaryIngressUpdatePayload {
|
|||
"""
|
||||
Represents an epic
|
||||
"""
|
||||
type Epic implements CurrentUserTodos & Noteable {
|
||||
type Epic implements CurrentUserTodos & Eventable & Noteable {
|
||||
"""
|
||||
Author of the epic.
|
||||
"""
|
||||
|
@ -8921,6 +8946,31 @@ type Epic implements CurrentUserTodos & Noteable {
|
|||
"""
|
||||
dueDateIsFixed: Boolean
|
||||
|
||||
"""
|
||||
A list of events associated with the object.
|
||||
"""
|
||||
events(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): EventConnection
|
||||
|
||||
"""
|
||||
Group to which the epic belongs.
|
||||
"""
|
||||
|
@ -10251,6 +10301,168 @@ enum EpicWildcardId {
|
|||
NONE
|
||||
}
|
||||
|
||||
"""
|
||||
Representing an event
|
||||
"""
|
||||
type Event {
|
||||
"""
|
||||
Action of the event.
|
||||
"""
|
||||
action: EventAction!
|
||||
|
||||
"""
|
||||
Author of this event.
|
||||
"""
|
||||
author: User!
|
||||
|
||||
"""
|
||||
When this event was created.
|
||||
"""
|
||||
createdAt: Time!
|
||||
|
||||
"""
|
||||
ID of the event.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
When this event was updated.
|
||||
"""
|
||||
updatedAt: Time!
|
||||
}
|
||||
|
||||
"""
|
||||
Event action
|
||||
"""
|
||||
enum EventAction {
|
||||
"""
|
||||
Approved action
|
||||
"""
|
||||
APPROVED
|
||||
|
||||
"""
|
||||
Archived action
|
||||
"""
|
||||
ARCHIVED
|
||||
|
||||
"""
|
||||
Closed action
|
||||
"""
|
||||
CLOSED
|
||||
|
||||
"""
|
||||
Commented action
|
||||
"""
|
||||
COMMENTED
|
||||
|
||||
"""
|
||||
Created action
|
||||
"""
|
||||
CREATED
|
||||
|
||||
"""
|
||||
Destroyed action
|
||||
"""
|
||||
DESTROYED
|
||||
|
||||
"""
|
||||
Expired action
|
||||
"""
|
||||
EXPIRED
|
||||
|
||||
"""
|
||||
Joined action
|
||||
"""
|
||||
JOINED
|
||||
|
||||
"""
|
||||
Left action
|
||||
"""
|
||||
LEFT
|
||||
|
||||
"""
|
||||
Merged action
|
||||
"""
|
||||
MERGED
|
||||
|
||||
"""
|
||||
Pushed action
|
||||
"""
|
||||
PUSHED
|
||||
|
||||
"""
|
||||
Reopened action
|
||||
"""
|
||||
REOPENED
|
||||
|
||||
"""
|
||||
Updated action
|
||||
"""
|
||||
UPDATED
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for Event.
|
||||
"""
|
||||
type EventConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [EventEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [Event]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type EventEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: Event
|
||||
}
|
||||
|
||||
interface Eventable {
|
||||
"""
|
||||
A list of events associated with the object.
|
||||
"""
|
||||
events(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): EventConnection
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of ExportRequirements
|
||||
"""
|
||||
|
|
|
@ -4855,6 +4855,59 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "events",
|
||||
"description": "A list of events associated with the object.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "EventConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"description": "Group to which the epic belongs.",
|
||||
|
@ -5516,6 +5569,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "Eventable",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -24566,6 +24624,59 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "events",
|
||||
"description": "A list of events associated with the object.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "EventConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "group",
|
||||
"description": "Group to which the epic belongs.",
|
||||
|
@ -25213,6 +25324,11 @@
|
|||
"kind": "INTERFACE",
|
||||
"name": "CurrentUserTodos",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "Eventable",
|
||||
"ofType": null
|
||||
}
|
||||
],
|
||||
"enumValues": null,
|
||||
|
@ -28276,6 +28392,385 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Event",
|
||||
"description": "Representing an event",
|
||||
"fields": [
|
||||
{
|
||||
"name": "action",
|
||||
"description": "Action of the event.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "ENUM",
|
||||
"name": "EventAction",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"description": "Author of this event.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "User",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "createdAt",
|
||||
"description": "When this event was created.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the event.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updatedAt",
|
||||
"description": "When this event was updated.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "EventAction",
|
||||
"description": "Event action",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{
|
||||
"name": "CREATED",
|
||||
"description": "Created action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "UPDATED",
|
||||
"description": "Updated action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "CLOSED",
|
||||
"description": "Closed action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "REOPENED",
|
||||
"description": "Reopened action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "PUSHED",
|
||||
"description": "Pushed action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "COMMENTED",
|
||||
"description": "Commented action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "MERGED",
|
||||
"description": "Merged action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "JOINED",
|
||||
"description": "Joined action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "LEFT",
|
||||
"description": "Left action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "DESTROYED",
|
||||
"description": "Destroyed action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "EXPIRED",
|
||||
"description": "Expired action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "APPROVED",
|
||||
"description": "Approved action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "ARCHIVED",
|
||||
"description": "Archived action",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "EventConnection",
|
||||
"description": "The connection type for Event.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "EventEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Event",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "EventEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Event",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INTERFACE",
|
||||
"name": "Eventable",
|
||||
"description": null,
|
||||
"fields": [
|
||||
{
|
||||
"name": "events",
|
||||
"description": "A list of events associated with the object.",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "EventConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "BoardEpic",
|
||||
"ofType": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Epic",
|
||||
"ofType": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "ExportRequirementsInput",
|
||||
|
|
|
@ -309,6 +309,7 @@ Represents an epic on an issue board.
|
|||
| `dueDateFixed` | Time | Fixed due date of the epic. |
|
||||
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones. |
|
||||
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set. |
|
||||
| `events` | EventConnection | A list of events associated with the object. |
|
||||
| `group` | Group! | Group to which the epic belongs. |
|
||||
| `hasChildren` | Boolean! | Indicates if the epic has children. |
|
||||
| `hasIssues` | Boolean! | Indicates if the epic has direct issues. |
|
||||
|
@ -1467,6 +1468,7 @@ Represents an epic.
|
|||
| `dueDateFixed` | Time | Fixed due date of the epic. |
|
||||
| `dueDateFromMilestones` | Time | Inherited due date of the epic from milestones. |
|
||||
| `dueDateIsFixed` | Boolean | Indicates if the due date has been manually set. |
|
||||
| `events` | EventConnection | A list of events associated with the object. |
|
||||
| `group` | Group! | Group to which the epic belongs. |
|
||||
| `hasChildren` | Boolean! | Indicates if the epic has children. |
|
||||
| `hasIssues` | Boolean! | Indicates if the epic has direct issues. |
|
||||
|
@ -1678,6 +1680,18 @@ Autogenerated return type of EpicTreeReorder.
|
|||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### Event
|
||||
|
||||
Representing an event.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `action` | EventAction! | Action of the event. |
|
||||
| `author` | User! | Author of this event. |
|
||||
| `createdAt` | Time! | When this event was created. |
|
||||
| `id` | ID! | ID of the event. |
|
||||
| `updatedAt` | Time! | When this event was updated. |
|
||||
|
||||
### ExportRequirementsPayload
|
||||
|
||||
Autogenerated return type of ExportRequirements.
|
||||
|
@ -4879,6 +4893,26 @@ Epic ID wildcard values.
|
|||
| `ANY` | Any epic is assigned |
|
||||
| `NONE` | No epic is assigned |
|
||||
|
||||
### EventAction
|
||||
|
||||
Event action.
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| `APPROVED` | Approved action |
|
||||
| `ARCHIVED` | Archived action |
|
||||
| `CLOSED` | Closed action |
|
||||
| `COMMENTED` | Commented action |
|
||||
| `CREATED` | Created action |
|
||||
| `DESTROYED` | Destroyed action |
|
||||
| `EXPIRED` | Expired action |
|
||||
| `JOINED` | Joined action |
|
||||
| `LEFT` | Left action |
|
||||
| `MERGED` | Merged action |
|
||||
| `PUSHED` | Pushed action |
|
||||
| `REOPENED` | Reopened action |
|
||||
| `UPDATED` | Updated action |
|
||||
|
||||
### GroupMemberRelation
|
||||
|
||||
Group member relation.
|
||||
|
|
|
@ -311,6 +311,9 @@ Custom event tracking and instrumentation can be added by directly calling the `
|
|||
| `property` | String | nil | As described in [Structured event taxonomy](#structured-event-taxonomy). |
|
||||
| `value` | Numeric | nil | As described in [Structured event taxonomy](#structured-event-taxonomy). |
|
||||
| `context` | Array\[SelfDescribingJSON\] | nil | An array of custom contexts to send with this event. Most events should not have any custom contexts. |
|
||||
| `project` | Project | nil | The project associated with the event |
|
||||
| `user` | User | nil | The user associated with the event |
|
||||
| `namespace` | Namespace | nil | The namespace associated with the event |
|
||||
|
||||
Tracking can be viewed as either tracking user behavior, or can be used for instrumentation to monitor and visualize performance over time in an area or aspect of code.
|
||||
|
||||
|
@ -321,10 +324,8 @@ class Projects::CreateService < BaseService
|
|||
def execute
|
||||
project = Project.create(params)
|
||||
|
||||
Gitlab::Tracking.event('Projects::CreateService', 'create_project',
|
||||
label: project.errors.full_messages.to_sentence,
|
||||
value: project.valid?
|
||||
)
|
||||
Gitlab::Tracking.event('Projects::CreateService', 'create_project', label: project.errors.full_messages.to_sentence,
|
||||
property: project.valid?.to_s, project: project, user: current_user, namespace: namespace)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
|
|
@ -33,6 +33,7 @@ SAST supports the following official analyzers:
|
|||
- [`phpcs-security-audit`](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) (PHP CS security-audit)
|
||||
- [`pmd-apex`](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) (PMD (Apex only))
|
||||
- [`security-code-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) (Security Code Scan (.NET))
|
||||
- [`semgrep`](https://gitlab.com/gitlab-org/security-products/analyzers/semgrep) (Semgrep)
|
||||
- [`sobelow`](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) (Sobelow (Elixir Phoenix))
|
||||
- [`spotbugs`](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) (SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT))
|
||||
|
||||
|
@ -153,24 +154,24 @@ The [Security Scanner Integration](../../../development/integrations/secure.md)
|
|||
|
||||
## Analyzers Data
|
||||
|
||||
| Property / Tool | Apex | Bandit | Brakeman | ESLint security | SpotBugs | Flawfinder | Gosec | Kubesec Scanner | MobSF | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Sobelow |
|
||||
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: |
|
||||
| Severity | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ |
|
||||
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Description | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ |
|
||||
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| End line | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Start column | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ |
|
||||
| End column | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| External ID (for example, CVE) | ✗ | ✗ | ⚠ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| URLs | ✓ | ✗ | ✓ | ✗ | ⚠ | ✗ | ⚠ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Internal doc/explanation | ✓ | ⚠ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Solution | ✓ | ✗ | ✗ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Affected item (for example, class or package) | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Confidence | ✗ | ✓ | ✓ | ✗ | ✓ | x | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Source code extract | ✗ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ |
|
||||
| Property / Tool | Apex | Bandit | Brakeman | ESLint security | SpotBugs | Flawfinder | Gosec | Kubesec Scanner | MobSF | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Semgrep | Sobelow |
|
||||
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :-------------------------: | :----------------: |
|
||||
| Severity | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ⚠ | ✗ |
|
||||
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Description | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ |
|
||||
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| End line | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Start column | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ |
|
||||
| End column | ✓ | ✗ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| External ID (for example, CVE) | ✗ | ✗ | ⚠ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✗ |
|
||||
| URLs | ✓ | ✗ | ✓ | ✗ | ⚠ | ✗ | ⚠ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Internal doc/explanation | ✓ | ⚠ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
|
||||
| Solution | ✓ | ✗ | ✗ | ✗ | ⚠ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✗ |
|
||||
| Affected item (for example, class or package) | ✓ | ✗ | ✓ | ✗ | ✓ | ✓ | ✗ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Confidence | ✗ | ✓ | ✓ | ✗ | ✓ | x | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ⚠ | ✓ |
|
||||
| Source code extract | ✗ | ✓ | ✓ | ✓ | ✗ | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
||||
- ✓ => we have that data
|
||||
- ⚠ => we have that data but it's partially reliable, or we need to extract it from unstructured content
|
||||
|
|
|
@ -83,6 +83,7 @@ You can also [view our language roadmap](https://about.gitlab.com/direction/secu
|
|||
| Objective-C (iOS) | [MobSF (beta)](https://github.com/MobSF/Mobile-Security-Framework-MobSF) | 13.5 |
|
||||
| PHP | [phpcs-security-audit](https://github.com/FloeDesignTechnologies/phpcs-security-audit) | 10.8 |
|
||||
| Python ([pip](https://pip.pypa.io/en/stable/)) | [bandit](https://github.com/PyCQA/bandit) | 10.3 |
|
||||
| Python | [semgrep](https://semgrep.dev) | 13.9 |
|
||||
| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5 |
|
||||
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3 |
|
||||
| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/), and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven) |
|
||||
|
@ -111,6 +112,7 @@ The following analyzers have multi-project support:
|
|||
- MobSF
|
||||
- PMD
|
||||
- Security Code Scan
|
||||
- Semgrep
|
||||
- SpotBugs
|
||||
- Sobelow
|
||||
|
||||
|
@ -681,20 +683,21 @@ For details on saving and transporting Docker images as a file, see Docker's doc
|
|||
|
||||
Support for custom certificate authorities was introduced in the following versions.
|
||||
|
||||
| Analyzer | Version |
|
||||
| -------- | ------- |
|
||||
| `bandit` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/bandit/-/releases/v2.3.0) |
|
||||
| `brakeman` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman/-/releases/v2.1.0) |
|
||||
| `eslint` | [v2.9.2](https://gitlab.com/gitlab-org/security-products/analyzers/eslint/-/releases/v2.9.2) |
|
||||
| `flawfinder` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder/-/releases/v2.3.0) |
|
||||
| `gosec` | [v2.5.0](https://gitlab.com/gitlab-org/security-products/analyzers/gosec/-/releases/v2.5.0) |
|
||||
| `kubesec` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec/-/releases/v2.1.0) |
|
||||
| `nodejs-scan` | [v2.9.5](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan/-/releases/v2.9.5) |
|
||||
| Analyzer | Version |
|
||||
| -------- | ------- |
|
||||
| `bandit` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/bandit/-/releases/v2.3.0) |
|
||||
| `brakeman` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman/-/releases/v2.1.0) |
|
||||
| `eslint` | [v2.9.2](https://gitlab.com/gitlab-org/security-products/analyzers/eslint/-/releases/v2.9.2) |
|
||||
| `flawfinder` | [v2.3.0](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder/-/releases/v2.3.0) |
|
||||
| `gosec` | [v2.5.0](https://gitlab.com/gitlab-org/security-products/analyzers/gosec/-/releases/v2.5.0) |
|
||||
| `kubesec` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/kubesec/-/releases/v2.1.0) |
|
||||
| `nodejs-scan` | [v2.9.5](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan/-/releases/v2.9.5) |
|
||||
| `phpcs-security-audit` | [v2.8.2](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit/-/releases/v2.8.2) |
|
||||
| `pmd-apex` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex/-/releases/v2.1.0) |
|
||||
| `security-code-scan` | [v2.7.3](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/releases/v2.7.3) |
|
||||
| `sobelow` | [v2.2.0](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow/-/releases/v2.2.0) |
|
||||
| `spotbugs` | [v2.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs/-/releases/v2.7.1) |
|
||||
| `pmd-apex` | [v2.1.0](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex/-/releases/v2.1.0) |
|
||||
| `security-code-scan` | [v2.7.3](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/releases/v2.7.3) |
|
||||
| `semgrep` | [v0.0.1](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan/-/releases/v0.0.1) |
|
||||
| `sobelow` | [v2.2.0](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow/-/releases/v2.2.0) |
|
||||
| `spotbugs` | [v2.7.1](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs/-/releases/v2.7.1) |
|
||||
|
||||
### Set SAST CI job variables to use local SAST analyzers
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5 KiB |
|
@ -319,15 +319,24 @@ their own authors. To change this setting:
|
|||
|
||||
Note that users can edit the approval rules in every merge request and override pre-defined settings unless it's set [**not to allow** overrides](#prevent-overriding-default-approvals).
|
||||
|
||||
You can prevent authors from approving their own merge requests
|
||||
[at the instance level](../../admin_area/merge_requests_approvals.md). When enabled,
|
||||
this setting is disabled on the project level, and not editable.
|
||||
|
||||
#### Prevent approval of merge requests by their committers **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10441) in GitLab 11.10.
|
||||
> - Moved to GitLab Premium in 13.9.
|
||||
|
||||
You can prevent users who have committed to a merge request from approving it,
|
||||
though code authors can still approve. To enable this feature:
|
||||
though code authors can still approve. You can enable this feature
|
||||
[at the instance level](../../admin_area/merge_requests_approvals.md), which
|
||||
disables changes to this feature at the project level. If you prefer to manage
|
||||
this feature at the project level, you can:
|
||||
|
||||
1. Check the **Prevent MR approvals from users who make commits to the MR.** checkbox.
|
||||
If this check box is disabled, this feature has been disabled
|
||||
[at the instance level](../../admin_area/merge_requests_approvals.md).
|
||||
1. Click **Save changes**.
|
||||
|
||||
Read the official Git documentation for an explanation of the
|
||||
|
|
|
@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# Service Desk **(FREE)**
|
||||
|
||||
> - Moved to GitLab Free in 13.2.
|
||||
> Moved to GitLab Free in 13.2.
|
||||
|
||||
Service Desk is a module that allows your team to connect
|
||||
with any external party through email, without any external tools.
|
||||
|
@ -54,36 +54,41 @@ Here's how Service Desk works for you:
|
|||
|
||||
## Configuring Service Desk
|
||||
|
||||
NOTE:
|
||||
Service Desk is enabled on GitLab.com.
|
||||
You can skip step 1 below; you only need to enable it per project.
|
||||
Users with Maintainer and higher access in a project can configure Service Desk.
|
||||
|
||||
If you have project maintainer access you have the option to set up Service Desk. Follow these steps:
|
||||
Service Desk issues are [confidential](issues/confidential_issues.md), so they are
|
||||
only visible to project members. In GitLab 11.7 we updated the generated email
|
||||
address format. The older format is still supported, so existing aliases or
|
||||
contacts still work.
|
||||
|
||||
1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance.
|
||||
If you have [templates](description_templates.md) in your repository, you can optionally select
|
||||
one from the selector menu to append it to all Service Desk issues.
|
||||
|
||||
To enable Service Desk in your project:
|
||||
|
||||
1. (GitLab self-managed only) [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance.
|
||||
We recommend using [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing),
|
||||
but in GitLab 11.7 and later you can also use [catch-all mailboxes](../../administration/incoming_email.md#catch-all-mailbox).
|
||||
1. Navigate to your project's **Settings > General** and locate the **Service Desk** section.
|
||||
but you can also use [catch-all mailboxes](../../administration/incoming_email.md#catch-all-mailbox).
|
||||
1. In a project, in the left sidebar, go to **Settings > General** and expand the **Service Desk** section.
|
||||
1. Enable the **Activate Service Desk** toggle. This reveals a unique email address to email issues
|
||||
to the project. These issues are [confidential](issues/confidential_issues.md), so they are
|
||||
only visible to project members. Note that in GitLab 11.7, we updated the generated email
|
||||
address's format. The older format is still supported, however, allowing existing aliases or
|
||||
contacts to continue working.
|
||||
to the project.
|
||||
|
||||
WARNING:
|
||||
This email address can be used by anyone to create an issue on this project, regardless
|
||||
of their access level to your GitLab instance. We recommend **putting this behind an alias on your email system** so it can be
|
||||
changed if needed. We also recommend **[enabling Akismet](../../integration/akismet.md)** on your GitLab
|
||||
instance to add spam checking to this service. Unblocked email spam would result in many spam
|
||||
issues being created.
|
||||
Service Desk is now enabled for this project! To access it in a project, in the left sidebar, select
|
||||
**Issues > Service Desk**.
|
||||
|
||||
If you have [templates](description_templates.md) in your repository, you can optionally select
|
||||
one from the selector menu to append it to all Service Desk issues.
|
||||
WARNING:
|
||||
Anyone in your project can use the Service Desk email address to create an issue in this project, **regardless
|
||||
of their access level** to your GitLab instance.
|
||||
|
||||
Service Desk is now enabled for this project! You should be able to access it from your project's
|
||||
**Issues** menu.
|
||||
To improve your project's security, we recommend the following:
|
||||
|
||||
![Service Desk Navigation Item](img/service_desk_nav_item.png)
|
||||
- Put the Service Desk email address behind an alias on your email system so you can change it later.
|
||||
- [Enable Akismet](../../integration/akismet.md) on your GitLab instance to add spam checking to this service.
|
||||
Unblocked email spam can result in many spam issues being created.
|
||||
|
||||
The unique internal email address is visible to all project members in your GitLab instance.
|
||||
However, when using an email alias externally, an end user (issue creator) cannot see the internal
|
||||
email address displayed in the information note.
|
||||
|
||||
### Using customized email templates
|
||||
|
||||
|
@ -232,7 +237,8 @@ The configuration options are the same as for configuring
|
|||
|
||||
## Using Service Desk
|
||||
|
||||
There are a few ways Service Desk can be used.
|
||||
You can use Service Desk to [create an issue](#as-an-end-user-issue-creator) or [respond to one](#as-a-responder-to-the-issue).
|
||||
In these issues, you can also see our friendly neighborhood [Support Bot](#support-bot-user).
|
||||
|
||||
### As an end user (issue creator)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
type: howto
|
||||
stage: Fulfillment
|
||||
group: Provision
|
||||
group: Utilization
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ variables:
|
|||
# (SAST, Dependency Scanning, ...)
|
||||
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
|
||||
|
||||
SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, sobelow, pmd-apex, kubesec, mobsf"
|
||||
SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, sobelow, pmd-apex, kubesec, mobsf, semgrep"
|
||||
SAST_EXCLUDED_ANALYZERS: ""
|
||||
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
|
||||
SAST_ANALYZER_IMAGE_TAG: 2
|
||||
|
@ -244,6 +244,23 @@ security-code-scan-sast:
|
|||
- '**/*.csproj'
|
||||
- '**/*.vbproj'
|
||||
|
||||
semgrep-sast:
|
||||
extends: .sast-analyzer
|
||||
image:
|
||||
name: "$SAST_ANALYZER_IMAGE"
|
||||
variables:
|
||||
SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/semgrep:latest"
|
||||
rules:
|
||||
- if: $SAST_DISABLED
|
||||
when: never
|
||||
- if: $SAST_EXCLUDED_ANALYZERS =~ /semgrep/
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH &&
|
||||
$SAST_DEFAULT_ANALYZERS =~ /semgrep/ &&
|
||||
$SAST_EXPERIMENTAL_FEATURES == 'true'
|
||||
exists:
|
||||
- '**/*.py'
|
||||
|
||||
sobelow-sast:
|
||||
extends: .sast-analyzer
|
||||
image:
|
||||
|
|
|
@ -17,7 +17,7 @@ module Gitlab
|
|||
# to a structured log
|
||||
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
def enqueue_stats_job(request_id)
|
||||
return unless Feature.enabled?(:performance_bar_stats)
|
||||
return unless gather_stats?
|
||||
|
||||
@client.sadd(GitlabPerformanceBarStatsWorker::STATS_KEY, request_id)
|
||||
|
||||
|
@ -43,6 +43,12 @@ module Gitlab
|
|||
)
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
||||
def gather_stats?
|
||||
return unless Feature.enabled?(:performance_bar_stats)
|
||||
|
||||
Gitlab.com? || Gitlab.staging? || !Rails.env.production?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,8 +24,8 @@ module Gitlab
|
|||
Gitlab::CurrentSettings.snowplow_enabled?
|
||||
end
|
||||
|
||||
def event(category, action, label: nil, property: nil, value: nil, context: [], standard_context: nil)
|
||||
context.push(standard_context.to_context) if standard_context
|
||||
def event(category, action, label: nil, property: nil, value: nil, context: [], project: nil, user: nil, namespace: nil) # rubocop:disable Metrics/ParameterLists
|
||||
context += [Tracking::StandardContext.new(project: project, user: user, namespace: namespace).to_context]
|
||||
|
||||
snowplow.event(category, action, label: label, property: property, value: value, context: context)
|
||||
product_analytics.event(category, action, label: label, property: property, value: value, context: context)
|
||||
|
|
|
@ -29,11 +29,10 @@ module Gitlab
|
|||
private
|
||||
|
||||
def to_h
|
||||
public_methods(false).each_with_object({}) do |method, hash|
|
||||
next if method == :to_context
|
||||
|
||||
hash[method] = public_send(method) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end.merge(@data)
|
||||
{
|
||||
environment: environment,
|
||||
source: source
|
||||
}.merge(@data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,10 @@ namespace :gitlab do
|
|||
task migrate_legacy_storage: :gitlab_environment do
|
||||
logger.info('Starting to migrate legacy pages storage to zip deployments')
|
||||
|
||||
result = ::Pages::MigrateFromLegacyStorageService.new(logger, migration_threads, batch_size).execute
|
||||
result = ::Pages::MigrateFromLegacyStorageService.new(logger,
|
||||
migration_threads: migration_threads,
|
||||
batch_size: batch_size,
|
||||
ignore_invalid_entries: ignore_invalid_entries).execute
|
||||
|
||||
logger.info("A total of #{result[:migrated] + result[:errored]} projects were processed.")
|
||||
logger.info("- The #{result[:migrated]} projects migrated successfully")
|
||||
|
@ -42,5 +45,11 @@ namespace :gitlab do
|
|||
def batch_size
|
||||
ENV.fetch('PAGES_MIGRATION_BATCH_SIZE', '10').to_i
|
||||
end
|
||||
|
||||
def ignore_invalid_entries
|
||||
Gitlab::Utils.to_boolean(
|
||||
ENV.fetch('PAGES_MIGRATION_IGNORE_INVALID_ENTRIES', 'false')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9224,6 +9224,9 @@ msgstr ""
|
|||
msgid "DastProfiles|Request headers"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Run scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "DastProfiles|Run the AJAX spider, in addition to the traditional spider, to crawl the target site."
|
||||
msgstr ""
|
||||
|
||||
|
@ -21480,9 +21483,6 @@ msgstr ""
|
|||
msgid "PerformanceBar|SQL queries"
|
||||
msgstr ""
|
||||
|
||||
msgid "PerformanceBar|Stats"
|
||||
msgstr ""
|
||||
|
||||
msgid "PerformanceBar|trace"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
# Cop that blacklists keyword arguments usage in Sidekiq workers
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'set'
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../migration_helpers'
|
||||
|
||||
module RuboCop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
class ProjectPathHelper < RuboCop::Cop::Cop
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
# Module containing helper methods for writing migration cops.
|
||||
module MigrationHelpers
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
# Module containing helper methods for writing QA cops.
|
||||
module QAHelpers
|
||||
|
|
|
@ -7,6 +7,43 @@ RSpec.describe HelpController do
|
|||
|
||||
let(:user) { create(:user) }
|
||||
|
||||
shared_examples 'documentation pages local render' do
|
||||
it 'renders HTML' do
|
||||
aggregate_failures do
|
||||
is_expected.to render_template('show.html.haml')
|
||||
expect(response.media_type).to eq 'text/html'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'documentation pages redirect' do |documentation_base_url|
|
||||
let(:gitlab_version) { '13.4.0-ee' }
|
||||
|
||||
before do
|
||||
stub_version(gitlab_version, 'ignored_revision_value')
|
||||
end
|
||||
|
||||
it 'redirects user to custom documentation url with a specified version' do
|
||||
is_expected.to redirect_to("#{documentation_base_url}/13.4/ee/#{path}.html")
|
||||
end
|
||||
|
||||
context 'when it is a pre-release' do
|
||||
let(:gitlab_version) { '13.4.0-pre' }
|
||||
|
||||
it 'redirects user to custom documentation url without a version' do
|
||||
is_expected.to redirect_to("#{documentation_base_url}/ee/#{path}.html")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(help_page_documentation_redirect: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'documentation pages local render'
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
@ -99,69 +136,70 @@ RSpec.describe HelpController do
|
|||
|
||||
describe 'GET #show' do
|
||||
context 'for Markdown formats' do
|
||||
subject { get :show, params: { path: path }, format: :md }
|
||||
|
||||
let(:path) { 'ssh/README' }
|
||||
|
||||
context 'when requested file exists' do
|
||||
before do
|
||||
expect_file_read(File.join(Rails.root, 'doc/ssh/README.md'), content: fixture_file('blockquote_fence_after.md'))
|
||||
|
||||
get :show, params: { path: 'ssh/README' }, format: :md
|
||||
subject
|
||||
end
|
||||
|
||||
it 'assigns to @markdown' do
|
||||
expect(assigns[:markdown]).not_to be_empty
|
||||
end
|
||||
|
||||
it 'renders HTML' do
|
||||
aggregate_failures do
|
||||
expect(response).to render_template('show.html.haml')
|
||||
expect(response.media_type).to eq 'text/html'
|
||||
end
|
||||
it_behaves_like 'documentation pages local render'
|
||||
end
|
||||
|
||||
context 'when a custom help_page_documentation_url is set in database' do
|
||||
before do
|
||||
stub_application_setting(help_page_documentation_base_url: 'https://in-db.gitlab.com')
|
||||
end
|
||||
|
||||
it_behaves_like 'documentation pages redirect', 'https://in-db.gitlab.com'
|
||||
end
|
||||
|
||||
context 'when a custom help_page_documentation_url is set in configuration file' do
|
||||
let(:host) { 'https://in-yaml.gitlab.com' }
|
||||
let(:docs_enabled) { true }
|
||||
|
||||
before do
|
||||
allow(Settings).to receive(:gitlab_docs) { double(enabled: docs_enabled, host: host) }
|
||||
end
|
||||
|
||||
it_behaves_like 'documentation pages redirect', 'https://in-yaml.gitlab.com'
|
||||
|
||||
context 'when gitlab_docs is disabled' do
|
||||
let(:docs_enabled) { false }
|
||||
|
||||
it_behaves_like 'documentation pages local render'
|
||||
end
|
||||
|
||||
context 'when host is missing' do
|
||||
let(:host) { nil }
|
||||
|
||||
it_behaves_like 'documentation pages local render'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a custom help_page_documentation_url is set' do
|
||||
context 'when help_page_documentation_url is set in both db and configuration file' do
|
||||
before do
|
||||
stub_application_setting(help_page_documentation_base_url: documentation_base_url)
|
||||
stub_version(gitlab_version, 'deadbeaf')
|
||||
stub_application_setting(help_page_documentation_base_url: 'https://in-db.gitlab.com')
|
||||
allow(Settings).to receive(:gitlab_docs) { double(enabled: true, host: 'https://in-yaml.gitlab.com') }
|
||||
end
|
||||
|
||||
subject { get :show, params: { path: path }, format: 'html' }
|
||||
it_behaves_like 'documentation pages redirect', 'https://in-yaml.gitlab.com'
|
||||
end
|
||||
|
||||
let(:gitlab_version) { '13.4.0-ee' }
|
||||
let(:documentation_base_url) { 'https://docs.gitlab.com' }
|
||||
let(:path) { 'ssh/README' }
|
||||
|
||||
it 'redirects user to custom documentation url with a specified version' do
|
||||
is_expected.to redirect_to("#{documentation_base_url}/13.4/ee/#{path}.html")
|
||||
context 'when help_page_documentation_url has a trailing slash' do
|
||||
before do
|
||||
allow(Settings).to receive(:gitlab_docs) { double(enabled: true, host: 'https://in-yaml.gitlab.com/') }
|
||||
end
|
||||
|
||||
context 'when documentation url ends with a slash' do
|
||||
let(:documentation_base_url) { 'https://docs.gitlab.com/' }
|
||||
|
||||
it 'redirects user to custom documentation url without slash duplicates' do
|
||||
is_expected.to redirect_to("https://docs.gitlab.com/13.4/ee/#{path}.html")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is a pre-release' do
|
||||
let(:gitlab_version) { '13.4.0-pre' }
|
||||
|
||||
it 'redirects user to custom documentation url without a version' do
|
||||
is_expected.to redirect_to("#{documentation_base_url}/ee/#{path}.html")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(help_page_documentation_redirect: false)
|
||||
end
|
||||
|
||||
it 'renders HTML' do
|
||||
aggregate_failures do
|
||||
is_expected.to render_template('show.html.haml')
|
||||
expect(response.media_type).to eq 'text/html'
|
||||
end
|
||||
end
|
||||
end
|
||||
it_behaves_like 'documentation pages redirect', 'https://in-yaml.gitlab.com'
|
||||
end
|
||||
|
||||
context 'when requested file is missing' do
|
||||
|
|
|
@ -49,10 +49,6 @@ RSpec.describe 'User can display performance bar', :js do
|
|||
|
||||
let(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
allow(GitlabPerformanceBarStatsWorker).to receive(:perform_in)
|
||||
end
|
||||
|
||||
context 'when user is logged-out' do
|
||||
before do
|
||||
visit root_path
|
||||
|
@ -101,28 +97,6 @@ RSpec.describe 'User can display performance bar', :js do
|
|||
|
||||
it_behaves_like 'performance bar is enabled by default in development'
|
||||
it_behaves_like 'performance bar can be displayed'
|
||||
|
||||
it 'does not show Stats link by default' do
|
||||
find('body').native.send_keys('pb')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_link('Stats')
|
||||
end
|
||||
|
||||
context 'when GITLAB_PERFORMANCE_BAR_STATS_URL environment variable is set' do
|
||||
let(:stats_url) { 'https://log.gprd.gitlab.net/app/dashboards#/view/' }
|
||||
|
||||
before do
|
||||
stub_env('GITLAB_PERFORMANCE_BAR_STATS_URL', stats_url)
|
||||
end
|
||||
|
||||
it 'shows Stats link' do
|
||||
find('body').native.send_keys('pb')
|
||||
wait_for_requests
|
||||
|
||||
expect(page).to have_link('Stats', href: stats_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,46 +1,42 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
|
||||
|
||||
describe('DelayedJobMixin', () => {
|
||||
let wrapper;
|
||||
const delayedJobFixture = getJSONFixture('jobs/delayed.json');
|
||||
const dummyComponent = Vue.extend({
|
||||
mixins: [delayedJobMixin],
|
||||
const dummyComponent = {
|
||||
props: {
|
||||
job: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('div', this.remainingTime);
|
||||
},
|
||||
});
|
||||
|
||||
let vm;
|
||||
mixins: [delayedJobMixin],
|
||||
template: '<div>{{remainingTime}}</div>',
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
jest.clearAllTimers();
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('if job is empty object', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(dummyComponent, {
|
||||
job: {},
|
||||
wrapper = shallowMount(dummyComponent, {
|
||||
propsData: {
|
||||
job: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('sets remaining time to 00:00:00', () => {
|
||||
expect(vm.$el.innerText).toBe('00:00:00');
|
||||
expect(wrapper.text()).toBe('00:00:00');
|
||||
});
|
||||
|
||||
describe('after mounting', () => {
|
||||
beforeEach(() => vm.$nextTick());
|
||||
it('does not update remaining time after mounting', async () => {
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
it('does not update remaining time', () => {
|
||||
expect(vm.$el.innerText).toBe('00:00:00');
|
||||
});
|
||||
expect(wrapper.text()).toBe('00:00:00');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -48,33 +44,32 @@ describe('DelayedJobMixin', () => {
|
|||
describe('if job is delayed job', () => {
|
||||
let remainingTimeInMilliseconds = 42000;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest
|
||||
.spyOn(Date, 'now')
|
||||
.mockImplementation(
|
||||
() => new Date(delayedJobFixture.scheduled_at).getTime() - remainingTimeInMilliseconds,
|
||||
);
|
||||
|
||||
vm = mountComponent(dummyComponent, {
|
||||
job: delayedJobFixture,
|
||||
wrapper = shallowMount(dummyComponent, {
|
||||
propsData: {
|
||||
job: delayedJobFixture,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
describe('after mounting', () => {
|
||||
beforeEach(() => vm.$nextTick());
|
||||
it('sets remaining time', () => {
|
||||
expect(wrapper.text()).toBe('00:00:42');
|
||||
});
|
||||
|
||||
it('sets remaining time', () => {
|
||||
expect(vm.$el.innerText).toBe('00:00:42');
|
||||
});
|
||||
it('updates remaining time', async () => {
|
||||
remainingTimeInMilliseconds = 41000;
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
it('updates remaining time', () => {
|
||||
remainingTimeInMilliseconds = 41000;
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
return vm.$nextTick().then(() => {
|
||||
expect(vm.$el.innerText).toBe('00:00:41');
|
||||
});
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.text()).toBe('00:00:41');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -96,33 +91,32 @@ describe('DelayedJobMixin', () => {
|
|||
describe('if job is delayed job', () => {
|
||||
let remainingTimeInMilliseconds = 42000;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
jest
|
||||
.spyOn(Date, 'now')
|
||||
.mockImplementation(
|
||||
() => mockGraphQlJob.scheduledAt.getTime() - remainingTimeInMilliseconds,
|
||||
);
|
||||
|
||||
vm = mountComponent(dummyComponent, {
|
||||
job: mockGraphQlJob,
|
||||
wrapper = shallowMount(dummyComponent, {
|
||||
propsData: {
|
||||
job: mockGraphQlJob,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
describe('after mounting', () => {
|
||||
beforeEach(() => vm.$nextTick());
|
||||
it('sets remaining time', () => {
|
||||
expect(wrapper.text()).toBe('00:00:42');
|
||||
});
|
||||
|
||||
it('sets remaining time', () => {
|
||||
expect(vm.$el.innerText).toBe('00:00:42');
|
||||
});
|
||||
it('updates remaining time', async () => {
|
||||
remainingTimeInMilliseconds = 41000;
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
it('updates remaining time', () => {
|
||||
remainingTimeInMilliseconds = 41000;
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
return vm.$nextTick().then(() => {
|
||||
expect(vm.$el.innerText).toBe('00:00:41');
|
||||
});
|
||||
});
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.text()).toBe('00:00:41');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,7 +9,6 @@ describe('performance bar app', () => {
|
|||
store,
|
||||
env: 'development',
|
||||
requestId: '123',
|
||||
statsUrl: 'https://log.gprd.gitlab.net/app/dashboards#/view/',
|
||||
peekUrl: '/-/peek/results',
|
||||
profileUrl: '?lineprofiler=true',
|
||||
},
|
||||
|
|
|
@ -19,7 +19,6 @@ describe('performance bar wrapper', () => {
|
|||
peekWrapper.setAttribute('data-env', 'development');
|
||||
peekWrapper.setAttribute('data-request-id', '123');
|
||||
peekWrapper.setAttribute('data-peek-url', '/-/peek/results');
|
||||
peekWrapper.setAttribute('data-stats-url', 'https://log.gprd.gitlab.net/app/dashboards#/view/');
|
||||
peekWrapper.setAttribute('data-profile-url', '?lineprofiler=true');
|
||||
|
||||
mock = new MockAdapter(axios);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
|
|||
import { GlIcon } from '@gitlab/ui';
|
||||
import CommitComponent from '~/vue_shared/components/commit.vue';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
describe('Commit component', () => {
|
||||
let props;
|
||||
|
@ -13,11 +14,14 @@ describe('Commit component', () => {
|
|||
};
|
||||
|
||||
const findUserAvatar = () => wrapper.find(UserAvatarLink);
|
||||
const findRefName = () => wrapper.findByTestId('ref-name');
|
||||
|
||||
const createComponent = (propsData) => {
|
||||
wrapper = shallowMount(CommitComponent, {
|
||||
propsData,
|
||||
});
|
||||
wrapper = extendedWrapper(
|
||||
shallowMount(CommitComponent, {
|
||||
propsData,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -223,4 +227,20 @@ describe('Commit component', () => {
|
|||
expect(wrapper.find('.ref-name').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When commitRef has a path property instead of ref_url property', () => {
|
||||
it('should render path as href attribute', () => {
|
||||
props = {
|
||||
commitRef: {
|
||||
name: 'master',
|
||||
path: 'http://localhost/namespace2/gitlabhq/tree/master',
|
||||
},
|
||||
};
|
||||
|
||||
createComponent(props);
|
||||
|
||||
expect(findRefName().exists()).toBe(true);
|
||||
expect(findRefName().attributes('href')).toBe(props.commitRef.path);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
11
spec/graphql/types/event_type_spec.rb
Normal file
11
spec/graphql/types/event_type_spec.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::EventType do
|
||||
specify { expect(described_class.graphql_name).to eq('Event') }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:read_event) }
|
||||
|
||||
specify { expect(described_class).to have_graphql_fields(:id, :author, :action, :created_at, :updated_at) }
|
||||
end
|
9
spec/graphql/types/eventable_type_spec.rb
Normal file
9
spec/graphql/types/eventable_type_spec.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::EventableType do
|
||||
it 'exposes events field' do
|
||||
expect(described_class).to have_graphql_fields(:events)
|
||||
end
|
||||
end
|
|
@ -9,38 +9,36 @@ RSpec.describe Gitlab::Tracking::StandardContext do
|
|||
let(:snowplow_context) { subject.to_context }
|
||||
|
||||
describe '#to_context' do
|
||||
context 'default fields' do
|
||||
context 'environment' do
|
||||
shared_examples 'contains environment' do |expected_environment|
|
||||
it 'contains environment' do
|
||||
expect(snowplow_context.to_json.dig(:data, :environment)).to eq(expected_environment)
|
||||
end
|
||||
end
|
||||
|
||||
context 'development or test' do
|
||||
include_examples 'contains environment', 'development'
|
||||
end
|
||||
|
||||
context 'staging' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:staging?).and_return(true)
|
||||
end
|
||||
|
||||
include_examples 'contains environment', 'staging'
|
||||
end
|
||||
|
||||
context 'production' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com_and_canary?).and_return(true)
|
||||
end
|
||||
|
||||
include_examples 'contains environment', 'production'
|
||||
context 'environment' do
|
||||
shared_examples 'contains environment' do |expected_environment|
|
||||
it 'contains environment' do
|
||||
expect(snowplow_context.to_json.dig(:data, :environment)).to eq(expected_environment)
|
||||
end
|
||||
end
|
||||
|
||||
it 'contains source' do
|
||||
expect(snowplow_context.to_json.dig(:data, :source)).to eq(described_class::GITLAB_RAILS_SOURCE)
|
||||
context 'development or test' do
|
||||
include_examples 'contains environment', 'development'
|
||||
end
|
||||
|
||||
context 'staging' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:staging?).and_return(true)
|
||||
end
|
||||
|
||||
include_examples 'contains environment', 'staging'
|
||||
end
|
||||
|
||||
context 'production' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com_and_canary?).and_return(true)
|
||||
end
|
||||
|
||||
include_examples 'contains environment', 'production'
|
||||
end
|
||||
end
|
||||
|
||||
it 'contains source' do
|
||||
expect(snowplow_context.to_json.dig(:data, :source)).to eq(described_class::GITLAB_RAILS_SOURCE)
|
||||
end
|
||||
|
||||
context 'with extra data' do
|
||||
|
@ -51,31 +49,8 @@ RSpec.describe Gitlab::Tracking::StandardContext do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with namespace' do
|
||||
subject { described_class.new(namespace: namespace) }
|
||||
|
||||
it 'creates a Snowplow context without namespace and project' do
|
||||
expect(snowplow_context.to_json.dig(:data, :namespace_id)).to be_nil
|
||||
expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project' do
|
||||
subject { described_class.new(project: project) }
|
||||
|
||||
it 'creates a Snowplow context without namespace and project' do
|
||||
expect(snowplow_context.to_json.dig(:data, :namespace_id)).to be_nil
|
||||
expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project and namespace' do
|
||||
subject { described_class.new(namespace: namespace, project: project) }
|
||||
|
||||
it 'creates a Snowplow context without namespace and project' do
|
||||
expect(snowplow_context.to_json.dig(:data, :namespace_id)).to be_nil
|
||||
expect(snowplow_context.to_json.dig(:data, :project_id)).to be_nil
|
||||
end
|
||||
it 'does not contain any ids' do
|
||||
expect(snowplow_context.to_json[:data].keys).not_to include(:user_id, :project_id, :namespace_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,36 +42,31 @@ RSpec.describe Gitlab::Tracking do
|
|||
end
|
||||
|
||||
shared_examples 'delegates to destination' do |klass|
|
||||
context 'with standard context' do
|
||||
it "delegates to #{klass} destination" do
|
||||
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
|
||||
expect(category).to eq('category')
|
||||
expect(action).to eq('action')
|
||||
expect(args[:label]).to eq('label')
|
||||
expect(args[:property]).to eq('property')
|
||||
expect(args[:value]).to eq(1.5)
|
||||
expect(args[:context].length).to eq(1)
|
||||
expect(args[:context].first.to_json[:schema]).to eq(Gitlab::Tracking::StandardContext::GITLAB_STANDARD_SCHEMA_URL)
|
||||
expect(args[:context].first.to_json[:data]).to include(foo: 'bar')
|
||||
end
|
||||
it "delegates to #{klass} destination" do
|
||||
other_context = double(:context)
|
||||
|
||||
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
|
||||
standard_context: Gitlab::Tracking::StandardContext.new(foo: 'bar'))
|
||||
project = double(:project)
|
||||
user = double(:user)
|
||||
namespace = double(:namespace)
|
||||
|
||||
expect(Gitlab::Tracking::StandardContext)
|
||||
.to receive(:new)
|
||||
.with(project: project, user: user, namespace: namespace)
|
||||
.and_call_original
|
||||
|
||||
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
|
||||
expect(category).to eq('category')
|
||||
expect(action).to eq('action')
|
||||
expect(args[:label]).to eq('label')
|
||||
expect(args[:property]).to eq('property')
|
||||
expect(args[:value]).to eq(1.5)
|
||||
expect(args[:context].length).to eq(2)
|
||||
expect(args[:context].first).to eq(other_context)
|
||||
expect(args[:context].last.to_json[:schema]).to eq(Gitlab::Tracking::StandardContext::GITLAB_STANDARD_SCHEMA_URL)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without standard context' do
|
||||
it "delegates to #{klass} destination" do
|
||||
expect_any_instance_of(klass).to receive(:event) do |_, category, action, args|
|
||||
expect(category).to eq('category')
|
||||
expect(action).to eq('action')
|
||||
expect(args[:label]).to eq('label')
|
||||
expect(args[:property]).to eq('property')
|
||||
expect(args[:value]).to eq(1.5)
|
||||
end
|
||||
|
||||
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
|
||||
end
|
||||
described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
|
||||
context: [other_context], project: project, user: user, namespace: namespace)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Pages::MigrateFromLegacyStorageService do
|
||||
let(:service) { described_class.new(Rails.logger, 3, 10) }
|
||||
let(:service) { described_class.new(Rails.logger, migration_threads: 3, batch_size: 10, ignore_invalid_entries: false) }
|
||||
|
||||
it 'does not try to migrate pages if pages are not deployed' do
|
||||
expect(::Pages::MigrateLegacyStorageToDeploymentService).not_to receive(:new)
|
||||
|
@ -22,7 +22,7 @@ RSpec.describe Pages::MigrateFromLegacyStorageService do
|
|||
end
|
||||
end
|
||||
|
||||
service = described_class.new(Rails.logger, 3, 2)
|
||||
service = described_class.new(Rails.logger, migration_threads: 3, batch_size: 2, ignore_invalid_entries: false)
|
||||
|
||||
threads = Concurrent::Set.new
|
||||
|
||||
|
@ -49,7 +49,7 @@ RSpec.describe Pages::MigrateFromLegacyStorageService do
|
|||
|
||||
context 'when pages directory does not exist' do
|
||||
it 'tries to migrate the project, but does not crash' do
|
||||
expect_next_instance_of(::Pages::MigrateLegacyStorageToDeploymentService, project) do |service|
|
||||
expect_next_instance_of(::Pages::MigrateLegacyStorageToDeploymentService, project, ignore_invalid_entries: false) do |service|
|
||||
expect(service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
|
@ -66,7 +66,7 @@ RSpec.describe Pages::MigrateFromLegacyStorageService do
|
|||
end
|
||||
|
||||
it 'migrates pages projects without deployments' do
|
||||
expect_next_instance_of(::Pages::MigrateLegacyStorageToDeploymentService, project) do |service|
|
||||
expect_next_instance_of(::Pages::MigrateLegacyStorageToDeploymentService, project, ignore_invalid_entries: false) do |service|
|
||||
expect(service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,14 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do
|
|||
let(:project) { create(:project, :repository) }
|
||||
let(:service) { described_class.new(project) }
|
||||
|
||||
it 'calls ::Pages::ZipDirectoryService' do
|
||||
expect_next_instance_of(::Pages::ZipDirectoryService, project.pages_path, ignore_invalid_entries: true) do |zip_service|
|
||||
expect(zip_service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
expect(described_class.new(project, ignore_invalid_entries: true).execute[:status]).to eq(:error)
|
||||
end
|
||||
|
||||
it 'marks pages as not deployed if public directory is absent' do
|
||||
project.mark_pages_as_deployed
|
||||
|
||||
|
|
|
@ -10,8 +10,14 @@ RSpec.describe Pages::ZipDirectoryService do
|
|||
end
|
||||
end
|
||||
|
||||
let(:ignore_invalid_entries) { false }
|
||||
|
||||
let(:service) do
|
||||
described_class.new(@work_dir, ignore_invalid_entries: ignore_invalid_entries)
|
||||
end
|
||||
|
||||
let(:result) do
|
||||
described_class.new(@work_dir).execute
|
||||
service.execute
|
||||
end
|
||||
|
||||
let(:status) { result[:status] }
|
||||
|
@ -20,6 +26,8 @@ RSpec.describe Pages::ZipDirectoryService do
|
|||
let(:entries_count) { result[:entries_count] }
|
||||
|
||||
it 'returns error if project pages dir does not exist' do
|
||||
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
|
||||
|
||||
expect(
|
||||
described_class.new("/tmp/not/existing/dir").execute
|
||||
).to eq(status: :error, message: "Can not find valid public dir in /tmp/not/existing/dir")
|
||||
|
@ -132,32 +140,69 @@ RSpec.describe Pages::ZipDirectoryService do
|
|||
end
|
||||
end
|
||||
|
||||
it 'ignores the symlink pointing outside of public directory' do
|
||||
create_file("target.html", "hello")
|
||||
create_link("public/link.html", "../target.html")
|
||||
shared_examples "raises or ignores file" do |raised_exception, file|
|
||||
it 'raises error' do
|
||||
expect do
|
||||
result
|
||||
end.to raise_error(raised_exception)
|
||||
end
|
||||
|
||||
with_zip_file do |zip_file|
|
||||
expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
|
||||
context 'when errors are ignored' do
|
||||
let(:ignore_invalid_entries) { true }
|
||||
|
||||
it 'does not create entry' do
|
||||
with_zip_file do |zip_file|
|
||||
expect { zip_file.get_entry(file) }.to raise_error(Errno::ENOENT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'ignores the symlink if target is absent' do
|
||||
create_link("public/link.html", "./target.html")
|
||||
|
||||
with_zip_file do |zip_file|
|
||||
expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
|
||||
context 'when symlink points outside of public directory' do
|
||||
before do
|
||||
create_file("target.html", "hello")
|
||||
create_link("public/link.html", "../target.html")
|
||||
end
|
||||
|
||||
include_examples "raises or ignores file", described_class::InvalidEntryError, "public/link.html"
|
||||
end
|
||||
|
||||
it 'ignores symlink if is absolute and points to outside of directory' do
|
||||
target = File.join(@work_dir, "target")
|
||||
FileUtils.touch(target)
|
||||
|
||||
create_link("public/link.html", target)
|
||||
|
||||
with_zip_file do |zip_file|
|
||||
expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
|
||||
context 'when target of the symlink is absent' do
|
||||
before do
|
||||
create_link("public/link.html", "./target.html")
|
||||
end
|
||||
|
||||
include_examples "raises or ignores file", Errno::ENOENT, "public/link.html"
|
||||
end
|
||||
|
||||
context 'when targets itself' do
|
||||
before do
|
||||
create_link("public/link.html", "./link.html")
|
||||
end
|
||||
|
||||
include_examples "raises or ignores file", Errno::ELOOP, "public/link.html"
|
||||
end
|
||||
|
||||
context 'when symlink is absolute and points to outside of directory' do
|
||||
before do
|
||||
target = File.join(@work_dir, "target")
|
||||
FileUtils.touch(target)
|
||||
|
||||
create_link("public/link.html", target)
|
||||
end
|
||||
|
||||
include_examples "raises or ignores file", described_class::InvalidEntryError, "public/link.html"
|
||||
end
|
||||
|
||||
context 'when entry has unknown ftype' do
|
||||
before do
|
||||
file = create_file("public/index.html", "hello")
|
||||
|
||||
allow(File).to receive(:lstat).and_call_original
|
||||
expect(File).to receive(:lstat).with(file) { double("lstat", ftype: "unknown") }
|
||||
end
|
||||
|
||||
include_examples "raises or ignores file", described_class::InvalidEntryError, "public/index.html"
|
||||
end
|
||||
|
||||
it "includes raw symlink if it's target is a valid directory" do
|
||||
|
@ -204,9 +249,13 @@ RSpec.describe Pages::ZipDirectoryService do
|
|||
end
|
||||
|
||||
def create_file(name, content)
|
||||
File.open(File.join(@work_dir, name), "w") do |f|
|
||||
file_path = File.join(@work_dir, name)
|
||||
|
||||
File.open(file_path, "w") do |f|
|
||||
f.write(content)
|
||||
end
|
||||
|
||||
file_path
|
||||
end
|
||||
|
||||
def create_dir(dir)
|
||||
|
|
|
@ -46,7 +46,7 @@ module SnowplowHelpers
|
|||
# }
|
||||
# ]
|
||||
# )
|
||||
def expect_snowplow_event(category:, action:, context: nil, standard_context: nil, **kwargs)
|
||||
def expect_snowplow_event(category:, action:, context: nil, **kwargs)
|
||||
if context
|
||||
kwargs[:context] = []
|
||||
context.each do |c|
|
||||
|
@ -56,14 +56,6 @@ module SnowplowHelpers
|
|||
end
|
||||
end
|
||||
|
||||
if standard_context
|
||||
expect(Gitlab::Tracking::StandardContext)
|
||||
.to have_received(:new)
|
||||
.with(**standard_context)
|
||||
|
||||
kwargs[:standard_context] = an_instance_of(Gitlab::Tracking::StandardContext)
|
||||
end
|
||||
|
||||
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
.with(category, action, **kwargs).at_least(:once)
|
||||
end
|
||||
|
|
|
@ -18,7 +18,6 @@ RSpec.configure do |config|
|
|||
stub_application_setting(snowplow_enabled: true)
|
||||
|
||||
allow(SnowplowTracker::SelfDescribingJson).to receive(:new).and_call_original
|
||||
allow(Gitlab::Tracking::StandardContext).to receive(:new).and_call_original
|
||||
allow(Gitlab::Tracking).to receive(:event).and_call_original # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,10 @@ RSpec.describe 'gitlab:pages' do
|
|||
subject { run_rake_task('gitlab:pages:migrate_legacy_storage') }
|
||||
|
||||
it 'calls migration service' do
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything, 3, 10) do |service|
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
|
||||
migration_threads: 3,
|
||||
batch_size: 10,
|
||||
ignore_invalid_entries: false) do |service|
|
||||
expect(service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
|
@ -21,7 +24,10 @@ RSpec.describe 'gitlab:pages' do
|
|||
it 'uses PAGES_MIGRATION_THREADS environment variable' do
|
||||
stub_env('PAGES_MIGRATION_THREADS', '5')
|
||||
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything, 5, 10) do |service|
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
|
||||
migration_threads: 5,
|
||||
batch_size: 10,
|
||||
ignore_invalid_entries: false) do |service|
|
||||
expect(service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
|
@ -31,7 +37,23 @@ RSpec.describe 'gitlab:pages' do
|
|||
it 'uses PAGES_MIGRATION_BATCH_SIZE environment variable' do
|
||||
stub_env('PAGES_MIGRATION_BATCH_SIZE', '100')
|
||||
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything, 3, 100) do |service|
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
|
||||
migration_threads: 3,
|
||||
batch_size: 100,
|
||||
ignore_invalid_entries: false) do |service|
|
||||
expect(service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'uses PAGES_MIGRATION_IGNORE_INVALID_ENTRIES environment variable' do
|
||||
stub_env('PAGES_MIGRATION_IGNORE_INVALID_ENTRIES', 'true')
|
||||
|
||||
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService, anything,
|
||||
migration_threads: 3,
|
||||
batch_size: 10,
|
||||
ignore_invalid_entries: true) do |service|
|
||||
expect(service).to receive(:execute).and_call_original
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue