Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue4
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue27
-rw-r--r--app/assets/javascripts/repository/mixins/preload.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue7
-rw-r--r--app/controllers/projects/releases_controller.rb3
-rw-r--r--app/controllers/projects/tree_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb1
-rw-r--r--app/finders/issuables/label_filter.rb6
-rw-r--r--app/graphql/types/project_type.rb2
-rw-r--r--app/graphql/types/release_links_type.rb17
-rw-r--r--app/graphql/types/release_type.rb3
-rw-r--r--app/graphql/types/repository_type.rb3
-rw-r--r--app/models/analytics/cycle_analytics/issue_stage_event.rb4
-rw-r--r--app/models/analytics/cycle_analytics/merge_request_stage_event.rb4
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage_event_model.rb6
-rw-r--r--app/models/label_link.rb12
-rw-r--r--app/models/webauthn_registration.rb3
-rw-r--r--app/presenters/release_presenter.rb4
-rw-r--r--app/services/packages/npm/create_package_service.rb8
-rw-r--r--config/application.rb2
-rw-r--r--config/feature_flags/development/paginated_tree_graphql_query.yml8
-rw-r--r--db/post_migrate/20211112113300_remove_ci_pipeline_chat_data_fk_on_chat_names.rb22
-rw-r--r--db/schema_migrations/202111121133001
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/ci/review_apps/index.md2
-rw-r--r--doc/ci/test_cases/index.md20
-rw-r--r--doc/development/documentation/feature_flags.md2
-rw-r--r--doc/development/fe_guide/accessibility.md11
-rw-r--r--doc/update/index.md17
-rw-r--r--doc/user/clusters/agent/install/index.md3
-rw-r--r--doc/user/gitlab_com/index.md1
-rw-r--r--doc/user/permissions.md2
-rw-r--r--doc/user/project/releases/index.md15
-rw-r--r--doc/user/tasks.md2
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb6
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/label_filter.rb31
-rw-r--r--lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb116
-rw-r--r--lib/gitlab/analytics/cycle_analytics/data_collector.rb6
-rw-r--r--lib/gitlab/analytics/cycle_analytics/records_fetcher.rb18
-rw-r--r--lib/gitlab/ci/parsers/security/common.rb2
-rw-r--r--lib/gitlab/ci/reports/security/finding.rb32
-rw-r--r--spec/controllers/projects/releases_controller_spec.rb13
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/factories/ci/reports/security/findings.rb4
-rw-r--r--spec/features/cycle_analytics_spec.rb4
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb4
-rw-r--r--spec/graphql/types/release_links_type_spec.rb44
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb130
-rw-r--r--spec/models/u2f_registration_spec.rb28
-rw-r--r--spec/models/webauthn_registration_spec.rb23
-rw-r--r--spec/presenters/release_presenter_spec.rb14
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb186
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb15
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb17
-rw-r--r--spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb2
57 files changed, 798 insertions, 152 deletions
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 5010d60f374..bd06c064ab7 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -11,7 +11,6 @@ import {
GlIntersectionObserver,
} from '@gitlab/ui';
import { escapeRegExp } from 'lodash';
-import filesQuery from 'shared_queries/repository/files.query.graphql';
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import { TREE_PAGE_SIZE } from '~/repository/constants';
@@ -178,8 +177,7 @@ export default {
return this.isFolder ? this.loadFolder() : this.loadBlob();
},
loadFolder() {
- const query = this.glFeatures.paginatedTreeGraphqlQuery ? paginatedTreeQuery : filesQuery;
- this.apolloQuery(query, {
+ this.apolloQuery(paginatedTreeQuery, {
projectPath: this.projectPath,
ref: this.ref,
path: this.path,
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index 16dfe3cfb14..ffe8d5531f8 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -1,5 +1,4 @@
<script>
-import filesQuery from 'shared_queries/repository/files.query.graphql';
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import createFlash from '~/flash';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -72,9 +71,6 @@ export default {
hasShowMore() {
return !this.clickedShowMore && this.pageLimitReached;
},
- paginatedTreeEnabled() {
- return this.glFeatures.paginatedTreeGraphqlQuery;
- },
},
watch: {
@@ -101,7 +97,7 @@ export default {
return this.$apollo
.query({
- query: this.paginatedTreeEnabled ? paginatedTreeQuery : filesQuery,
+ query: paginatedTreeQuery,
variables: {
projectPath: this.projectPath,
ref: this.ref,
@@ -114,20 +110,19 @@ export default {
if (data.errors) throw data.errors;
if (!data?.project?.repository || originalPath !== (this.path || '/')) return;
- const pageInfo = this.paginatedTreeEnabled
- ? data.project.repository.paginatedTree.pageInfo
- : this.hasNextPage(data.project.repository.tree);
+ const {
+ project: {
+ repository: {
+ paginatedTree: { pageInfo },
+ },
+ },
+ } = data;
this.isLoadingFiles = false;
this.entries = Object.keys(this.entries).reduce(
(acc, key) => ({
...acc,
- [key]: this.normalizeData(
- key,
- this.paginatedTreeEnabled
- ? data.project.repository.paginatedTree.nodes[0][key]
- : data.project.repository.tree[key].edges,
- ),
+ [key]: this.normalizeData(key, data.project.repository.paginatedTree.nodes[0][key]),
}),
{},
);
@@ -149,9 +144,7 @@ export default {
});
},
normalizeData(key, data) {
- return this.entries[key].concat(
- this.paginatedTreeEnabled ? data.nodes : data.map(({ node }) => node),
- );
+ return this.entries[key].concat(data.nodes);
},
hasNextPage(data) {
return []
diff --git a/app/assets/javascripts/repository/mixins/preload.js b/app/assets/javascripts/repository/mixins/preload.js
index a2ddcbf0e4c..30c36dee48f 100644
--- a/app/assets/javascripts/repository/mixins/preload.js
+++ b/app/assets/javascripts/repository/mixins/preload.js
@@ -1,4 +1,3 @@
-import filesQuery from 'shared_queries/repository/files.query.graphql';
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import projectPathQuery from '../queries/project_path.query.graphql';
import getRefMixin from './get_ref';
@@ -22,7 +21,7 @@ export default {
return this.$apollo
.query({
- query: gon.features.paginatedTreeGraphqlQuery ? paginatedTreeQuery : filesQuery,
+ query: paginatedTreeQuery,
variables: {
projectPath: this.projectPath,
ref: this.ref,
diff --git a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
index 40044e518c3..2c74d56f617 100644
--- a/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon } from '@gitlab/ui';
+import { GlIcon, GlSafeHtmlDirective } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { HIGHLIGHT_CLASS_NAME } from './constants';
import ViewerMixin from './mixins';
@@ -9,6 +9,9 @@ export default {
components: {
GlIcon,
},
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
mixins: [ViewerMixin, glFeatureFlagsMixin()],
inject: ['blobHash'],
data() {
@@ -65,7 +68,7 @@ export default {
<div class="blob-content">
<pre
class="code highlight"
- ><code :data-blob-hash="blobHash" v-html="content /* eslint-disable-line vue/no-v-html */"></code></pre>
+ ><code v-safe-html="content" :data-blob-hash="blobHash"></code></pre>
</div>
</div>
</div>
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index be2abc5cddf..7fba6cc5bf4 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -5,9 +5,6 @@ class Projects::ReleasesController < Projects::ApplicationController
before_action :require_non_empty_project, except: [:index]
before_action :release, only: %i[edit show update downloads]
before_action :authorize_read_release!
- # We have to check `download_code` permission because detail URL path
- # contains git-tag name.
- before_action :authorize_download_code!, except: [:index]
before_action :authorize_update_release!, only: %i[edit update]
before_action :authorize_create_release!, only: :new
before_action only: :index do
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index c36b30e198b..f8f2c1f0836 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -17,7 +17,6 @@ class Projects::TreeController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:lazy_load_commits, @project, default_enabled: :yaml)
- push_frontend_feature_flag(:paginated_tree_graphql_query, @project, default_enabled: :yaml)
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index a5fa1807cc9..5b17b75a963 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -37,7 +37,6 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_text_viewer, @project, default_enabled: :yaml)
push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
- push_frontend_feature_flag(:paginated_tree_graphql_query, @project, default_enabled: :yaml)
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
end
diff --git a/app/finders/issuables/label_filter.rb b/app/finders/issuables/label_filter.rb
index 98e98c0d44e..9a6ca107b19 100644
--- a/app/finders/issuables/label_filter.rb
+++ b/app/finders/issuables/label_filter.rb
@@ -135,11 +135,7 @@ module Issuables
# rubocop: disable CodeReuse/ActiveRecord
def label_link_query(target_model, label_ids: nil, label_names: nil)
- relation = LabelLink
- .where(target_type: target_model.name)
- .where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id']))
-
- relation = relation.where(label_id: label_ids) if label_ids
+ relation = LabelLink.by_target_for_exists_query(target_model.name, target_model.arel_table['id'], label_ids)
relation = relation.joins(:label).where(labels: { name: label_names }) if label_names
relation
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index dd73fb8dceb..b6cb9cd3302 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -306,7 +306,7 @@ module Types
null: true,
description: 'A single release of the project.',
resolver: Resolvers::ReleasesResolver.single,
- authorize: :download_code
+ authorize: :read_release
field :container_expiration_policy,
Types::ContainerExpirationPolicyType,
diff --git a/app/graphql/types/release_links_type.rb b/app/graphql/types/release_links_type.rb
index 7830e29f3cd..37ad52ce6d0 100644
--- a/app/graphql/types/release_links_type.rb
+++ b/app/graphql/types/release_links_type.rb
@@ -4,7 +4,7 @@ module Types
class ReleaseLinksType < BaseObject
graphql_name 'ReleaseLinks'
- authorize :download_code
+ authorize :read_release
alias_method :release, :object
@@ -16,14 +16,19 @@ module Types
description: "HTTP URL of the release's edit page.",
authorize: :update_release
field :opened_merge_requests_url, GraphQL::Types::String, null: true,
- description: 'HTTP URL of the merge request page, filtered by this release and `state=open`.'
+ description: 'HTTP URL of the merge request page, filtered by this release and `state=open`.',
+ authorize: :download_code
field :merged_merge_requests_url, GraphQL::Types::String, null: true,
- description: 'HTTP URL of the merge request page , filtered by this release and `state=merged`.'
+ description: 'HTTP URL of the merge request page , filtered by this release and `state=merged`.',
+ authorize: :download_code
field :closed_merge_requests_url, GraphQL::Types::String, null: true,
- description: 'HTTP URL of the merge request page , filtered by this release and `state=closed`.'
+ description: 'HTTP URL of the merge request page , filtered by this release and `state=closed`.',
+ authorize: :download_code
field :opened_issues_url, GraphQL::Types::String, null: true,
- description: 'HTTP URL of the issues page, filtered by this release and `state=open`.'
+ description: 'HTTP URL of the issues page, filtered by this release and `state=open`.',
+ authorize: :download_code
field :closed_issues_url, GraphQL::Types::String, null: true,
- description: 'HTTP URL of the issues page, filtered by this release and `state=closed`.'
+ description: 'HTTP URL of the issues page, filtered by this release and `state=closed`.',
+ authorize: :download_code
end
end
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
index 6dda93c7329..fcc9ec49252 100644
--- a/app/graphql/types/release_type.rb
+++ b/app/graphql/types/release_type.rb
@@ -14,8 +14,7 @@ module Types
present_using ReleasePresenter
field :tag_name, GraphQL::Types::String, null: true, method: :tag,
- description: 'Name of the tag associated with the release.',
- authorize: :download_code
+ description: 'Name of the tag associated with the release.'
field :tag_path, GraphQL::Types::String, null: true,
description: 'Relative web path to the tag associated with the release.',
authorize: :download_code
diff --git a/app/graphql/types/repository_type.rb b/app/graphql/types/repository_type.rb
index 63d1eef5b59..fc9860900c9 100644
--- a/app/graphql/types/repository_type.rb
+++ b/app/graphql/types/repository_type.rb
@@ -16,8 +16,7 @@ module Types
description: 'Tree of the repository.'
field :paginated_tree, Types::Tree::TreeType.connection_type, null: true, resolver: Resolvers::PaginatedTreeResolver, calls_gitaly: true,
max_page_size: 100,
- description: 'Paginated tree of the repository.',
- feature_flag: :paginated_tree_graphql_query
+ description: 'Paginated tree of the repository.'
field :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true,
description: 'Blobs contained within the repository'
field :branch_names, [GraphQL::Types::String], null: true, calls_gitaly: true,
diff --git a/app/models/analytics/cycle_analytics/issue_stage_event.rb b/app/models/analytics/cycle_analytics/issue_stage_event.rb
index 099bb671f10..837eb35c839 100644
--- a/app/models/analytics/cycle_analytics/issue_stage_event.rb
+++ b/app/models/analytics/cycle_analytics/issue_stage_event.rb
@@ -20,6 +20,10 @@ module Analytics
def self.issuable_id_column
:issue_id
end
+
+ def self.issuable_model
+ ::Issue
+ end
end
end
end
diff --git a/app/models/analytics/cycle_analytics/merge_request_stage_event.rb b/app/models/analytics/cycle_analytics/merge_request_stage_event.rb
index 632b7079b2e..0dfa322b2c3 100644
--- a/app/models/analytics/cycle_analytics/merge_request_stage_event.rb
+++ b/app/models/analytics/cycle_analytics/merge_request_stage_event.rb
@@ -20,6 +20,10 @@ module Analytics
def self.issuable_id_column
:merge_request_id
end
+
+ def self.issuable_model
+ ::MergeRequest
+ end
end
end
end
diff --git a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
index cca1a0b41cc..324e0fb57cb 100644
--- a/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
+++ b/app/models/concerns/analytics/cycle_analytics/stage_event_model.rb
@@ -18,6 +18,10 @@ module Analytics
scope :end_event_is_not_happened_yet, -> { where(end_event_timestamp: nil) }
end
+ def issuable_id
+ attributes[self.class.issuable_id_column.to_s]
+ end
+
class_methods do
def upsert_data(data)
upsert_values = data.map do |row|
@@ -26,8 +30,8 @@ module Analytics
:issuable_id,
:group_id,
:project_id,
- :author_id,
:milestone_id,
+ :author_id,
:state_id,
:start_event_timestamp,
:end_event_timestamp
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 4fb5fd8c58a..d326b07ad31 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -11,4 +11,16 @@ class LabelLink < ApplicationRecord
validates :label, presence: true, unless: :importing?
scope :for_target, -> (target_id, target_type) { where(target_id: target_id, target_type: target_type) }
+
+ # Example: Issues has at least one label within a project
+ # > Issue.where(project_id: 100) # or any scope on issues
+ # > .where(LabelLink.by_target_for_exists_query('Issue', Issue.arel_table[:id]).arel.exists)
+ scope :by_target_for_exists_query, -> (target_type, arel_join_column, label_ids = nil) do
+ relation = LabelLink
+ .where(target_type: target_type)
+ .where(arel_table['target_id'].eq(arel_join_column))
+
+ relation = relation.where(label_id: label_ids) if label_ids
+ relation
+ end
end
diff --git a/app/models/webauthn_registration.rb b/app/models/webauthn_registration.rb
index 76f8faa11c7..71b50192e29 100644
--- a/app/models/webauthn_registration.rb
+++ b/app/models/webauthn_registration.rb
@@ -5,7 +5,8 @@
class WebauthnRegistration < ApplicationRecord
belongs_to :user
- validates :credential_xid, :public_key, :name, :counter, presence: true
+ validates :credential_xid, :public_key, :counter, presence: true
+ validates :name, length: { minimum: 0, allow_nil: false }
validates :counter,
numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 2**32 - 1 }
end
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
index c919c7f4c60..dac42af38bf 100644
--- a/app/presenters/release_presenter.rb
+++ b/app/presenters/release_presenter.rb
@@ -22,8 +22,6 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
end
def self_url
- return unless can_download_code?
-
project_release_url(project, release)
end
@@ -64,7 +62,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
delegator_override :name
def name
- can_download_code? ? release.name : "Release-#{release.id}"
+ release.name
end
def download_url(filepath)
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index 6945c94e0bb..ae9c92a3d3a 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -4,6 +4,8 @@ module Packages
class CreatePackageService < ::Packages::CreatePackageService
include Gitlab::Utils::StrongMemoize
+ PACKAGE_JSON_NOT_ALLOWED_FIELDS = %w[readme readmeFilename].freeze
+
def execute
return error('Version is empty.', 400) if version.blank?
return error('Package already exists.', 403) if current_package_exists?
@@ -22,7 +24,7 @@ module Packages
::Packages::Npm::CreateTagService.new(package, dist_tag).execute
if Feature.enabled?(:packages_npm_abbreviated_metadata, project, default_enabled: :yaml)
- package.create_npm_metadatum!(package_json: version_data)
+ package.create_npm_metadatum!(package_json: package_json)
end
package
@@ -50,6 +52,10 @@ module Packages
params[:versions][version]
end
+ def package_json
+ version_data.except(*PACKAGE_JSON_NOT_ALLOWED_FIELDS)
+ end
+
def dist_tag
params['dist-tags'].each_key.first
end
diff --git a/config/application.rb b/config/application.rb
index 1fc963a1c1e..a2dce593677 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -224,7 +224,7 @@ module Gitlab
config.assets.precompile << "page_bundles/build.css"
config.assets.precompile << "page_bundles/ci_status.css"
config.assets.precompile << "page_bundles/cycle_analytics.css"
- config.assets.precompile << "page_bundles/dev_ops_report.css"
+ config.assets.precompile << "page_bundles/dev_ops_reports.css"
config.assets.precompile << "page_bundles/environments.css"
config.assets.precompile << "page_bundles/epics.css"
config.assets.precompile << "page_bundles/error_tracking_details.css"
diff --git a/config/feature_flags/development/paginated_tree_graphql_query.yml b/config/feature_flags/development/paginated_tree_graphql_query.yml
deleted file mode 100644
index d56d8fc336c..00000000000
--- a/config/feature_flags/development/paginated_tree_graphql_query.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: paginated_tree_graphql_query
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66751
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337214
-milestone: '14.2'
-type: development
-group: group::source code
-default_enabled: true
diff --git a/db/post_migrate/20211112113300_remove_ci_pipeline_chat_data_fk_on_chat_names.rb b/db/post_migrate/20211112113300_remove_ci_pipeline_chat_data_fk_on_chat_names.rb
new file mode 100644
index 00000000000..19adc6c7084
--- /dev/null
+++ b/db/post_migrate/20211112113300_remove_ci_pipeline_chat_data_fk_on_chat_names.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class RemoveCiPipelineChatDataFkOnChatNames < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists(:ci_pipeline_chat_data, :chat_names, name: "fk_rails_f300456b63")
+ end
+ end
+
+ def down
+ # Remove orphaned rows
+ execute <<~SQL
+ DELETE FROM ci_pipeline_chat_data
+ WHERE
+ NOT EXISTS (SELECT 1 FROM chat_names WHERE chat_names.id=ci_pipeline_chat_data.chat_name_id)
+ SQL
+
+ add_concurrent_foreign_key(:ci_pipeline_chat_data, :chat_names, name: "fk_rails_f300456b63", column: :chat_name_id, target_column: :id, on_delete: "cascade")
+ end
+end
diff --git a/db/schema_migrations/20211112113300 b/db/schema_migrations/20211112113300
new file mode 100644
index 00000000000..8074c437751
--- /dev/null
+++ b/db/schema_migrations/20211112113300
@@ -0,0 +1 @@
+be11c0b1c7b9c99c28d44c164742815da57bfc4a32afd54df9135e3ce6edeff9 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 7e366cd67d3..5b6614be454 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -31078,9 +31078,6 @@ ALTER TABLE ONLY snippet_repositories
ALTER TABLE ONLY elastic_reindexing_subtasks
ADD CONSTRAINT fk_rails_f2cc190164 FOREIGN KEY (elastic_reindexing_task_id) REFERENCES elastic_reindexing_tasks(id) ON DELETE CASCADE;
-ALTER TABLE ONLY ci_pipeline_chat_data
- ADD CONSTRAINT fk_rails_f300456b63 FOREIGN KEY (chat_name_id) REFERENCES chat_names(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY approval_project_rules_users
ADD CONSTRAINT fk_rails_f365da8250 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index ccbdce386dc..ec09d581483 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -14030,7 +14030,7 @@ Returns [`[String!]`](#string).
##### `Repository.paginatedTree`
-Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is enabled by default.
+Paginated tree of the repository.
Returns [`TreeConnection`](#treeconnection).
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 8f633ad3561..c67282643a4 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -98,7 +98,7 @@ The following are example projects that demonstrate Review App configuration:
- [NGINX](https://gitlab.com/gitlab-examples/review-apps-nginx).
- [OpenShift](https://gitlab.com/gitlab-examples/review-apps-openshift).
-- [HashiCorp Nomad](https://gitlab.com/gitlab-examples/review-apps-nomad)
+- [HashiCorp Nomad](https://gitlab.com/gitlab-examples/review-apps-nomad).
Other examples of Review Apps:
diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md
index 4bd38f4043a..384bfc10779 100644
--- a/doc/ci/test_cases/index.md
+++ b/doc/ci/test_cases/index.md
@@ -18,7 +18,9 @@ use external test planning tools, which require additional overhead, context swi
## Create a test case
-Users with Reporter or higher [permissions](../../user/permissions.md) can create test cases.
+Prerequisite:
+
+- You must have at least the Reporter [role](../../user/permissions.md).
To create a test case in a GitLab project:
@@ -32,7 +34,9 @@ To create a test case in a GitLab project:
You can view all test cases in the project in the Test Cases list. Filter the
issue list with a search query, including labels or the test case's title.
-Users with Guest or higher [permissions](../../user/permissions.md) can view test cases.
+Prerequisite:
+
+- You must have at least the Guest [role](../../user/permissions.md).
To view a test case:
@@ -45,8 +49,10 @@ To view a test case:
You can edit a test case's title and description.
-Users with Reporter or higher [permissions](../../user/permissions.md) can edit test cases.
-Users demoted to the Guest role can continue to edit the test cases they created
+Prerequisite:
+
+- You must have at least the Reporter [role](../../user/permissions.md).
+- Users demoted to the Guest role can continue to edit the test cases they created
when they were in the higher role.
To edit a test case:
@@ -60,7 +66,9 @@ To edit a test case:
When you want to stop using a test case, you can archive it. You can [reopen an archived test case](#reopen-an-archived-test-case) later.
-Users with Reporter or higher [permissions](../../user/permissions.md) can archive test cases.
+Prerequisite:
+
+- You must have at least the Reporter [role](../../user/permissions.md).
To archive a test case, on the test case's page, select the **Archive test case** button.
@@ -73,6 +81,6 @@ To view archived test cases:
If you decide to start using an archived test case again, you can reopen it.
-Users with Reporter or higher [permissions](../../user/permissions.md) can reopen test cases.
+You must have at least the Reporter [role](../../user/permissions.md).
To reopen an archived test case, on the test case's page, select **Reopen test case**.
diff --git a/doc/development/documentation/feature_flags.md b/doc/development/documentation/feature_flags.md
index f234ec6f2e2..8fc6f2e2641 100644
--- a/doc/development/documentation/feature_flags.md
+++ b/doc/development/documentation/feature_flags.md
@@ -90,7 +90,7 @@ The following examples show the progression of a feature flag.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the featured flag](../administration/feature_flags.md) named `forti_token_cloud`.
+ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `forti_token_cloud`.
The feature is not ready for production use.
```
diff --git a/doc/development/fe_guide/accessibility.md b/doc/development/fe_guide/accessibility.md
index c4ebef4c289..e71e414002a 100644
--- a/doc/development/fe_guide/accessibility.md
+++ b/doc/development/fe_guide/accessibility.md
@@ -23,6 +23,17 @@ they found that "ARIA correlated to higher detectable errors".
It is likely that *misuse* of ARIA is a big cause of increased errors,
so when in doubt don't use `aria-*`, `role`, and `tabindex` and stick with semantic HTML.
+## Enable keyboard navigation on macOS
+
+By default, macOS limits the <kbd>tab</kbd> key to **Text boxes and lists only**. To enable full keyboard navigation:
+
+1. Open **System Preferences**.
+1. Select **Keyboard**.
+1. Open the **Shortcuts** tab.
+1. Enable the setting **Use keyboard navigation to move focus between controls**.
+
+You can read more about enabling browser-specific keyboard navigation on [a11yproject](https://www.a11yproject.com/posts/2017-12-29-macos-browser-keyboard-navigation/).
+
## Quick checklist
- [Text](#text-inputs-with-accessible-names),
diff --git a/doc/update/index.md b/doc/update/index.md
index 216ead2128f..bb7fdaa93c0 100644
--- a/doc/update/index.md
+++ b/doc/update/index.md
@@ -338,6 +338,23 @@ or [init scripts](upgrading_from_source.md#configure-sysv-init-script) by [follo
Workhorse can no longer connect. As a workaround, [disable the temporary `workhorse_use_sidechannel`](../administration/feature_flags.md#enable-or-disable-the-feature)
feature flag. If you need a proxy between Workhorse and Gitaly, use a TCP proxy. If you have feedback about this change, please go to [this issue](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1301).
+- In 14.1 we introduced a background migration that changes how we store merge request diff commits
+ in order to significantly reduce the amount of storage needed.
+ In 14.5 we introduce a set of migrations that wrap up this process by making sure
+ that all remaining jobs over the `merge_request_diff_commits` table are completed.
+ These jobs will have already been processed in most cases so that no extra time is necessary during an upgrade to 14.5.
+ But if there are remaining jobs, the deployment may take a few extra minutes to complete.
+
+ All merge request diff commits will automatically incorporate these changes, and there are no
+ additional requirements to perform the upgrade.
+ Existing data in the `merge_request_diff_commits` table remains unpacked until you run `VACUUM FULL merge_request_diff_commits`.
+ But note that the `VACUUM FULL` operation locks and rewrites the entire `merge_request_diff_commits` table,
+ so the operation takes some time to complete and it blocks access to this table until the end of the process.
+ We advise you to only run this command while GitLab is not actively used or it is taken offline for the duration of the process.
+ The time it takes to complete depends on the size of the table, which can be obtained by using `select pg_size_pretty(pg_total_relation_size('merge_request_diff_commits'));`.
+
+ For more information, refer to [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/331823).
+
### 14.4.0
Git 2.33.x and later is required. We recommend you use the
diff --git a/doc/user/clusters/agent/install/index.md b/doc/user/clusters/agent/install/index.md
index 68ff1b8579b..fad9d4f08f1 100644
--- a/doc/user/clusters/agent/install/index.md
+++ b/doc/user/clusters/agent/install/index.md
@@ -70,7 +70,8 @@ synchronizations is:
```yaml
gitops:
manifest_projects:
- - id: "path-to/your-manifest-project-1"
+ # The `id` is the path to the Git repository holding your manifest files
+ - id: "path/to/your-manifest-project-1"
paths:
- glob: '/**/*.{yaml,yml,json}'
```
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 813e358e72b..a3aecff6f73 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -298,6 +298,7 @@ after the limits change in January, 2021:
| **All** traffic (from a given **IP address**) | **600** requests per minute | **2,000** requests per minute | **2,000** requests per minute |
| **Issue creation** | | **300** requests per minute | **300** requests per minute |
| **Note creation** (on issues and merge requests) | | **300** requests per minute | **60** requests per minute |
+| **Advanced, project, and group search** API (for a given **IP address**) | | | **10** requests per minute |
More details are available on the rate limits for [protected
paths](#protected-paths-throttle) and [raw
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index f87603895ec..47163a824c7 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -216,7 +216,7 @@ The following table lists project permissions available for each role:
1. If **Public pipelines** is enabled in **Project Settings > CI/CD**.
1. Not allowed for Guest, Reporter, Developer, Maintainer, or Owner. See [protected branches](project/protected_branches.md).
1. If the [branch is protected](project/protected_branches.md), this depends on the access Developers and Maintainers are given.
-1. Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see repository information like tags and commits.
+1. Guest users can access GitLab [**Releases**](project/releases/index.md) for downloading assets but are not allowed to download the source code nor see [repository information like commits and release evidence](project/releases/index.md#view-a-release-and-download-assets).
1. Actions are limited only to records owned (referenced) by user.
1. When [Share Group Lock](group/index.md#prevent-a-project-from-being-shared-with-groups) is enabled the project can't be shared with other groups. It does not affect group with group sharing.
1. For information on eligible approvers for merge requests, see
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index d16c6cb24f1..9177e63369d 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -393,14 +393,6 @@ The release title can be customized using the **Release title** field when
creating or editing a release. If no title is provided, the release's tag name
is used instead.
-Guest users of private projects are allowed to view the **Releases** page
-but are _not_ allowed to view details about the Git repository (in particular,
-tag names). Because of this, release titles are replaced with a generic
-title like "Release-1234" for Guest users to avoid leaking tag name information.
-
-See the [Release permissions](#release-permissions) section for
-more information about permissions.
-
### Tag name
The release tag name should include the release version. GitLab uses [Semantic Versioning](https://semver.org/)
@@ -724,11 +716,14 @@ In the API:
### View a release and download assets
+> [The Guest permission for read action was adjusted](https://gitlab.com/gitlab-org/gitlab/-/issues/335209) in GitLab 14.5.
+
- Users with [Reporter role or above](../../../user/permissions.md#project-members-permissions)
have read and download access to the project releases.
- Users with [Guest role](../../../user/permissions.md#project-members-permissions)
- have read and download access to the project releases, however,
- repository-related information are redacted (for example the Git tag name).
+ have read and download access to the project releases.
+ This includes associated Git-tag-names, release description, author information of the releases.
+ However, other repository-related information, such as [source code](#source-code), [release evidence](#release-evidence) are redacted.
### Create, update, and delete a release and its assets
diff --git a/doc/user/tasks.md b/doc/user/tasks.md
index a266b386618..e5e5510407e 100644
--- a/doc/user/tasks.md
+++ b/doc/user/tasks.md
@@ -14,7 +14,7 @@ For the latest updates, check the [Tasks Roadmap](https://gitlab.com/groups/gitl
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the featured flag](../administration/feature_flags.md) named `work_items`.
+ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items`.
The feature is not ready for production use.
Use tasks to track steps needed for the [issue](project/issues/index.md) to be closed.
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb
index ee459f2790a..1e50c980a3a 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/base_query_builder.rb
@@ -63,23 +63,12 @@ module Gitlab
def filter_label_names(query)
return query if params[:label_name].blank?
- all_label_ids = Issuables::LabelFilter
- .new(group: root_ancestor, project: nil, params: { label_name: params[:label_name] })
- .find_label_ids(params[:label_name])
-
- return query.none if params[:label_name].size != all_label_ids.size
-
- all_label_ids.each do |label_ids|
- relation = LabelLink
- .where(target_type: stage.subject_class.name)
- .where(LabelLink.arel_table['target_id'].eq(query.model.arel_table[query.model.issuable_id_column]))
-
- relation = relation.where(label_id: label_ids)
-
- query = query.where(relation.arel.exists)
- end
-
- query
+ LabelFilter.new(
+ stage: stage,
+ params: params,
+ project: nil,
+ group: root_ancestor
+ ).filter(query)
end
def filter_assignees(query)
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb
index ab3ae93f5ff..c8b11ecb4a8 100644
--- a/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/data_collector.rb
@@ -30,6 +30,12 @@ module Gitlab
strong_memoize(:count) { limit_count }
end
+ def records_fetcher
+ strong_memoize(:records_fetcher) do
+ RecordsFetcher.new(stage: stage, query: query, params: params)
+ end
+ end
+
private
attr_reader :stage, :params
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/label_filter.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/label_filter.rb
new file mode 100644
index 00000000000..6d87ae91a9c
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/label_filter.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module Aggregated
+ # This class makes it possible to add label filters to stage event tables
+ class LabelFilter < Issuables::LabelFilter
+ extend ::Gitlab::Utils::Override
+
+ def initialize(stage:, project:, group:, **kwargs)
+ @stage = stage
+
+ super(project: project, group: group, **kwargs)
+ end
+
+ private
+
+ attr_reader :stage
+
+ override :label_link_query
+ def label_link_query(target_model, label_ids: nil)
+ join_column = target_model.arel_table[target_model.issuable_id_column]
+
+ LabelLink.by_target_for_exists_query(stage.subject_class.name, join_column, label_ids)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb b/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
new file mode 100644
index 00000000000..7dce757cdc8
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module Aggregated
+ class RecordsFetcher
+ include Gitlab::Utils::StrongMemoize
+ include StageQueryHelpers
+
+ MAX_RECORDS = 20
+
+ MAPPINGS = {
+ Issue => {
+ serializer_class: AnalyticsIssueSerializer,
+ includes_for_query: { project: { namespace: [:route] }, author: [] },
+ columns_for_select: %I[title iid id created_at author_id project_id]
+ },
+ MergeRequest => {
+ serializer_class: AnalyticsMergeRequestSerializer,
+ includes_for_query: { target_project: [:namespace], author: [] },
+ columns_for_select: %I[title iid id created_at author_id state_id target_project_id]
+ }
+ }.freeze
+
+ def initialize(stage:, query:, params: {})
+ @stage = stage
+ @query = query
+ @params = params
+ @sort = params[:sort] || :end_event
+ @direction = params[:direction] || :desc
+ @page = params[:page] || 1
+ @per_page = MAX_RECORDS
+ @stage_event_model = query.model
+ end
+
+ def serialized_records
+ strong_memoize(:serialized_records) do
+ records = ordered_and_limited_query.select(stage_event_model.arel_table[Arel.star], duration.as('total_time'))
+
+ yield records if block_given?
+ issuables_and_records = load_issuables(records)
+
+ preload_associations(issuables_and_records.map(&:first))
+
+ issuables_and_records.map do |issuable, record|
+ project = issuable.project
+ attributes = issuable.attributes.merge({
+ project_path: project.path,
+ namespace_path: project.namespace.route.path,
+ author: issuable.author,
+ total_time: record.total_time
+ })
+ serializer.represent(attributes)
+ end
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def ordered_and_limited_query
+ sorting_options = {
+ end_event: {
+ asc: -> { query.order(end_event_timestamp: :asc) },
+ desc: -> { query.order(end_event_timestamp: :desc) }
+ },
+ duration: {
+ asc: -> { query.order(duration.asc) },
+ desc: -> { query.order(duration.desc) }
+ }
+ }
+
+ sort_lambda = sorting_options.dig(sort, direction) || sorting_options.dig(:end_event, :desc)
+
+ sort_lambda.call
+ .page(page)
+ .per(per_page)
+ .without_count
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ attr_reader :stage, :query, :sort, :direction, :params, :page, :per_page, :stage_event_model
+
+ delegate :subject_class, to: :stage
+
+ def load_issuables(stage_event_records)
+ stage_event_records_by_issuable_id = stage_event_records.index_by(&:issuable_id)
+
+ issuable_model = stage_event_model.issuable_model
+ issuables_by_id = issuable_model.id_in(stage_event_records_by_issuable_id.keys).index_by(&:id)
+
+ stage_event_records_by_issuable_id.map do |issuable_id, record|
+ [issuables_by_id[issuable_id], record] if issuables_by_id[issuable_id]
+ end.compact
+ end
+
+ def serializer
+ MAPPINGS.fetch(subject_class).fetch(:serializer_class).new
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def preload_associations(records)
+ ActiveRecord::Associations::Preloader.new.preload(
+ records,
+ MAPPINGS.fetch(subject_class).fetch(:includes_for_query)
+ )
+
+ records
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/data_collector.rb b/lib/gitlab/analytics/cycle_analytics/data_collector.rb
index a26df55bd0a..a20481dd39e 100644
--- a/lib/gitlab/analytics/cycle_analytics/data_collector.rb
+++ b/lib/gitlab/analytics/cycle_analytics/data_collector.rb
@@ -23,7 +23,11 @@ module Gitlab
def records_fetcher
strong_memoize(:records_fetcher) do
- RecordsFetcher.new(stage: stage, query: query, params: params)
+ if use_aggregated_data_collector?
+ aggregated_data_collector.records_fetcher
+ else
+ RecordsFetcher.new(stage: stage, query: query, params: params)
+ end
end
end
diff --git a/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb b/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb
index f94696e3186..140c4a300ca 100644
--- a/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb
+++ b/lib/gitlab/analytics/cycle_analytics/records_fetcher.rb
@@ -8,23 +8,11 @@ module Gitlab
include StageQueryHelpers
include Gitlab::CycleAnalytics::MetricsTables
- MAX_RECORDS = 20
-
- MAPPINGS = {
- Issue => {
- serializer_class: AnalyticsIssueSerializer,
- includes_for_query: { project: { namespace: [:route] }, author: [] },
- columns_for_select: %I[title iid id created_at author_id project_id]
- },
- MergeRequest => {
- serializer_class: AnalyticsMergeRequestSerializer,
- includes_for_query: { target_project: [:namespace], author: [] },
- columns_for_select: %I[title iid id created_at author_id state_id target_project_id]
- }
- }.freeze
-
delegate :subject_class, to: :stage
+ MAX_RECORDS = Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher::MAX_RECORDS
+ MAPPINGS = Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher::MAPPINGS
+
def initialize(stage:, query:, params: {})
@stage = stage
@query = query
diff --git a/lib/gitlab/ci/parsers/security/common.rb b/lib/gitlab/ci/parsers/security/common.rb
index 2086ac5668c..0c969daf7fd 100644
--- a/lib/gitlab/ci/parsers/security/common.rb
+++ b/lib/gitlab/ci/parsers/security/common.rb
@@ -114,7 +114,7 @@ module Gitlab
flags: flags,
links: links,
remediations: remediations,
- raw_metadata: data.to_json,
+ original_data: data,
metadata_version: report_version,
details: data['details'] || {},
signatures: signatures,
diff --git a/lib/gitlab/ci/reports/security/finding.rb b/lib/gitlab/ci/reports/security/finding.rb
index 7a81c471267..47ec82ac86c 100644
--- a/lib/gitlab/ci/reports/security/finding.rb
+++ b/lib/gitlab/ci/reports/security/finding.rb
@@ -17,7 +17,6 @@ module Gitlab
attr_reader :name
attr_reader :old_location
attr_reader :project_fingerprint
- attr_reader :raw_metadata
attr_reader :report_type
attr_reader :scanner
attr_reader :scan
@@ -28,10 +27,13 @@ module Gitlab
attr_reader :details
attr_reader :signatures
attr_reader :project_id
+ attr_reader :original_data
delegate :file_path, :start_line, :end_line, to: :location
- def initialize(compare_key:, identifiers:, flags: [], links: [], remediations: [], location:, metadata_version:, name:, raw_metadata:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil, details: {}, signatures: [], project_id: nil, vulnerability_finding_signatures_enabled: false) # rubocop:disable Metrics/ParameterLists
+ alias_method :cve, :compare_key
+
+ def initialize(compare_key:, identifiers:, flags: [], links: [], remediations: [], location:, metadata_version:, name:, original_data:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil, details: {}, signatures: [], project_id: nil, vulnerability_finding_signatures_enabled: false) # rubocop:disable Metrics/ParameterLists
@compare_key = compare_key
@confidence = confidence
@identifiers = identifiers
@@ -40,7 +42,7 @@ module Gitlab
@location = location
@metadata_version = metadata_version
@name = name
- @raw_metadata = raw_metadata
+ @original_data = original_data
@report_type = report_type
@scanner = scanner
@scan = scan
@@ -74,6 +76,10 @@ module Gitlab
uuid
details
signatures
+ description
+ message
+ cve
+ solution
].each_with_object({}) do |key, hash|
hash[key] = public_send(key) # rubocop:disable GitlabSecurity/PublicSend
end
@@ -145,6 +151,26 @@ module Gitlab
signatures.present?
end
+ def raw_metadata
+ @raw_metadata ||= original_data.to_json
+ end
+
+ def description
+ original_data['description']
+ end
+
+ def message
+ original_data['message']
+ end
+
+ def solution
+ original_data['solution']
+ end
+
+ def location_data
+ original_data['location']
+ end
+
private
def generate_project_fingerprint
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb
index a1e36ec5c4c..120020273f9 100644
--- a/spec/controllers/projects/releases_controller_spec.rb
+++ b/spec/controllers/projects/releases_controller_spec.rb
@@ -207,7 +207,18 @@ RSpec.describe Projects::ReleasesController do
let(:project) { private_project }
let(:user) { guest }
- it_behaves_like 'not found'
+ it_behaves_like 'successful request'
+ end
+
+ context 'when user is an external user for the project' do
+ let(:project) { private_project }
+ let(:user) { create(:user) }
+
+ it 'behaves like not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
end
end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 7785233875c..aebe13c39fe 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -29,6 +29,7 @@ RSpec.describe 'Database schema' do
ci_builds: %w[erased_by_id runner_id trigger_request_id user_id],
ci_namespace_monthly_usages: %w[namespace_id],
ci_pipelines: %w[user_id],
+ ci_pipeline_chat_data: %w[chat_name_id], # it uses the loose foreign key featue
ci_runner_projects: %w[runner_id],
ci_trigger_requests: %w[commit_id],
cluster_providers_aws: %w[security_group_id vpc_id access_key_id],
diff --git a/spec/factories/ci/reports/security/findings.rb b/spec/factories/ci/reports/security/findings.rb
index e3971bc48f3..8a39fce971f 100644
--- a/spec/factories/ci/reports/security/findings.rb
+++ b/spec/factories/ci/reports/security/findings.rb
@@ -9,7 +9,7 @@ FactoryBot.define do
metadata_version { 'sast:1.0' }
name { 'Cipher with no integrity' }
report_type { :sast }
- raw_metadata do
+ original_data do
{
description: "The cipher does not provide data integrity update 1",
solution: "GCM mode introduces an HMAC into the resulting encrypted data, providing integrity of the result.",
@@ -26,7 +26,7 @@ FactoryBot.define do
url: "https://crypto.stackexchange.com/questions/31428/pbewithmd5anddes-cipher-does-not-check-for-integrity-first"
}
]
- }.to_json
+ }.deep_stringify_keys
end
scanner factory: :ci_reports_security_scanner
severity { :high }
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 0580e28512d..69361f66a71 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -34,6 +34,10 @@ RSpec.describe 'Value Stream Analytics', :js do
wait_for_all_requests
end
+ before do
+ stub_feature_flags(use_vsa_aggregated_tables: false)
+ end
+
context 'as an allowed user' do
context 'when project is new' do
before do
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 6bc4c66b8ca..98935fdf872 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -123,11 +123,11 @@ RSpec.describe 'User views releases', :js do
within('.release-block', match: :first) do
expect(page).to have_content(release_v3.description)
+ expect(page).to have_content(release_v3.tag)
+ expect(page).to have_content(release_v3.name)
# The following properties (sometimes) include Git info,
# so they are not rendered for Guest users
- expect(page).not_to have_content(release_v3.name)
- expect(page).not_to have_content(release_v3.tag)
expect(page).not_to have_content(release_v3.commit.short_id)
end
end
diff --git a/spec/graphql/types/release_links_type_spec.rb b/spec/graphql/types/release_links_type_spec.rb
index 38c38d58baa..e77c4e3ddd1 100644
--- a/spec/graphql/types/release_links_type_spec.rb
+++ b/spec/graphql/types/release_links_type_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['ReleaseLinks'] do
- it { expect(described_class).to require_graphql_authorizations(:download_code) }
+ it { expect(described_class).to require_graphql_authorizations(:read_release) }
it 'has the expected fields' do
expected_fields = %w[
@@ -18,4 +18,46 @@ RSpec.describe GitlabSchema.types['ReleaseLinks'] do
expect(described_class).to include_graphql_fields(*expected_fields)
end
+
+ context 'individual field authorization' do
+ def fetch_authorizations(field_name)
+ described_class.fields.dig(field_name).instance_variable_get(:@authorize)
+ end
+
+ describe 'openedMergeRequestsUrl' do
+ it 'has valid authorization' do
+ expect(fetch_authorizations('openedMergeRequestsUrl')).to include(:download_code)
+ end
+ end
+
+ describe 'mergedMergeRequestsUrl' do
+ it 'has valid authorization' do
+ expect(fetch_authorizations('mergedMergeRequestsUrl')).to include(:download_code)
+ end
+ end
+
+ describe 'closedMergeRequestsUrl' do
+ it 'has valid authorization' do
+ expect(fetch_authorizations('closedMergeRequestsUrl')).to include(:download_code)
+ end
+ end
+
+ describe 'openedIssuesUrl' do
+ it 'has valid authorization' do
+ expect(fetch_authorizations('openedIssuesUrl')).to include(:download_code)
+ end
+ end
+
+ describe 'closedIssuesUrl' do
+ it 'has valid authorization' do
+ expect(fetch_authorizations('closedIssuesUrl')).to include(:download_code)
+ end
+ end
+
+ describe 'editUrl' do
+ it 'has valid authorization' do
+ expect(fetch_authorizations('editUrl')).to include(:update_release)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb
new file mode 100644
index 00000000000..045cdb129cb
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/aggregated/records_fetcher_spec.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue_1) { create(:issue, project: project) }
+ let_it_be(:issue_2) { create(:issue, project: project) }
+ let_it_be(:issue_3) { create(:issue, project: project) }
+
+ let_it_be(:stage_event_1) { create(:cycle_analytics_issue_stage_event, issue_id: issue_1.id, start_event_timestamp: 2.years.ago, end_event_timestamp: 1.year.ago) } # duration: 1 year
+ let_it_be(:stage_event_2) { create(:cycle_analytics_issue_stage_event, issue_id: issue_2.id, start_event_timestamp: 5.years.ago, end_event_timestamp: 2.years.ago) } # duration: 3 years
+ let_it_be(:stage_event_3) { create(:cycle_analytics_issue_stage_event, issue_id: issue_3.id, start_event_timestamp: 6.years.ago, end_event_timestamp: 3.months.ago) } # duration: 5+ years
+
+ let_it_be(:stage) { create(:cycle_analytics_project_stage, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production, project: project) }
+
+ let(:params) { {} }
+
+ subject(:records_fetcher) do
+ described_class.new(stage: stage, query: Analytics::CycleAnalytics::IssueStageEvent.all, params: params)
+ end
+
+ shared_examples 'match returned records' do
+ it 'returns issues in the correct order' do
+ returned_iids = records_fetcher.serialized_records.pluck(:iid).map(&:to_i)
+
+ expect(returned_iids).to eq(expected_issue_ids)
+ end
+ end
+
+ describe '#serialized_records' do
+ describe 'sorting' do
+ context 'when sorting by end event DESC' do
+ let(:expected_issue_ids) { [issue_3.iid, issue_1.iid, issue_2.iid] }
+
+ before do
+ params[:sort] = :end_event
+ params[:direction] = :desc
+ end
+
+ it_behaves_like 'match returned records'
+ end
+
+ context 'when sorting by end event ASC' do
+ let(:expected_issue_ids) { [issue_2.iid, issue_1.iid, issue_3.iid] }
+
+ before do
+ params[:sort] = :end_event
+ params[:direction] = :asc
+ end
+
+ it_behaves_like 'match returned records'
+ end
+
+ context 'when sorting by duration DESC' do
+ let(:expected_issue_ids) { [issue_3.iid, issue_2.iid, issue_1.iid] }
+
+ before do
+ params[:sort] = :duration
+ params[:direction] = :desc
+ end
+
+ it_behaves_like 'match returned records'
+ end
+
+ context 'when sorting by duration ASC' do
+ let(:expected_issue_ids) { [issue_1.iid, issue_2.iid, issue_3.iid] }
+
+ before do
+ params[:sort] = :duration
+ params[:direction] = :asc
+ end
+
+ it_behaves_like 'match returned records'
+ end
+ end
+
+ describe 'pagination' do
+ let(:expected_issue_ids) { [issue_3.iid] }
+
+ before do
+ params[:sort] = :duration
+ params[:direction] = :asc
+ params[:page] = 2
+
+ stub_const('Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher::MAX_RECORDS', 2)
+ end
+
+ it_behaves_like 'match returned records'
+ end
+
+ context 'when passing a block to serialized_records method' do
+ before do
+ params[:sort] = :duration
+ params[:direction] = :asc
+ end
+
+ it 'yields the underlying stage event scope' do
+ stage_event_records = []
+
+ records_fetcher.serialized_records do |scope|
+ stage_event_records.concat(scope.to_a)
+ end
+
+ expect(stage_event_records.map(&:issue_id)).to eq([issue_1.id, issue_2.id, issue_3.id])
+ end
+ end
+
+ context 'when the issue record no longer exists' do
+ it 'skips non-existing issue records' do
+ create(:cycle_analytics_issue_stage_event, {
+ issue_id: 0, # non-existing id
+ start_event_timestamp: 5.months.ago,
+ end_event_timestamp: 3.months.ago
+ })
+
+ stage_event_count = nil
+
+ records_fetcher.serialized_records do |scope|
+ stage_event_count = scope.to_a.size
+ end
+
+ issue_count = records_fetcher.serialized_records.to_a.size
+
+ expect(stage_event_count).to eq(4)
+ expect(issue_count).to eq(3)
+ end
+ end
+ end
+end
diff --git a/spec/models/u2f_registration_spec.rb b/spec/models/u2f_registration_spec.rb
index aba2f27d104..7a70cf69566 100644
--- a/spec/models/u2f_registration_spec.rb
+++ b/spec/models/u2f_registration_spec.rb
@@ -5,9 +5,11 @@ require 'spec_helper'
RSpec.describe U2fRegistration do
let_it_be(:user) { create(:user) }
+ let(:u2f_registration_name) { 'u2f_device' }
+
let(:u2f_registration) do
device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5))
- create(:u2f_registration, name: 'u2f_device',
+ create(:u2f_registration, name: u2f_registration_name,
user: user,
certificate: Base64.strict_encode64(device.cert_raw),
key_handle: U2F.urlsafe_encode64(device.key_handle_raw),
@@ -16,11 +18,27 @@ RSpec.describe U2fRegistration do
describe 'callbacks' do
describe '#create_webauthn_registration' do
- it 'creates webauthn registration' do
- u2f_registration.save!
+ shared_examples_for 'creates webauthn registration' do
+ it 'creates webauthn registration' do
+ u2f_registration.save!
+
+ webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id)
+ expect(webauthn_registration).to exist
+ end
+ end
+
+ it_behaves_like 'creates webauthn registration'
+
+ context 'when the u2f_registration has a blank name' do
+ let(:u2f_registration_name) { '' }
+
+ it_behaves_like 'creates webauthn registration'
+ end
+
+ context 'when the u2f_registration has the name as `nil`' do
+ let(:u2f_registration_name) { nil }
- webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id)
- expect(webauthn_registration).to exist
+ it_behaves_like 'creates webauthn registration'
end
it 'logs error' do
diff --git a/spec/models/webauthn_registration_spec.rb b/spec/models/webauthn_registration_spec.rb
new file mode 100644
index 00000000000..6813854bf6c
--- /dev/null
+++ b/spec/models/webauthn_registration_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe WebauthnRegistration do
+ describe 'relations' do
+ it { is_expected.to belong_to(:user) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:credential_xid) }
+ it { is_expected.to validate_presence_of(:public_key) }
+ it { is_expected.to validate_presence_of(:counter) }
+ it { is_expected.to validate_length_of(:name).is_at_least(0) }
+ it { is_expected.not_to allow_value(nil).for(:name) }
+ it do
+ is_expected.to validate_numericality_of(:counter)
+ .only_integer
+ .is_greater_than_or_equal_to(0)
+ .is_less_than_or_equal_to(4294967295)
+ end
+ end
+end
diff --git a/spec/presenters/release_presenter_spec.rb b/spec/presenters/release_presenter_spec.rb
index b2e7b684644..925a69ca92d 100644
--- a/spec/presenters/release_presenter_spec.rb
+++ b/spec/presenters/release_presenter_spec.rb
@@ -63,12 +63,6 @@ RSpec.describe ReleasePresenter do
it 'returns its own url' do
is_expected.to eq(project_release_url(project, release))
end
-
- context 'when user is guest' do
- let(:user) { guest }
-
- it { is_expected.to be_nil }
- end
end
describe '#opened_merge_requests_url' do
@@ -147,13 +141,5 @@ RSpec.describe ReleasePresenter do
it 'returns the release name' do
is_expected.to eq release.name
end
-
- context "when a user is not allowed to access any repository information" do
- let(:presenter) { described_class.new(release, current_user: guest) }
-
- it 'returns a replacement name to avoid potentially leaking tag information' do
- is_expected.to eq "Release-#{release.id}"
- end
- end
end
end
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index 7f24d051457..77abac4ef04 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -228,6 +228,189 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
end
end
+ shared_examples 'restricted access to release fields' do
+ describe 'scalar fields' do
+ let(:path) { path_prefix }
+
+ let(:release_fields) do
+ %{
+ tagName
+ tagPath
+ description
+ descriptionHtml
+ name
+ createdAt
+ releasedAt
+ upcomingRelease
+ }
+ end
+
+ before do
+ post_query
+ end
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'tagName' => release.tag,
+ 'tagPath' => nil,
+ 'description' => release.description,
+ 'descriptionHtml' => release.description_html,
+ 'name' => release.name,
+ 'createdAt' => release.created_at.iso8601,
+ 'releasedAt' => release.released_at.iso8601,
+ 'upcomingRelease' => false
+ })
+ end
+ end
+
+ describe 'milestones' do
+ let(:path) { path_prefix + %w[milestones nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:milestones, nil, 'nodes { id title }')
+ end
+
+ it 'finds milestones associated to a release' do
+ post_query
+
+ expected = release.milestones.order_by_dates_and_title.map do |milestone|
+ { 'id' => global_id_of(milestone), 'title' => milestone.title }
+ end
+
+ expect(data).to eq(expected)
+ end
+ end
+
+ describe 'author' do
+ let(:path) { path_prefix + %w[author] }
+
+ let(:release_fields) do
+ query_graphql_field(:author, nil, 'id username')
+ end
+
+ it 'finds the author of the release' do
+ post_query
+
+ expect(data).to eq(
+ 'id' => global_id_of(release.author),
+ 'username' => release.author.username
+ )
+ end
+ end
+
+ describe 'commit' do
+ let(:path) { path_prefix + %w[commit] }
+
+ let(:release_fields) do
+ query_graphql_field(:commit, nil, 'sha')
+ end
+
+ it 'restricts commit associated with the release' do
+ post_query
+
+ expect(data).to eq(nil)
+ end
+ end
+
+ describe 'assets' do
+ describe 'count' do
+ let(:path) { path_prefix + %w[assets] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil, 'count')
+ end
+
+ it 'returns non source release links count' do
+ post_query
+
+ expect(data).to eq('count' => release.assets_count(except: [:sources]))
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[assets links nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:links, nil, 'nodes { id name url external, directAssetUrl }'))
+ end
+
+ it 'finds all non source external release links' do
+ post_query
+
+ expected = release.links.map do |link|
+ {
+ 'id' => global_id_of(link),
+ 'name' => link.name,
+ 'url' => link.url,
+ 'external' => true,
+ 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << "/downloads#{link.filepath}" : link.url
+ }
+ end
+
+ expect(data).to match_array(expected)
+ end
+ end
+
+ describe 'sources' do
+ let(:path) { path_prefix + %w[assets sources nodes] }
+
+ let(:release_fields) do
+ query_graphql_field(:assets, nil,
+ query_graphql_field(:sources, nil, 'nodes { format url }'))
+ end
+
+ it 'restricts release sources' do
+ post_query
+
+ expect(data).to match_array([])
+ end
+ end
+ end
+
+ describe 'links' do
+ let(:path) { path_prefix + %w[links] }
+
+ let(:release_fields) do
+ query_graphql_field(:links, nil, %{
+ selfUrl
+ openedMergeRequestsUrl
+ mergedMergeRequestsUrl
+ closedMergeRequestsUrl
+ openedIssuesUrl
+ closedIssuesUrl
+ })
+ end
+
+ it 'finds only selfUrl' do
+ post_query
+
+ expect(data).to eq(
+ 'selfUrl' => project_release_url(project, release),
+ 'openedMergeRequestsUrl' => nil,
+ 'mergedMergeRequestsUrl' => nil,
+ 'closedMergeRequestsUrl' => nil,
+ 'openedIssuesUrl' => nil,
+ 'closedIssuesUrl' => nil
+ )
+ end
+ end
+
+ describe 'evidences' do
+ let(:path) { path_prefix + %w[evidences] }
+
+ let(:release_fields) do
+ query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
+ end
+
+ it 'restricts all evidence fields' do
+ post_query
+
+ expect(data).to eq('nodes' => [])
+ end
+ end
+ end
+
shared_examples 'no access to the release field' do
describe 'repository-related fields' do
let(:path) { path_prefix }
@@ -302,7 +485,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
context 'when the user has Guest permissions' do
let(:current_user) { guest }
- it_behaves_like 'no access to the release field'
+ it_behaves_like 'restricted access to release fields'
+ it_behaves_like 'no access to editUrl'
end
context 'when the user has Reporter permissions' do
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
index 2816ce90a6b..c28a6fa7666 100644
--- a/spec/requests/api/graphql/project/releases_spec.rb
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -129,10 +129,12 @@ RSpec.describe 'Query.project(fullPath).releases()' do
end
it 'does not return data for fields that expose repository information' do
+ tag_name = release.tag
+ release_name = release.name
expect(data).to eq(
- 'tagName' => nil,
+ 'tagName' => tag_name,
'tagPath' => nil,
- 'name' => "Release-#{release.id}",
+ 'name' => release_name,
'commit' => nil,
'assets' => {
'count' => release.assets_count(except: [:sources]),
@@ -143,7 +145,14 @@ RSpec.describe 'Query.project(fullPath).releases()' do
'evidences' => {
'nodes' => []
},
- 'links' => nil
+ 'links' => {
+ 'closedIssuesUrl' => nil,
+ 'closedMergeRequestsUrl' => nil,
+ 'mergedMergeRequestsUrl' => nil,
+ 'openedIssuesUrl' => nil,
+ 'openedMergeRequestsUrl' => nil,
+ 'selfUrl' => project_release_url(project, release)
+ }
)
end
end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index 9598355a640..b1beb2adb3b 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -73,6 +73,23 @@ RSpec.describe Packages::Npm::CreatePackageService do
end
end
+ described_class::PACKAGE_JSON_NOT_ALLOWED_FIELDS.each do |field|
+ context "with not allowed #{field} field" do
+ before do
+ params[:versions][version][field] = 'test'
+ end
+
+ it 'is persisted without the field' do
+ expect { subject }
+ .to change { Packages::Package.count }.by(1)
+ .and change { Packages::Package.npm.count }.by(1)
+ .and change { Packages::Tag.count }.by(1)
+ .and change { Packages::Npm::Metadatum.count }.by(1)
+ expect(subject.npm_metadatum.package_json[field]).to be_blank
+ end
+ end
+ end
+
context 'with packages_npm_abbreviated_metadata disabled' do
before do
stub_feature_flags(packages_npm_abbreviated_metadata: false)
diff --git a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb
index b853d517d83..d823e7ac221 100644
--- a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb
+++ b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb
@@ -36,8 +36,8 @@ RSpec.shared_examples 'StageEventModel' do
described_class.issuable_id_column,
:group_id,
:project_id,
- :milestone_id,
:author_id,
+ :milestone_id,
:state_id,
:start_event_timestamp,
:end_event_timestamp