Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-02-06 15:08:52 +00:00
parent d75e21489f
commit 7b69a22d49
77 changed files with 735 additions and 324 deletions

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
bbf6542b726466ae98323f1e7dd636874e01228ec584166ab617a917822b3fa1

View File

@ -0,0 +1 @@
e69eabf71bfdfc9c5aa50829d08b3ef1473e5359d01e08e1bdc94fcbb7c58e6e

View File

@ -0,0 +1 @@
6af88109e5186a6a2f18418f441e232757ee0b03cb8af62e72c86ca4d12075c9

View File

@ -0,0 +1 @@
49e256cdd550386c989cb6edea22873547b96120cfd8b5652de532dbbe21928c

View File

@ -0,0 +1 @@
bb8b177385489eeefda9b8c1e9534398ec759d95fbf46ee3af02a3964a03e1ae

View File

@ -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;

View File

@ -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.

View File

@ -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) |

View File

@ -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"
```

116
doc/api/member_roles.md Normal file
View File

@ -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"
```

View File

@ -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 +}

View File

@ -1,5 +1,5 @@
---
status: ready
status: accepted
creation-date: "2021-11-18"
authors: [ "@nolith" ]
coach: "@glopezfernandez"

View File

@ -1,5 +1,5 @@
---
status: ready
status: accepted
creation-date: "2022-09-08"
authors: [ "@grzesiek", "@marshall007", "@fabiopitino", "@hswimelar" ]
coach: "@andrewn"

View File

@ -1,5 +1,5 @@
---
status: ready
status: ongoing
creation-date: "2022-10-27"
authors: [ "@pedropombeiro", "@tmaczukin" ]
coach: "@ayufan"

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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.
![GitLab Rendered Grafana Panel](img/rendered_grafana_embed_v12_5.png)
<!--- end_remove -->

View File

@ -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)

18
doc/topics/your_work.md Normal file
View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

86
scripts/lint-docs-blueprints.rb Executable file
View File

@ -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

View File

@ -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],

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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));
});
});
});

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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) }

View File

@ -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 }

View File

@ -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

View File

@ -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'

View File

@ -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?)