summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_manual_todo.yml20
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue8
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue7
-rw-r--r--app/assets/javascripts/performance_bar/index.js2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue111
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue6
-rw-r--r--app/controllers/help_controller.rb11
-rw-r--r--app/controllers/projects/issues_controller.rb3
-rw-r--r--app/graphql/types/event_action_enum.rb12
-rw-r--r--app/graphql/types/event_type.rb36
-rw-r--r--app/graphql/types/eventable_type.rb9
-rw-r--r--app/models/concerns/enums/ci/pipeline.rb5
-rw-r--r--app/policies/event_policy.rb9
-rw-r--r--app/services/pages/migrate_from_legacy_storage_service.rb11
-rw-r--r--app/services/pages/migrate_legacy_storage_to_deployment_service.rb5
-rw-r--r--app/services/pages/zip_directory_service.rb53
-rw-r--r--app/views/peek/_bar.html.haml1
-rw-r--r--changelogs/unreleased/gl-button-job.yml5
-rw-r--r--changelogs/unreleased/semgrep-docs-n-template.yml5
-rw-r--r--config/initializers/1_settings.rb7
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql216
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json495
-rw-r--r--doc/api/graphql/reference/index.md34
-rw-r--r--doc/development/snowplow.md9
-rw-r--r--doc/user/application_security/sast/analyzers.md37
-rw-r--r--doc/user/application_security/sast/index.md29
-rw-r--r--doc/user/project/img/service_desk_nav_item.pngbin5158 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md11
-rw-r--r--doc/user/project/service_desk.md54
-rw-r--r--doc/user/usage_quotas.md2
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml19
-rw-r--r--lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb8
-rw-r--r--lib/gitlab/tracking.rb4
-rw-r--r--lib/gitlab/tracking/standard_context.rb9
-rw-r--r--lib/tasks/gitlab/pages.rake11
-rw-r--r--locale/gitlab.pot6
-rw-r--r--rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb2
-rw-r--r--rubocop/cop/gitlab/finder_with_find_by.rb2
-rw-r--r--rubocop/cop/gitlab/keys-first-and-values-first.rb2
-rw-r--r--rubocop/cop/migration/add_concurrent_foreign_key.rb2
-rw-r--r--rubocop/cop/migration/add_concurrent_index.rb2
-rw-r--r--rubocop/cop/migration/add_index.rb2
-rw-r--r--rubocop/cop/migration/add_timestamps.rb2
-rw-r--r--rubocop/cop/migration/datetime.rb2
-rw-r--r--rubocop/cop/migration/hash_index.rb2
-rw-r--r--rubocop/cop/migration/remove_column.rb2
-rw-r--r--rubocop/cop/migration/remove_concurrent_index.rb2
-rw-r--r--rubocop/cop/migration/remove_index.rb2
-rw-r--r--rubocop/cop/migration/safer_boolean_column.rb2
-rw-r--r--rubocop/cop/migration/timestamps.rb2
-rw-r--r--rubocop/cop/migration/update_column_in_batches.rb2
-rw-r--r--rubocop/cop/project_path_helper.rb2
-rw-r--r--rubocop/migration_helpers.rb2
-rw-r--r--rubocop/qa_helpers.rb2
-rw-r--r--spec/controllers/help_controller_spec.rb110
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb26
-rw-r--r--spec/frontend/jobs/mixins/delayed_job_mixin_spec.js98
-rw-r--r--spec/frontend/performance_bar/components/performance_bar_app_spec.js1
-rw-r--r--spec/frontend/performance_bar/index_spec.js1
-rw-r--r--spec/frontend/vue_shared/components/commit_spec.js26
-rw-r--r--spec/graphql/types/event_type_spec.rb11
-rw-r--r--spec/graphql/types/eventable_type_spec.rb9
-rw-r--r--spec/lib/gitlab/tracking/standard_context_spec.rb71
-rw-r--r--spec/lib/gitlab/tracking_spec.rb47
-rw-r--r--spec/services/pages/migrate_from_legacy_storage_service_spec.rb8
-rw-r--r--spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb8
-rw-r--r--spec/services/pages/zip_directory_service_spec.rb83
-rw-r--r--spec/support/helpers/snowplow_helpers.rb10
-rw-r--r--spec/support/snowplow.rb1
-rw-r--r--spec/tasks/gitlab/pages_rake_spec.rb28
73 files changed, 1365 insertions, 487 deletions
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index f675b785b9c..57c17da243f 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -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'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b102cab1df4..6be36cd7794 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-d0a79053ba4fef55b59543b99327fc89aed64876
+8cdbdb46b4fa31e0c2f1e2646baaf0ffb271b3a0
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index 0ee8cd6c5ad..35b16d73cc7 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -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
>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 83eddc232a1..c51a3f459c7 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -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>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 232de605e07..85789cd1fdf 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -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>
diff --git a/app/assets/javascripts/performance_bar/index.js b/app/assets/javascripts/performance_bar/index.js
index 9a9b4325fee..0d5c294ea56 100644
--- a/app/assets/javascripts/performance_bar/index.js
+++ b/app/assets/javascripts/performance_bar/index.js
@@ -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,
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
index 5231fe0b112..9fdba93f50a 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table_row.vue
@@ -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"
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index deca934e283..27f136059fa 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -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>
diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb
index 5a5200452de..e995562f0c4 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -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
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c8bdbe548c8..2816977277a 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -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
diff --git a/app/graphql/types/event_action_enum.rb b/app/graphql/types/event_action_enum.rb
new file mode 100644
index 00000000000..79931fa48cb
--- /dev/null
+++ b/app/graphql/types/event_action_enum.rb
@@ -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
diff --git a/app/graphql/types/event_type.rb b/app/graphql/types/event_type.rb
new file mode 100644
index 00000000000..2a4c2e7c60a
--- /dev/null
+++ b/app/graphql/types/event_type.rb
@@ -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
diff --git a/app/graphql/types/eventable_type.rb b/app/graphql/types/eventable_type.rb
new file mode 100644
index 00000000000..eba2154e7fa
--- /dev/null
+++ b/app/graphql/types/eventable_type.rb
@@ -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
diff --git a/app/models/concerns/enums/ci/pipeline.rb b/app/models/concerns/enums/ci/pipeline.rb
index e1f07fa162c..e1cf579eefc 100644
--- a/app/models/concerns/enums/ci/pipeline.rb
+++ b/app/models/concerns/enums/ci/pipeline.rb
@@ -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')
diff --git a/app/policies/event_policy.rb b/app/policies/event_policy.rb
new file mode 100644
index 00000000000..5587956855e
--- /dev/null
+++ b/app/policies/event_policy.rb
@@ -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
diff --git a/app/services/pages/migrate_from_legacy_storage_service.rb b/app/services/pages/migrate_from_legacy_storage_service.rb
index d805ae2418c..9b36b3f11b4 100644
--- a/app/services/pages/migrate_from_legacy_storage_service.rb
+++ b/app/services/pages/migrate_from_legacy_storage_service.rb
@@ -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
diff --git a/app/services/pages/migrate_legacy_storage_to_deployment_service.rb b/app/services/pages/migrate_legacy_storage_to_deployment_service.rb
index dac994b2ccc..63410b9fe4a 100644
--- a/app/services/pages/migrate_legacy_storage_to_deployment_service.rb
+++ b/app/services/pages/migrate_legacy_storage_to_deployment_service.rb
@@ -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 &&
diff --git a/app/services/pages/zip_directory_service.rb b/app/services/pages/zip_directory_service.rb
index ba7a8571e88..ae08d40ee37 100644
--- a/app/services/pages/zip_directory_service.rb
+++ b/app/services/pages/zip_directory_service.rb
@@ -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
diff --git a/app/views/peek/_bar.html.haml b/app/views/peek/_bar.html.haml
index 8914bfab336..9725f640be9 100644
--- a/app/views/peek/_bar.html.haml
+++ b/app/views/peek/_bar.html.haml
@@ -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 }
diff --git a/changelogs/unreleased/gl-button-job.yml b/changelogs/unreleased/gl-button-job.yml
new file mode 100644
index 00000000000..a9b6b679448
--- /dev/null
+++ b/changelogs/unreleased/gl-button-job.yml
@@ -0,0 +1,5 @@
+---
+title: Apply new GitLab UI for buttons in pipeline page
+merge_request: 53364
+author: Yogi (@yo)
+type: other
diff --git a/changelogs/unreleased/semgrep-docs-n-template.yml b/changelogs/unreleased/semgrep-docs-n-template.yml
new file mode 100644
index 00000000000..12509881ff3
--- /dev/null
+++ b/changelogs/unreleased/semgrep-docs-n-template.yml
@@ -0,0 +1,5 @@
+---
+title: Add semgrep SAST analyzer
+merge_request: 53815
+author: Daniel Paul Searles
+type: added
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index e15e035b513..b53c36cf029 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -311,6 +311,13 @@ 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
#
Gitlab.ee do
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index 4f89021b4a6..0b67f8c11ba 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -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
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 8880c26fe3c..233cc427966 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -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.
"""
@@ -1879,6 +1879,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.
"""
group: Group!
@@ -8682,7 +8707,7 @@ type EnvironmentsCanaryIngressUpdatePayload {
"""
Represents an epic
"""
-type Epic implements CurrentUserTodos & Noteable {
+type Epic implements CurrentUserTodos & Eventable & Noteable {
"""
Author of the epic.
"""
@@ -8922,6 +8947,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.
"""
group: Group!
@@ -10252,6 +10302,168 @@ enum EpicWildcardId {
}
"""
+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
"""
input ExportRequirementsInput {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 4474f1700f5..f11ed31c1a7 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -4856,6 +4856,59 @@
"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.",
"args": [
@@ -5516,6 +5569,11 @@
"kind": "INTERFACE",
"name": "CurrentUserTodos",
"ofType": null
+ },
+ {
+ "kind": "INTERFACE",
+ "name": "Eventable",
+ "ofType": null
}
],
"enumValues": null,
@@ -24567,6 +24625,59 @@
"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.",
"args": [
@@ -25213,6 +25324,11 @@
"kind": "INTERFACE",
"name": "CurrentUserTodos",
"ofType": null
+ },
+ {
+ "kind": "INTERFACE",
+ "name": "Eventable",
+ "ofType": null
}
],
"enumValues": null,
@@ -28277,6 +28393,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",
"description": "Autogenerated input type of ExportRequirements",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index b6312e4c9a9..ce312944cf1 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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.
diff --git a/doc/development/snowplow.md b/doc/development/snowplow.md
index fa912f8f41f..729738351b8 100644
--- a/doc/development/snowplow.md
+++ b/doc/development/snowplow.md
@@ -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
```
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
index 768b27bafe5..93dc55e8797 100644
--- a/doc/user/application_security/sast/analyzers.md
+++ b/doc/user/application_security/sast/analyzers.md
@@ -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
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index a51de02244d..bb013d71686 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -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
diff --git a/doc/user/project/img/service_desk_nav_item.png b/doc/user/project/img/service_desk_nav_item.png
deleted file mode 100644
index fdf8fa024c3..00000000000
--- a/doc/user/project/img/service_desk_nav_item.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 5ee18d50fa0..1fcc09a9d8a 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -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
diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md
index 70ed6e70059..adcf4657945 100644
--- a/doc/user/project/service_desk.md
+++ b/doc/user/project/service_desk.md
@@ -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.
+
+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.
-If you have project maintainer access you have the option to set up Service Desk. Follow these steps:
+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.
-1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance.
+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.
+
+Service Desk is now enabled for this project! To access it in a project, in the left sidebar, select
+**Issues > Service Desk**.
- 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.
+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.
- 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 improve your project's security, we recommend the following:
-Service Desk is now enabled for this project! You should be able to access it from your project's
-**Issues** menu.
+- 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.
-![Service Desk Navigation Item](img/service_desk_nav_item.png)
+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)
diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md
index c82fc6ab432..16ba2582101 100644
--- a/doc/user/usage_quotas.md
+++ b/doc/user/usage_quotas.md
@@ -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
---
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 91750af9a79..828352743b4 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -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:
diff --git a/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb b/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
index ac5c907465e..133d777fc32 100644
--- a/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
+++ b/lib/gitlab/performance_bar/redis_adapter_when_peek_enabled.rb
@@ -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
diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb
index ca4afb4c19c..09697705361 100644
--- a/lib/gitlab/tracking.rb
+++ b/lib/gitlab/tracking.rb
@@ -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)
diff --git a/lib/gitlab/tracking/standard_context.rb b/lib/gitlab/tracking/standard_context.rb
index 0c4911ba47e..92fdd008249 100644
--- a/lib/gitlab/tracking/standard_context.rb
+++ b/lib/gitlab/tracking/standard_context.rb
@@ -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
diff --git a/lib/tasks/gitlab/pages.rake b/lib/tasks/gitlab/pages.rake
index d9b7864e1c5..b598dab901d 100644
--- a/lib/tasks/gitlab/pages.rake
+++ b/lib/tasks/gitlab/pages.rake
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f82b60cfdc1..e4a429a31db 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb b/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb
index eba38c1630f..e1c6a984e75 100644
--- a/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb
+++ b/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RuboCop
module Cop
# Cop that blacklists keyword arguments usage in Sidekiq workers
diff --git a/rubocop/cop/gitlab/finder_with_find_by.rb b/rubocop/cop/gitlab/finder_with_find_by.rb
index 764a5073143..8fa9fe4a2f9 100644
--- a/rubocop/cop/gitlab/finder_with_find_by.rb
+++ b/rubocop/cop/gitlab/finder_with_find_by.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RuboCop
module Cop
module Gitlab
diff --git a/rubocop/cop/gitlab/keys-first-and-values-first.rb b/rubocop/cop/gitlab/keys-first-and-values-first.rb
index 544f9800304..e9bf266cdd7 100644
--- a/rubocop/cop/gitlab/keys-first-and-values-first.rb
+++ b/rubocop/cop/gitlab/keys-first-and-values-first.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RuboCop
module Cop
module Gitlab
diff --git a/rubocop/cop/migration/add_concurrent_foreign_key.rb b/rubocop/cop/migration/add_concurrent_foreign_key.rb
index 31cf426b2d4..957bd30af63 100644
--- a/rubocop/cop/migration/add_concurrent_foreign_key.rb
+++ b/rubocop/cop/migration/add_concurrent_foreign_key.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/add_concurrent_index.rb b/rubocop/cop/migration/add_concurrent_index.rb
index a2e4ac72565..510f98ce373 100644
--- a/rubocop/cop/migration/add_concurrent_index.rb
+++ b/rubocop/cop/migration/add_concurrent_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/add_index.rb b/rubocop/cop/migration/add_index.rb
index 4aea3c0cce3..7415880e554 100644
--- a/rubocop/cop/migration/add_index.rb
+++ b/rubocop/cop/migration/add_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/add_timestamps.rb b/rubocop/cop/migration/add_timestamps.rb
index ba32d6a9960..d16e8b1f45b 100644
--- a/rubocop/cop/migration/add_timestamps.rb
+++ b/rubocop/cop/migration/add_timestamps.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/datetime.rb b/rubocop/cop/migration/datetime.rb
index 5a6cdc74ca4..51e0c3e5a22 100644
--- a/rubocop/cop/migration/datetime.rb
+++ b/rubocop/cop/migration/datetime.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/hash_index.rb b/rubocop/cop/migration/hash_index.rb
index 3206b73bd3d..dba202ef0e3 100644
--- a/rubocop/cop/migration/hash_index.rb
+++ b/rubocop/cop/migration/hash_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'set'
require_relative '../../migration_helpers'
diff --git a/rubocop/cop/migration/remove_column.rb b/rubocop/cop/migration/remove_column.rb
index fffb4ab7fab..f63df71467c 100644
--- a/rubocop/cop/migration/remove_column.rb
+++ b/rubocop/cop/migration/remove_column.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/remove_concurrent_index.rb b/rubocop/cop/migration/remove_concurrent_index.rb
index 2328740cf36..8c2c6fb157e 100644
--- a/rubocop/cop/migration/remove_concurrent_index.rb
+++ b/rubocop/cop/migration/remove_concurrent_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/remove_index.rb b/rubocop/cop/migration/remove_index.rb
index 4df3b1ba756..15c2f37b4b0 100644
--- a/rubocop/cop/migration/remove_index.rb
+++ b/rubocop/cop/migration/remove_index.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/safer_boolean_column.rb b/rubocop/cop/migration/safer_boolean_column.rb
index 22d5d37a83d..06bb24707bd 100644
--- a/rubocop/cop/migration/safer_boolean_column.rb
+++ b/rubocop/cop/migration/safer_boolean_column.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/timestamps.rb b/rubocop/cop/migration/timestamps.rb
index 6cf5648b996..5584d49ee8c 100644
--- a/rubocop/cop/migration/timestamps.rb
+++ b/rubocop/cop/migration/timestamps.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb
index b1c43393f6a..d23e0d28380 100644
--- a/rubocop/cop/migration/update_column_in_batches.rb
+++ b/rubocop/cop/migration/update_column_in_batches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../../migration_helpers'
module RuboCop
diff --git a/rubocop/cop/project_path_helper.rb b/rubocop/cop/project_path_helper.rb
index f3810622eb1..bc2454e5b1f 100644
--- a/rubocop/cop/project_path_helper.rb
+++ b/rubocop/cop/project_path_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RuboCop
module Cop
class ProjectPathHelper < RuboCop::Cop::Cop
diff --git a/rubocop/migration_helpers.rb b/rubocop/migration_helpers.rb
index dffceb2b2c8..63b3766e126 100644
--- a/rubocop/migration_helpers.rb
+++ b/rubocop/migration_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RuboCop
# Module containing helper methods for writing migration cops.
module MigrationHelpers
diff --git a/rubocop/qa_helpers.rb b/rubocop/qa_helpers.rb
index f4adf7f4e9f..9d6396e412e 100644
--- a/rubocop/qa_helpers.rb
+++ b/rubocop/qa_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RuboCop
# Module containing helper methods for writing QA cops.
module QAHelpers
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 6927df3b1c7..629d9b50d73 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -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
- end
+ it_behaves_like 'documentation pages local render'
end
- context 'when a custom help_page_documentation_url is set' do
+ context 'when a custom help_page_documentation_url is set in database' 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')
end
- subject { get :show, params: { path: path }, format: 'html' }
+ it_behaves_like 'documentation pages redirect', 'https://in-db.gitlab.com'
+ end
- let(:gitlab_version) { '13.4.0-ee' }
- let(:documentation_base_url) { 'https://docs.gitlab.com' }
- let(:path) { 'ssh/README' }
+ 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 }
- 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")
+ before do
+ allow(Settings).to receive(:gitlab_docs) { double(enabled: docs_enabled, host: host) }
end
- context 'when documentation url ends with a slash' do
- let(:documentation_base_url) { 'https://docs.gitlab.com/' }
+ it_behaves_like 'documentation pages redirect', 'https://in-yaml.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
+ context 'when gitlab_docs is disabled' do
+ let(:docs_enabled) { false }
+
+ it_behaves_like 'documentation pages local render'
end
- context 'when it is a pre-release' do
- let(:gitlab_version) { '13.4.0-pre' }
+ context 'when host is missing' do
+ let(:host) { nil }
- it 'redirects user to custom documentation url without a version' do
- is_expected.to redirect_to("#{documentation_base_url}/ee/#{path}.html")
- end
+ it_behaves_like 'documentation pages local render'
end
+ end
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(help_page_documentation_redirect: false)
- end
+ 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: 'https://in-db.gitlab.com')
+ allow(Settings).to receive(:gitlab_docs) { double(enabled: true, host: 'https://in-yaml.gitlab.com') }
+ 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
+ it_behaves_like 'documentation pages redirect', 'https://in-yaml.gitlab.com'
+ end
+
+ 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
+
+ it_behaves_like 'documentation pages redirect', 'https://in-yaml.gitlab.com'
end
context 'when requested file is missing' do
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index e74301fb2d8..9c67523f88f 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -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
diff --git a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
index 2175610b7a6..838323df755 100644
--- a/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
+++ b/spec/frontend/jobs/mixins/delayed_job_mixin_spec.js
@@ -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,
+ },
});
- });
- describe('after mounting', () => {
- beforeEach(() => vm.$nextTick());
+ await wrapper.vm.$nextTick();
+ });
- it('sets remaining time', () => {
- expect(vm.$el.innerText).toBe('00:00:42');
- });
+ it('sets remaining time', () => {
+ expect(wrapper.text()).toBe('00:00:42');
+ });
- it('updates remaining time', () => {
- remainingTimeInMilliseconds = 41000;
- jest.advanceTimersByTime(1000);
+ it('updates remaining time', async () => {
+ 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,
+ },
});
- });
- describe('after mounting', () => {
- beforeEach(() => vm.$nextTick());
+ await wrapper.vm.$nextTick();
+ });
- it('sets remaining time', () => {
- expect(vm.$el.innerText).toBe('00:00:42');
- });
+ it('sets remaining time', () => {
+ expect(wrapper.text()).toBe('00:00:42');
+ });
- it('updates remaining time', () => {
- remainingTimeInMilliseconds = 41000;
- jest.advanceTimersByTime(1000);
+ it('updates remaining time', async () => {
+ 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');
});
});
});
diff --git a/spec/frontend/performance_bar/components/performance_bar_app_spec.js b/spec/frontend/performance_bar/components/performance_bar_app_spec.js
index 67a4259a8e3..417a655093c 100644
--- a/spec/frontend/performance_bar/components/performance_bar_app_spec.js
+++ b/spec/frontend/performance_bar/components/performance_bar_app_spec.js
@@ -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',
},
diff --git a/spec/frontend/performance_bar/index_spec.js b/spec/frontend/performance_bar/index_spec.js
index 819b2bcbacf..8d9c32b7f12 100644
--- a/spec/frontend/performance_bar/index_spec.js
+++ b/spec/frontend/performance_bar/index_spec.js
@@ -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);
diff --git a/spec/frontend/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js
index 6f3c97f7194..4ed91497663 100644
--- a/spec/frontend/vue_shared/components/commit_spec.js
+++ b/spec/frontend/vue_shared/components/commit_spec.js
@@ -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);
+ });
+ });
});
diff --git a/spec/graphql/types/event_type_spec.rb b/spec/graphql/types/event_type_spec.rb
new file mode 100644
index 00000000000..10c3b5e18ca
--- /dev/null
+++ b/spec/graphql/types/event_type_spec.rb
@@ -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
diff --git a/spec/graphql/types/eventable_type_spec.rb b/spec/graphql/types/eventable_type_spec.rb
new file mode 100644
index 00000000000..c1c7bf6d65a
--- /dev/null
+++ b/spec/graphql/types/eventable_type_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/tracking/standard_context_spec.rb b/spec/lib/gitlab/tracking/standard_context_spec.rb
index b12a0310dac..7a0a4f0cc46 100644
--- a/spec/lib/gitlab/tracking/standard_context_spec.rb
+++ b/spec/lib/gitlab/tracking/standard_context_spec.rb
@@ -9,40 +9,38 @@ 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'
+ 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 'staging' do
- before do
- allow(Gitlab).to receive(:staging?).and_return(true)
- end
+ context 'development or test' do
+ include_examples 'contains environment', 'development'
+ end
- include_examples 'contains environment', 'staging'
+ context 'staging' do
+ before do
+ allow(Gitlab).to receive(:staging?).and_return(true)
end
- context 'production' do
- before do
- allow(Gitlab).to receive(:com_and_canary?).and_return(true)
- end
+ include_examples 'contains environment', 'staging'
+ end
- include_examples 'contains environment', 'production'
+ context 'production' do
+ before do
+ allow(Gitlab).to receive(:com_and_canary?).and_return(true)
end
- end
- it 'contains source' do
- expect(snowplow_context.to_json.dig(:data, :source)).to eq(described_class::GITLAB_RAILS_SOURCE)
+ 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
subject { described_class.new(foo: 'bar') }
@@ -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
diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb
index 8f1fd49f4c5..80740c8112e 100644
--- a/spec/lib/gitlab/tracking_spec.rb
+++ b/spec/lib/gitlab/tracking_spec.rb
@@ -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'))
- end
- end
+ project = double(:project)
+ user = double(:user)
+ namespace = double(:namespace)
- 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
+ expect(Gitlab::Tracking::StandardContext)
+ .to receive(:new)
+ .with(project: project, user: user, namespace: namespace)
+ .and_call_original
- described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5)
+ 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
+
+ described_class.event('category', 'action', label: 'label', property: 'property', value: 1.5,
+ context: [other_context], project: project, user: user, namespace: namespace)
end
end
diff --git a/spec/services/pages/migrate_from_legacy_storage_service_spec.rb b/spec/services/pages/migrate_from_legacy_storage_service_spec.rb
index 5d335143719..4ec57044912 100644
--- a/spec/services/pages/migrate_from_legacy_storage_service_spec.rb
+++ b/spec/services/pages/migrate_from_legacy_storage_service_spec.rb
@@ -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
diff --git a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb
index 29023621413..d95303c3e85 100644
--- a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb
+++ b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb
@@ -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
diff --git a/spec/services/pages/zip_directory_service_spec.rb b/spec/services/pages/zip_directory_service_spec.rb
index dcab6b2dada..9de68dd62bb 100644
--- a/spec/services/pages/zip_directory_service_spec.rb
+++ b/spec/services/pages/zip_directory_service_spec.rb
@@ -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")
+ context 'when symlink points outside of public directory' do
+ before do
+ create_file("target.html", "hello")
+ create_link("public/link.html", "../target.html")
+ end
- with_zip_file do |zip_file|
- expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
+ include_examples "raises or ignores file", described_class::InvalidEntryError, "public/link.html"
+ end
+
+ 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
- it 'ignores symlink if is absolute and points to outside of directory' do
- target = File.join(@work_dir, "target")
- FileUtils.touch(target)
+ context 'when targets itself' do
+ before do
+ create_link("public/link.html", "./link.html")
+ end
- create_link("public/link.html", target)
+ include_examples "raises or ignores file", Errno::ELOOP, "public/link.html"
+ end
- with_zip_file do |zip_file|
- expect { zip_file.get_entry("public/link.html") }.to raise_error(Errno::ENOENT)
+ 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)
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
index a6de8dabdac..70a4eadd8de 100644
--- a/spec/support/helpers/snowplow_helpers.rb
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -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
diff --git a/spec/support/snowplow.rb b/spec/support/snowplow.rb
index 91325d867d4..0d6102f1705 100644
--- a/spec/support/snowplow.rb
+++ b/spec/support/snowplow.rb
@@ -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
diff --git a/spec/tasks/gitlab/pages_rake_spec.rb b/spec/tasks/gitlab/pages_rake_spec.rb
index 608458b2393..08194f4d1c9 100644
--- a/spec/tasks/gitlab/pages_rake_spec.rb
+++ b/spec/tasks/gitlab/pages_rake_spec.rb
@@ -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