summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--app/assets/javascripts/alert_management/components/alert_details.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/alert_details_table.vue11
-rw-r--r--app/assets/stylesheets/framework/modal.scss2
-rw-r--r--app/controllers/projects/alert_management_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb12
-rw-r--r--app/finders/packages/group_packages_finder.rb9
-rw-r--r--app/graphql/types/environment_type.rb5
-rw-r--r--app/graphql/types/issue_type.rb7
-rw-r--r--app/models/audit_event.rb11
-rw-r--r--app/presenters/environment_presenter.rb4
-rw-r--r--app/services/ci/create_pipeline_service.rb1
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml4
-rw-r--r--app/views/projects/issues/export_csv/_modal.html.haml22
-rw-r--r--app/views/projects/merge_requests/_nav_btns.html.haml7
-rw-r--r--app/views/projects/show.html.haml1
-rw-r--r--app/views/shared/issuable/csv_export/_button.html.haml (renamed from app/views/projects/issues/export_csv/_button.html.haml)2
-rw-r--r--app/views/shared/issuable/csv_export/_modal.html.haml29
-rw-r--r--changelogs/unreleased/258638-feature-flag-rollout-of-enable_environment_path_in_alert_details.yml5
-rw-r--r--changelogs/unreleased/267010-improve-group-packages-finder.yml5
-rw-r--r--changelogs/unreleased/268315-restructure-project-page-title-to-show-project-name-first.yml5
-rw-r--r--changelogs/unreleased/272984-fj-forbid-autocomplete-projects.yml5
-rw-r--r--changelogs/unreleased/issue_233479-add_updated_by_for_issue_type.yml5
-rw-r--r--config/feature_flags/development/ci_seed_block_run_before_workflow_rules.yml (renamed from config/feature_flags/development/expose_environment_path_in_alert_details.yml)8
-rw-r--r--config/feature_flags/development/export_merge_requests_as_csv.yml7
-rw-r--r--config/routes/merge_requests.rb1
-rw-r--r--doc/administration/auth/ldap/index.md6
-rw-r--r--doc/administration/consul.md2
-rw-r--r--doc/administration/nfs.md2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql15
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json40
-rw-r--r--doc/api/graphql/reference/index.md4
-rw-r--r--doc/ci/ci_cd_for_external_repos/github_integration.md2
-rw-r--r--doc/ci/docker/using_docker_build.md2
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md2
-rw-r--r--doc/ci/migration/circleci.md2
-rw-r--r--doc/development/adding_service_component.md2
-rw-r--r--doc/development/documentation/styleguide.md8
-rw-r--r--doc/operations/incident_management/alerts.md41
-rw-r--r--lib/gitlab/ci/features.rb4
-rw-r--r--lib/gitlab/ci/pipeline/chain/seed.rb10
-rw-r--r--lib/gitlab/ci/pipeline/chain/seed_block.rb31
-rw-r--r--locale/gitlab.pot18
-rw-r--r--package.json4
-rw-r--r--public/robots.txt1
-rw-r--r--qa/qa/page/project/issue/index.rb8
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb33
-rw-r--r--spec/features/alert_management_spec.rb15
-rw-r--r--spec/features/merge_requests/user_exports_as_csv_spec.rb46
-rw-r--r--spec/finders/packages/group_packages_finder_spec.rb146
-rw-r--r--spec/frontend/alert_management/components/alert_details_spec.js45
-rw-r--r--spec/frontend/vue_shared/components/alert_details_table_spec.js23
-rw-r--r--spec/graphql/types/environment_type_spec.rb8
-rw-r--r--spec/graphql/types/issue_type_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb78
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb66
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb20
-rw-r--r--spec/requests/api/graphql/issue/issue_spec.rb5
-rw-r--r--spec/requests/robots_txt_spec.rb1
-rw-r--r--spec/services/ci/create_downstream_pipeline_service_spec.rb35
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb205
-rw-r--r--yarn.lock18
64 files changed, 834 insertions, 316 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 2e96a9ff62e..69664070920 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b85367529ca34c8c423b94b7486c44e109ed553f
+bee8517ab043ff98c283a5f191e68e2bd75eb9de
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue
index f7a5d31b835..1f3fdd5eef2 100644
--- a/app/assets/javascripts/alert_management/components/alert_details.vue
+++ b/app/assets/javascripts/alert_management/components/alert_details.vue
@@ -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() {
diff --git a/app/assets/javascripts/vue_shared/components/alert_details_table.vue b/app/assets/javascripts/vue_shared/components/alert_details_table.vue
index 34f6d384f7b..3e2b4cd35ab 100644
--- a/app/assets/javascripts/vue_shared/components/alert_details_table.vue
+++ b/app/assets/javascripts/vue_shared/components/alert_details_table.vue
@@ -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);
},
},
};
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index 399e4c97ad8..372e3bed6e0 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -128,7 +128,7 @@ body.modal-open {
}
.issues-import-modal,
-.issues-export-modal {
+.issuable-export-modal {
.modal-header {
justify-content: flex-start;
diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb
index 0d0ef9b05cb..8ecf8fadefd 100644
--- a/app/controllers/projects/alert_management_controller.rb
+++ b/app/controllers/projects/alert_management_controller.rb
@@ -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
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 670bde6103c..a3444c516d4 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -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
diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb
index 8b948bb056d..a51057571f1 100644
--- a/app/finders/packages/group_packages_finder.rb
+++ b/app/finders/packages/group_packages_finder.rb
@@ -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
diff --git a/app/graphql/types/environment_type.rb b/app/graphql/types/environment_type.rb
index e4631a4a903..e3885668643 100644
--- a/app/graphql/types/environment_type.rb
+++ b/app/graphql/types/environment_type.rb
@@ -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',
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 57e6fd4cb2c..6bf7d812420 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -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
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 34f03e769a0..55e8a5d4535 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -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'
diff --git a/app/presenters/environment_presenter.rb b/app/presenters/environment_presenter.rb
index 3fa31eb69a2..6f67bbe2a5a 100644
--- a/app/presenters/environment_presenter.rb
+++ b/app/presenters/environment_presenter.rb
@@ -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
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index e7ede98fea4..e3bab2de44e 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -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,
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index fa560942c5d..a1210bf2df4 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -1,5 +1,4 @@
- breadcrumb_title _("Details")
-- page_title _("Groups")
- @content_class = "limit-container-width" unless fluid_layout
- if show_invite_banner?(@group)
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 2583c33fa03..dbf6a1f1b94 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -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'
diff --git a/app/views/projects/issues/export_csv/_modal.html.haml b/app/views/projects/issues/export_csv/_modal.html.haml
deleted file mode 100644
index 6eae84efdad..00000000000
--- a/app/views/projects/issues/export_csv/_modal.html.haml
+++ /dev/null
@@ -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" }
diff --git a/app/views/projects/merge_requests/_nav_btns.html.haml b/app/views/projects/merge_requests/_nav_btns.html.haml
index 01bc7347dbe..fb055c62647 100644
--- a/app/views/projects/merge_requests/_nav_btns.html.haml
+++ b/app/views/projects/merge_requests/_nav_btns.html.haml
@@ -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'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 67bdcd0d9d6..f7c51e9ada9 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -1,5 +1,4 @@
- breadcrumb_title _("Details")
-- page_title _("Projects")
- @content_class = "limit-container-width" unless fluid_layout
= content_for :meta_tags do
diff --git a/app/views/projects/issues/export_csv/_button.html.haml b/app/views/shared/issuable/csv_export/_button.html.haml
index e5710fcdb60..3584c9c1ed5 100644
--- a/app/views/projects/issues/export_csv/_button.html.haml
+++ b/app/views/shared/issuable/csv_export/_button.html.haml
@@ -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')
diff --git a/app/views/shared/issuable/csv_export/_modal.html.haml b/app/views/shared/issuable/csv_export/_modal.html.haml
new file mode 100644
index 00000000000..4a4c6b90cd9
--- /dev/null
+++ b/app/views/shared/issuable/csv_export/_modal.html.haml
@@ -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" }
diff --git a/changelogs/unreleased/258638-feature-flag-rollout-of-enable_environment_path_in_alert_details.yml b/changelogs/unreleased/258638-feature-flag-rollout-of-enable_environment_path_in_alert_details.yml
new file mode 100644
index 00000000000..7fe8b2860ba
--- /dev/null
+++ b/changelogs/unreleased/258638-feature-flag-rollout-of-enable_environment_path_in_alert_details.yml
@@ -0,0 +1,5 @@
+---
+title: Show the environment link on alert details page
+merge_request: 44130
+author:
+type: added
diff --git a/changelogs/unreleased/267010-improve-group-packages-finder.yml b/changelogs/unreleased/267010-improve-group-packages-finder.yml
new file mode 100644
index 00000000000..97cfdb86a7a
--- /dev/null
+++ b/changelogs/unreleased/267010-improve-group-packages-finder.yml
@@ -0,0 +1,5 @@
+---
+title: Fix an N+1 issue in Packages::GroupPackagesFinder
+merge_request: 45875
+author:
+type: fixed
diff --git a/changelogs/unreleased/268315-restructure-project-page-title-to-show-project-name-first.yml b/changelogs/unreleased/268315-restructure-project-page-title-to-show-project-name-first.yml
new file mode 100644
index 00000000000..80e45708196
--- /dev/null
+++ b/changelogs/unreleased/268315-restructure-project-page-title-to-show-project-name-first.yml
@@ -0,0 +1,5 @@
+---
+title: Remove page_title from single project and group pages
+merge_request: 46223
+author:
+type: fixed
diff --git a/changelogs/unreleased/272984-fj-forbid-autocomplete-projects.yml b/changelogs/unreleased/272984-fj-forbid-autocomplete-projects.yml
new file mode 100644
index 00000000000..f0da305ecc2
--- /dev/null
+++ b/changelogs/unreleased/272984-fj-forbid-autocomplete-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow /autocomplete/projects route in robots.txt
+merge_request: 46115
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_233479-add_updated_by_for_issue_type.yml b/changelogs/unreleased/issue_233479-add_updated_by_for_issue_type.yml
new file mode 100644
index 00000000000..1c77d28b4f3
--- /dev/null
+++ b/changelogs/unreleased/issue_233479-add_updated_by_for_issue_type.yml
@@ -0,0 +1,5 @@
+---
+title: Expose issue updated by on GraphQL
+merge_request: 46015
+author:
+type: added
diff --git a/config/feature_flags/development/expose_environment_path_in_alert_details.yml b/config/feature_flags/development/ci_seed_block_run_before_workflow_rules.yml
index f1e35cffbe0..70444126da9 100644
--- a/config/feature_flags/development/expose_environment_path_in_alert_details.yml
+++ b/config/feature_flags/development/ci_seed_block_run_before_workflow_rules.yml
@@ -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
diff --git a/config/feature_flags/development/export_merge_requests_as_csv.yml b/config/feature_flags/development/export_merge_requests_as_csv.yml
new file mode 100644
index 00000000000..ad033d5feaa
--- /dev/null
+++ b/config/feature_flags/development/export_merge_requests_as_csv.yml
@@ -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
diff --git a/config/routes/merge_requests.rb b/config/routes/merge_requests.rb
index b2635a7fa74..c11e5a5c3d9 100644
--- a/config/routes/merge_requests.rb
+++ b/config/routes/merge_requests.rb
@@ -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
diff --git a/doc/administration/auth/ldap/index.md b/doc/administration/auth/ldap/index.md
index 3df85babc94..b6c9e248858 100644
--- a/doc/administration/auth/ldap/index.md
+++ b/doc/administration/auth/ldap/index.md
@@ -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.
diff --git a/doc/administration/consul.md b/doc/administration/consul.md
index 4eed020c284..926716c45ec 100644
--- a/doc/administration/consul.md
+++ b/doc/administration/consul.md
@@ -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
diff --git a/doc/administration/nfs.md b/doc/administration/nfs.md
index fabbfb2552e..d3f77dab989 100644
--- a/doc/administration/nfs.md
+++ b/doc/administration/nfs.md
@@ -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.
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 885e3e1308e..f5ed40ad802 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -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
@@ -7215,6 +7214,11 @@ type EpicIssue implements CurrentUserTodos & Noteable {
updatedAt: Time!
"""
+ User that last updated the issue
+ """
+ updatedBy: User
+
+ """
Number of upvotes the issue has received
"""
upvotes: Int!
@@ -9468,6 +9472,11 @@ type Issue implements CurrentUserTodos & Noteable {
updatedAt: Time!
"""
+ User that last updated the issue
+ """
+ updatedBy: User
+
+ """
Number of upvotes the issue has received
"""
upvotes: Int!
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 9fe4f487e72..58568bd7ea6 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -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
@@ -19866,6 +19870,20 @@
"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",
"args": [
@@ -25766,6 +25784,20 @@
"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",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 9b271f17f81..d73972a4622 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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 |
diff --git a/doc/ci/ci_cd_for_external_repos/github_integration.md b/doc/ci/ci_cd_for_external_repos/github_integration.md
index 7ee9f98bd39..b6f885ff220 100644
--- a/doc/ci/ci_cd_for_external_repos/github_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/github_integration.md
@@ -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:
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index d8b6eb8a7a7..7f76c64796b 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -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
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index c62f0dec598..9bc9ac5423e 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -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.
diff --git a/doc/ci/migration/circleci.md b/doc/ci/migration/circleci.md
index 106c53429a2..261d30fccc0 100644
--- a/doc/ci/migration/circleci.md
+++ b/doc/ci/migration/circleci.md
@@ -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.
diff --git a/doc/development/adding_service_component.md b/doc/development/adding_service_component.md
index bb5af286c1d..33f63ac7ffe 100644
--- a/doc/development/adding_service_component.md
+++ b/doc/development/adding_service_component.md
@@ -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)
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 90ccbe68847..128fd2fa9a0 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -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.`.
diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md
index a6168386024..9802d47dceb 100644
--- a/doc/operations/incident_management/alerts.md
+++ b/doc/operations/incident_management/alerts.md
@@ -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)
-```
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index 1734f3c9c2b..6480c591942 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -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
diff --git a/lib/gitlab/ci/pipeline/chain/seed.rb b/lib/gitlab/ci/pipeline/chain/seed.rb
index e10a0bc3718..ba86b08d209 100644
--- a/lib/gitlab/ci/pipeline/chain/seed.rb
+++ b/lib/gitlab/ci/pipeline/chain/seed.rb
@@ -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
diff --git a/lib/gitlab/ci/pipeline/chain/seed_block.rb b/lib/gitlab/ci/pipeline/chain/seed_block.rb
new file mode 100644
index 00000000000..f8e62949bea
--- /dev/null
+++ b/lib/gitlab/ci/pipeline/chain/seed_block.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f9ba799faf6..87240e5a50f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/package.json b/package.json
index f33dc7ebfd5..623cc9a969f 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/public/robots.txt b/public/robots.txt
index c6d1733cac6..d4183c5cafb 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -14,6 +14,7 @@
# Global routes
User-Agent: *
Disallow: /autocomplete/users
+Disallow: /autocomplete/projects
Disallow: /search
Disallow: /admin
Disallow: /profile
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index 0b47bec2cf1..64bd62c2b54 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -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
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index d4f66220f4d..31ce37a0607 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -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
diff --git a/spec/features/alert_management_spec.rb b/spec/features/alert_management_spec.rb
index 2989f72e356..3322c9c574f 100644
--- a/spec/features/alert_management_spec.rb
+++ b/spec/features/alert_management_spec.rb
@@ -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
diff --git a/spec/features/merge_requests/user_exports_as_csv_spec.rb b/spec/features/merge_requests/user_exports_as_csv_spec.rb
new file mode 100644
index 00000000000..63ed1cb5231
--- /dev/null
+++ b/spec/features/merge_requests/user_exports_as_csv_spec.rb
@@ -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
diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb
index 163c920f621..0db69de65a5 100644
--- a/spec/finders/packages/group_packages_finder_spec.rb
+++ b/spec/finders/packages/group_packages_finder_spec.rb
@@ -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
diff --git a/spec/frontend/alert_management/components/alert_details_spec.js b/spec/frontend/alert_management/components/alert_details_spec.js
index f3ebdfc5cc2..e2d913398f9 100644
--- a/spec/frontend/alert_management/components/alert_details_spec.js
+++ b/spec/frontend/alert_management/components/alert_details_spec.js
@@ -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 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();
- 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);
});
diff --git a/spec/frontend/vue_shared/components/alert_details_table_spec.js b/spec/frontend/vue_shared/components/alert_details_table_spec.js
index dff307e92c2..ef7815f9e9e 100644
--- a/spec/frontend/vue_shared/components/alert_details_table_spec.js
+++ b/spec/frontend/vue_shared/components/alert_details_table_spec.js
@@ -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);
});
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index 2220f847e4e..3671d35e8a5 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -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
%(
diff --git a/spec/graphql/types/issue_type_spec.rb b/spec/graphql/types/issue_type_spec.rb
index be45dbdc2e3..b8f1c4a0fcc 100644
--- a/spec/graphql/types/issue_type_spec.rb
+++ b/spec/graphql/types/issue_type_spec.rb
@@ -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]
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 8c02121857a..5506b079d0f 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
new file mode 100644
index 00000000000..85c8e20767f
--- /dev/null
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_block_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
index f83cd49d780..d849c768a3c 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 9f7433be759..0c39ee361cb 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -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
diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb
index 1c9d6b25856..e68f7937abf 100644
--- a/spec/requests/api/graphql/issue/issue_spec.rb
+++ b/spec/requests/api/graphql/issue/issue_spec.rb
@@ -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
diff --git a/spec/requests/robots_txt_spec.rb b/spec/requests/robots_txt_spec.rb
index 0563daae28a..61a5dc68fdd 100644
--- a/spec/requests/robots_txt_spec.rb
+++ b/spec/requests/robots_txt_spec.rb
@@ -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',
diff --git a/spec/services/ci/create_downstream_pipeline_service_spec.rb b/spec/services/ci/create_downstream_pipeline_service_spec.rb
index 0cc380439a7..03cea4074bf 100644
--- a/spec/services/ci/create_downstream_pipeline_service_spec.rb
+++ b/spec/services/ci/create_downstream_pipeline_service_spec.rb
@@ -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
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index c28c3449485..7a9d88781a3 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -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
diff --git a/yarn.lock b/yarn.lock
index 24bc7883dcc..d896bad4d8e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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/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/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.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"