Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-03 09:08:43 +00:00
parent b646822e52
commit 6be8ed5a95
55 changed files with 3059 additions and 937 deletions

View File

@ -1626,7 +1626,6 @@
.review:rules:review-qa-all: .review:rules:review-qa-all:
rules: rules:
- <<: *if-merge-request-labels-run-review-app # we explicitly don't allow the job to fail in that case
- <<: *if-dot-com-gitlab-org-merge-request - <<: *if-dot-com-gitlab-org-merge-request
changes: *code-patterns changes: *code-patterns
when: manual when: manual

View File

@ -126,14 +126,6 @@ export function findDiffFile(files, match, matchKey = 'file_hash') {
return files.find((file) => file[matchKey] === match); return files.find((file) => file[matchKey] === match);
} }
export const getReversePosition = (linePosition) => {
if (linePosition === LINE_POSITION_RIGHT) {
return LINE_POSITION_LEFT;
}
return LINE_POSITION_RIGHT;
};
export function getFormData(params) { export function getFormData(params) {
const { const {
commit, commit,

View File

@ -131,6 +131,7 @@
"VulnerabilityLocationSecretDetection" "VulnerabilityLocationSecretDetection"
], ],
"WorkItemWidget": [ "WorkItemWidget": [
"WorkItemWidgetDescription" "WorkItemWidgetDescription",
"WorkItemWidgetHierarchy"
] ]
} }

View File

@ -67,17 +67,6 @@ export const parseBooleanDataAttributes = ({ dataset }, names) =>
export const isElementVisible = (element) => export const isElementVisible = (element) =>
Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length); Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
/**
* The opposite of `isElementVisible`.
* Returns whether or not the provided element is currently hidden.
* This function operates identically to jQuery's `:hidden` pseudo-selector.
* Documentation for this selector: https://api.jquery.com/hidden-selector/
* Implementation of this selector: https://github.com/jquery/jquery/blob/d0ce00cdfa680f1f0c38460bc51ea14079ae8b07/src/css/hiddenVisibleSelectors.js#L6
* @param {HTMLElement} element The element to test
* @returns {Boolean} `true` if the element is currently hidden, otherwise false
*/
export const isElementHidden = (element) => !isElementVisible(element);
export const getParents = (element) => { export const getParents = (element) => {
const parents = []; const parents = [];
let parent = element.parentNode; let parent = element.parentNode;

View File

@ -1,25 +1,4 @@
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { secondsToMilliseconds } from '~/lib/utils/datetime_utility';
import { dateFormatMask } from './constants'; import { dateFormatMask } from './constants';
/**
* Returns a time range (`start`, `end`) where `start` is the
* current time minus a given number of seconds and `end`
* is the current time (`now()`).
*
* @param {Number} seconds Seconds duration, defaults to 0.
* @returns {Object} range Time range
* @returns {String} range.start ISO String of current time minus given seconds
* @returns {String} range.end ISO String of current time
*/
export const getTimeRange = (seconds = 0) => {
const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
const start = end - seconds;
return {
start: new Date(secondsToMilliseconds(start)).toISOString(),
end: new Date(secondsToMilliseconds(end)).toISOString(),
};
};
export const formatDate = (timestamp) => dateFormat(timestamp, dateFormatMask); export const formatDate = (timestamp) => dateFormat(timestamp, dateFormatMask);

View File

@ -1 +0,0 @@
export const toLabelGid = (id) => `gid://gitlab/Label/${id}`;

View File

@ -233,6 +233,9 @@ export default {
shouldShowSecurityExtension() { shouldShowSecurityExtension() {
return window.gon?.features?.refactorSecurityExtension; return window.gon?.features?.refactorSecurityExtension;
}, },
shouldShowCodeQualityExtension() {
return window.gon?.features?.refactorCodeQualityExtension;
},
isRestructuredMrWidgetEnabled() { isRestructuredMrWidgetEnabled() {
return window.gon?.features?.restructuredMrWidget; return window.gon?.features?.restructuredMrWidget;
}, },
@ -520,7 +523,7 @@ export default {
} }
}, },
registerCodeQualityExtension() { registerCodeQualityExtension() {
if (this.shouldRenderCodeQuality && this.shouldShowExtension) { if (this.shouldRenderCodeQuality && this.shouldShowCodeQualityExtension) {
registerExtension(codeQualityExtension); registerExtension(codeQualityExtension);
} }
}, },

View File

@ -5,10 +5,12 @@ module SortingPreference
include CookiesHelper include CookiesHelper
def set_sort_order(field = sorting_field, default_order = default_sort_order) def set_sort_order(field = sorting_field, default_order = default_sort_order)
set_sort_order_from_user_preference(field) || sort_order = set_sort_order_from_user_preference(field) || set_sort_order_from_cookie(field) || params[:sort]
set_sort_order_from_cookie(field) ||
params[:sort] || # some types of sorting might not be available on the dashboard
default_order return default_order unless valid_sort_order?(sort_order)
sort_order
end end
# Implement sorting_field method on controllers # Implement sorting_field method on controllers
@ -85,4 +87,11 @@ module SortingPreference
else value else value
end end
end end
def valid_sort_order?(sort_order)
return false unless sort_order
return can_sort_by_issue_weight?(action_name == 'issues') if sort_order.include?('weight')
true
end
end end

View File

@ -39,6 +39,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:confidential_notes, project) push_frontend_feature_flag(:confidential_notes, project)
push_frontend_feature_flag(:restructured_mr_widget, project) push_frontend_feature_flag(:restructured_mr_widget, project)
push_frontend_feature_flag(:refactor_mr_widgets_extensions, project) push_frontend_feature_flag(:refactor_mr_widgets_extensions, project)
push_frontend_feature_flag(:refactor_code_quality_extension, project)
push_frontend_feature_flag(:refactor_mr_widget_test_summary, project) push_frontend_feature_flag(:refactor_mr_widget_test_summary, project)
push_frontend_feature_flag(:rebase_without_ci_ui, project) push_frontend_feature_flag(:rebase_without_ci_ui, project)
push_frontend_feature_flag(:issue_assignees_widget, @project) push_frontend_feature_flag(:issue_assignees_widget, @project)

View File

@ -14,12 +14,15 @@ module Types
case object case object
when ::WorkItems::Widgets::Description when ::WorkItems::Widgets::Description
::Types::WorkItems::Widgets::DescriptionType ::Types::WorkItems::Widgets::DescriptionType
when ::WorkItems::Widgets::Hierarchy
::Types::WorkItems::Widgets::HierarchyType
else else
raise "Unknown GraphQL type for widget #{object}" raise "Unknown GraphQL type for widget #{object}"
end end
end end
orphan_types ::Types::WorkItems::Widgets::DescriptionType orphan_types ::Types::WorkItems::Widgets::DescriptionType,
::Types::WorkItems::Widgets::HierarchyType
end end
end end
end end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Types
module WorkItems
module Widgets
# Disabling widget level authorization as it might be too granular
# and we already authorize the parent work item
# rubocop:disable Graphql/AuthorizeTypes
class HierarchyType < BaseObject
graphql_name 'WorkItemWidgetHierarchy'
description 'Represents a hierarchy widget'
implements Types::WorkItems::WidgetInterface
field :parent, ::Types::WorkItemType, null: true,
description: 'Parent work item.',
complexity: 5
field :children, ::Types::WorkItemType.connection_type, null: true,
description: 'Child work items.',
complexity: 5
def children
object.children.inc_relations_for_permission_check
end
end
# rubocop:enable Graphql/AuthorizeTypes
end
end
end

View File

@ -267,6 +267,10 @@ module SortingHelper
options.concat([title_option]) options.concat([title_option])
end end
def can_sort_by_issue_weight?(_viewing_issues)
false
end
def due_date_option def due_date_option
{ value: sort_value_due_date, text: sort_title_due_date, href: page_filter_path(sort: sort_value_due_date) } { value: sort_value_due_date, text: sort_title_due_date, href: page_filter_path(sort: sort_value_due_date) }
end end

View File

@ -1010,7 +1010,7 @@ module Ci
end end
def collect_test_reports!(test_reports) def collect_test_reports!(test_reports)
test_reports.get_suite(group_name).tap do |test_suite| test_reports.get_suite(test_suite_name).tap do |test_suite|
each_report(Ci::JobArtifact::TEST_REPORT_FILE_TYPES) do |file_type, blob| each_report(Ci::JobArtifact::TEST_REPORT_FILE_TYPES) do |file_type, blob|
Gitlab::Ci::Parsers.fabricate!(file_type).parse!( Gitlab::Ci::Parsers.fabricate!(file_type).parse!(
blob, blob,
@ -1184,6 +1184,18 @@ module Ci
private private
def test_suite_name
if matrix_build?
name
else
group_name
end
end
def matrix_build?
options.dig(:parallel, :matrix).present?
end
def stick_build_if_status_changed def stick_build_if_status_changed
return unless saved_change_to_status? return unless saved_change_to_status?
return unless running? return unless running?

View File

@ -1055,7 +1055,7 @@ module Ci
end end
def latest_test_report_builds def latest_test_report_builds
latest_report_builds(Ci::JobArtifact.test_reports).preload(:project) latest_report_builds(Ci::JobArtifact.test_reports).preload(:project, :metadata)
end end
def builds_with_coverage def builds_with_coverage

View File

@ -11,6 +11,8 @@ class WorkItem < Issue
has_many :work_item_children, through: :child_links, class_name: 'WorkItem', has_many :work_item_children, through: :child_links, class_name: 'WorkItem',
foreign_key: :work_item_id, source: :work_item foreign_key: :work_item_id, source: :work_item
scope :inc_relations_for_permission_check, -> { includes(:author, project: :project_feature) }
def noteable_target_type_name def noteable_target_type_name
'issue' 'issue'
end end

View File

@ -21,11 +21,11 @@ module WorkItems
}.freeze }.freeze
WIDGETS_FOR_TYPE = { WIDGETS_FOR_TYPE = {
issue: [Widgets::Description], issue: [Widgets::Description, Widgets::Hierarchy],
incident: [Widgets::Description], incident: [Widgets::Description],
test_case: [Widgets::Description], test_case: [Widgets::Description],
requirement: [Widgets::Description], requirement: [Widgets::Description],
task: [Widgets::Description] task: [Widgets::Description, Widgets::Hierarchy]
}.freeze }.freeze
cache_markdown_field :description, pipeline: :single_line cache_markdown_field :description, pipeline: :single_line

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module WorkItems
module Widgets
class Hierarchy < Base
def parent
return unless Feature.enabled?(:work_items_hierarchy, work_item.project)
work_item.work_item_parent
end
def children
return WorkItem.none unless Feature.enabled?(:work_items_hierarchy, work_item.project)
work_item.work_item_children
end
end
end
end

View File

@ -0,0 +1,8 @@
---
name: refactor_code_quality_extension
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88865
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/363845
milestone: '15.1'
type: development
group: group::secure
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: usage_data_i_incident_management_oncall_notification_sent
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58606
rollout_issue_url:
milestone: '13.11'
type: development
group: group::respond
default_enabled: true

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class ScheduleDeleteInvalidEpicIssuesRevised < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
MIGRATION = 'DeleteInvalidEpicIssues'
INTERVAL = 2.minutes
BATCH_SIZE = 1_000
MAX_BATCH_SIZE = 2_000
SUB_BATCH_SIZE = 50
def up
queue_batched_background_migration(
MIGRATION,
:epics,
:id,
job_interval: 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, :epics, :id, [])
end
end

View File

@ -0,0 +1 @@
57813d4c107938d8e58d6223719c2c67206172342b52655ca4a068c845edeb3a

View File

@ -8811,6 +8811,29 @@ The edge type for [`VulnerabilityScanner`](#vulnerabilityscanner).
| <a id="vulnerabilityscanneredgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="vulnerabilityscanneredgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="vulnerabilityscanneredgenode"></a>`node` | [`VulnerabilityScanner`](#vulnerabilityscanner) | The item at the end of the edge. | | <a id="vulnerabilityscanneredgenode"></a>`node` | [`VulnerabilityScanner`](#vulnerabilityscanner) | The item at the end of the edge. |
#### `WorkItemConnection`
The connection type for [`WorkItem`](#workitem).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemconnectionedges"></a>`edges` | [`[WorkItemEdge]`](#workitemedge) | A list of edges. |
| <a id="workitemconnectionnodes"></a>`nodes` | [`[WorkItem]`](#workitem) | A list of nodes. |
| <a id="workitemconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `WorkItemEdge`
The edge type for [`WorkItem`](#workitem).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="workitemedgenode"></a>`node` | [`WorkItem`](#workitem) | The item at the end of the edge. |
#### `WorkItemTypeConnection` #### `WorkItemTypeConnection`
The connection type for [`WorkItemType`](#workitemtype). The connection type for [`WorkItemType`](#workitemtype).
@ -9196,6 +9219,10 @@ Represents an epic on an issue board.
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="boardepicauthor"></a>`author` | [`UserCore!`](#usercore) | Author of the epic. | | <a id="boardepicauthor"></a>`author` | [`UserCore!`](#usercore) | Author of the epic. |
| <a id="boardepicawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) | | <a id="boardepicawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) |
| <a id="boardepicblocked"></a>`blocked` | [`Boolean`](#boolean) | Indicates the epic is blocked. |
| <a id="boardepicblockedbycount"></a>`blockedByCount` | [`Int`](#int) | Count of epics blocking this epic. |
| <a id="boardepicblockedbyepics"></a>`blockedByEpics` | [`EpicConnection`](#epicconnection) | Epics blocking this epic. (see [Connections](#connections)) |
| <a id="boardepicblockingcount"></a>`blockingCount` | [`Int`](#int) | Count of epics that this epic is blocking. |
| <a id="boardepicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. | | <a id="boardepicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. |
| <a id="boardepiccolor"></a>`color` | [`String!`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | <a id="boardepiccolor"></a>`color` | [`String!`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. |
| <a id="boardepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | | <a id="boardepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
@ -10776,6 +10803,10 @@ Represents an epic.
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="epicauthor"></a>`author` | [`UserCore!`](#usercore) | Author of the epic. | | <a id="epicauthor"></a>`author` | [`UserCore!`](#usercore) | Author of the epic. |
| <a id="epicawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) | | <a id="epicawardemoji"></a>`awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of award emojis associated with the epic. (see [Connections](#connections)) |
| <a id="epicblocked"></a>`blocked` | [`Boolean`](#boolean) | Indicates the epic is blocked. |
| <a id="epicblockedbycount"></a>`blockedByCount` | [`Int`](#int) | Count of epics blocking this epic. |
| <a id="epicblockedbyepics"></a>`blockedByEpics` | [`EpicConnection`](#epicconnection) | Epics blocking this epic. (see [Connections](#connections)) |
| <a id="epicblockingcount"></a>`blockingCount` | [`Int`](#int) | Count of epics that this epic is blocking. |
| <a id="epicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. | | <a id="epicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. |
| <a id="epiccolor"></a>`color` | [`String!`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. | | <a id="epiccolor"></a>`color` | [`String!`](#string) | Color of the epic. Available only when feature flag `epic_color_highlight` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. |
| <a id="epicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. | | <a id="epicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
@ -17955,6 +17986,18 @@ Represents a description widget.
| <a id="workitemwidgetdescriptiondescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. | | <a id="workitemwidgetdescriptiondescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
| <a id="workitemwidgetdescriptiontype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | | <a id="workitemwidgetdescriptiontype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
### `WorkItemWidgetHierarchy`
Represents a hierarchy widget.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgethierarchychildren"></a>`children` | [`WorkItemConnection`](#workitemconnection) | Child work items. (see [Connections](#connections)) |
| <a id="workitemwidgethierarchyparent"></a>`parent` | [`WorkItem`](#workitem) | Parent work item. |
| <a id="workitemwidgethierarchytype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
## Enumeration types ## Enumeration types
Also called _Enums_, enumeration types are a special kind of scalar that Also called _Enums_, enumeration types are a special kind of scalar that
@ -19730,6 +19773,7 @@ Type of a work item widget.
| Value | Description | | Value | Description |
| ----- | ----------- | | ----- | ----------- |
| <a id="workitemwidgettypedescription"></a>`DESCRIPTION` | Description widget. | | <a id="workitemwidgettypedescription"></a>`DESCRIPTION` | Description widget. |
| <a id="workitemwidgettypehierarchy"></a>`HIERARCHY` | Hierarchy widget. |
## Scalar types ## Scalar types
@ -20937,6 +20981,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
Implementations: Implementations:
- [`WorkItemWidgetDescription`](#workitemwidgetdescription) - [`WorkItemWidgetDescription`](#workitemwidgetdescription)
- [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy)
##### Fields ##### Fields

View File

@ -107,6 +107,11 @@ use the [`artifacts:when:always`](../yaml/index.md#artifactswhen) keyword.
You cannot have multiple tests with the same name and class in your JUnit report format XML file. You cannot have multiple tests with the same name and class in your JUnit report format XML file.
In GitLab 15.0 and earlier, test reports from [parallel:matrix](../yaml/index.md#parallel:matrix)
jobs are aggregated together, which can cause some report information to not be displayed.
In GitLab 15.1 and later, [this bug is fixed](https://gitlab.com/gitlab-org/gitlab/-/issues/296814),
and all report information is displayed.
## View Unit test reports on GitLab ## View Unit test reports on GitLab
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24792) in GitLab 12.5 behind a feature flag (`junit_pipeline_view`), disabled by default. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24792) in GitLab 12.5 behind a feature flag (`junit_pipeline_view`), disabled by default.

View File

@ -128,9 +128,12 @@ The general flow of contributing to GitLab is:
1. In the merge request's description: 1. In the merge request's description:
- Ensure you provide complete and accurate information. - Ensure you provide complete and accurate information.
- Review the provided checklist. - Review the provided checklist.
1. Assign the merge request (if possible) to, or [mention](../../user/discussions/index.md#mentions), 1. Once you're ready, mark your MR as ready for review with `@gitlab-bot ready`.
one of the [code owners](../../user/project/code_owners.md) for the relevant project, - This will add the `~"workflow::ready for review"` label, and then automatically assign a merge request coach as reviewer.
and explain that you are ready for review. - If you know a relevant reviewer (e.g. someone that was involved a related issue), you can also
assign them directly with `@gitlab-bot ready @username`.
#### Review process
When you submit code to GitLab, we really want it to get merged! However, we always review When you submit code to GitLab, we really want it to get merged! However, we always review
submissions carefully, and this takes time. Code submissions will usually be reviewed by two submissions carefully, and this takes time. Code submissions will usually be reviewed by two
@ -139,7 +142,11 @@ submissions carefully, and this takes time. Code submissions will usually be rev
- A [reviewer](../code_review.md#the-responsibility-of-the-reviewer). - A [reviewer](../code_review.md#the-responsibility-of-the-reviewer).
- A [maintainer](../code_review.md#the-responsibility-of-the-maintainer). - A [maintainer](../code_review.md#the-responsibility-of-the-maintainer).
Keep the following in mind when submitting merge requests: After review, the reviewer could ask the author to update the merge request. In that case, the reviewer would set the `~"workflow::in dev"` label.
Once the merge request has been updated and set as ready for review again (e.g. with `@gitlab-bot ready`), they will review the code again.
This process may repeat any number of times before merge, to help make the contribution the best it can be.
Lastly, keep the following in mind when submitting merge requests:
- When reviewers are reading through a merge request they may request guidance from other - When reviewers are reading through a merge request they may request guidance from other
reviewers. reviewers.
@ -154,13 +161,11 @@ Keep the following in mind when submitting merge requests:
[approval](../../user/project/merge_requests/approvals/index.md) of merge requests, the [approval](../../user/project/merge_requests/approvals/index.md) of merge requests, the
maintainer may require [approvals from certain reviewers](../code_review.md#approval-guidelines) maintainer may require [approvals from certain reviewers](../code_review.md#approval-guidelines)
before merging a merge request. before merging a merge request.
- After review, the author may be asked to update the merge request. Once the merge request has been - Sometimes a maintainer may choose to close a merge request. They will fully disclose why it will not
updated and reassigned to the reviewer, they will review the code again. This process may repeat be merged, as well as some guidance. The maintainers will be open to discussion about how to change
any number of times before merge, to help make the contribution the best it can be. the code so it can be approved and merged in the future.
Sometimes a maintainer may choose to close a merge request. They will fully disclose why it will not #### Getting attention on your merge request
be merged, as well as some guidance. The maintainers will be open to discussion about how to change
the code so it can be approved and merged in the future.
GitLab will do its best to review community contributions as quickly as possible. Specially GitLab will do its best to review community contributions as quickly as possible. Specially
appointed developers review community contributions daily. Look at the appointed developers review community contributions daily. Look at the
@ -170,8 +175,9 @@ written some front-end code, you should mention the frontend merge request coach
your code has multiple disciplines, you may mention multiple merge request coaches. your code has multiple disciplines, you may mention multiple merge request coaches.
GitLab receives a lot of community contributions. If your code has not been reviewed within two GitLab receives a lot of community contributions. If your code has not been reviewed within two
working days of its initial submission, feel free to mention all merge request coaches with working days of its initial submission, you can ask for help with `@gitlab-bot help`.
`@gitlab-org/coaches` to get their attention.
#### Addition of external libraries
When submitting code to GitLab, you may feel that your contribution requires the aid of an external When submitting code to GitLab, you may feel that your contribution requires the aid of an external
library. If your code includes an external library, please provide a link to the library, as well as library. If your code includes an external library, please provide a link to the library, as well as

View File

@ -191,11 +191,11 @@ to ignore AMI changes.
#### Ansible: Specify the FIPS Omnibus builds #### Ansible: Specify the FIPS Omnibus builds
The standard Omnibus GitLab releases build their own OpenSSL library, The standard Omnibus GitLab releases build their own OpenSSL library, which is
which is not FIPS-validated. However, we have nightly builds that create not FIPS-validated. However, we have nightly builds that create Omnibus packages
Omnibus packages that link against the operating system's OpenSSL library. To that link against the operating system's OpenSSL library. To use this package,
use this package, update the `gitlab_repo_script_url` field in the update the `gitlab_edition` and `gitlab_repo_script_url` fields in the Ansible
Ansible `vars.yml`. For example, you might modify `vars.yml`. For example, you might modify
`gitlab-environment-toolkit/ansible/environments/gitlab-10k/inventory/vars.yml` `gitlab-environment-toolkit/ansible/environments/gitlab-10k/inventory/vars.yml`
in this way: in this way:
@ -204,6 +204,7 @@ all:
vars: vars:
... ...
gitlab_repo_script_url: "https://packages.gitlab.com/install/repositories/gitlab/nightly-fips-builds/script.deb.sh" gitlab_repo_script_url: "https://packages.gitlab.com/install/repositories/gitlab/nightly-fips-builds/script.deb.sh"
gitlab_edition: "gitlab-fips"
``` ```
### Cloud Native Hybrid ### Cloud Native Hybrid

View File

@ -10,7 +10,7 @@ Configure your organization and its users. Determine user roles
and give everyone access to the projects they need. and give everyone access to the projects they need.
- [Members](../user/project/members/index.md) - [Members](../user/project/members/index.md)
- [Workspace](../user/workspace/index.md) _(Coming soon)_ - [Workspace](../user/workspace/index.md) _(In development)_
- [Groups](../user/group/index.md) - [Groups](../user/group/index.md)
- [User account options](../user/profile/index.md) - [User account options](../user/profile/index.md)
- [SSH keys](../user/ssh.md) - [SSH keys](../user/ssh.md)

View File

@ -231,7 +231,7 @@ To configure project permissions:
1. Expand the **Visibility, project features, permissions** section. 1. Expand the **Visibility, project features, permissions** section.
1. To change the project visibility, select the dropdown list. If you select to **Public**, you limit access to some features to **Only Project Members**. 1. To change the project visibility, select the dropdown list. If you select to **Public**, you limit access to some features to **Only Project Members**.
1. To allow users to request access to the project, select the **Users can request access** checkbox. 1. To allow users to request access to the project, select the **Users can request access** checkbox.
1. Use the toggles to enable or disable features in the project. 1. Use the [toggles](#project-feature-settings) to enable or disable features in the project.
1. Select **Save changes**. 1. Select **Save changes**.
When you disable a feature, the following additional features are disabled: When you disable a feature, the following additional features are disabled:
@ -255,6 +255,29 @@ When you disable a feature, the following additional features are disabled:
- Metrics dashboard access requires reading project environments and deployments. - Metrics dashboard access requires reading project environments and deployments.
Users with access to the metrics dashboard can also access environments and deployments. Users with access to the metrics dashboard can also access environments and deployments.
### Project feature settings
Use the toggles to enable or disable features in the project.
| Option | More access limit options | Description |
|:---------------------------------|:--------------------------|:--------------|
| **Issues** | ✓ | Activates the GitLab issues tracker. |
| **Repository** | ✓ | Enables [repository](../repository/) functionality |
| **Merge requests** | ✓ | Enables [merge request](../merge_requests/) functionality; also see [Merge request settings](#configure-merge-request-settings-for-a-project). |
| **Forks** | ✓ | Enables [forking](../repository/forking_workflow.md) functionality. |
| **Git Large File Storage (LFS)** | | Enables the use of [large files](../../../topics/git/lfs/index.md#git-large-file-storage-lfs). |
| **Packages** | | Supports configuration of a [package registry](../../../administration/packages/index.md#gitlab-package-registry-administration) functionality. |
| **CI/CD** | ✓ | Enables [CI/CD](../../../ci/index.md) functionality. |
| **Container Registry** | | Activates a [registry](../../packages/container_registry/) for your Docker images. |
| **Analytics** | ✓ | Enables [analytics](../../analytics/). |
| **Requirements** | ✓ | Control access to [Requirements Management](../requirements/index.md). |
| **Security & Compliance** | ✓ | Control access to [security features](../../application_security/index.md). |
| **Wiki** | ✓ | Enables a separate system for [documentation](../wiki/). |
| **Snippets** | ✓ | Enables [sharing of code and text](../../snippets.md). |
| **Pages** | ✓ | Allows you to [publish static websites](../pages/). |
| **Operations** | ✓ | Control access to Operations-related features, including [Operations Dashboard](../../../operations/index.md), [Environments and Deployments](../../../ci/environments/index.md), [Feature Flags](../../../operations/feature_flags.md). |
| **Metrics Dashboard** | ✓ | Control access to [metrics dashboard](../integrations/prometheus.md). |
## Disabling the CVE ID request button **(FREE SAAS)** ## Disabling the CVE ID request button **(FREE SAAS)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41203) in GitLab 13.4, only for public projects on GitLab.com. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41203) in GitLab 13.4, only for public projects on GitLab.com.

View File

@ -145,7 +145,7 @@ module API
use :with_custom_attributes use :with_custom_attributes
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get ":id", feature_category: :users, urgency: :default do get ":id", feature_category: :users, urgency: :low do
forbidden!('Not authorized!') unless current_user forbidden!('Not authorized!') unless current_user
unless current_user.admin? unless current_user.admin?
@ -921,7 +921,7 @@ module API
desc 'Get the currently authenticated user' do desc 'Get the currently authenticated user' do
success Entities::UserPublic success Entities::UserPublic
end end
get feature_category: :users, urgency: :default do get feature_category: :users, urgency: :low do
entity = entity =
if current_user.admin? if current_user.admin?
Entities::UserWithAdmin Entities::UserWithAdmin

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# rubocop: disable Style/Documentation
class DeleteInvalidEpicIssues < BatchedMigrationJob
def perform
end
end
end
end
# rubocop:disable Layout/LineLength
Gitlab::BackgroundMigration::DeleteInvalidEpicIssues.prepend_mod_with('Gitlab::BackgroundMigration::DeleteInvalidEpicIssues')

View File

@ -19,7 +19,7 @@ module Gitlab
::Projects::CreateService.new( ::Projects::CreateService.new(
current_user, current_user,
name: name, name: name,
path: name, path: repo_slug,
description: repo.description, description: repo.description,
namespace_id: namespace.id, namespace_id: namespace.id,
visibility_level: repo.visibility_level, visibility_level: repo.visibility_level,

View File

@ -139,7 +139,6 @@
redis_slot: incident_management redis_slot: incident_management
category: incident_management_oncall category: incident_management_oncall
aggregation: weekly aggregation: weekly
feature_flag: usage_data_i_incident_management_oncall_notification_sent
# Testing category # Testing category
- name: i_testing_test_case_parsed - name: i_testing_test_case_parsed
category: testing category: testing

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
RSpec.describe 'Non-devops' do RSpec.describe 'Product Intelligence' do
describe 'Performance bar display', :requires_admin, :skip_live_env do describe 'Performance bar display', :requires_admin, :skip_live_env do
context 'when logged in as an admin user' do context 'when logged in as an admin user' do
# performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided) # performance metrics: pg, gitaly, redis, rugged (feature flagged), total (not always provided)
@ -20,7 +20,10 @@ module QA
end end
end end
it 'shows results for the original request and AJAX requests', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348030' do it(
'shows results for the original request and AJAX requests',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348030'
) do
# Issue pages always make AJAX requests # Issue pages always make AJAX requests
Resource::Issue.fabricate_via_browser_ui! do |issue| Resource::Issue.fabricate_via_browser_ui! do |issue|
issue.title = 'Performance bar test' issue.title = 'Performance bar test'

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
RSpec.describe 'Non-devops' do RSpec.describe 'Product Intelligence' do
describe 'Service ping default enabled' do describe 'Service ping default enabled' do
context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do
before do before do
@ -11,7 +11,10 @@ module QA
Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings) Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
end end
it 'has service ping toggle enabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335' do it(
'has service ping toggle enabled',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348335'
) do
Page::Admin::Settings::MetricsAndProfiling.perform do |setting| Page::Admin::Settings::MetricsAndProfiling.perform do |setting|
setting.expand_usage_statistics do |page| setting.expand_usage_statistics do |page|
expect(page).not_to have_disabled_usage_data_checkbox expect(page).not_to have_disabled_usage_data_checkbox

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
RSpec.describe 'Non-devops' do RSpec.describe 'Product Intelligence' do
describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do describe 'Service ping disabled', :orchestrated, :service_ping_disabled, :requires_admin do
context 'when disabled from gitlab.yml config' do context 'when disabled from gitlab.yml config' do
before do before do
@ -11,7 +11,10 @@ module QA
Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings) Page::Admin::Menu.perform(&:go_to_metrics_and_profiling_settings)
end end
it 'has service ping toggle is disabled', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348340' do it(
'has service ping toggle is disabled',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/348340'
) do
Page::Admin::Settings::MetricsAndProfiling.perform do |settings| Page::Admin::Settings::MetricsAndProfiling.perform do |settings|
settings.expand_usage_statistics do |usage_statistics| settings.expand_usage_statistics do |usage_statistics|
expect(usage_statistics).to have_disabled_usage_data_checkbox expect(usage_statistics).to have_disabled_usage_data_checkbox

View File

@ -19,13 +19,15 @@ module Glfm
markdown_yml_path = ARGV[0] markdown_yml_path = ARGV[0]
markdown_hash = YAML.load_file(markdown_yml_path) markdown_hash = YAML.load_file(markdown_yml_path)
context = build_context
# NOTE: We COULD parallelize this loop like the Javascript WYSIWYG example generation does, # NOTE: We COULD parallelize this loop like the Javascript WYSIWYG example generation does,
# but it wouldn't save much time. Most of the time is spent loading the Rails environment # but it wouldn't save much time. Most of the time is spent loading the Rails environment
# via `rails runner`. In initial testing, this loop only took ~7 seconds while the entire # via `rails runner`. In initial testing, this loop only took ~7 seconds while the entire
# script took ~20 seconds. Unfortunately, there's no easy way to execute # script took ~20 seconds. Unfortunately, there's no easy way to execute
# `ApplicationController.helpers.markdown` without using `rails runner` # `Banzai.render_and_post_process` without using `rails runner`
static_html_hash = markdown_hash.transform_values do |markdown| static_html_hash = markdown_hash.transform_values do |markdown|
ApplicationController.helpers.markdown(markdown) Banzai.render_and_post_process(markdown, context)
end end
static_html_tempfile_path = Dir::Tmpname.create(STATIC_HTML_TEMPFILE_BASENAME) do |path| static_html_tempfile_path = Dir::Tmpname.create(STATIC_HTML_TEMPFILE_BASENAME) do |path|
@ -37,14 +39,42 @@ module Glfm
# Write the path to the output file to stdout # Write the path to the output file to stdout
print static_html_tempfile_path print static_html_tempfile_path
end end
private
def build_context
user_username = 'glfm_user_username'
user = User.find_by_username(user_username) ||
User.create!(
email: "glfm_user_email@example.com",
name: "glfm_user_name",
password: "glfm_user_password",
username: user_username
)
# Ensure that we never try to hit Gitaly, even if we
# reload the project
Project.define_method(:skip_disk_validation) do
true
end
project_name = 'glfm_project_name'
project = Project.find_by_name(project_name) ||
Project.create!(
creator: user,
description: "glfm_project_description",
name: project_name,
namespace: user.namespace,
path: 'glfm_project_path'
)
{
only_path: false,
current_user: user,
project: project
}
end
end end
end end
# current_user must be in global scope for `markdown` helper to work. Currently it's not supported
# to pass it in the context.
def current_user
# TODO: This will likely need to be a more realistic user object for some of the GLFM examples
User.new
end
Glfm::RenderStaticHtml.new.process Glfm::RenderStaticHtml.new.process

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe SortingPreference do RSpec.describe SortingPreference do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:params) { {} }
let(:controller_class) do let(:controller_class) do
Class.new do Class.new do
@ -23,6 +24,46 @@ RSpec.describe SortingPreference do
allow(controller).to receive(:sorting_field).and_return(:issues_sort) allow(controller).to receive(:sorting_field).and_return(:issues_sort)
end end
describe '#set_sort_order' do
let(:group) { build(:group) }
let(:issue_weights_available) { true }
before do
allow(controller).to receive(:default_sort_order).and_return('updated_desc')
allow(controller).to receive(:action_name).and_return('issues')
allow(controller).to receive(:can_sort_by_issue_weight?).and_return(issue_weights_available)
user.user_preference.update!(issues_sort: sorting_field)
end
subject { controller.send(:set_sort_order) }
context 'when user preference contains allowed sorting' do
let(:sorting_field) { 'updated_asc' }
it 'sets sort order from user_preference' do
is_expected.to eq('updated_asc')
end
end
context 'when user preference contains weight sorting' do
let(:sorting_field) { 'weight_desc' }
context 'when user can sort by issue weight' do
it 'sets sort order from user_preference' do
is_expected.to eq('weight_desc')
end
end
context 'when user cannot sort by issue weight' do
let(:issue_weights_available) { false }
it 'sets default sort order' do
is_expected.to eq('updated_desc')
end
end
end
end
describe '#set_sort_order_from_user_preference' do describe '#set_sort_order_from_user_preference' do
subject { controller.send(:set_sort_order_from_user_preference) } subject { controller.send(:set_sort_order_from_user_preference) }
@ -49,8 +90,6 @@ RSpec.describe SortingPreference do
end end
context 'when a user sorting preference exists' do context 'when a user sorting preference exists' do
let(:params) { {} }
before do before do
user.user_preference.update!(issues_sort: 'updated_asc') user.user_preference.update!(issues_sort: 'updated_asc')
end end
@ -81,7 +120,6 @@ RSpec.describe SortingPreference do
context 'when cookie exists' do context 'when cookie exists' do
let(:cookies) { { 'issue_sort' => 'id_asc' } } let(:cookies) { { 'issue_sort' => 'id_asc' } }
let(:params) { {} }
it 'sets the cookie with the right values and flags' do it 'sets the cookie with the right values and flags' do
subject subject

View File

@ -33,6 +33,18 @@ FactoryBot.define do
name { generate(:job_name) } name { generate(:job_name) }
end end
trait :matrix do
sequence(:name) { |n| "job: [#{n}]" }
options do
{
parallel: {
total: 2,
matrix: [{ ID: %w[1 2] }]
}
}
end
end
trait :dependent do trait :dependent do
scheduling_type { 'dag' } scheduling_type { 'dag' }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -30,13 +30,6 @@ describe('DiffsStoreUtils', () => {
}); });
}); });
describe('getReversePosition', () => {
it('should return correct line position name', () => {
expect(utils.getReversePosition(LINE_POSITION_RIGHT)).toEqual(LINE_POSITION_LEFT);
expect(utils.getReversePosition(LINE_POSITION_LEFT)).toEqual(LINE_POSITION_RIGHT);
});
});
describe('findIndexInInlineLines', () => { describe('findIndexInInlineLines', () => {
const expectSet = (method, lines, invalidLines) => { const expectSet = (method, lines, invalidLines) => {
expect(method(lines, { oldLineNumber: 3, newLineNumber: 5 })).toEqual(4); expect(method(lines, { oldLineNumber: 3, newLineNumber: 5 })).toEqual(4);

View File

@ -5,7 +5,6 @@ import {
canScrollDown, canScrollDown,
parseBooleanDataAttributes, parseBooleanDataAttributes,
isElementVisible, isElementVisible,
isElementHidden,
getParents, getParents,
getParentByTagName, getParentByTagName,
setAttributes, setAttributes,
@ -181,30 +180,21 @@ describe('DOM Utils', () => {
${1} | ${0} | ${0} | ${true} ${1} | ${0} | ${0} | ${true}
${0} | ${1} | ${0} | ${true} ${0} | ${1} | ${0} | ${true}
${0} | ${0} | ${1} | ${true} ${0} | ${0} | ${1} | ${true}
`( `('isElementVisible', ({ offsetWidth, offsetHeight, clientRectsLength, visible }) => {
'isElementVisible and isElementHidden', const element = {
({ offsetWidth, offsetHeight, clientRectsLength, visible }) => { offsetWidth,
const element = { offsetHeight,
offsetWidth, getClientRects: () => new Array(clientRectsLength),
offsetHeight, };
getClientRects: () => new Array(clientRectsLength),
};
const paramDescription = `offsetWidth=${offsetWidth}, offsetHeight=${offsetHeight}, and getClientRects().length=${clientRectsLength}`; const paramDescription = `offsetWidth=${offsetWidth}, offsetHeight=${offsetHeight}, and getClientRects().length=${clientRectsLength}`;
describe('isElementVisible', () => { describe('isElementVisible', () => {
it(`returns ${visible} when ${paramDescription}`, () => { it(`returns ${visible} when ${paramDescription}`, () => {
expect(isElementVisible(element)).toBe(visible); expect(isElementVisible(element)).toBe(visible);
});
}); });
});
describe('isElementHidden', () => { });
it(`returns ${!visible} when ${paramDescription}`, () => {
expect(isElementHidden(element)).toBe(!visible);
});
});
},
);
describe('getParents', () => { describe('getParents', () => {
it('gets all parents of an element', () => { it('gets all parents of an element', () => {

View File

@ -1,38 +0,0 @@
import { getTimeRange } from '~/logs/utils';
describe('logs/utils', () => {
describe('getTimeRange', () => {
const nowTimestamp = 1577836800000;
const nowString = '2020-01-01T00:00:00.000Z';
beforeEach(() => {
jest.spyOn(Date, 'now').mockImplementation(() => nowTimestamp);
});
afterEach(() => {
Date.now.mockRestore();
});
it('returns the right values', () => {
expect(getTimeRange(0)).toEqual({
start: '2020-01-01T00:00:00.000Z',
end: nowString,
});
expect(getTimeRange(60 * 30)).toEqual({
start: '2019-12-31T23:30:00.000Z',
end: nowString,
});
expect(getTimeRange(60 * 60 * 24 * 7 * 1)).toEqual({
start: '2019-12-25T00:00:00.000Z',
end: nowString,
});
expect(getTimeRange(60 * 60 * 24 * 7 * 4)).toEqual({
start: '2019-12-04T00:00:00.000Z',
end: nowString,
});
});
});
});

View File

@ -12,10 +12,19 @@ RSpec.describe Types::WorkItems::WidgetInterface do
end end
describe ".resolve_type" do describe ".resolve_type" do
it 'knows the correct type for objects' do using RSpec::Parameterized::TableSyntax
expect(
described_class.resolve_type(WorkItems::Widgets::Description.new(build(:work_item)), {}) where(:widget_class, :widget_type_name) do
).to eq(Types::WorkItems::Widgets::DescriptionType) WorkItems::Widgets::Description | Types::WorkItems::Widgets::DescriptionType
WorkItems::Widgets::Hierarchy | Types::WorkItems::Widgets::HierarchyType
end
with_them do
it 'knows the correct type for objects' do
expect(
described_class.resolve_type(widget_class.new(build(:work_item)), {})
).to eq(widget_type_name)
end
end end
it 'raises an error for an unknown type' do it 'raises an error for an unknown type' do

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::WorkItems::Widgets::HierarchyType do
it 'exposes the expected fields' do
expected_fields = %i[parent children type]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -62,6 +62,12 @@ RSpec.describe SortingHelper do
end end
end end
describe '#can_sort_by_issue_weight?' do
it 'returns false' do
expect(helper.can_sort_by_issue_weight?(false)).to be_falsey
end
end
def stub_controller_path(value) def stub_controller_path(value)
allow(helper.controller).to receive(:controller_path).and_return(value) allow(helper.controller).to receive(:controller_path).and_return(value)
end end

View File

@ -12,7 +12,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importer do
let(:project) { create(:project, :repository, import_url: import_url, creator: project_creator) } let(:project) { create(:project, :repository, import_url: import_url, creator: project_creator) }
let(:now) { Time.now.utc.change(usec: 0) } let(:now) { Time.now.utc.change(usec: 0) }
let(:project_key) { 'TEST' } let(:project_key) { 'TEST' }
let(:repo_slug) { 'rouge' } let(:repo_slug) { 'rouge-repo' }
let(:sample) { RepoHelpers.sample_compare } let(:sample) { RepoHelpers.sample_compare }
subject { described_class.new(project, recover_missing_commits: true) } subject { described_class.new(project, recover_missing_commits: true) }

View File

@ -4350,6 +4350,56 @@ RSpec.describe Ci::Build do
end end
end end
end end
context 'when build is part of parallel build' do
let(:build_1) { create(:ci_build, name: 'build 1/2') }
let(:test_report) { Gitlab::Ci::Reports::TestReports.new }
before do
build_1.collect_test_reports!(test_report)
end
it 'uses the group name for test suite name' do
expect(test_report.test_suites.keys).to contain_exactly('build')
end
context 'when there are more than one parallel builds' do
let(:build_2) { create(:ci_build, name: 'build 2/2') }
before do
build_2.collect_test_reports!(test_report)
end
it 'merges the test suite from parallel builds' do
expect(test_report.test_suites.keys).to contain_exactly('build')
end
end
end
context 'when build is part of matrix build' do
let(:test_report) { Gitlab::Ci::Reports::TestReports.new }
let(:matrix_build_1) { create(:ci_build, :matrix) }
before do
matrix_build_1.collect_test_reports!(test_report)
end
it 'uses the job name for the test suite' do
expect(test_report.test_suites.keys).to contain_exactly(matrix_build_1.name)
end
context 'when there are more than one matrix builds' do
let(:matrix_build_2) { create(:ci_build, :matrix) }
before do
matrix_build_2.collect_test_reports!(test_report)
end
it 'keeps separate test suites' do
expect(test_report.test_suites.keys).to match_array([matrix_build_1.name, matrix_build_2.name])
end
end
end
end end
describe '#collect_accessibility_reports!' do describe '#collect_accessibility_reports!' do

View File

@ -36,7 +36,10 @@ RSpec.describe WorkItem do
describe '#widgets' do describe '#widgets' do
subject { build(:work_item).widgets } subject { build(:work_item).widgets }
it { is_expected.to contain_exactly(instance_of(WorkItems::Widgets::Description)) } it 'returns instances of supported widgets' do
is_expected.to match_array([instance_of(WorkItems::Widgets::Description),
instance_of(WorkItems::Widgets::Hierarchy)])
end
end end
describe 'callbacks' do describe 'callbacks' do

View File

@ -63,7 +63,10 @@ RSpec.describe WorkItems::Type do
describe '.available_widgets' do describe '.available_widgets' do
subject { described_class.available_widgets } subject { described_class.available_widgets }
it { is_expected.to contain_exactly(::WorkItems::Widgets::Description) } it 'returns list of all possible widgets' do
is_expected.to match_array([::WorkItems::Widgets::Description,
::WorkItems::Widgets::Hierarchy])
end
end end
describe '#default?' do describe '#default?' do

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Widgets::Hierarchy do
let_it_be(:work_item) { create(:work_item) }
describe '.type' do
subject { described_class.type }
it { is_expected.to eq(:hierarchy) }
end
describe '#type' do
subject { described_class.new(work_item).type }
it { is_expected.to eq(:hierarchy) }
end
describe '#parent' do
let_it_be(:parent_link) { create(:parent_link) }
subject { described_class.new(parent_link.work_item).parent }
it { is_expected.to eq parent_link.work_item_parent }
context 'when work_items_hierarchy flag is disabled' do
before do
stub_feature_flags(work_items_hierarchy: false)
end
it { is_expected.to be_nil }
end
end
describe '#children' do
let_it_be(:parent_link1) { create(:parent_link, work_item_parent: work_item) }
let_it_be(:parent_link2) { create(:parent_link, work_item_parent: work_item) }
subject { described_class.new(work_item).children }
it { is_expected.to match_array([parent_link1.work_item, parent_link2.work_item]) }
context 'when work_items_hierarchy flag is disabled' do
before do
stub_feature_flags(work_items_hierarchy: false)
end
it { is_expected.to be_empty }
end
end
end

View File

@ -6,8 +6,13 @@ RSpec.describe 'Query.work_item(id)' do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let_it_be(:project) { create(:project, :private).tap { |project| project.add_developer(developer) } } let_it_be(:guest) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:work_item) { create(:work_item, project: project, description: '- List item') } let_it_be(:work_item) { create(:work_item, project: project, description: '- List item') }
let_it_be(:child_item1) { create(:work_item, :task, project: project) }
let_it_be(:child_item2) { create(:work_item, :task, confidential: true, project: project) }
let_it_be(:child_link1) { create(:parent_link, work_item_parent: work_item, work_item: child_item1) }
let_it_be(:child_link2) { create(:parent_link, work_item_parent: work_item, work_item: child_item2) }
let(:current_user) { developer } let(:current_user) { developer }
let(:work_item_data) { graphql_data['workItem'] } let(:work_item_data) { graphql_data['workItem'] }
@ -20,6 +25,9 @@ RSpec.describe 'Query.work_item(id)' do
context 'when the user can read the work item' do context 'when the user can read the work item' do
before do before do
project.add_developer(developer)
project.add_guest(guest)
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
end end
@ -39,30 +47,132 @@ RSpec.describe 'Query.work_item(id)' do
end end
context 'when querying widgets' do context 'when querying widgets' do
let(:work_item_fields) do describe 'description widget' do
<<~GRAPHQL let(:work_item_fields) do
id <<~GRAPHQL
widgets { id
type widgets {
... on WorkItemWidgetDescription { type
description ... on WorkItemWidgetDescription {
descriptionHtml description
descriptionHtml
}
} }
} GRAPHQL
GRAPHQL end
it 'returns widget information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
'widgets' => match_array([
hash_including(
'type' => 'DESCRIPTION',
'description' => work_item.description,
'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {})
),
hash_including(
'type' => 'HIERARCHY'
)
])
)
end
end end
it 'returns widget information' do describe 'hierarchy widget' do
expect(work_item_data).to include( let(:work_item_fields) do
'id' => work_item.to_gid.to_s, <<~GRAPHQL
'widgets' => contain_exactly( id
hash_including( widgets {
'type' => 'DESCRIPTION', type
'description' => work_item.description, ... on WorkItemWidgetHierarchy {
'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {}) parent {
) id
}
children {
nodes {
id
}
}
}
}
GRAPHQL
end
it 'returns widget information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
'widgets' => match_array([
hash_including(
'type' => 'DESCRIPTION'
),
hash_including(
'type' => 'HIERARCHY',
'parent' => nil,
'children' => { 'nodes' => match_array([
hash_including('id' => child_link1.work_item.to_gid.to_s),
hash_including('id' => child_link2.work_item.to_gid.to_s)
]) }
)
])
) )
) end
it 'avoids N+1 queries' do
post_graphql(query, current_user: current_user) # warm up
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
end
create_list(:parent_link, 3, work_item_parent: work_item)
expect do
post_graphql(query, current_user: current_user)
end.not_to exceed_all_query_limit(control_count)
end
context 'when user is guest' do
let(:current_user) { guest }
it 'filters out not accessible children or parent' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
'widgets' => match_array([
hash_including(
'type' => 'DESCRIPTION'
),
hash_including(
'type' => 'HIERARCHY',
'parent' => nil,
'children' => { 'nodes' => match_array([
hash_including('id' => child_link1.work_item.to_gid.to_s)
]) }
)
])
)
end
end
context 'when requesting child item' do
let_it_be(:work_item) { create(:work_item, :task, project: project, description: '- List item') }
let_it_be(:parent_link) { create(:parent_link, work_item: work_item) }
it 'returns parent information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
'widgets' => match_array([
hash_including(
'type' => 'DESCRIPTION'
),
hash_including(
'type' => 'HIERARCHY',
'parent' => hash_including('id' => parent_link.work_item_parent.to_gid.to_s),
'children' => { 'nodes' => match_array([]) }
)
])
)
end
end
end end
end end

View File

@ -431,7 +431,9 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do
bold bold
</strong></p> </strong></p>
static: |- static: |-
<strong>&#x000A;bold&#x000A;</strong> <strong>
bold
</strong>
05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001: 05_02__third_gitlab_specific_section_with_skipped_examples__strong_but_manually_modified_and_skipped__001:
canonical: | canonical: |
<p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p> <p><strong>This example will have its manually modified static HTML, WYSIWYG HTML, and ProseMirror JSON preserved</strong></p>

View File

@ -79,7 +79,7 @@ require (
github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20210210170715-a8dfcb80d3a7 // indirect
github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect github.com/lightstep/lightstep-tracer-go v0.25.0 // indirect
github.com/mattn/go-ieproxy v0.0.3 // indirect github.com/mattn/go-ieproxy v0.0.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect
github.com/oklog/ulid/v2 v2.0.2 // indirect github.com/oklog/ulid/v2 v2.0.2 // indirect

View File

@ -768,8 +768,9 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-ieproxy v0.0.3 h1:YkaHmK1CzE5C4O7A3hv3TCbfNDPSCf0RKZFX+VhBeYk=
github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko= github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko=
github.com/mattn/go-ieproxy v0.0.6 h1:tVDlituRyeHMMkHpGpUu8CJG+hxPMwbYCkIUK2PUCbo=
github.com/mattn/go-ieproxy v0.0.6/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=