Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-10-28 09:08:37 +00:00
parent b312c3d3b4
commit a2ee63d924
64 changed files with 833 additions and 315 deletions

View File

@ -1 +1 @@
b85367529ca34c8c423b94b7486c44e109ed553f
bee8517ab043ff98c283a5f191e68e2bd75eb9de

View File

@ -30,7 +30,6 @@ import AlertSidebar from './alert_sidebar.vue';
import AlertMetrics from './alert_metrics.vue';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
import AlertSummaryRow from './alert_summary_row.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const containerEl = document.querySelector('.page-with-contextual-sidebar');
@ -77,7 +76,6 @@ export default {
SystemNote,
AlertMetrics,
},
mixins: [glFeatureFlagsMixin()],
inject: {
projectPath: {
default: '',
@ -150,13 +148,10 @@ export default {
},
},
environmentName() {
return this.shouldDisplayEnvironment && this.alert?.environment?.name;
return this.alert?.environment?.name;
},
environmentPath() {
return this.shouldDisplayEnvironment && this.alert?.environment?.path;
},
shouldDisplayEnvironment() {
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
return this.alert?.environment?.path;
},
},
mounted() {

View File

@ -7,7 +7,6 @@ import {
convertToSentenceCase,
splitCamelCase,
} from '~/lib/utils/text_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const thClass = 'gl-bg-transparent! gl-border-1! gl-border-b-solid! gl-border-gray-200!';
const tdClass = 'gl-border-gray-100! gl-p-5!';
@ -25,6 +24,7 @@ const allowedFields = [
'endedAt',
'details',
'hosts',
'environment',
];
export default {
@ -32,7 +32,6 @@ export default {
GlLoadingIcon,
GlTable,
},
mixins: [glFeatureFlagsMixin()],
props: {
alert: {
type: Object,
@ -60,9 +59,6 @@ export default {
},
],
computed: {
flaggedAllowedFields() {
return this.shouldDisplayEnvironment ? [...allowedFields, 'environment'] : allowedFields;
},
items() {
if (!this.alert) {
return [];
@ -84,13 +80,10 @@ export default {
[],
);
},
shouldDisplayEnvironment() {
return this.glFeatures.exposeEnvironmentPathInAlertDetails;
},
},
methods: {
isAllowed(fieldName) {
return this.flaggedAllowedFields.includes(fieldName);
return allowedFields.includes(fieldName);
},
},
};

View File

@ -128,7 +128,7 @@ body.modal-open {
}
.issues-import-modal,
.issues-export-modal {
.issuable-export-modal {
.modal-header {
justify-content: flex-start;

View File

@ -10,6 +10,5 @@ class Projects::AlertManagementController < Projects::ApplicationController
def details
@alert_id = params[:id]
push_frontend_feature_flag(:expose_environment_path_in_alert_details, @project)
end
end

View File

@ -12,7 +12,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include SourcegraphDecorator
include DiffHelper
skip_before_action :merge_request, only: [:index, :bulk_update]
skip_before_action :merge_request, only: [:index, :bulk_update, :export_csv]
before_action :apply_diff_view_cookie!, only: [:show]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
@ -318,6 +318,16 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
super
end
def export_csv
return render_404 unless Feature.enabled?(:export_merge_requests_as_csv, project)
IssuableExportCsvWorker.perform_async(:merge_request, current_user.id, project.id, finder_options.to_h) # rubocop:disable CodeReuse/Worker
index_path = project_merge_requests_path(project)
message = _('Your CSV export has started. It will be emailed to %{email} when complete.') % { email: current_user.notification_email }
redirect_to(index_path, notice: message)
end
protected
alias_method :subscribable_resource, :merge_request

View File

@ -25,7 +25,7 @@ module Packages
.including_build_info
.including_project_route
.including_tags
.for_projects(group_projects_visible_to_current_user)
.for_projects(group_projects_visible_to_current_user.select(:id))
.processed
.has_version
.sort_by_attribute("#{params[:order_by]}_#{params[:sort]}")
@ -36,11 +36,14 @@ module Packages
end
def group_projects_visible_to_current_user
# according to project_policy.rb
# access to packages is ruled by:
# - project is public or the current user has access to it with at least the reporter level
# - the repository feature is available to the current_user
::Project
.in_namespace(groups)
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
.with_project_feature
.select { |project| Ability.allowed?(current_user, :read_package, project) }
.with_feature_available_for_user(:repository, current_user)
end
def package_type

View File

@ -18,9 +18,8 @@ module Types
field :state, GraphQL::STRING_TYPE, null: false,
description: 'State of the environment, for example: available/stopped'
field :path, GraphQL::STRING_TYPE, null: true,
description: 'The path to the environment. Will always return null ' \
'if `expose_environment_path_in_alert_details` feature flag is disabled'
field :path, GraphQL::STRING_TYPE, null: false,
description: 'The path to the environment.'
field :metrics_dashboard, Types::Metrics::DashboardType, null: true,
description: 'Metrics dashboard schema for the environment',

View File

@ -41,6 +41,9 @@ module Types
field :assignees, Types::UserType.connection_type, null: true,
description: 'Assignees of the issue'
field :updated_by, Types::UserType, null: true,
description: 'User that last updated the issue'
field :labels, Types::LabelType.connection_type, null: true,
description: 'Labels of the issue'
field :milestone, Types::MilestoneType, null: true,
@ -114,6 +117,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
def updated_by
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.updated_by_id).find
end
def milestone
Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, object.milestone_id).find
end

View File

@ -2,7 +2,6 @@
class AuditEvent < ApplicationRecord
include CreatedAtFilterable
include IgnorableColumns
include BulkInsertSafe
include EachBatch
@ -14,8 +13,6 @@ class AuditEvent < ApplicationRecord
:target_id
].freeze
ignore_column :type, remove_with: '13.6', remove_after: '2020-11-22'
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id
@ -37,14 +34,6 @@ class AuditEvent < ApplicationRecord
# https://gitlab.com/groups/gitlab-org/-/epics/2765
after_validation :parallel_persist
# Note: After loading records, do not attempt to type cast objects it finds.
# We are in the process of deprecating STI (i.e. SecurityEvent) out of AuditEvent.
#
# https://gitlab.com/gitlab-org/gitlab/-/issues/216845
def self.inheritance_column
:_type_disabled
end
def self.order_by(method)
case method.to_s
when 'created_asc'

View File

@ -6,8 +6,6 @@ class EnvironmentPresenter < Gitlab::View::Presenter::Delegated
presents :environment
def path
if Feature.enabled?(:expose_environment_path_in_alert_details, project)
project_environment_path(project, self)
end
project_environment_path(project, self)
end
end

View File

@ -14,6 +14,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Config::Process,
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::Skip,
Gitlab::Ci::Pipeline::Chain::SeedBlock,
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,

View File

@ -1,5 +1,4 @@
- breadcrumb_title _("Details")
- page_title _("Groups")
- @content_class = "limit-container-width" unless fluid_layout
- if show_invite_banner?(@group)

View File

@ -8,7 +8,7 @@
.btn-group
- if show_export_button
= render 'projects/issues/export_csv/button'
= render 'shared/issuable/csv_export/button', issuable_type: 'issues'
- if show_import_button
= render 'projects/issues/import_csv/button'
@ -23,7 +23,7 @@
id: "new_issue_link"
- if show_export_button
= render 'projects/issues/export_csv/modal'
= render 'shared/issuable/csv_export/modal', issuable_type: 'issues'
- if show_import_button
= render 'projects/issues/import_csv/modal'

View File

@ -1,22 +0,0 @@
- if current_user
.issues-export-modal.modal
.modal-dialog
.modal-content{ data: { qa_selector: 'export_issues_modal' } }
.modal-header
%h3
= _('Export issues')
.svg-content.import-export-svg-container
= image_tag 'illustrations/export-import.svg', alt: _('Import/Export illustration'), class: 'illustration'
%a.close{ href: '#', 'data-dismiss' => 'modal' }
= sprite_icon('close', css_class: 'gl-icon')
.modal-body
- issues_count = issuables_count_for_state(:issues, params[:state])
- unless issues_count == -1 # The count timed out
.modal-subheader
= sprite_icon('check', css_class: 'gl-text-green-400')
%strong.gl-ml-3
= n_('%d issue selected', '%d issues selected', issues_count) % issues_count
.modal-text
= html_escape(_('The CSV export will be created in the background. Once finished, it will be sent to %{strong_open}%{email}%{strong_close} in an attachment.')) % { email: @current_user.notification_email, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
.modal-footer
= link_to _('Export issues'), export_csv_project_issues_path(@project, request.query_parameters), method: :post, class: 'btn gl-button btn-success float-left', title: _('Export issues'), data: { track_label: "export_issues_csv", track_event: "click_button", track_value: "", qa_selector: "export_issues_button" }

View File

@ -1,5 +1,12 @@
- if Feature.enabled?(:export_merge_requests_as_csv, @project)
.btn-group
= render 'shared/issuable/csv_export/button', issuable_type: 'merge-requests'
- if @can_bulk_update
= button_tag "Edit merge requests", class: "gl-button btn gl-mr-3 js-bulk-update-toggle"
- if merge_project
= link_to new_merge_request_path, class: "gl-button btn btn-success", title: "New merge request" do
New merge request
- if Feature.enabled?(:export_merge_requests_as_csv, @project)
= render 'shared/issuable/csv_export/modal', issuable_type: 'merge_requests'

View File

@ -1,5 +1,4 @@
- breadcrumb_title _("Details")
- page_title _("Projects")
- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do

View File

@ -1,4 +1,4 @@
- if current_user
%button.csv_download_link.btn.gl-button.has-tooltip{ title: _('Export as CSV'),
data: { toggle: 'modal', target: '.issues-export-modal', qa_selector: 'export_as_csv_button' } }
data: { toggle: 'modal', target: ".#{issuable_type}-export-modal", qa_selector: 'export_as_csv_button' } }
= sprite_icon('export')

View File

@ -0,0 +1,29 @@
- class_name = "#{issuable_type.dasherize}-export-modal"
- if current_user
.modal.issuable-export-modal{ class: class_name }
.modal-dialog
.modal-content{ data: { qa_selector: "export_issuable_modal" } }
.modal-header
%h3
= _("Export %{issuable_type}" % { issuable_type: issuable_type.humanize(capitalize: false) })
.svg-content.import-export-svg-container
= image_tag 'illustrations/export-import.svg', role: "presentation", class: 'illustration'
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
= sprite_icon('close', css_class: 'gl-icon')
.modal-body
- issuable_count = issuables_count_for_state(issuable_type.to_sym, params[:state])
- unless issuable_count == -1 # The count timed out
.modal-subheader
= sprite_icon('check', css_class: 'gl-icon gl-color-green-400')
%strong.gl-ml-3
- if issuable_type.eql?('merge_requests')
= n_("%{count} merge request selected", "%{count} merge requests selected", issuable_count) % { count: issuable_count }
- else
= n_("%{count} issue selected", "%{count} issues selected", issuable_count) % { count: issuable_count }
.modal-text
= html_escape(_('The CSV export will be created in the background. Once finished, it will be sent to %{strong_open}%{email}%{strong_close} in an attachment.')) % { email: @current_user.notification_email, strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
.modal-footer
- if issuable_type.eql?('merge_requests')
= link_to _("Export merge requests"), export_csv_project_merge_requests_path(@project, request.query_parameters), method: :post, class: 'btn gl-button btn-success', data: { track_label: "export_merge_requests_csv", track_event: "click_button", track_value: "" }
- else
= link_to _('Export issues'), export_csv_project_issues_path(@project, request.query_parameters), method: :post, class: 'btn gl-button btn-success', data: { track_label: "export_issues_csv", track_event: "click_button", track_value: "", qa_selector: "export_issues_button" }

View File

@ -0,0 +1,5 @@
---
title: Show the environment link on alert details page
merge_request: 44130
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix an N+1 issue in Packages::GroupPackagesFinder
merge_request: 45875
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Remove page_title from single project and group pages
merge_request: 46223
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Disallow /autocomplete/projects route in robots.txt
merge_request: 46115
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Expose issue updated by on GraphQL
merge_request: 46015
author:
type: added

View File

@ -1,7 +1,7 @@
---
name: expose_environment_path_in_alert_details
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43414
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258638
name: ci_seed_block_run_before_workflow_rules
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45674
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/270439
type: development
group: group::progressive delivery
group: group::pipeline authoring
default_enabled: false

View File

@ -0,0 +1,7 @@
---
name: export_merge_requests_as_csv
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45130
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267129
type: development
group: group::compliance
default_enabled: false

View File

@ -47,6 +47,7 @@ resources :merge_requests, concerns: :awardable, except: [:new, :create, :show],
collection do
get :diff_for_path
post :bulk_update
post :export_csv
end
resources :discussions, only: [:show], constraints: { id: /\h{40}/ } do

View File

@ -460,16 +460,12 @@ The LDAP sync process:
### Adjusting LDAP user sync schedule **(STARTER ONLY)**
NOTE: **Note:**
These are cron formatted values. You can use a crontab generator to create
these values, for example <http://www.crontabgenerator.com/>.
By default, GitLab runs a worker once per day at 01:30 a.m. server time to
check and update GitLab users against LDAP.
You can manually configure LDAP user sync times by setting the
following configuration values, in cron format. If needed, you can
use a [crontab generator](http://crontabgenerator.com).
use a [crontab generator](http://www.crontabgenerator.com).
The example below shows how to set LDAP user
sync to run once every 12 hours at the top of the hour.

View File

@ -87,7 +87,7 @@ Consul nodes communicate using the raft protocol. If the current leader goes
offline, there needs to be a leader election. A leader node must exist to facilitate
synchronization across the cluster. If too many nodes go offline at the same time,
the cluster will lose quorum and not elect a leader due to
[broken consensus](https://www.consul.io/docs/internals/consensus.html).
[broken consensus](https://www.consul.io/docs/architecture/consensus).
Consult the [troubleshooting section](#troubleshooting-consul) if the cluster is not
able to recover after the upgrade. The [outage recovery](#outage-recovery) may

View File

@ -323,7 +323,7 @@ Any `Operation not permitted` errors means you should investigate your NFS serve
If the traffic between your NFS server and NFS client(s) is subject to port filtering
by a firewall, then you will need to reconfigure that firewall to allow NFS communication.
[This guide from TDLP](http://tldp.org/HOWTO/NFS-HOWTO/security.html#FIREWALLS)
[This guide from TDLP](https://tldp.org/HOWTO/NFS-HOWTO/security.html#FIREWALLS)
covers the basics of using NFS in a firewalled environment. Additionally, we encourage you to
search for and review the specific documentation for your operating system or distribution and your firewall software.

View File

@ -6231,10 +6231,9 @@ type Environment {
name: String!
"""
The path to the environment. Will always return null if
`expose_environment_path_in_alert_details` feature flag is disabled
The path to the environment.
"""
path: String
path: String!
"""
State of the environment, for example: available/stopped
@ -7214,6 +7213,11 @@ type EpicIssue implements CurrentUserTodos & Noteable {
"""
updatedAt: Time!
"""
User that last updated the issue
"""
updatedBy: User
"""
Number of upvotes the issue has received
"""
@ -9467,6 +9471,11 @@ type Issue implements CurrentUserTodos & Noteable {
"""
updatedAt: Time!
"""
User that last updated the issue
"""
updatedBy: User
"""
Number of upvotes the issue has received
"""

View File

@ -17214,14 +17214,18 @@
},
{
"name": "path",
"description": "The path to the environment. Will always return null if `expose_environment_path_in_alert_details` feature flag is disabled",
"description": "The path to the environment.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
@ -19865,6 +19869,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedBy",
"description": "User that last updated the issue",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "upvotes",
"description": "Number of upvotes the issue has received",
@ -25765,6 +25783,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedBy",
"description": "User that last updated the issue",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "User",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "upvotes",
"description": "Number of upvotes the issue has received",

View File

@ -1020,7 +1020,7 @@ Describes where code is deployed for a project.
| `latestOpenedMostSevereAlert` | AlertManagementAlert | The most severe open alert for the environment. If multiple alerts have equal severity, the most recent is returned |
| `metricsDashboard` | MetricsDashboard | Metrics dashboard schema for the environment |
| `name` | String! | Human-readable name of the environment |
| `path` | String | The path to the environment. Will always return null if `expose_environment_path_in_alert_details` feature flag is disabled |
| `path` | String! | The path to the environment. |
| `state` | String! | State of the environment, for example: available/stopped |
### Epic
@ -1149,6 +1149,7 @@ Relationship between an epic and an issue.
| `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `type` | IssueType | Type of the issue |
| `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `updatedBy` | User | User that last updated the issue |
| `upvotes` | Int! | Number of upvotes the issue has received |
| `userNotesCount` | Int! | Number of user notes of the issue |
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
@ -1342,6 +1343,7 @@ Represents a recorded measurement (object count) for the Admins.
| `totalTimeSpent` | Int! | Total time reported as spent on the issue |
| `type` | IssueType | Type of the issue |
| `updatedAt` | Time! | Timestamp of when the issue was last updated |
| `updatedBy` | User | User that last updated the issue |
| `upvotes` | Int! | Number of upvotes the issue has received |
| `userNotesCount` | Int! | Number of user notes of the issue |
| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |

View File

@ -22,7 +22,7 @@ cannot be used to authenticate with GitHub as an external CI/CD repository.
## Connect with Personal Access Token
Personal access tokens can only be used to connect GitHub.com
repositories to GitLab, and the GitHub user must have the [owner role](https://docs.github.com/en/github/getting-started-with-github/access-permissions-on-github).
repositories to GitLab, and the GitHub user must have the [owner role](https://docs.github.com/en/free-pro-team@latest/github/getting-started-with-github/access-permissions-on-github).
To perform a one-off authorization with GitHub to grant GitLab access your
repositories:

View File

@ -90,7 +90,7 @@ GitLab Runner then executes job scripts as the `gitlab-runner` user.
1. You can now use `docker` command (and **install** `docker-compose` if needed).
By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
For more information please read [On Docker security: `docker` group considered harmful](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful).
For more information please read [On Docker security: `docker` group considered harmful](https://blog.zopyx.com/on-docker-security-docker-group-considered-harmful/).
### Use Docker-in-Docker workflow with Docker executor

View File

@ -298,7 +298,7 @@ project.
- mix ecto.migrate
```
This ensures that [rebar3](https://www.rebar3.org) and [hex](https://hex.pm) are both installed
This ensures that [rebar3](https://rebar3.org) and [hex](https://hex.pm) are both installed
before attempting to fetch the dependencies that are required to run the tests. Next, the `postgres` db
is created and migrated with `ecto`, to ensure it's up-to-date.

View File

@ -10,7 +10,7 @@ type: index, howto
If you are currently using CircleCI, you can migrate your CI/CD pipelines to [GitLab CI/CD](../introduction/index.md),
and start making use of all its powerful features. Check out our
[CircleCI vs GitLab](https://about.gitlab.com/devops-tools/circle-ci-vs-gitlab.html)
[CircleCI vs GitLab](https://about.gitlab.com/devops-tools/circle-ci-vs-gitlab/)
comparison to see what's different.
We have collected several resources that you may find useful before starting to migrate.

View File

@ -4,7 +4,7 @@ The GitLab product is made up of several service components that run as independ
## Integration phases
The following outline re-uses the [maturity metric](https://about.gitlab.com/direction/maturity) naming as an example of the various phases of integrating a component. These are only loosely coupled to a components actual maturity, and are intended as a guide for implementation order (for example, a component does not need to be enabled by default to be Lovable, and being enabled by default does not on its own cause a component to be Lovable).
The following outline re-uses the [maturity metric](https://about.gitlab.com/direction/maturity/) naming as an example of the various phases of integrating a component. These are only loosely coupled to a components actual maturity, and are intended as a guide for implementation order (for example, a component does not need to be enabled by default to be Lovable, and being enabled by default does not on its own cause a component to be Lovable).
- Proposed
- [Proposing a new component](#proposing-a-new-component)

View File

@ -12,7 +12,7 @@ files.
For broader information about the documentation, see the [Documentation guidelines](index.md).
For guidelines specific to text in the GitLab interface, see the Pajamas [Content](https://design.gitlab.com/content/error-messages) section.
For guidelines specific to text in the GitLab interface, see the Pajamas [Content](https://design.gitlab.com/content/error-messages/) section.
For information on how to validate styles locally or by using GitLab CI/CD, see [Testing](index.md#testing).
@ -25,7 +25,7 @@ If you can't find what you need:
- View the GitLab Handbook for [writing style guidelines](https://about.gitlab.com/handbook/communication/#writing-style-guidelines) that apply to all GitLab content.
- Refer to one of the following:
- [Microsoft Style Guide](https://docs.microsoft.com/en-us/style-guide/).
- [Microsoft Style Guide](https://docs.microsoft.com/en-us/style-guide/welcome/).
- [Google Developer Documentation Style Guide](https://developers.google.com/style).
If you have questions about style, mention `@tw-style` in an issue or merge request, or, if you have access to the GitLab Slack workspace, use `#docs-process`.
@ -740,7 +740,7 @@ This is a list of available features:
- Don't add commas (`,`) or semicolons (`;`) to the ends of list items.
- Only add periods to the end of a list item if the item consists of a complete
sentence. The [definition of full sentence](https://www2.le.ac.uk/offices/ld/all-resources/writing/grammar/grammar-guides/sentence)
sentence. The [definition of full sentence](https://www.le.ac.uk/oerresources/ssds/grammarguides/page_02.htm)
is: _"a complete sentence always contains a verb, expresses a complete idea, and makes sense standing alone"_.
- Be consistent throughout the list: if the majority of the items do not end in
a period, do not end any of the items in a period, even if they consist of a
@ -1693,7 +1693,7 @@ own line and surrounded by blank lines.
- `> [Introduced](<link-to-issue>) in GitLab 11.3.`.
- If the feature is only available in GitLab Enterprise Edition, mention
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the [paid tier](https://about.gitlab.com/handbook/marketing/strategic-marketing/tiers/)
the feature is available in:
- `> [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.`.

View File

@ -219,46 +219,11 @@ the correct runbook:
## View the environment that generated the alert
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232492) in GitLab 13.5.
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-environment-link-in-alert-details). **(CORE ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232492) in GitLab 13.5
behind a feature flag, disabled by default.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/232492) in GitLab 13.6.
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
The environment information and the link are displayed in the [Alert Details tab](#alert-details-tab).
### Enable or disable Environment Link in Alert Details **(CORE ONLY)**
Viewing the environment is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:expose_environment_path_in_alert_details)
```
To enable for just a particular project:
```ruby
project = Project.find_by_full_path('your-group/your-project')
Feature.enable(:expose_environment_path_in_alert_details, project)
```
To disable it:
```ruby
Feature.disable(:expose_environment_path_in_alert_details)
```
To disable for just a particular project:
```ruby
project = Project.find_by_full_path('your-group/your-project')
Feature.disable(:expose_environment_path_in_alert_details, project)
```

View File

@ -62,6 +62,10 @@ module Gitlab
def self.manual_bridges_enabled?(project)
::Feature.enabled?(:ci_manual_bridges, project, default_enabled: true)
end
def self.seed_block_run_before_workflow_rules_enabled?(project)
::Feature.enabled?(:ci_seed_block_run_before_workflow_rules, project, default_enabled: false)
end
end
end
end

View File

@ -19,10 +19,12 @@ module Gitlab
# Build to prevent erroring out on ambiguous refs.
pipeline.protected = @command.protected_ref?
##
# Populate pipeline with block argument of CreatePipelineService#execute.
#
@command.seeds_block&.call(pipeline)
unless ::Gitlab::Ci::Features.seed_block_run_before_workflow_rules_enabled?(project)
##
# Populate pipeline with block argument of CreatePipelineService#execute.
#
@command.seeds_block&.call(pipeline)
end
##
# Gather all runtime build/stage errors

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Chain
class SeedBlock < Chain::Base
include Chain::Helpers
include Gitlab::Utils::StrongMemoize
def perform!
return unless ::Gitlab::Ci::Features.seed_block_run_before_workflow_rules_enabled?(project)
##
# Populate pipeline with block argument of CreatePipelineService#execute.
#
@command.seeds_block&.call(pipeline)
raise "Pipeline cannot be persisted by `seeds_block`" if pipeline.persisted?
end
def break?
return false unless ::Gitlab::Ci::Features.seed_block_run_before_workflow_rules_enabled?(project)
pipeline.errors.any?
end
end
end
end
end
end

View File

@ -220,11 +220,6 @@ msgid_plural "%d issues in this group"
msgstr[0] ""
msgstr[1] ""
msgid "%d issue selected"
msgid_plural "%d issues selected"
msgstr[0] ""
msgstr[1] ""
msgid "%d issue successfully imported with the label"
msgid_plural "%d issues successfully imported with the label"
msgstr[0] ""
@ -412,6 +407,16 @@ msgstr ""
msgid "%{count} files touched"
msgstr ""
msgid "%{count} issue selected"
msgid_plural "%{count} issues selected"
msgstr[0] ""
msgstr[1] ""
msgid "%{count} merge request selected"
msgid_plural "%{count} merge requests selected"
msgstr[0] ""
msgstr[1] ""
msgid "%{count} more"
msgstr ""
@ -10870,6 +10875,9 @@ msgstr ""
msgid "Export issues"
msgstr ""
msgid "Export merge requests"
msgstr ""
msgid "Export project"
msgstr ""

View File

@ -42,8 +42,8 @@
"@babel/plugin-syntax-import-meta": "^7.10.1",
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.173.0",
"@gitlab/ui": "21.35.2",
"@gitlab/svgs": "1.174.0",
"@gitlab/ui": "21.38.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-3",
"@rails/ujs": "^6.0.3-2",

View File

@ -14,6 +14,7 @@
# Global routes
User-Agent: *
Disallow: /autocomplete/users
Disallow: /autocomplete/projects
Disallow: /search
Disallow: /admin
Disallow: /profile

View File

@ -15,13 +15,13 @@ module QA
element :avatar_counter_content
end
view 'app/views/projects/issues/export_csv/_button.html.haml' do
view 'app/views/shared/issuable/csv_export/_button.html.haml' do
element :export_as_csv_button
end
view 'app/views/projects/issues/export_csv/_modal.html.haml' do
view 'app/views/shared/issuable/csv_export/_modal.html.haml' do
element :export_issues_button
element :export_issues_modal
element :export_issuable_modal
end
view 'app/views/projects/issues/import_csv/_button.html.haml' do
@ -64,7 +64,7 @@ module QA
end
def export_issues_modal
find_element(:export_issues_modal)
find_element(:export_issuable_modal)
end
def go_to_jira_import_form

View File

@ -1994,4 +1994,37 @@ RSpec.describe Projects::MergeRequestsController do
expect(assigns(:noteable)).not_to be_nil
end
end
describe 'POST export_csv' do
subject { post :export_csv, params: { namespace_id: project.namespace, project_id: project } }
before do
stub_feature_flags(export_merge_requests_as_csv: project)
end
it 'redirects to the merge request index' do
subject
expect(response).to redirect_to(project_merge_requests_path(project))
expect(response.flash[:notice]).to match(/\AYour CSV export has started/i)
end
it 'enqueues an IssuableExportCsvWorker worker' do
expect(IssuableExportCsvWorker).to receive(:perform_async).with(:merge_request, user.id, project.id, anything)
subject
end
context 'feature is disabled' do
before do
stub_feature_flags(export_merge_requests_as_csv: false)
end
it 'expects a 404 response' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end

View File

@ -41,21 +41,6 @@ RSpec.describe 'Alert management', :js do
expect(page).to have_content(environment.name)
end
end
context 'when expose_environment_path_in_alert_details feature flag is disabled' do
before do
stub_feature_flags(expose_environment_path_in_alert_details: false)
end
it 'does not show the environment name' do
visit(details_project_alert_management_path(project, alert))
within('.alert-management-details-table') do
expect(page).to have_content(alert.title)
expect(page).not_to have_content(environment.name)
end
end
end
end
end
end

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Merge Requests > Exports as CSV', :js do
let!(:project) { create(:project, :public, :repository) }
let!(:user) { project.creator }
let!(:open_mr) { create(:merge_request, title: 'Bugfix1', source_project: project, target_project: project, source_branch: 'bugfix1') }
before do
sign_in(user)
end
subject { page.find('.nav-controls') }
context 'feature is not enabled' do
before do
stub_feature_flags(export_merge_requests_as_csv: false)
visit(project_merge_requests_path(project))
end
it { is_expected.not_to have_button('Export as CSV') }
end
context 'feature is enabled for a project' do
before do
stub_feature_flags(export_merge_requests_as_csv: project)
visit(project_merge_requests_path(project))
end
it { is_expected.to have_button('Export as CSV') }
context 'button is clicked' do
before do
click_button('Export as CSV')
end
it 'shows a success message' do
click_link('Export merge requests')
expect(page).to have_content 'Your CSV export has started.'
expect(page).to have_content "It will be emailed to #{user.email} when complete"
end
end
end
end

View File

@ -2,13 +2,16 @@
require 'spec_helper'
RSpec.describe Packages::GroupPackagesFinder do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, namespace: group) }
let(:another_group) { create(:group) }
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project) { create(:project, namespace: group, builds_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE) }
let(:add_user_to_group) { true }
before do
group.add_developer(user)
group.add_developer(user) if add_user_to_group
end
describe '#execute' do
@ -27,16 +30,16 @@ RSpec.describe Packages::GroupPackagesFinder do
end
context 'group has packages' do
let!(:package1) { create(:maven_package, project: project) }
let!(:package2) { create(:maven_package, project: project) }
let!(:package3) { create(:maven_package) }
let_it_be(:package1) { create(:maven_package, project: project) }
let_it_be(:package2) { create(:maven_package, project: project) }
let_it_be(:package3) { create(:maven_package) }
it { is_expected.to match_array([package1, package2]) }
context 'subgroup has packages' do
let(:subgroup) { create(:group, parent: group) }
let(:subproject) { create(:project, namespace: subgroup) }
let!(:package4) { create(:npm_package, project: subproject) }
let_it_be_with_reload(:subgroup) { create(:group, parent: group) }
let_it_be_with_reload(:subproject) { create(:project, namespace: subgroup, builds_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE) }
let_it_be(:package4) { create(:npm_package, project: subproject) }
it { is_expected.to match_array([package1, package2, package4]) }
@ -45,16 +48,87 @@ RSpec.describe Packages::GroupPackagesFinder do
it { is_expected.to match_array([package1, package2]) }
end
context 'permissions' do
let(:add_user_to_group) { false }
where(:role, :project_visibility, :repository_visibility, :packages_returned) do
:anonymous | :public | :enabled | :all
:guest | :public | :enabled | :all
:reporter | :public | :enabled | :all
:developer | :public | :enabled | :all
:maintainer | :public | :enabled | :all
:anonymous | :public | :private | :none
:guest | :public | :private | :all
:reporter | :public | :private | :all
:developer | :public | :private | :all
:maintainer | :public | :private | :all
:anonymous | :private | :enabled | :none
:guest | :private | :enabled | :none
:reporter | :private | :enabled | :all
:developer | :private | :enabled | :all
:maintainer | :private | :enabled | :all
:anonymous | :private | :private | :none
:guest | :private | :private | :none
:reporter | :private | :private | :all
:developer | :private | :private | :all
:maintainer | :private | :private | :all
end
with_them do
let(:expected_packages) do
case packages_returned
when :all
[package1, package2, package4]
when :none
[]
end
end
before do
subgroup.update!(visibility: project_visibility.to_s)
group.update!(visibility: project_visibility.to_s)
project.update!(
visibility: project_visibility.to_s,
repository_access_level: repository_visibility.to_s
)
subproject.update!(
visibility: project_visibility.to_s,
repository_access_level: repository_visibility.to_s
)
unless role == :anonymous
project.add_user(user, role)
subproject.add_user(user, role)
end
end
it { is_expected.to match_array(expected_packages) }
end
end
context 'avoid N+1 query' do
it 'avoids N+1 database queries' do
count = ActiveRecord::QueryRecorder.new { subject }
.count
Packages::Package.package_types.keys.each do |package_type|
create("#{package_type}_package", project: create(:project, namespace: subgroup))
end
expect { described_class.new(user, group, params).execute }.not_to exceed_query_limit(count)
end
end
end
context 'when there are processing packages' do
let!(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
let_it_be(:package4) { create(:nuget_package, project: project, name: Packages::Nuget::CreatePackageService::TEMPORARY_PACKAGE_NAME) }
it { is_expected.to match_array([package1, package2]) }
end
context 'does not include packages without version number' do
let!(:package_without_version) { create(:maven_package, project: project, version: nil) }
let_it_be(:package_without_version) { create(:maven_package, project: project, version: nil) }
it { is_expected.not_to include(package_without_version) }
end
@ -80,7 +154,7 @@ RSpec.describe Packages::GroupPackagesFinder do
end
context 'group has package of all types' do
package_types.each { |pt| let!("package_#{pt}") { create("#{pt}_package", project: project) } }
package_types.each { |pt| let_it_be("package_#{pt}") { create("#{pt}_package", project: project) } }
package_types.each do |package_type|
it_behaves_like 'with package type', package_type
@ -98,7 +172,7 @@ RSpec.describe Packages::GroupPackagesFinder do
end
context 'package type is nil' do
let!(:package1) { create(:maven_package, project: project) }
let_it_be(:package1) { create(:maven_package, project: project) }
subject { described_class.new(user, group, package_type: nil).execute }
@ -110,47 +184,5 @@ RSpec.describe Packages::GroupPackagesFinder do
it { expect { subject }.to raise_exception(described_class::InvalidPackageTypeError) }
end
context 'when project is public' do
let_it_be(:other_user) { create(:user) }
let(:finder) { described_class.new(other_user, group) }
before do
project.update!(visibility_level: ProjectFeature::ENABLED)
end
context 'when packages are public' do
before do
project.project_feature.update!(
builds_access_level: ProjectFeature::PRIVATE,
merge_requests_access_level: ProjectFeature::PRIVATE,
repository_access_level: ProjectFeature::ENABLED)
end
it 'returns group packages' do
package1 = create(:maven_package, project: project)
package2 = create(:maven_package, project: project)
create(:maven_package)
expect(finder.execute).to match_array([package1, package2])
end
end
context 'packages are members only' do
before do
project.project_feature.update!(
builds_access_level: ProjectFeature::PRIVATE,
merge_requests_access_level: ProjectFeature::PRIVATE,
repository_access_level: ProjectFeature::PRIVATE)
create(:maven_package, project: project)
create(:maven_package)
end
it 'filters out the project if the user doesn\'t have permission' do
expect(finder.execute).to be_empty
end
end
end
end
end

View File

@ -20,11 +20,7 @@ const environmentName = 'Production';
const environmentPath = '/fake/path';
describe('AlertDetails', () => {
let environmentData = {
name: environmentName,
path: environmentPath,
};
let glFeatures = { exposeEnvironmentPathInAlertDetails: false };
let environmentData = { name: environmentName, path: environmentPath };
let mock;
let wrapper;
const projectPath = 'root/alerts';
@ -40,7 +36,6 @@ describe('AlertDetails', () => {
projectPath,
projectIssuesPath,
projectId,
glFeatures,
},
data() {
return {
@ -159,33 +154,21 @@ describe('AlertDetails', () => {
});
describe('environment fields', () => {
describe('when exposeEnvironmentPathInAlertDetails is disabled', () => {
beforeEach(mountComponent);
it('should show the environment name with a link to the path', () => {
mountComponent();
const path = findEnvironmentPath();
it('should not show the environment', () => {
expect(findEnvironmentName().exists()).toBe(false);
expect(findEnvironmentPath().exists()).toBe(false);
});
expect(findEnvironmentName().exists()).toBe(false);
expect(path.text()).toBe(environmentName);
expect(path.attributes('href')).toBe(environmentPath);
});
describe('when exposeEnvironmentPathInAlertDetails is enabled', () => {
beforeEach(() => {
glFeatures = { exposeEnvironmentPathInAlertDetails: true };
mountComponent();
});
it('should only show the environment name if the path is not provided', () => {
environmentData = { name: environmentName, path: null };
mountComponent();
it('should show the environment name with link to path', () => {
expect(findEnvironmentName().exists()).toBe(false);
expect(findEnvironmentPath().text()).toBe(environmentName);
expect(findEnvironmentPath().attributes('href')).toBe(environmentPath);
});
it('should only show the environment name if the path is not provided', () => {
environmentData = { name: environmentName, path: null };
mountComponent();
expect(findEnvironmentPath().exists()).toBe(false);
expect(findEnvironmentName().text()).toBe(environmentName);
});
expect(findEnvironmentPath().exists()).toBe(false);
expect(findEnvironmentName().text()).toBe(environmentName);
});
});
@ -195,6 +178,7 @@ describe('AlertDetails', () => {
mountComponent({
data: { alert: { ...mockAlert, issueIid }, sidebarStatus: false },
});
expect(findViewIncidentBtn().exists()).toBe(true);
expect(findViewIncidentBtn().attributes('href')).toBe(
joinPaths(projectIssuesPath, issueIid),
@ -220,8 +204,8 @@ describe('AlertDetails', () => {
jest
.spyOn(wrapper.vm.$apollo, 'mutate')
.mockResolvedValue({ data: { createAlertIssue: { issue: { iid: issueIid } } } });
findCreateIncidentBtn().trigger('click');
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createIssueMutation,
variables: {
@ -251,6 +235,7 @@ describe('AlertDetails', () => {
beforeEach(() => {
mountComponent({ data: { alert: mockAlert } });
});
it('should display a table of raw alert details data', () => {
expect(findDetailsTable().exists()).toBe(true);
});

View File

@ -23,14 +23,10 @@ const environmentPath = '/fake/path';
describe('AlertDetails', () => {
let environmentData = { name: environmentName, path: environmentPath };
let glFeatures = { exposeEnvironmentPathInAlertDetails: false };
let wrapper;
function mountComponent(propsData = {}) {
wrapper = mount(AlertDetailsTable, {
provide: {
glFeatures,
},
propsData: {
alert: {
...mockAlert,
@ -97,34 +93,19 @@ describe('AlertDetails', () => {
expect(findTableField(fields, 'Severity').exists()).toBe(true);
expect(findTableField(fields, 'Status').exists()).toBe(true);
expect(findTableField(fields, 'Hosts').exists()).toBe(true);
expect(findTableField(fields, 'Environment').exists()).toBe(false);
expect(findTableField(fields, 'Environment').exists()).toBe(true);
});
it('should not show disallowed and flaggedAllowed alert fields', () => {
it('should not show disallowed alert fields', () => {
const fields = findTableKeys();
expect(findTableField(fields, 'Typename').exists()).toBe(false);
expect(findTableField(fields, 'Todos').exists()).toBe(false);
expect(findTableField(fields, 'Notes').exists()).toBe(false);
expect(findTableField(fields, 'Assignees').exists()).toBe(false);
expect(findTableField(fields, 'Environment').exists()).toBe(false);
});
});
describe('when exposeEnvironmentPathInAlertDetails is enabled', () => {
beforeEach(() => {
glFeatures = { exposeEnvironmentPathInAlertDetails: true };
mountComponent();
});
it('should show flaggedAllowed alert fields', () => {
const fields = findTableKeys();
expect(findTableField(fields, 'Environment').exists()).toBe(true);
});
it('should display only the name for the environment', () => {
expect(findTableFieldValueByKey('Iid').text()).toBe('1527542');
expect(findTableFieldValueByKey('Environment').text()).toBe(environmentName);
});

View File

@ -44,18 +44,12 @@ RSpec.describe GitlabSchema.types['Environment'] do
expect(subject['data']['project']['environment']['name']).to eq(environment.name)
end
it 'returns the path when the feature is enabled' do
it 'returns the path to the environment' do
expect(subject['data']['project']['environment']['path']).to eq(
Gitlab::Routing.url_helpers.project_environment_path(project, environment)
)
end
it 'does not return the path when the feature is disabled' do
stub_feature_flags(expose_environment_path_in_alert_details: false)
expect(subject['data']['project']['environment']['path']).to be_nil
end
context 'when query alert data for the environment' do
let_it_be(:query) do
%(

View File

@ -14,7 +14,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
specify { expect(described_class.interfaces).to include(Types::CurrentUserTodos) }
it 'has specific fields' do
fields = %i[id iid title description state reference author assignees participants labels milestone due_date
fields = %i[id iid title description state reference author assignees updated_by participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status
designs design_collection alert_management_alert severity current_user_todos]

View File

@ -22,6 +22,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
[
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::SeedBlock.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Seed.new(pipeline, command)
]
end
@ -180,23 +181,21 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
->(pipeline) { pipeline.variables.create!(key: 'VAR', value: '123') }
end
it 'wastes pipeline iid' do
expect { run_chain }.to raise_error(ActiveRecord::RecordNotSaved)
last_iid = InternalId.ci_pipelines
.where(project_id: project.id)
.last.last_value
expect(last_iid).to be > 0
it 'raises error' do
expect { run_chain }.to raise_error(ActiveRecord::RecordNotSaved,
'You cannot call create unless the parent is saved')
end
end
end
context 'when pipeline gets persisted during the process' do
let(:pipeline) { create(:ci_pipeline, project: project) }
before do
dependencies.each(&:perform!)
pipeline.save!
end
it 'raises error' do
expect { run_chain }.to raise_error(described_class::PopulateError)
expect { step.perform! }.to raise_error(described_class::PopulateError)
end
end

View File

@ -0,0 +1,78 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::SeedBlock do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
let(:seeds_block) { }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
current_user: user,
origin_ref: 'master',
seeds_block: seeds_block)
end
let(:pipeline) { build(:ci_pipeline, project: project) }
describe '#perform!' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
subject(:run_chain) do
[
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
].map(&:perform!)
described_class.new(pipeline, command).perform!
end
let(:config) do
{ rspec: { script: 'rake' } }
end
context 'when there is not seeds_block' do
it 'does nothing' do
expect { run_chain }.not_to raise_error
end
end
context 'when there is seeds_block' do
let(:seeds_block) do
->(pipeline) { pipeline.variables.build(key: 'VAR', value: '123') }
end
it 'executes the block' do
run_chain
expect(pipeline.variables.size).to eq(1)
end
context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
before do
stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
end
it 'does not execute the block' do
run_chain
expect(pipeline.variables.size).to eq(0)
end
end
end
context 'when the seeds_block tries to save the pipelie' do
let(:seeds_block) do
->(pipeline) { pipeline.save! }
end
it 'raises error' do
expect { run_chain }.to raise_error('Pipeline cannot be persisted by `seeds_block`')
end
end
end
end

View File

@ -5,22 +5,14 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let(:project) { create(:project, :repository) }
let(:user) { create(:user, developer_projects: [project]) }
let(:seeds_block) { }
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
current_user: user,
origin_ref: 'master',
seeds_block: nil)
end
def run_chain(pipeline, command)
[
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
].map(&:perform!)
described_class.new(pipeline, command).perform!
seeds_block: seeds_block)
end
let(:pipeline) { build(:ci_pipeline, project: project) }
@ -28,22 +20,36 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
describe '#perform!' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
run_chain(pipeline, command)
end
let(:config) do
{ rspec: { script: 'rake' } }
end
subject(:run_chain) do
[
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
].map(&:perform!)
described_class.new(pipeline, command).perform!
end
it 'allocates next IID' do
run_chain
expect(pipeline.iid).to be_present
end
it 'ensures ci_ref' do
run_chain
expect(pipeline.ci_ref).to be_present
end
it 'sets the seeds in the command object' do
run_chain
expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
expect(command.stage_seeds.count).to eq 1
end
@ -58,6 +64,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'correctly fabricates a stage seeds object' do
run_chain
seeds = command.stage_seeds
expect(seeds.size).to eq 2
expect(seeds.first.attributes[:name]).to eq 'test'
@ -81,6 +89,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns stage seeds only assigned to master' do
run_chain
seeds = command.stage_seeds
expect(seeds.size).to eq 1
@ -100,6 +110,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns stage seeds only assigned to schedules' do
run_chain
seeds = command.stage_seeds
expect(seeds.size).to eq 1
@ -127,6 +139,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
let(:pipeline) { build(:ci_pipeline, project: project) }
it 'returns seeds for kubernetes dependent job' do
run_chain
seeds = command.stage_seeds
expect(seeds.size).to eq 2
@ -138,6 +152,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
context 'when kubernetes is not active' do
it 'does not return seeds for kubernetes dependent job' do
run_chain
seeds = command.stage_seeds
expect(seeds.size).to eq 1
@ -155,11 +171,39 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
end
it 'returns stage seeds only when variables expression is truthy' do
run_chain
seeds = command.stage_seeds
expect(seeds.size).to eq 1
expect(seeds.dig(0, 0, :name)).to eq 'unit'
end
end
context 'when there is seeds_block' do
let(:seeds_block) do
->(pipeline) { pipeline.variables.build(key: 'VAR', value: '123') }
end
context 'when FF ci_seed_block_run_before_workflow_rules is enabled' do
it 'does not execute the block' do
run_chain
expect(pipeline.variables.size).to eq(0)
end
end
context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
before do
stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
end
it 'executes the block' do
run_chain
expect(pipeline.variables.size).to eq(1)
end
end
end
end
end

View File

@ -591,9 +591,21 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe '.system_usage_data_monthly' do
let_it_be(:project) { create(:project) }
let!(:ud) { build(:usage_data) }
before do
project = create(:project)
env = create(:environment)
create(:package, project: project, created_at: 3.days.ago)
create(:package, created_at: 2.months.ago, project: project)
[3, 31].each do |n|
deployment_options = { created_at: n.days.ago, project: env.project, environment: env }
create(:deployment, :failed, deployment_options)
create(:deployment, :success, deployment_options)
create(:project_snippet, project: project, created_at: n.days.ago)
create(:personal_snippet, created_at: n.days.ago)
end
stub_application_setting(self_monitoring_project: project)
for_defined_days_back do
@ -609,10 +621,10 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(counts_monthly[:deployments]).to eq(2)
expect(counts_monthly[:successful_deployments]).to eq(1)
expect(counts_monthly[:failed_deployments]).to eq(1)
expect(counts_monthly[:snippets]).to eq(3)
expect(counts_monthly[:snippets]).to eq(2)
expect(counts_monthly[:personal_snippets]).to eq(1)
expect(counts_monthly[:project_snippets]).to eq(2)
expect(counts_monthly[:packages]).to eq(3)
expect(counts_monthly[:project_snippets]).to eq(1)
expect(counts_monthly[:packages]).to eq(1)
expect(counts_monthly[:promoted_issues]).to eq(1)
end
end

View File

@ -71,14 +71,15 @@ RSpec.describe 'Query.issue(id)' do
end
context 'selecting multiple fields' do
let(:issue_fields) { %w(title description) }
let(:issue_fields) { ['title', 'description', 'updatedBy { username }'] }
it 'returns the Issue with the specified fields' do
post_graphql(query, current_user: current_user)
expect(issue_data.keys).to eq( %w(title description) )
expect(issue_data.keys).to eq( %w(title description updatedBy) )
expect(issue_data['title']).to eq(issue.title)
expect(issue_data['description']).to eq(issue.description)
expect(issue_data['updatedBy']['username']).to eq(issue.author.username)
end
end

View File

@ -27,6 +27,7 @@ RSpec.describe 'Robots.txt Requests', :aggregate_failures do
it 'blocks the requests' do
requests = [
'/autocomplete/users',
'/autocomplete/projects',
'/search',
'/admin',
'/profile',

View File

@ -581,5 +581,40 @@ RSpec.describe Ci::CreateDownstreamPipelineService, '#execute' do
)
end
end
context 'when downstream pipeline has workflow rule' do
before do
stub_ci_pipeline_yaml_file(config)
end
let(:config) do
<<-EOY
workflow:
rules:
- if: $my_var
regular-job:
script: 'echo Hello, World!'
EOY
end
context 'when passing the required variable' do
before do
bridge.yaml_variables = [{ key: 'my_var', value: 'var', public: true }]
end
it 'creates the pipeline' do
expect { service.execute(bridge) }.to change(downstream_project.ci_pipelines, :count).by(1)
expect(bridge.reload).to be_success
end
end
context 'when not passing the required variable' do
it 'does not create the pipeline' do
expect { service.execute(bridge) }.not_to change(downstream_project.ci_pipelines, :count)
end
end
end
end
end

View File

@ -41,7 +41,9 @@ RSpec.describe Ci::CreatePipelineService do
save_on_errors: save_on_errors,
trigger_request: trigger_request,
merge_request: merge_request,
external_pull_request: external_pull_request)
external_pull_request: external_pull_request) do |pipeline|
yield(pipeline) if block_given?
end
end
# rubocop:enable Metrics/ParameterLists
@ -2274,6 +2276,207 @@ RSpec.describe Ci::CreatePipelineService do
end
end
end
context 'with workflow rules with persisted variables' do
let(:config) do
<<-EOY
workflow:
rules:
- if: $CI_COMMIT_REF_NAME == "master"
regular-job:
script: 'echo Hello, World!'
EOY
end
context 'with matches' do
it 'creates a pipeline' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('regular-job')
end
end
context 'with no matches' do
let(:ref_name) { 'refs/heads/feature' }
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
end
end
context 'with workflow rules with pipeline variables' do
let(:pipeline) do
execute_service(variables_attributes: variables_attributes)
end
let(:config) do
<<-EOY
workflow:
rules:
- if: $SOME_VARIABLE
regular-job:
script: 'echo Hello, World!'
EOY
end
context 'with matches' do
let(:variables_attributes) do
[{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }]
end
it 'creates a pipeline' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('regular-job')
end
end
context 'with no matches' do
let(:variables_attributes) { {} }
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
end
end
context 'with workflow rules with trigger variables' do
let(:pipeline) do
execute_service do |pipeline|
pipeline.variables.build(variables)
end
end
let(:config) do
<<-EOY
workflow:
rules:
- if: $SOME_VARIABLE
regular-job:
script: 'echo Hello, World!'
EOY
end
context 'with matches' do
let(:variables) do
[{ key: 'SOME_VARIABLE', secret_value: 'SOME_VAR' }]
end
it 'creates a pipeline' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('regular-job')
end
context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
before do
stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
end
it 'does not a pipeline' do
expect(pipeline).not_to be_persisted
end
end
context 'when a job requires the same variable' do
let(:config) do
<<-EOY
workflow:
rules:
- if: $SOME_VARIABLE
build:
stage: build
script: 'echo build'
rules:
- if: $SOME_VARIABLE
test1:
stage: test
script: 'echo test1'
needs: [build]
test2:
stage: test
script: 'echo test2'
EOY
end
it 'creates a pipeline' do
expect(pipeline).to be_persisted
expect(build_names).to contain_exactly('build', 'test1', 'test2')
end
context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
before do
stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
end
it 'does not a pipeline' do
expect(pipeline).not_to be_persisted
end
end
end
end
context 'with no matches' do
let(:variables) { {} }
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
before do
stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
end
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
end
context 'when a job requires the same variable' do
let(:config) do
<<-EOY
workflow:
rules:
- if: $SOME_VARIABLE
build:
stage: build
script: 'echo build'
rules:
- if: $SOME_VARIABLE
test1:
stage: test
script: 'echo test1'
needs: [build]
test2:
stage: test
script: 'echo test2'
EOY
end
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
context 'when FF ci_seed_block_run_before_workflow_rules is disabled' do
before do
stub_feature_flags(ci_seed_block_run_before_workflow_rules: false)
end
it 'does not create a pipeline' do
expect(pipeline).not_to be_persisted
end
end
end
end
end
end
end

View File

@ -861,15 +861,15 @@
eslint-plugin-vue "^6.2.1"
vue-eslint-parser "^7.0.0"
"@gitlab/svgs@1.173.0":
version "1.173.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.173.0.tgz#4030cdbb8efb7386941169fc9cefda1bae75b282"
integrity sha512-EmfoLgh0Jnz7i28zHROnwvpOcdPaavAXJEpSg8JfbYw9KcWlJyr4Zm5V6h38dNU5AcUFG1/qjUtFbfgmyBx4gA==
"@gitlab/svgs@1.174.0":
version "1.174.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.174.0.tgz#954b4d908a6188a2fcc45f00f748beeb23f054b0"
integrity sha512-CgnZvO2miZkWxANhFdaK+2S4qRgkrMRE3vh3Xxwc+hIV9ki9KavlAAez9MNIs0Um/SJ1UpfmqKoM/dMyZX7K/w==
"@gitlab/ui@21.35.2":
version "21.35.2"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-21.35.2.tgz#2409b3f2979f470b4072e42e32393f024a70630e"
integrity sha512-2/kdx7wXVC59UJjk2tC+HViLhDGVm0Jj/FeIQLHeJYVn0R2ZaEYAkevjyXRN33TuOcRHnInQjVXwSclCdQ7K5A==
"@gitlab/ui@21.38.0":
version "21.38.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-21.38.0.tgz#ff67908cdb4cb5efb84138842a181640d75b1524"
integrity sha512-T1Bvts25s+OS+v5/z6rL24whjGjO8b4KOBZy1+bp3UcQFLC/+gwJAimtiCsILPojlf7WJAPDK1KqJiDciy8gWg==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"