Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
d75e21489f
commit
7b69a22d49
|
|
@ -70,6 +70,16 @@ docs-lint markdown:
|
|||
script:
|
||||
- scripts/lint-doc.sh
|
||||
|
||||
docs-lint blueprint:
|
||||
extends:
|
||||
- .default-retry
|
||||
- .docs:rules:docs-blueprints-lint
|
||||
image: ${GITLAB_DEPENDENCY_PROXY_ADDRESS}ruby:${RUBY_VERSION}-slim
|
||||
stage: lint
|
||||
needs: []
|
||||
script:
|
||||
- scripts/lint-docs-blueprints.rb
|
||||
|
||||
docs code_quality:
|
||||
extends:
|
||||
- .reports:rules:code_quality
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ review-build-cng:
|
|||
environment:
|
||||
name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it
|
||||
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
|
||||
on_stop: review-stop
|
||||
on_stop: trigger-review-stop
|
||||
|
||||
review-deploy:
|
||||
extends:
|
||||
|
|
@ -173,12 +173,6 @@ review-deploy-sample-projects:
|
|||
# because some repos are private and CI_JOB_TOKEN cannot access files.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/issues/191273
|
||||
GIT_DEPTH: 1
|
||||
before_script:
|
||||
- source ./scripts/utils.sh
|
||||
- source ./scripts/review_apps/review-apps.sh
|
||||
- !reference [".use-kube-context", before_script]
|
||||
script:
|
||||
- retry delete_helm_release
|
||||
|
||||
review-delete-deployment:
|
||||
extends:
|
||||
|
|
@ -186,11 +180,23 @@ review-delete-deployment:
|
|||
- .review:rules:review-delete-deployment
|
||||
dependencies: []
|
||||
stage: prepare
|
||||
before_script:
|
||||
- source ./scripts/utils.sh
|
||||
- source ./scripts/review_apps/review-apps.sh
|
||||
- !reference [".use-kube-context", before_script]
|
||||
script:
|
||||
- retry delete_helm_release
|
||||
|
||||
review-stop:
|
||||
trigger-review-stop:
|
||||
extends:
|
||||
- .review-stop-base
|
||||
- .review:rules:review-stop
|
||||
resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment
|
||||
- .review:rules:trigger-review-stop
|
||||
stage: deploy
|
||||
needs: []
|
||||
before_script:
|
||||
- source ./scripts/utils.sh
|
||||
- install_gitlab_gem
|
||||
script:
|
||||
- review_stop_job_id="$(scripts/api/get_job_id.rb --pipeline-id "${PARENT_PIPELINE_ID}" --job-name "review-stop")"
|
||||
- |
|
||||
curl --request POST --header "Private-Token: ${PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE}" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/jobs/${review_stop_job_id}/play"
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ review-cleanup:
|
|||
- .review:rules:review-cleanup
|
||||
image: ${REVIEW_APPS_IMAGE}
|
||||
stage: prepare
|
||||
needs: []
|
||||
environment:
|
||||
name: review/regular-cleanup
|
||||
action: access
|
||||
variables:
|
||||
GIT_DEPTH: 1
|
||||
before_script:
|
||||
- source scripts/utils.sh
|
||||
- !reference [".use-kube-context", before_script]
|
||||
|
|
@ -15,6 +18,21 @@ review-cleanup:
|
|||
script:
|
||||
- scripts/review_apps/automated_cleanup.rb || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL} - <https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/review-apps.md#review-cleanup-job-failed|📗 RUNBOOK 📕>" warning "GitLab Bot" && exit 1);
|
||||
|
||||
review-stop:
|
||||
extends:
|
||||
- review-cleanup
|
||||
- .review:rules:review-stop
|
||||
environment:
|
||||
name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it
|
||||
action: stop
|
||||
resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment
|
||||
before_script:
|
||||
- source ./scripts/utils.sh
|
||||
- source ./scripts/review_apps/review-apps.sh
|
||||
- !reference [".use-kube-context", before_script]
|
||||
script:
|
||||
- retry delete_helm_release
|
||||
|
||||
.base-review-checks:
|
||||
extends:
|
||||
- .default-retry
|
||||
|
|
|
|||
|
|
@ -228,6 +228,11 @@
|
|||
- "scripts/lint-doc.sh"
|
||||
- ".gitlab/ci/docs.gitlab-ci.yml"
|
||||
|
||||
.docs-blueprints-patterns: &docs-blueprints-patterns
|
||||
- "doc/architecture/blueprints/**/*"
|
||||
- "scripts/lint-docs-blueprints.rb"
|
||||
- ".gitlab/ci/docs.gitlab-ci.yml"
|
||||
|
||||
.docs-deprecations-and-removals-patterns: &docs-deprecations-and-removals-patterns
|
||||
- "doc/update/deprecations.md"
|
||||
- "doc/update/removals.md"
|
||||
|
|
@ -851,6 +856,11 @@
|
|||
- <<: *if-default-refs
|
||||
changes: *docs-patterns
|
||||
|
||||
.docs:rules:docs-blueprints-lint:
|
||||
rules:
|
||||
- <<: *if-default-refs
|
||||
changes: *docs-blueprints-patterns
|
||||
|
||||
.docs:rules:deprecations-and-removals:
|
||||
rules:
|
||||
- <<: *if-default-refs
|
||||
|
|
@ -2060,7 +2070,7 @@
|
|||
# The following rules needs to be the same as the one for .review:rules:start-review-app-pipeline
|
||||
# except that:
|
||||
# - all rules have `when: manual` and `allow_failure: true` here
|
||||
.review:rules:review-cleanup:
|
||||
.review:rules:review-stop-merge-requests:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
|
|
@ -2097,9 +2107,20 @@
|
|||
changes: *code-patterns
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
.review:rules:review-cleanup:
|
||||
rules:
|
||||
- !reference [".review:rules:review-stop-merge-requests", rules]
|
||||
- <<: *if-dot-com-ee-schedule-default-branch-maintenance
|
||||
allow_failure: true
|
||||
|
||||
.review:rules:review-stop:
|
||||
rules:
|
||||
- !reference [".review:rules:review-stop-merge-requests", rules]
|
||||
- <<: *if-dot-com-gitlab-org-schedule
|
||||
when: manual
|
||||
allow_failure: true
|
||||
|
||||
.review:rules:review-k8s-resources-count-checks:
|
||||
rules:
|
||||
- <<: *if-dot-com-ee-schedule-default-branch-maintenance
|
||||
|
|
@ -2118,7 +2139,7 @@
|
|||
- "scripts/review_apps/gcp-quotas-checks.rb"
|
||||
allow_failure: true
|
||||
|
||||
.review:rules:review-stop:
|
||||
.review:rules:trigger-review-stop:
|
||||
rules:
|
||||
- when: manual
|
||||
allow_failure: true
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { escape, uniqueId } from 'lodash';
|
|||
import Vue from 'vue';
|
||||
import { renderGFM } from '~/behaviors/markdown/render_gfm';
|
||||
import { createAlert, VARIANT_INFO } from '~/flash';
|
||||
import { sanitize } from '~/lib/dompurify';
|
||||
import '~/lib/utils/jquery_at_who';
|
||||
import AjaxCache from '~/lib/utils/ajax_cache';
|
||||
import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
|
||||
|
|
@ -517,8 +518,36 @@ export default class Notes {
|
|||
if (discussionContainer.length === 0) {
|
||||
if (noteEntity.diff_discussion_html) {
|
||||
const discussionElement = document.createElement('table');
|
||||
// eslint-disable-next-line no-unsanitized/method
|
||||
discussionElement.insertAdjacentHTML('afterbegin', noteEntity.diff_discussion_html);
|
||||
let internalNote;
|
||||
let discussionDOM;
|
||||
|
||||
if (!noteEntity.on_image) {
|
||||
/*
|
||||
DOMPurify will strip table-less <tr>/<td>, so to get it to stop deleting
|
||||
nodes (since our note HTML starts with a table-less <tr>), we need to wrap
|
||||
the noteEntity discussion HTML in a <table> to perform the other
|
||||
sanitization.
|
||||
*/
|
||||
internalNote = sanitize(`<table>${noteEntity.diff_discussion_html}</table>`, {
|
||||
RETURN_DOM: true,
|
||||
});
|
||||
/*
|
||||
Since we wrapped the <tr> in a <table>, we need to extract the <tr> back out.
|
||||
DOMPurify returns a Body Element, so we have to start there, then get the
|
||||
wrapping table, and then get the content we actually want.
|
||||
Curiously, DOMPurify **ADDS** a totally novel <tbody>, so we're actually
|
||||
inserting a completely as-yet-unseen <tbody> element here.
|
||||
*/
|
||||
discussionDOM = internalNote.querySelector('table').firstChild;
|
||||
} else {
|
||||
// Image comments don't need <table> manipulation, they're already <div>s
|
||||
internalNote = sanitize(noteEntity.diff_discussion_html, {
|
||||
RETURN_DOM: true,
|
||||
});
|
||||
discussionDOM = internalNote.firstChild;
|
||||
}
|
||||
|
||||
discussionElement.insertAdjacentElement('afterbegin', discussionDOM);
|
||||
renderGFM(discussionElement);
|
||||
const $discussion = $(discussionElement).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export default {
|
|||
rulesLeft() {
|
||||
return getApprovalRuleNamesLeft(
|
||||
this.multipleApprovalRulesAvailable,
|
||||
(this.approvalState.approvalState?.rules || []).filter((r) => r.approvalsRequired !== 0),
|
||||
(this.approvalState.approvalState?.rules || []).filter((r) => !r.approved),
|
||||
);
|
||||
},
|
||||
approvalLeftMessage() {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def load_value_stream
|
||||
@value_stream = Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(@project)
|
||||
@value_stream = Analytics::CycleAnalytics::ValueStream.build_default_value_stream(@project.project_namespace)
|
||||
end
|
||||
|
||||
def cycle_analytics_json
|
||||
|
|
|
|||
|
|
@ -11,15 +11,21 @@
|
|||
class ProtectedBranchesFinder
|
||||
LIMIT = 100
|
||||
|
||||
attr_accessor :project, :params
|
||||
attr_accessor :project_or_group, :params
|
||||
|
||||
def initialize(project, params = {})
|
||||
@project = project
|
||||
def initialize(project_or_group, params = {})
|
||||
@project_or_group = project_or_group
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
protected_branches = project.limited_protected_branches(LIMIT)
|
||||
protected_branches = if project_or_group.is_a?(Group)
|
||||
project_or_group.protected_branches
|
||||
else
|
||||
project_or_group.all_protected_branches
|
||||
end
|
||||
|
||||
protected_branches = protected_branches.limit(LIMIT)
|
||||
by_name(protected_branches)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ module Analytics
|
|||
private
|
||||
|
||||
def build_stage(stage_name)
|
||||
stage_params = stage_params_by_name(stage_name).merge(project: project)
|
||||
Analytics::CycleAnalytics::ProjectStage.new(stage_params)
|
||||
stage_params = stage_params_by_name(stage_name).merge(namespace: project.project_namespace)
|
||||
Analytics::CycleAnalytics::Stage.new(stage_params)
|
||||
end
|
||||
|
||||
def stage_params_by_name(name)
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
class ProjectStage < ApplicationRecord
|
||||
include Analytics::CycleAnalytics::Stageable
|
||||
|
||||
belongs_to :project, optional: false
|
||||
belongs_to :value_stream, class_name: 'Analytics::CycleAnalytics::ProjectValueStream', foreign_key: :project_value_stream_id
|
||||
|
||||
alias_attribute :value_stream_id, :project_value_stream_id
|
||||
|
||||
delegate :group, to: :project
|
||||
alias_attribute :parent, :project
|
||||
alias_attribute :parent_id, :project_id
|
||||
|
||||
validate :validate_project_group_for_label_events, if: -> { start_event_label_based? || end_event_label_based? }
|
||||
|
||||
def self.distinct_stages_within_hierarchy(group)
|
||||
with_preloaded_labels
|
||||
.where(project_id: group.all_projects.select(:id))
|
||||
.select("DISTINCT ON(stage_event_hash_id) #{quoted_table_name}.*")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Project should belong to a group when the stage has Label based events since only GroupLabels are allowed.
|
||||
def validate_project_group_for_label_events
|
||||
errors.add(:project, s_('CycleAnalyticsStage|should be under a group')) unless project.group
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Analytics::CycleAnalytics::ProjectValueStream < ApplicationRecord
|
||||
belongs_to :project
|
||||
|
||||
has_many :stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
|
||||
|
||||
validates :project, :name, presence: true
|
||||
validates :name, length: { minimum: 3, maximum: 100, allow_nil: false }, uniqueness: { scope: :project_id }
|
||||
|
||||
def custom?
|
||||
false
|
||||
end
|
||||
|
||||
def stages
|
||||
[]
|
||||
end
|
||||
|
||||
def self.build_default_value_stream(project)
|
||||
new(name: Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME, project: project)
|
||||
end
|
||||
end
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
module Analytics
|
||||
module CycleAnalytics
|
||||
class StageEventHash < ApplicationRecord
|
||||
has_many :cycle_analytics_project_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage', inverse_of: :stage_event_hash
|
||||
has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::Stage', inverse_of: :stage_event_hash
|
||||
|
||||
validates :hash_sha256, presence: true
|
||||
|
|
@ -34,13 +33,9 @@ module Analytics
|
|||
end
|
||||
|
||||
def self.unused_hashes_for(id)
|
||||
project_stage_exists_query = Analytics::CycleAnalytics::ProjectStage.where(stage_event_hash_id: id).select('1').limit(1)
|
||||
stage_exists_query = ::Analytics::CycleAnalytics::Stage.where(stage_event_hash_id: id).select('1').limit(1)
|
||||
|
||||
where
|
||||
.not('EXISTS (?)', project_stage_exists_query)
|
||||
.where
|
||||
.not('EXISTS (?)', stage_exists_query)
|
||||
where.not('EXISTS (?)', stage_exists_query)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -329,13 +329,22 @@ class Issue < ApplicationRecord
|
|||
'#'
|
||||
end
|
||||
|
||||
# Alternative prefix for situations where the standard prefix would be
|
||||
# interpreted as a comment, most notably to begin commit messages with
|
||||
# (e.g. "GL-123: My commit")
|
||||
def self.alternative_reference_prefix
|
||||
'GL-'
|
||||
end
|
||||
|
||||
# Pattern used to extract `#123` issue references from text
|
||||
#
|
||||
# This pattern supports cross-project references.
|
||||
def self.reference_pattern
|
||||
@reference_pattern ||= %r{
|
||||
(#{Project.reference_pattern})?
|
||||
#{Regexp.escape(reference_prefix)}#{Gitlab::Regex.issue}
|
||||
(?:
|
||||
(#{Project.reference_pattern})?#{Regexp.escape(reference_prefix)} |
|
||||
#{Regexp.escape(alternative_reference_prefix)}
|
||||
)#{Gitlab::Regex.issue}
|
||||
}x
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -396,9 +396,6 @@ class Project < ApplicationRecord
|
|||
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
has_many :remote_mirrors, inverse_of: :project
|
||||
has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage', inverse_of: :project
|
||||
has_many :value_streams, class_name: 'Analytics::CycleAnalytics::ProjectValueStream', inverse_of: :project
|
||||
|
||||
has_many :external_pull_requests, inverse_of: :project
|
||||
|
||||
has_many :sourced_pipelines, class_name: 'Ci::Sources::Pipeline', foreign_key: :source_project_id
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ module Projects
|
|||
refresh = Projects::RefreshBuildArtifactsSizeStatisticsService.new.execute
|
||||
return unless refresh
|
||||
|
||||
log_extra_metadata_on_done(:refresh_id, refresh.id)
|
||||
log_extra_metadata_on_done(:project_id, refresh.project_id)
|
||||
log_extra_metadata_on_done(:last_job_artifact_id, refresh.last_job_artifact_id)
|
||||
log_extra_metadata_on_done(:last_batch, refresh.destroyed?)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
---
|
||||
table_name: analytics_cycle_analytics_project_stages
|
||||
classes:
|
||||
- Analytics::CycleAnalytics::ProjectStage
|
||||
feature_categories:
|
||||
- value_stream_management
|
||||
description: Persists project level value stream analytics stages.
|
||||
description: Persists project level value stream analytics stages. Scheduled for removal in https://gitlab.com/gitlab-org/gitlab/-/issues/390194
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/15061
|
||||
milestone: '12.2'
|
||||
gitlab_schema: gitlab_main
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
---
|
||||
table_name: analytics_cycle_analytics_project_value_streams
|
||||
classes:
|
||||
- Analytics::CycleAnalytics::ProjectValueStream
|
||||
feature_categories:
|
||||
- value_stream_management
|
||||
description: Used to store the value stream configurations for projects
|
||||
description: Used to store the value stream configurations for projects. Scheduled for removal in https://gitlab.com/gitlab-org/gitlab/-/issues/390194
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60925
|
||||
milestone: '13.12'
|
||||
gitlab_schema: gitlab_main
|
||||
|
|
|
|||
|
|
@ -14,23 +14,10 @@ class ScheduleVulnerabilitiesFeedbackMigration2 < Gitlab::Database::Migration[2.
|
|||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
TABLE_NAME,
|
||||
BATCH_COLUMN,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
max_batch_size: MAX_BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
# rescheduled by 20230203122602_schedule_vulnerabilities_feedback_migration3.rb
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
TABLE_NAME,
|
||||
BATCH_COLUMN,
|
||||
[]
|
||||
)
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFkIndexToCiResourcesOnPartitionIdAndBuildId < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = :index_ci_resources_on_partition_id_build_id
|
||||
TABLE_NAME = :ci_resources
|
||||
COLUMNS = [:partition_id, :build_id]
|
||||
|
||||
def up
|
||||
add_concurrent_index(TABLE_NAME, COLUMNS, name: INDEX_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddFkToCiResourcesOnPartitionIdAndBuildId < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = :ci_resources
|
||||
TARGET_TABLE_NAME = :ci_builds
|
||||
COLUMN = :build_id
|
||||
TARGET_COLUMN = :id
|
||||
FK_NAME = :fk_e169a8e3d5_p
|
||||
PARTITION_COLUMN = :partition_id
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: [PARTITION_COLUMN, COLUMN],
|
||||
target_column: [PARTITION_COLUMN, TARGET_COLUMN],
|
||||
validate: false,
|
||||
reverse_lock_order: true,
|
||||
on_update: :cascade,
|
||||
on_delete: :nullify,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(SOURCE_TABLE_NAME, name: FK_NAME)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ValidateFkOnCiResourcesPartitionIdAndBuildId < Gitlab::Database::Migration[2.1]
|
||||
TABLE_NAME = :ci_resources
|
||||
FK_NAME = :fk_e169a8e3d5_p
|
||||
COLUMNS = [:partition_id, :build_id]
|
||||
|
||||
def up
|
||||
validate_foreign_key(TABLE_NAME, COLUMNS, name: FK_NAME)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveFkToCiBuildsCiResourcesOnBuildId < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = :ci_resources
|
||||
TARGET_TABLE_NAME = :ci_builds
|
||||
COLUMN = :build_id
|
||||
TARGET_COLUMN = :id
|
||||
FK_NAME = :fk_e169a8e3d5
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(SOURCE_TABLE_NAME, name: FK_NAME)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(
|
||||
SOURCE_TABLE_NAME,
|
||||
TARGET_TABLE_NAME,
|
||||
column: COLUMN,
|
||||
target_column: TARGET_COLUMN,
|
||||
validate: true,
|
||||
reverse_lock_order: true,
|
||||
on_delete: :nullify,
|
||||
name: FK_NAME
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ScheduleVulnerabilitiesFeedbackMigration3 < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = 'MigrateVulnerabilitiesFeedbackToVulnerabilitiesStateTransition'
|
||||
TABLE_NAME = :vulnerability_feedback
|
||||
BATCH_COLUMN = :id
|
||||
DELAY_INTERVAL = 5.minutes
|
||||
BATCH_SIZE = 250
|
||||
MAX_BATCH_SIZE = 250
|
||||
SUB_BATCH_SIZE = 50
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
# Delete the previous jobs
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
TABLE_NAME,
|
||||
BATCH_COLUMN,
|
||||
[]
|
||||
)
|
||||
|
||||
# Reschedule the migration
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
TABLE_NAME,
|
||||
BATCH_COLUMN,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
max_batch_size: MAX_BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
TABLE_NAME,
|
||||
BATCH_COLUMN,
|
||||
[]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
bbf6542b726466ae98323f1e7dd636874e01228ec584166ab617a917822b3fa1
|
||||
|
|
@ -0,0 +1 @@
|
|||
e69eabf71bfdfc9c5aa50829d08b3ef1473e5359d01e08e1bdc94fcbb7c58e6e
|
||||
|
|
@ -0,0 +1 @@
|
|||
6af88109e5186a6a2f18418f441e232757ee0b03cb8af62e72c86ca4d12075c9
|
||||
|
|
@ -0,0 +1 @@
|
|||
49e256cdd550386c989cb6edea22873547b96120cfd8b5652de532dbbe21928c
|
||||
|
|
@ -0,0 +1 @@
|
|||
bb8b177385489eeefda9b8c1e9534398ec759d95fbf46ee3af02a3964a03e1ae
|
||||
|
|
@ -29246,6 +29246,8 @@ CREATE UNIQUE INDEX index_ci_resource_groups_on_project_id_and_key ON ci_resourc
|
|||
|
||||
CREATE INDEX index_ci_resources_on_build_id ON ci_resources USING btree (build_id);
|
||||
|
||||
CREATE INDEX index_ci_resources_on_partition_id_build_id ON ci_resources USING btree (partition_id, build_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_ci_resources_on_resource_group_id_and_build_id ON ci_resources USING btree (resource_group_id, build_id);
|
||||
|
||||
CREATE INDEX index_ci_runner_machines_on_contacted_at_desc_and_id_desc ON ci_runner_machines USING btree (contacted_at DESC, id DESC);
|
||||
|
|
@ -34347,7 +34349,7 @@ ALTER TABLE ONLY issues
|
|||
ADD CONSTRAINT fk_df75a7c8b8 FOREIGN KEY (promoted_to_epic_id) REFERENCES epics(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY ci_resources
|
||||
ADD CONSTRAINT fk_e169a8e3d5 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE SET NULL;
|
||||
ADD CONSTRAINT fk_e169a8e3d5_p FOREIGN KEY (partition_id, build_id) REFERENCES ci_builds(partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY ci_sources_pipelines
|
||||
ADD CONSTRAINT fk_e1bad85861 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
|
||||
|
|
|
|||
|
|
@ -866,6 +866,19 @@ Depending on your installation method, this file is located at:
|
|||
- Omnibus GitLab: `/var/log/gitlab/gitlab-rails/database_load_balancing.log`
|
||||
- Installations from source: `/home/git/gitlab/log/database_load_balancing.log`
|
||||
|
||||
## `zoekt.log` **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110980) in GitLab 15.9.
|
||||
|
||||
This file logs information related to the
|
||||
[Exact code search](../../user/search/exact_code_search.md) feature which is
|
||||
powered by Zoekt.
|
||||
|
||||
Depending on your installation method, this file is located at:
|
||||
|
||||
- Omnibus GitLab: `/var/log/gitlab/gitlab-rails/zoekt.log`
|
||||
- Installations from source: `/home/git/gitlab/log/zoekt.log`
|
||||
|
||||
## `elasticsearch.log` **(PREMIUM SELF)**
|
||||
|
||||
> Introduced in GitLab 12.6.
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ The following API resources are available in the group context:
|
|||
| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
|
||||
| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) |
|
||||
| [Linked epics](linked_epics.md) | `/groups/:id/epics/.../related_epics` |
|
||||
| [Member Roles](member_roles.md) | `/groups/:id/member_roles` |
|
||||
| [Members](members.md) | `/groups/:id/members` (also available for projects) |
|
||||
| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
|
||||
| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) |
|
||||
|
|
|
|||
|
|
@ -50,3 +50,46 @@ GET /projects/:id/merge_requests/:merge_request_iid/draft_notes
|
|||
curl --header "PRIVATE-TOKEN: <your_access_token>" \
|
||||
"https://gitlab.example.com/api/v4/projects/14/merge_requests/11/draft_notes"
|
||||
```
|
||||
|
||||
## Get a single draft note
|
||||
|
||||
Returns a single draft note for a given merge request.
|
||||
|
||||
```plaintext
|
||||
GET /projects/:id/merge_requests/:merge_request_iid/draft_notes/:draft_note_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding).
|
||||
| `draft_note_id` | integer | yes | The ID of a draft note.
|
||||
| `merge_request_iid` | integer | yes | The IID of a project merge request.
|
||||
|
||||
```json
|
||||
{
|
||||
id: 5,
|
||||
author_id: 23,
|
||||
merge_request_id: 11,
|
||||
resolve_discussion: false,
|
||||
discussion_id: nil,
|
||||
note: "Example title",
|
||||
commit_id: nil,
|
||||
line_code: nil,
|
||||
position:
|
||||
{
|
||||
base_sha: nil,
|
||||
start_sha: nil,
|
||||
head_sha: nil,
|
||||
old_path: nil,
|
||||
new_path: nil,
|
||||
position_type: "text",
|
||||
old_line: nil,
|
||||
new_line: nil,
|
||||
line_range: nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/14/merge_requests/11/draft_notes/5"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Authentication and Authorization
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Member roles API **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96996) in GitLab 15.4. [Deployed behind the `customizable_roles` flag](../administration/feature_flags.md), disabled by default.
|
||||
|
||||
## List all member roles of a group
|
||||
|
||||
Gets a list of group member roles viewable by the authenticated user.
|
||||
|
||||
```plaintext
|
||||
GET /groups/:id/member_roles
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
|
||||
If successful, returns [`200`](rest/index.md#status-codes) and the following response attributes:
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|:-------------------------|:---------|:----------------------|
|
||||
| `[].id` | integer | The ID of the member role. |
|
||||
| `[].group_id` | integer | The ID of the group that the member role belongs to. |
|
||||
| `[].base_access_level` | integer | Base access level for member role. |
|
||||
| `[].read_code` | boolean | Permission to read code. |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/member_roles"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"group_id": 84,
|
||||
"base_access_level": 10,
|
||||
"read_code": true
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"group_id": 84,
|
||||
"base_access_level": 10,
|
||||
"read_code": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## Add a member role to a group
|
||||
|
||||
Adds a member role to a group.
|
||||
|
||||
```plaintext
|
||||
POST /groups/:id/member_roles
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `base_access-level` | integer | yes | Base access level for configured role. |
|
||||
| `read_code` | boolean | no | Permission to read code. |
|
||||
|
||||
If successful, returns [`201`](rest/index.md#status-codes) and the following attributes:
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|:-------------------------|:---------|:----------------------|
|
||||
| `id` | integer | The ID of the member role. |
|
||||
| `group_id` | integer | The ID of the group that the member role belongs to. |
|
||||
| `base_access_level` | integer | Base access level for member role. |
|
||||
| `read_code` | boolean | Permission to read code. |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Content-Type: application/json" --header "Authorization: Bearer $YOUR_ACCESS_TOKEN" --data '{"base_access_level" : 10, "read_code" : true}' "https://example.gitlab.com/api/v4/groups/:id/member_roles"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3,
|
||||
"group_id": 84,
|
||||
"base_access_level": 10,
|
||||
"read_code": true
|
||||
}
|
||||
```
|
||||
|
||||
### Remove member role of a group
|
||||
|
||||
Deletes a member role of a group.
|
||||
|
||||
```plaintext
|
||||
DELETE /groups/:id/member_roles/:member_role_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
|
||||
| `member_role_id` | integer | yes | The ID of the member role. |
|
||||
|
||||
If successful, returns [`204`](rest/index.md#status-codes) and an empty response.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request DELETE --header "Content-Type: application/json" --header "Authorization: Bearer $YOUR_ACCESS_TOKEN" "https://example.gitlab.com/api/v4/groups/:group_id/member_roles/:member_role_id"
|
||||
```
|
||||
|
|
@ -47,13 +47,11 @@ of this file) is [here](/doc/architecture/blueprints/_template.md).
|
|||
Blueprint statuses you can use:
|
||||
|
||||
- "proposed"
|
||||
- "ongoing"
|
||||
- "accepted"
|
||||
- "ongoing"
|
||||
- "implemented"
|
||||
- "rejected"
|
||||
|
||||
Any other one-word status should be fine, see which ones have colors defined:
|
||||
https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/content/assets/stylesheets/labels.scss#L22
|
||||
-->
|
||||
|
||||
# {+ Title of Blueprint +}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
status: ready
|
||||
status: accepted
|
||||
creation-date: "2021-11-18"
|
||||
authors: [ "@nolith" ]
|
||||
coach: "@glopezfernandez"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
status: ready
|
||||
status: accepted
|
||||
creation-date: "2022-09-08"
|
||||
authors: [ "@grzesiek", "@marshall007", "@fabiopitino", "@hswimelar" ]
|
||||
coach: "@andrewn"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
status: ready
|
||||
status: ongoing
|
||||
creation-date: "2022-10-27"
|
||||
authors: [ "@pedropombeiro", "@tmaczukin" ]
|
||||
coach: "@ayufan"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Ruby to build and test, but not to run.
|
|||
GitLab Shell runs on `port 22` on an Omnibus installation. To use a regular SSH
|
||||
service, configure it on an alternative port.
|
||||
|
||||
Download and install the current version of Go from [golang.org](https://golang.org/dl/).
|
||||
Download and install the current version of Go from [golang.org](https://go.dev/dl/).
|
||||
We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy)
|
||||
and support:
|
||||
|
||||
|
|
|
|||
|
|
@ -1270,7 +1270,7 @@ This sensitive data must be handled carefully to avoid leaks which could lead to
|
|||
- The [Gitleaks Git hook](https://gitlab.com/gitlab-com/gl-security/security-research/gitleaks-endpoint-installer) is recommended for preventing credentials from being committed.
|
||||
- Never log credentials under any circumstance. Issue [#353857](https://gitlab.com/gitlab-org/gitlab/-/issues/353857) is an example of credential leaks through log file.
|
||||
- When credentials are required in a CI/CD job, use [masked variables](../ci/variables/index.md#mask-a-cicd-variable) to help prevent accidental exposure in the job logs. Be aware that when [debug logging](../ci/variables/index.md#enable-debug-logging) is enabled, all masked CI/CD variables are visible in job logs. Also consider using [protected variables](../ci/variables/index.md#protect-a-cicd-variable) when possible so that sensitive CI/CD variables are only available to pipelines running on protected branches or protected tags.
|
||||
- Proper scanners must be enabled depending on what data those credentials are protecting. See the [Application Security Inventory Policy](https://about.gitlab.com/handbook/security/security-engineering-and-research/application-security/inventory.html#policies) and our [Data Classification Standards](https://about.gitlab.com/handbook/security/data-classification-standard.html#data-classification-standards).
|
||||
- Proper scanners must be enabled depending on what data those credentials are protecting. See the [Application Security Inventory Policy](https://about.gitlab.com/handbook/security/security-engineering/application-security/inventory.html#policies) and our [Data Classification Standards](https://about.gitlab.com/handbook/security/data-classification-standard.html#data-classification-standards).
|
||||
- To store and/or share credentials between teams, refer to [1Password for Teams](https://about.gitlab.com/handbook/security/#1password-for-teams) and follow [the 1Password Guidelines](https://about.gitlab.com/handbook/security/#1password-guidelines).
|
||||
- If you need to share a secret with a team member, use 1Password. Do not share a secret over email, Slack, or other service on the Internet.
|
||||
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ considered legacy, which will be phased out at some point.
|
|||
|
||||
- Rails Controller (`Analytics::CycleAnalytics` module): Value stream analytics exposes its data via JSON endpoints, implemented within the `analytics` workspace. Configuring the stages are also implements JSON endpoints (CRUD).
|
||||
- Services (`Analytics::CycleAnalytics` module): All `Stage` related actions are delegated to respective service objects.
|
||||
- Models (`Analytics::CycleAnalytics` module): Models are used to persist the `Stage` objects `ProjectStage` and `Stage`.
|
||||
- Models (`Analytics::CycleAnalytics` module): Models are used to persist the `Stage` objects.
|
||||
- Feature classes (`Gitlab::Analytics::CycleAnalytics` module):
|
||||
- Responsible for composing queries and define feature specific business logic.
|
||||
- `DataCollector`, `Event`, `StageEvents`, etc.
|
||||
|
|
|
|||
|
|
@ -3,8 +3,13 @@ stage: Monitor
|
|||
group: Respond
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
<!--- start_remove The following content will be removed on remove_date: '2023-08-22' -->
|
||||
# Embed Grafana panels in Markdown (deprecated) **(FREE)**
|
||||
|
||||
# Embed Grafana panels in Markdown **(FREE)**
|
||||
WARNING:
|
||||
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110290) in GitLab 15.9
|
||||
and is planned for removal in 16.0. We intend to replace this feature with the ability to [embed charts](https://gitlab.com/groups/gitlab-org/opstrace/-/epics/33) with the [GitLab Observability UI](https://gitlab.com/gitlab-org/opstrace/opstrace-ui).
|
||||
This change is a breaking change.
|
||||
|
||||
Grafana panels can be embedded in [GitLab Flavored Markdown](../../user/markdown.md). You can
|
||||
embed Grafana panels using either:
|
||||
|
|
@ -83,3 +88,4 @@ To generate a link to a panel:
|
|||
See the following example of a rendered panel.
|
||||
|
||||

|
||||
<!--- end_remove -->
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ Align your work across teams.
|
|||
|
||||
Use these day-to-day planning features.
|
||||
|
||||
- [Your work sidebar](../topics/your_work.md)
|
||||
- [Keyboard shortcuts](../user/shortcuts.md)
|
||||
- [Quick actions](../user/project/quick_actions.md)
|
||||
- [Markdown](../user/markdown.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Foundations
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Your work sidebar
|
||||
|
||||
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384342) in GitLab 15.9.
|
||||
|
||||
The **Your work** left sidebar provides access to your:
|
||||
|
||||
- [Projects](../user/project/working_with_projects.md#view-projects)
|
||||
- [Groups](../user/group/index.md)
|
||||
- [Issues](../user/project/issues/index.md)
|
||||
- [Merge requests](../user/project/merge_requests/index.md)
|
||||
- [To-do List](../user/todos.md)
|
||||
- [Milestones](../user/project/milestones/index.md)
|
||||
|
|
@ -24,6 +24,13 @@ add `#xxx` to the commit message, where `xxx` is the issue number.
|
|||
git commit -m "this is my commit message. Ref #xxx"
|
||||
```
|
||||
|
||||
Since commit messages cannot usually begin with a `#` character, you may use
|
||||
the alternative `GL-xxx` notation as well:
|
||||
|
||||
```shell
|
||||
git commit -m "GL-xxx: this is my commit message"
|
||||
```
|
||||
|
||||
If they are in different projects, but in the same group,
|
||||
add `projectname#xxx` to the commit message.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ do more day-to-day tasks in Visual Studio Code, such as:
|
|||
from the Visual Studio Code [command palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette).
|
||||
- Create and [review](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#merge-request-reviews)
|
||||
merge requests directly from Visual Studio Code.
|
||||
- [Validate your GitLab CI configuration](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#validate-gitlab-ci-configuration).
|
||||
- [Validate your GitLab CI/CD configuration](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#validate-gitlab-cicd-configuration).
|
||||
- [View the status of your pipeline](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#information-about-your-branch-pipelines-mr-closing-issue).
|
||||
- [View the output of CI/CD jobs](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#view-the-job-output).
|
||||
- [Create](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#create-snippet)
|
||||
and paste snippets to, and from, your editor.
|
||||
- [Browse repositories](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow#browse-a-repository-without-cloning)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module Gitlab
|
|||
def initialize(stage:, params: {})
|
||||
@stage = stage
|
||||
@params = params
|
||||
@root_ancestor = stage.parent.root_ancestor
|
||||
@root_ancestor = stage.namespace.root_ancestor
|
||||
@stage_event_model = MODEL_CLASSES.fetch(stage.subject_class.to_s)
|
||||
end
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def filter_by_stage_parent(query)
|
||||
query.by_project_id(stage.parent_id)
|
||||
query.by_project_id(stage.namespace.project.id)
|
||||
end
|
||||
|
||||
def base_query
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
module CycleAnalytics
|
||||
module Aggregated
|
||||
# Arguments:
|
||||
# stage - an instance of CycleAnalytics::ProjectStage or CycleAnalytics::Stage
|
||||
# stage - an instance of CycleAnalytics::Stage
|
||||
# params:
|
||||
# current_user: an instance of User
|
||||
# from: DateTime
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module Gitlab
|
|||
module Analytics
|
||||
module CycleAnalytics
|
||||
# Arguments:
|
||||
# stage - an instance of CycleAnalytics::ProjectStage or CycleAnalytics::Stage
|
||||
# stage - an instance of CycleAnalytics::Stage
|
||||
# params:
|
||||
# current_user: an instance of User
|
||||
# from: DateTime
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# Example:
|
||||
#
|
||||
# params = Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_issue_stage
|
||||
# Analytics::CycleAnalytics::ProjectStage.new(params)
|
||||
# Analytics::CycleAnalytics::Stage.new(params)
|
||||
module Gitlab
|
||||
module Analytics
|
||||
module CycleAnalytics
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ module Gitlab
|
|||
return unless value_stream
|
||||
|
||||
strong_memoize(:stage) do
|
||||
::Analytics::CycleAnalytics::StageFinder.new(parent: project || group, stage_id: stage_id).execute if stage_id
|
||||
::Analytics::CycleAnalytics::StageFinder.new(parent: project&.project_namespace || group, stage_id: stage_id).execute if stage_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12276,9 +12276,6 @@ msgstr ""
|
|||
msgid "CycleAnalyticsStage|is not available for the selected group"
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalyticsStage|should be under a group"
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|%{selectedLabelsCount} selected (%{maxLabels} max)"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Taken from Jekyll
|
||||
# https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13
|
||||
YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m.freeze
|
||||
READ_LIMIT_BYTES = 1024
|
||||
|
||||
require 'yaml'
|
||||
|
||||
def extract_front_matter(path)
|
||||
File.open(path, 'r') do |f|
|
||||
data = if match = YAML_FRONT_MATTER_REGEXP.match(f.read(READ_LIMIT_BYTES))
|
||||
YAML.safe_load(match[1])
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
BlueprintFrontMatter.new(data)
|
||||
end
|
||||
end
|
||||
|
||||
class BlueprintFrontMatter
|
||||
STATUSES = %w[proposed accepted ongoing implemented rejected]
|
||||
|
||||
attr_reader :errors
|
||||
|
||||
def initialize(metadata)
|
||||
@metadata = metadata
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def validate
|
||||
validate_status
|
||||
validate_authors
|
||||
validate_creation_date
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_status
|
||||
status = @metadata['status']
|
||||
|
||||
add_error('Missing status') unless status
|
||||
|
||||
return if STATUSES.include?(status)
|
||||
|
||||
add_error("Unsupported status '#{status}': expected one of '#{STATUSES.join(', ')}'")
|
||||
end
|
||||
|
||||
def validate_authors
|
||||
authors = @metadata['authors']
|
||||
|
||||
add_error('Missing authors') unless authors
|
||||
add_error('Authors must be an array') unless authors.is_a?(Array)
|
||||
end
|
||||
|
||||
def validate_creation_date
|
||||
return if @metadata['creation-date'] =~ /\d{4}-[01]\d-[0123]\d/
|
||||
|
||||
add_error("Invalid creation-date: the date format must be 'yyyy-mm-dd'")
|
||||
end
|
||||
|
||||
def add_error(msg)
|
||||
@errors << msg
|
||||
end
|
||||
end
|
||||
|
||||
if $PROGRAM_NAME == __FILE__
|
||||
exit_code = 0
|
||||
|
||||
Dir['doc/architecture/blueprints/*/index.md'].each do |blueprint|
|
||||
meta = extract_front_matter(blueprint)
|
||||
meta.validate
|
||||
|
||||
next if meta.errors.empty?
|
||||
|
||||
exit_code = 1
|
||||
|
||||
puts("✖ ERROR: Invalid #{blueprint}:")
|
||||
meta.errors.each { |e| puts(" - #{e}") }
|
||||
end
|
||||
|
||||
exit(exit_code)
|
||||
end
|
||||
|
|
@ -46,7 +46,7 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
ci_pending_builds: %w[partition_id],
|
||||
ci_pipeline_variables: %w[partition_id],
|
||||
ci_pipelines: %w[partition_id],
|
||||
ci_resources: %w[partition_id],
|
||||
ci_resources: %w[partition_id build_id],
|
||||
ci_runner_projects: %w[runner_id],
|
||||
ci_running_builds: %w[partition_id],
|
||||
ci_sources_pipelines: %w[partition_id source_partition_id],
|
||||
|
|
@ -187,7 +187,6 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
# These pre-existing enums have limits > 2 bytes
|
||||
IGNORED_LIMIT_ENUMS = {
|
||||
'Analytics::CycleAnalytics::Stage' => %w[start_event_identifier end_event_identifier],
|
||||
'Analytics::CycleAnalytics::ProjectStage' => %w[start_event_identifier end_event_identifier],
|
||||
'Ci::Bridge' => %w[failure_reason],
|
||||
'Ci::Build' => %w[failure_reason],
|
||||
'Ci::BuildMetadata' => %w[timeout_source],
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :cycle_analytics_project_stage, class: 'Analytics::CycleAnalytics::ProjectStage' do
|
||||
project
|
||||
sequence(:name) { |n| "Stage ##{n}" }
|
||||
hidden { false }
|
||||
issue_stage
|
||||
value_stream { association(:cycle_analytics_project_value_stream, project: project) }
|
||||
|
||||
trait :issue_stage do
|
||||
start_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated.identifier }
|
||||
end_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::IssueStageEnd.identifier }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :cycle_analytics_project_value_stream, class: 'Analytics::CycleAnalytics::ProjectValueStream' do
|
||||
sequence(:name) { |n| "Value Stream ##{n}" }
|
||||
|
||||
project
|
||||
end
|
||||
end
|
||||
|
|
@ -2,11 +2,19 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :cycle_analytics_stage, class: 'Analytics::CycleAnalytics::Stage' do
|
||||
transient do
|
||||
project { nil }
|
||||
end
|
||||
|
||||
sequence(:name) { |n| "Stage ##{n}" }
|
||||
start_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated.identifier }
|
||||
end_event_identifier { Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged.identifier }
|
||||
|
||||
namespace { association(:group) }
|
||||
value_stream { association(:cycle_analytics_value_stream, namespace: namespace) }
|
||||
|
||||
after(:build) do |stage, evaluator|
|
||||
stage.namespace = evaluator.project.reload.project_namespace if evaluator.project
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe Analytics::CycleAnalytics::StageFinder do
|
|||
|
||||
let(:stage_id) { { id: Gitlab::Analytics::CycleAnalytics::DefaultStages.names.first } }
|
||||
|
||||
subject { described_class.new(parent: project, stage_id: stage_id[:id]).execute }
|
||||
subject { described_class.new(parent: project.project_namespace, stage_id: stage_id[:id]).execute }
|
||||
|
||||
context 'when looking up in-memory default stage by name exists' do
|
||||
it { expect(subject).not_to be_persisted }
|
||||
|
|
|
|||
|
|
@ -3,35 +3,57 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ProtectedBranchesFinder do
|
||||
let(:project) { create(:project) }
|
||||
let!(:protected_branch) { create(:protected_branch, project: project) }
|
||||
let!(:another_protected_branch) { create(:protected_branch, project: project) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, namespace: group) }
|
||||
|
||||
let!(:project_protected_branch) { create(:protected_branch, project: project) }
|
||||
let!(:another_project_protected_branch) { create(:protected_branch, project: project) }
|
||||
let!(:group_protected_branch) { create(:protected_branch, project: nil, group: group) }
|
||||
let!(:another_group_protected_branch) { create(:protected_branch, project: nil, group: group) }
|
||||
let!(:other_protected_branch) { create(:protected_branch) }
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
subject { described_class.new(entity, params).execute }
|
||||
|
||||
describe '#execute' do
|
||||
subject { described_class.new(project, params).execute }
|
||||
shared_examples 'execute by entity' do
|
||||
it 'returns all protected branches of project by default' do
|
||||
expect(subject).to match_array(expected_branches)
|
||||
end
|
||||
|
||||
it 'returns all protected branches of project by default' do
|
||||
expect(subject).to match_array([protected_branch, another_protected_branch])
|
||||
end
|
||||
context 'when search param is present' do
|
||||
let(:params) { { search: group_protected_branch.name } }
|
||||
|
||||
context 'when search param is present' do
|
||||
let(:params) { { search: protected_branch.name } }
|
||||
it 'filters by search param' do
|
||||
expect(subject).to eq([group_protected_branch])
|
||||
end
|
||||
end
|
||||
|
||||
it 'filters by search param' do
|
||||
expect(subject).to eq([protected_branch])
|
||||
context 'when there are more protected branches than the limit' do
|
||||
before do
|
||||
stub_const("#{described_class}::LIMIT", 1)
|
||||
end
|
||||
|
||||
it 'returns limited protected branches of project' do
|
||||
expect(subject.count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there are more protected branches than the limit' do
|
||||
before do
|
||||
stub_const("#{described_class}::LIMIT", 1)
|
||||
it_behaves_like 'execute by entity' do
|
||||
let(:entity) { project }
|
||||
let(:expected_branches) do
|
||||
[
|
||||
project_protected_branch, another_project_protected_branch,
|
||||
group_protected_branch, another_group_protected_branch
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns limited protected branches of project' do
|
||||
expect(subject.count).to eq(1)
|
||||
end
|
||||
it_behaves_like 'execute by entity' do
|
||||
let(:entity) { group }
|
||||
let(:expected_branches) { [group_protected_branch, another_group_protected_branch] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ window.gl = window.gl || {};
|
|||
gl.utils = gl.utils || {};
|
||||
gl.utils.disableButtonIfEmptyField = () => {};
|
||||
|
||||
function wrappedDiscussionNote(note) {
|
||||
return `<table><tbody>${note}</tbody></table>`;
|
||||
}
|
||||
|
||||
// the following test is unreliable and failing in main 2-3 times a day
|
||||
// see https://gitlab.com/gitlab-org/gitlab/issues/206906#note_290602581
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
|
|
@ -436,22 +440,40 @@ describe.skip('Old Notes (~/deprecated_notes.js)', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should append to row selected with line_code', () => {
|
||||
$form.length = 0;
|
||||
note.discussion_line_code = 'line_code';
|
||||
note.diff_discussion_html = '<tr></tr>';
|
||||
describe('HTML output', () => {
|
||||
let line;
|
||||
|
||||
const line = document.createElement('div');
|
||||
line.id = note.discussion_line_code;
|
||||
document.body.appendChild(line);
|
||||
beforeEach(() => {
|
||||
$form.length = 0;
|
||||
note.discussion_line_code = 'line_code';
|
||||
note.diff_discussion_html = '<tr></tr>';
|
||||
|
||||
// Override mocks for this single test
|
||||
$form.closest.mockReset();
|
||||
$form.closest.mockReturnValue($form);
|
||||
line = document.createElement('div');
|
||||
line.id = note.discussion_line_code;
|
||||
document.body.appendChild(line);
|
||||
|
||||
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
||||
// Override mocks for these tests
|
||||
$form.closest.mockReset();
|
||||
$form.closest.mockReturnValue($form);
|
||||
});
|
||||
|
||||
expect(line.nextSibling.outerHTML).toEqual(note.diff_discussion_html);
|
||||
it('should append to row selected with line_code', () => {
|
||||
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
||||
|
||||
expect(line.nextSibling.outerHTML).toEqual(
|
||||
wrappedDiscussionNote(note.diff_discussion_html),
|
||||
);
|
||||
});
|
||||
|
||||
it('sanitizes the output html without stripping leading <tr> or <td> elements', () => {
|
||||
const sanitizedDiscussion = '<tr><td><a>I am a dolphin!</a></td></tr>';
|
||||
note.diff_discussion_html =
|
||||
'<tr><td><a href="javascript:alert(1)">I am a dolphin!</a></td></tr>';
|
||||
|
||||
Notes.prototype.renderDiscussionNote.call(notes, note, $form);
|
||||
|
||||
expect(line.nextSibling.outerHTML).toEqual(wrappedDiscussionNote(sanitizedDiscussion));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -47,57 +47,55 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
end
|
||||
|
||||
context 'internal reference' do
|
||||
let(:reference) { "##{issue.iid}" }
|
||||
|
||||
shared_examples 'an internal reference' do
|
||||
it_behaves_like 'a reference containing an element node'
|
||||
|
||||
it_behaves_like 'a reference with issue type information'
|
||||
|
||||
it 'links to a valid reference' do
|
||||
doc = reference_filter("Fixed #{reference}")
|
||||
doc = reference_filter("Fixed #{written_reference}")
|
||||
|
||||
expect(doc.css('a').first.attr('href'))
|
||||
.to eq issue_url
|
||||
end
|
||||
|
||||
it 'links with adjacent text' do
|
||||
doc = reference_filter("Fixed (#{reference}.)")
|
||||
doc = reference_filter("Fixed (#{written_reference}.)")
|
||||
expect(doc.text).to eql("Fixed (#{reference}.)")
|
||||
end
|
||||
|
||||
it 'ignores invalid issue IDs' do
|
||||
invalid = invalidate_reference(reference)
|
||||
invalid = invalidate_reference(written_reference)
|
||||
exp = act = "Fixed #{invalid}"
|
||||
|
||||
expect(reference_filter(act).to_html).to eq exp
|
||||
end
|
||||
|
||||
it 'includes a title attribute' do
|
||||
doc = reference_filter("Issue #{reference}")
|
||||
doc = reference_filter("Issue #{written_reference}")
|
||||
expect(doc.css('a').first.attr('title')).to eq issue.title
|
||||
end
|
||||
|
||||
it 'escapes the title attribute' do
|
||||
issue.update_attribute(:title, %{"></a>whatever<a title="})
|
||||
|
||||
doc = reference_filter("Issue #{reference}")
|
||||
doc = reference_filter("Issue #{written_reference}")
|
||||
expect(doc.text).to eq "Issue #{reference}"
|
||||
end
|
||||
|
||||
it 'renders non-HTML tooltips' do
|
||||
doc = reference_filter("Issue #{reference}")
|
||||
doc = reference_filter("Issue #{written_reference}")
|
||||
|
||||
expect(doc.at_css('a')).not_to have_attribute('data-html')
|
||||
end
|
||||
|
||||
it 'includes default classes' do
|
||||
doc = reference_filter("Issue #{reference}")
|
||||
doc = reference_filter("Issue #{written_reference}")
|
||||
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue'
|
||||
end
|
||||
|
||||
it 'includes a data-project attribute' do
|
||||
doc = reference_filter("Issue #{reference}")
|
||||
doc = reference_filter("Issue #{written_reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-project')
|
||||
|
|
@ -105,7 +103,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
|
||||
it 'includes a data-issue attribute' do
|
||||
doc = reference_filter("See #{reference}")
|
||||
doc = reference_filter("See #{written_reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-issue')
|
||||
|
|
@ -113,7 +111,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
|
||||
it 'includes data attributes for issuable popover' do
|
||||
doc = reference_filter("See #{reference}")
|
||||
doc = reference_filter("See #{written_reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link.attr('data-project-path')).to eq project.full_path
|
||||
|
|
@ -121,21 +119,21 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
|
||||
it 'includes a data-original attribute' do
|
||||
doc = reference_filter("See #{reference}")
|
||||
doc = reference_filter("See #{written_reference}")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-original')
|
||||
expect(link.attr('data-original')).to eq reference
|
||||
expect(link.attr('data-original')).to eq written_reference
|
||||
end
|
||||
|
||||
it 'does not escape the data-original attribute' do
|
||||
inner_html = 'element <code>node</code> inside'
|
||||
doc = reference_filter(%{<a href="#{reference}">#{inner_html}</a>})
|
||||
doc = reference_filter(%{<a href="#{written_reference}">#{inner_html}</a>})
|
||||
expect(doc.children.first.attr('data-original')).to eq inner_html
|
||||
end
|
||||
|
||||
it 'includes a data-reference-format attribute' do
|
||||
doc = reference_filter("Issue #{reference}+")
|
||||
doc = reference_filter("Issue #{written_reference}+")
|
||||
link = doc.css('a').first
|
||||
|
||||
expect(link).to have_attribute('data-reference-format')
|
||||
|
|
@ -153,7 +151,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
|
||||
it 'supports an :only_path context' do
|
||||
doc = reference_filter("Issue #{reference}", only_path: true)
|
||||
doc = reference_filter("Issue #{written_reference}", only_path: true)
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
expect(link).not_to match %r(https?://)
|
||||
|
|
@ -161,7 +159,7 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
|
||||
it 'does not process links containing issue numbers followed by text' do
|
||||
href = "#{reference}st"
|
||||
href = "#{written_reference}st"
|
||||
doc = reference_filter("<a href='#{href}'></a>")
|
||||
link = doc.css('a').first.attr('href')
|
||||
|
||||
|
|
@ -169,6 +167,20 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
|
|||
end
|
||||
end
|
||||
|
||||
context 'standard internal reference' do
|
||||
let(:written_reference) { "##{issue.iid}" }
|
||||
let(:reference) { "##{issue.iid}" }
|
||||
|
||||
it_behaves_like 'an internal reference'
|
||||
end
|
||||
|
||||
context 'alternative internal_reference' do
|
||||
let(:written_reference) { "GL-#{issue.iid}" }
|
||||
let(:reference) { "##{issue.iid}" }
|
||||
|
||||
it_behaves_like 'an internal reference'
|
||||
end
|
||||
|
||||
context 'cross-project / cross-namespace complete reference' do
|
||||
let(:reference) { "#{project2.full_path}##{issue.iid}" }
|
||||
let(:issue) { create(:issue, project: project2) }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Aggregated::BaseQueryBuilder d
|
|||
let_it_be(:issue_outside_project) { create(:issue) }
|
||||
|
||||
let_it_be(:stage) do
|
||||
create(:cycle_analytics_project_stage,
|
||||
create(:cycle_analytics_stage,
|
||||
project: project,
|
||||
start_event_identifier: :issue_created,
|
||||
end_event_identifier: :issue_deployed_to_production
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Aggregated::RecordsFetcher do
|
|||
let_it_be(:issue_2) { create(:issue, project: project) }
|
||||
let_it_be(:issue_3) { create(:issue, project: project) }
|
||||
|
||||
let_it_be(:stage) { create(:cycle_analytics_project_stage, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production, project: project) }
|
||||
let_it_be(:stage) { create(:cycle_analytics_stage, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production, namespace: project.reload.project_namespace) }
|
||||
|
||||
let_it_be(:stage_event_1) { create(:cycle_analytics_issue_stage_event, stage_event_hash_id: stage.stage_event_hash_id, project_id: project.id, 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, stage_event_hash_id: stage.stage_event_hash_id, project_id: project.id, issue_id: issue_2.id, start_event_timestamp: 5.years.ago, end_event_timestamp: 2.years.ago) } # duration: 3 years
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Average do
|
|||
|
||||
let(:stage) do
|
||||
build(
|
||||
:cycle_analytics_project_stage,
|
||||
:cycle_analytics_stage,
|
||||
start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated.identifier,
|
||||
end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit.identifier,
|
||||
project: project
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder do
|
|||
|
||||
let(:params) { { current_user: user } }
|
||||
let(:records) do
|
||||
stage = build(:cycle_analytics_project_stage, {
|
||||
stage = build(:cycle_analytics_stage, {
|
||||
start_event_identifier: :merge_request_created,
|
||||
end_event_identifier: :merge_request_merged,
|
||||
project: project
|
||||
namespace: project.reload.project_namespace
|
||||
})
|
||||
described_class.new(stage: stage, params: params).build.to_a
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::Median do
|
|||
|
||||
let(:stage) do
|
||||
build(
|
||||
:cycle_analytics_project_stage,
|
||||
:cycle_analytics_stage,
|
||||
start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated.identifier,
|
||||
end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged.identifier,
|
||||
project: project
|
||||
namespace: project.reload.project_namespace
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
|
|||
let_it_be(:issue2) { create(:issue, project: project, confidential: true) }
|
||||
|
||||
let(:stage) do
|
||||
build(:cycle_analytics_project_stage, {
|
||||
build(:cycle_analytics_stage, {
|
||||
start_event_identifier: :plan_stage_start,
|
||||
end_event_identifier: :issue_first_mentioned_in_commit,
|
||||
project: project
|
||||
|
|
@ -88,7 +88,7 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
|
|||
let(:mr1) { create(:merge_request, created_at: 5.days.ago, source_project: project, allow_broken: true) }
|
||||
let(:mr2) { create(:merge_request, created_at: 4.days.ago, source_project: project, allow_broken: true) }
|
||||
let(:stage) do
|
||||
build(:cycle_analytics_project_stage, {
|
||||
build(:cycle_analytics_stage, {
|
||||
start_event_identifier: :merge_request_created,
|
||||
end_event_identifier: :merge_request_merged,
|
||||
project: project
|
||||
|
|
@ -110,10 +110,10 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
|
|||
let_it_be(:issue3) { create(:issue, project: project) }
|
||||
|
||||
let(:stage) do
|
||||
build(:cycle_analytics_project_stage, {
|
||||
build(:cycle_analytics_stage, {
|
||||
start_event_identifier: :plan_stage_start,
|
||||
end_event_identifier: :issue_first_mentioned_in_commit,
|
||||
project: project
|
||||
namespace: project.project_namespace
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Analytics::CycleAnalytics::Sorting do
|
||||
let(:stage) { build(:cycle_analytics_project_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
let(:stage) { build(:cycle_analytics_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
|
||||
subject(:order_values) { described_class.new(query: MergeRequest.joins(:metrics), stage: stage).apply(sort, direction).order_values }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe ScheduleVulnerabilitiesFeedbackMigration2, feature_category: :vulnerability_management do
|
||||
RSpec.describe ScheduleVulnerabilitiesFeedbackMigration3, feature_category: :vulnerability_management do
|
||||
let(:migration) { described_class::MIGRATION }
|
||||
|
||||
describe '#up' do
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::ProjectStage do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project).required }
|
||||
end
|
||||
|
||||
it 'default stages must be valid' do
|
||||
project = build(:project)
|
||||
|
||||
Gitlab::Analytics::CycleAnalytics::DefaultStages.all.each do |params|
|
||||
stage = described_class.new(params.merge(project: project))
|
||||
expect(stage).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'value stream analytics stage' do
|
||||
let(:factory) { :cycle_analytics_project_stage }
|
||||
let(:parent) { build(:project) }
|
||||
let(:parent_name) { :project }
|
||||
end
|
||||
|
||||
describe '.distinct_stages_within_hierarchy' do
|
||||
let_it_be(:top_level_group) { create(:group) }
|
||||
let_it_be(:sub_group_1) { create(:group, parent: top_level_group) }
|
||||
let_it_be(:sub_group_2) { create(:group, parent: sub_group_1) }
|
||||
|
||||
let_it_be(:project_1) { create(:project, group: sub_group_1) }
|
||||
let_it_be(:project_2) { create(:project, group: sub_group_2) }
|
||||
let_it_be(:project_3) { create(:project, group: top_level_group) }
|
||||
|
||||
let_it_be(:stage1) { create(:cycle_analytics_project_stage, project: project_1, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production) }
|
||||
let_it_be(:stage2) { create(:cycle_analytics_project_stage, project: project_3, start_event_identifier: :issue_created, end_event_identifier: :issue_deployed_to_production) }
|
||||
|
||||
let_it_be(:stage3) { create(:cycle_analytics_project_stage, project: project_1, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
let_it_be(:stage4) { create(:cycle_analytics_project_stage, project: project_3, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
|
||||
subject(:distinct_start_and_end_event_identifiers) { described_class.distinct_stages_within_hierarchy(top_level_group).to_a.pluck(:start_event_identifier, :end_event_identifier) }
|
||||
|
||||
it 'returns distinct stages by start and end events (using stage_event_hash_id)' do
|
||||
expect(distinct_start_and_end_event_identifiers).to match_array(
|
||||
[
|
||||
%w[issue_created issue_deployed_to_production],
|
||||
%w[merge_request_created merge_request_merged]
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::ProjectValueStream, type: :model do
|
||||
describe 'associations' do
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to have_many(:stages) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:project) }
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(100) }
|
||||
|
||||
it 'validates uniqueness of name' do
|
||||
project = create(:project)
|
||||
create(:cycle_analytics_project_value_stream, name: 'test', project: project)
|
||||
|
||||
value_stream = build(:cycle_analytics_project_value_stream, name: 'test', project: project)
|
||||
|
||||
expect(value_stream).to be_invalid
|
||||
expect(value_stream.errors.messages).to eq(name: [I18n.t('errors.messages.taken')])
|
||||
end
|
||||
end
|
||||
|
||||
it 'is not custom' do
|
||||
expect(described_class.new).not_to be_custom
|
||||
end
|
||||
|
||||
describe '.build_default_value_stream' do
|
||||
it 'builds the default value stream' do
|
||||
project = build(:project)
|
||||
|
||||
value_stream = described_class.build_default_value_stream(project)
|
||||
expect(value_stream.name).to eq('default')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,7 +7,6 @@ RSpec.describe Analytics::CycleAnalytics::StageEventHash, type: :model do
|
|||
let(:hash_sha256) { 'does_not_matter' }
|
||||
|
||||
describe 'associations' do
|
||||
it { is_expected.to have_many(:cycle_analytics_project_stages) }
|
||||
it { is_expected.to have_many(:cycle_analytics_stages) }
|
||||
end
|
||||
|
||||
|
|
@ -31,7 +30,7 @@ RSpec.describe Analytics::CycleAnalytics::StageEventHash, type: :model do
|
|||
end
|
||||
|
||||
describe '.cleanup_if_unused' do
|
||||
it 'removes the record if there is no project or group stages with given stage events hash' do
|
||||
it 'removes the record if there is no stages with given stage events hash' do
|
||||
described_class.cleanup_if_unused(stage_event_hash.id)
|
||||
|
||||
expect(described_class.find_by_id(stage_event_hash.id)).to be_nil
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ RSpec.describe CycleAnalytics::ProjectLevelStageAdapter, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
let_it_be(:project) { merge_request.target_project }
|
||||
let_it_be(:project) { merge_request.target_project.reload }
|
||||
|
||||
let(:stage) do
|
||||
params = Gitlab::Analytics::CycleAnalytics::DefaultStages.find_by_name!(stage_name).merge(project: project)
|
||||
Analytics::CycleAnalytics::ProjectStage.new(params)
|
||||
params = Gitlab::Analytics::CycleAnalytics::DefaultStages
|
||||
.find_by_name!(stage_name)
|
||||
.merge(namespace: project.project_namespace)
|
||||
|
||||
Analytics::CycleAnalytics::Stage.new(params)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
|
|
|
|||
|
|
@ -121,8 +121,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do
|
|||
it { is_expected.to have_many(:lfs_file_locks) }
|
||||
it { is_expected.to have_many(:project_deploy_tokens) }
|
||||
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
|
||||
it { is_expected.to have_many(:cycle_analytics_stages).inverse_of(:project) }
|
||||
it { is_expected.to have_many(:value_streams).inverse_of(:project) }
|
||||
it { is_expected.to have_many(:external_pull_requests) }
|
||||
it { is_expected.to have_many(:sourced_pipelines) }
|
||||
it { is_expected.to have_many(:source_pipelines) }
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Analytics::CycleAnalytics::StageEntity do
|
||||
let(:stage) { build(:cycle_analytics_project_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
let(:stage) { build(:cycle_analytics_stage, start_event_identifier: :merge_request_created, end_event_identifier: :merge_request_merged) }
|
||||
|
||||
subject(:entity_json) { described_class.new(Analytics::CycleAnalytics::StagePresenter.new(stage)).as_json }
|
||||
|
||||
|
|
|
|||
|
|
@ -13,14 +13,21 @@ module Ci
|
|||
def public_image_manifest(registry, repository, reference)
|
||||
token = public_image_repository_token(registry, repository)
|
||||
|
||||
headers = {
|
||||
'Authorization' => "Bearer #{token}",
|
||||
'Accept' => 'application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json'
|
||||
}
|
||||
response = with_net_connect_allowed do
|
||||
Gitlab::HTTP.get(image_manifest_url(registry, repository, reference),
|
||||
headers: { 'Authorization' => "Bearer #{token}" })
|
||||
Gitlab::HTTP.get(image_manifest_url(registry, repository, reference), headers: headers)
|
||||
end
|
||||
|
||||
return unless response.success?
|
||||
|
||||
Gitlab::Json.parse(response.body)
|
||||
if response.success?
|
||||
Gitlab::Json.parse(response.body)
|
||||
elsif response.not_found?
|
||||
nil
|
||||
else
|
||||
raise "Could not retrieve manifest: #{response.body}"
|
||||
end
|
||||
end
|
||||
|
||||
def public_image_repository_token(registry, repository)
|
||||
|
|
@ -31,17 +38,17 @@ module Ci
|
|||
Gitlab::HTTP.get(image_manifest_url(registry, repository, 'latest'))
|
||||
end
|
||||
|
||||
return unless response.unauthorized?
|
||||
raise 'Unauthorized' unless response.unauthorized?
|
||||
|
||||
www_authenticate = response.headers['www-authenticate']
|
||||
return unless www_authenticate
|
||||
raise 'Missing www-authenticate' unless www_authenticate
|
||||
|
||||
realm, service, scope = www_authenticate.split(',').map { |s| s[/\w+="(.*)"/, 1] }
|
||||
token_response = with_net_connect_allowed do
|
||||
Gitlab::HTTP.get(realm, query: { service: service, scope: scope })
|
||||
end
|
||||
|
||||
return unless token_response.success?
|
||||
raise "Could not get token: #{token_response.body}" unless token_response.success?
|
||||
|
||||
token_response['token']
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1636,7 +1636,6 @@
|
|||
- './ee/spec/models/allowed_email_domain_spec.rb'
|
||||
- './ee/spec/models/analytics/cycle_analytics/aggregation_context_spec.rb'
|
||||
- './ee/spec/models/analytics/cycle_analytics/group_level_spec.rb'
|
||||
- './ee/spec/models/analytics/cycle_analytics/project_stage_spec.rb'
|
||||
- './ee/spec/models/analytics/cycle_analytics/runtime_limiter_spec.rb'
|
||||
- './ee/spec/models/analytics/devops_adoption/enabled_namespace_spec.rb'
|
||||
- './ee/spec/models/analytics/devops_adoption/snapshot_spec.rb'
|
||||
|
|
@ -7729,7 +7728,6 @@
|
|||
- './spec/models/analytics/cycle_analytics/aggregation_spec.rb'
|
||||
- './spec/models/analytics/cycle_analytics/issue_stage_event_spec.rb'
|
||||
- './spec/models/analytics/cycle_analytics/merge_request_stage_event_spec.rb'
|
||||
- './spec/models/analytics/cycle_analytics/project_stage_spec.rb'
|
||||
- './spec/models/analytics/cycle_analytics/project_value_stream_spec.rb'
|
||||
- './spec/models/analytics/cycle_analytics/stage_event_hash_spec.rb'
|
||||
- './spec/models/analytics/usage_trends/measurement_spec.rb'
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsWorker do
|
|||
build(
|
||||
:project_build_artifacts_size_refresh,
|
||||
:running,
|
||||
id: 99,
|
||||
project_id: 77,
|
||||
last_job_artifact_id: 123
|
||||
)
|
||||
end
|
||||
|
||||
it 'logs refresh information' do
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:refresh_id, refresh.id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:project_id, refresh.project_id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:last_job_artifact_id, refresh.last_job_artifact_id)
|
||||
expect(worker).to receive(:log_extra_metadata_on_done).with(:last_batch, refresh.destroyed?)
|
||||
|
|
|
|||
Loading…
Reference in New Issue