diff --git a/.rubocop.yml b/.rubocop.yml
index 3ae3ce89be0..ad5bb5e9a13 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -308,6 +308,10 @@ Rails/RakeEnvironment:
# Context on why it's disabled: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93419#note_1048223982
Enabled: false
+# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96675#note_1094403693
+Rails/WhereExists:
+ Enabled: false
+
# GitLab ###################################################################
Gitlab/ModuleWithInstanceVariables:
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index d8d493b4731..ee095512645 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -3920,7 +3920,6 @@ Layout/LineLength:
- 'spec/features/groups/settings/repository_spec.rb'
- 'spec/features/groups_spec.rb'
- 'spec/features/ide/static_object_external_storage_csp_spec.rb'
- - 'spec/features/incidents/user_views_incident_spec.rb'
- 'spec/features/invites_spec.rb'
- 'spec/features/issuables/issuable_list_spec.rb'
- 'spec/features/issuables/markdown_references/internal_references_spec.rb'
diff --git a/.rubocop_todo/rails/negate_include.yml b/.rubocop_todo/rails/negate_include.yml
index 7bf78b82b60..c3f9ac25e7e 100644
--- a/.rubocop_todo/rails/negate_include.yml
+++ b/.rubocop_todo/rails/negate_include.yml
@@ -1,30 +1,28 @@
---
# Cop supports --auto-correct.
Rails/NegateInclude:
- # Offense count: 65
- # Temporarily disabled due to too many offenses
- Enabled: false
+ Details: grace period
Exclude:
- 'app/finders/projects_finder.rb'
- 'app/helpers/application_settings_helper.rb'
- 'app/helpers/projects_helper.rb'
- 'app/helpers/tree_helper.rb'
- - 'app/models/concerns/timebox.rb'
- 'app/models/integrations/chat_message/pipeline_message.rb'
+ - 'app/models/integrations/field.rb'
- 'app/models/label.rb'
- 'app/models/merge_request.rb'
+ - 'app/models/milestone.rb'
- 'app/services/todo_service.rb'
+ - 'app/services/work_items/parent_links/create_service.rb'
- 'config/application.rb'
- 'config/initializers/1_settings.rb'
- 'danger/roulette/Dangerfile'
- 'ee/app/finders/security/pipeline_vulnerabilities_finder.rb'
- - 'ee/app/models/ee/epic.rb'
- 'ee/app/models/ee/vulnerability.rb'
- 'ee/app/services/epic_issues/create_service.rb'
- 'ee/app/services/security/ingestion/tasks/ingest_remediations.rb'
- 'ee/app/services/security/security_orchestration_policies/validate_policy_service.rb'
- 'lib/api/maven_packages.rb'
- - 'lib/generators/gitlab/usage_metric_generator.rb'
- 'lib/gitlab/background_migration/legacy_upload_mover.rb'
- 'lib/gitlab/ci/build/rules/rule/clause/exists.rb'
- 'lib/gitlab/ci/parsers/coverage/sax_document.rb'
@@ -38,11 +36,10 @@ Rails/NegateInclude:
- 'lib/gitlab/task_helpers.rb'
- 'lib/gitlab/url_blocker.rb'
- 'lib/gitlab_edition.rb'
+ - 'qa/qa/page/merge_request/show.rb'
- 'qa/qa/runtime/ip_address.rb'
- 'qa/qa/support/run.rb'
- 'qa/qa/tools/delete_test_users.rb'
- - 'qa/qa/vendor/jenkins/page/configure_job.rb'
- - 'qa/qa/vendor/jenkins/page/last_job_console.rb'
- 'rubocop/cop/gitlab/feature_available_usage.rb'
- 'rubocop/cop/graphql/id_type.rb'
- 'rubocop/cop/migration/add_reference.rb'
@@ -56,3 +53,4 @@ Rails/NegateInclude:
- 'spec/support/matchers/pushed_frontend_feature_flags_matcher.rb'
- 'spec/support/shared_contexts/markdown_golden_master_shared_examples.rb'
- 'spec/uploaders/object_storage_spec.rb'
+ - 'tooling/danger/specs.rb'
diff --git a/.rubocop_todo/rails/where_exists.yml b/.rubocop_todo/rails/where_exists.yml
deleted file mode 100644
index 77722549722..00000000000
--- a/.rubocop_todo/rails/where_exists.yml
+++ /dev/null
@@ -1,44 +0,0 @@
----
-# Cop supports --auto-correct.
-Rails/WhereExists:
- # Offense count: 48
- # Temporarily disabled due to too many offenses
- Enabled: false
- Exclude:
- - 'app/models/application_setting/term.rb'
- - 'app/models/ci/pipeline_artifact.rb'
- - 'app/models/ci/ref.rb'
- - 'app/models/clusters/agent.rb'
- - 'app/models/concerns/has_wiki.rb'
- - 'app/models/concerns/noteable.rb'
- - 'app/models/container_repository.rb'
- - 'app/models/design_management/design.rb'
- - 'app/models/group.rb'
- - 'app/models/group_deploy_token.rb'
- - 'app/models/label.rb'
- - 'app/models/lfs_object.rb'
- - 'app/models/merge_request_diff.rb'
- - 'app/models/namespace.rb'
- - 'app/models/project.rb'
- - 'app/models/protected_branch/push_access_level.rb'
- - 'app/services/projects/transfer_service.rb'
- - 'app/services/todos/destroy/unauthorized_features_service.rb'
- - 'db/migrate/20210422195929_create_elastic_reindexing_slices.rb'
- - 'ee/app/models/approval_merge_request_rule_source.rb'
- - 'ee/app/models/concerns/ee/protected_ref_access.rb'
- - 'ee/app/models/ee/epic.rb'
- - 'ee/app/models/ee/group_member.rb'
- - 'ee/app/models/ee/milestone_release.rb'
- - 'ee/app/models/geo_node.rb'
- - 'ee/app/models/merge_requests/external_status_check.rb'
- - 'ee/app/models/merge_train.rb'
- - 'ee/app/workers/concerns/elastic/indexing_control.rb'
- - 'lib/gitlab/auth.rb'
- - 'lib/gitlab/checks/matching_merge_request.rb'
- - 'lib/gitlab/database/partitioning/detached_partition_dropper.rb'
- - 'spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb'
- - 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_features_spec.rb'
- - 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_with_new_features_spec.rb'
- - 'spec/models/user_spec.rb'
- - 'spec/services/clusters/cleanup/service_account_service_spec.rb'
- - 'spec/services/clusters/destroy_service_spec.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 8b4dcdffca0..47ff9d7c870 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-b55578ec476e8bc8ecd9775ee7e9960b52e0f6e0
+f75740430e51520d3edcd22065285cec050d2b74
diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue
index af753151be8..1335a3b108b 100644
--- a/app/assets/javascripts/boards/components/board_app.vue
+++ b/app/assets/javascripts/boards/components/board_app.vue
@@ -11,7 +11,7 @@ export default {
BoardSettingsSidebar,
BoardTopBar,
},
- inject: ['disabled'],
+ inject: ['disabled', 'fullBoardId'],
computed: {
...mapGetters(['isSidebarOpen']),
},
@@ -27,7 +27,7 @@ export default {
-
+
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index 0d2e49c966f..9230513ff93 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -3,12 +3,24 @@ import { GlAlert } from '@gitlab/ui';
import { sortBy, throttle } from 'lodash';
import Draggable from 'vuedraggable';
import { mapState, mapGetters, mapActions } from 'vuex';
+import { s__ } from '~/locale';
+import { formatBoardLists } from 'ee_else_ce/boards/boards_util';
import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue';
import { defaultSortableOptions } from '~/sortable/constants';
-import { DraggableItemTypes } from '../constants';
+import {
+ DraggableItemTypes,
+ issuableTypes,
+ BoardType,
+ listsQuery,
+} from 'ee_else_ce/boards/constants';
import BoardColumn from './board_column.vue';
export default {
+ i18n: {
+ fetchError: s__(
+ 'Boards|An error occurred while fetching the board lists. Please reload the page.',
+ ),
+ },
draggableItemTypes: DraggableItemTypes,
components: {
BoardAddNewColumn,
@@ -19,26 +31,76 @@ export default {
EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
GlAlert,
},
- inject: ['canAdminList'],
+ inject: ['canAdminList', 'boardType', 'fullPath', 'issuableType', 'isApolloBoard'],
props: {
disabled: {
type: Boolean,
required: true,
},
+ boardId: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
boardHeight: null,
+ boardListsApollo: {},
+ apolloError: null,
+ updatedBoardId: this.boardId,
};
},
+ apollo: {
+ boardListsApollo: {
+ query() {
+ return listsQuery[this.issuableType].query;
+ },
+ variables() {
+ return this.queryVariables;
+ },
+ skip() {
+ return !this.isApolloBoard;
+ },
+ update(data) {
+ const { lists } = data[this.boardType].board;
+ return formatBoardLists(lists);
+ },
+ result() {
+ // this allows us to delay fetching lists when we switch a board to fetch the actual board lists
+ // instead of fetching lists for the "previous" board
+ this.updatedBoardId = this.boardId;
+ },
+ error() {
+ this.apolloError = this.$options.i18n.fetchError;
+ },
+ },
+ },
computed: {
...mapState(['boardLists', 'error', 'addColumnForm']),
- ...mapGetters(['isSwimlanesOn', 'isEpicBoard', 'isIssueBoard']),
+ ...mapGetters(['isSwimlanesOn']),
+ isIssueBoard() {
+ return this.issuableType === issuableTypes.issue;
+ },
+ isEpicBoard() {
+ return this.issuableType === issuableTypes.epic;
+ },
addColumnFormVisible() {
return this.addColumnForm?.visible;
},
+ queryVariables() {
+ return {
+ ...(this.isIssueBoard && {
+ isGroup: this.boardType === BoardType.group,
+ isProject: this.boardType === BoardType.project,
+ }),
+ fullPath: this.fullPath,
+ boardId: this.boardId,
+ filterParams: this.filterParams,
+ };
+ },
boardListsToUse() {
- return sortBy([...Object.values(this.boardLists)], 'position');
+ const lists = this.isApolloBoard ? this.boardListsApollo : this.boardLists;
+ return sortBy([...Object.values(lists)], 'position');
},
canDragColumns() {
return this.canAdminList;
@@ -59,6 +121,9 @@ export default {
return this.canDragColumns ? options : {};
},
+ errorToDisplay() {
+ return this.isApolloBoard ? this.apolloError : this.error;
+ },
},
mounted() {
this.setBoardHeight();
@@ -88,8 +153,8 @@ export default {
-
- {{ error }}
+
+ {{ errorToDisplay }}
diff --git a/app/assets/javascripts/pipelines/components/pipeline_tabs.vue b/app/assets/javascripts/pipelines/components/pipeline_tabs.vue
index 2a78636261b..3798863ae60 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_tabs.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_tabs.vue
@@ -1,12 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
index 60180d6d44a..77bda5f9473 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
@@ -8,7 +8,7 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import { kebabCase, snakeCase } from 'lodash';
-import { IssuableType } from '~/issues/constants';
+import { IssuableType, WorkspaceType } from '~/issues/constants';
import { __ } from '~/locale';
import {
defaultEpicSort,
@@ -73,6 +73,14 @@ export default {
return [IssuableType.Issue, IssuableType.MergeRequest].includes(value);
},
},
+ workspaceType: {
+ type: String,
+ required: false,
+ default: WorkspaceType.project,
+ validator(value) {
+ return [WorkspaceType.group, WorkspaceType.project].includes(value);
+ },
+ },
},
data() {
return {
@@ -86,7 +94,7 @@ export default {
query() {
const { list } = this.issuableAttributeQuery;
const { query } = list[this.issuableType];
- return query;
+ return query[this.workspaceType] || query;
},
variables() {
if (!this.isEpic) {
diff --git a/app/assets/javascripts/sidebar/constants.js b/app/assets/javascripts/sidebar/constants.js
index 6248bcb8e2d..67b9b540e91 100644
--- a/app/assets/javascripts/sidebar/constants.js
+++ b/app/assets/javascripts/sidebar/constants.js
@@ -53,6 +53,7 @@ import updateIssueAssigneesMutation from '~/vue_shared/components/sidebar/querie
import updateMergeRequestAssigneesMutation from '~/vue_shared/components/sidebar/queries/update_mr_assignees.mutation.graphql';
import getEscalationStatusQuery from '~/sidebar/queries/escalation_status.query.graphql';
import updateEscalationStatusMutation from '~/sidebar/queries/update_escalation_status.mutation.graphql';
+import groupMilestonesQuery from './queries/group_milestones.query.graphql';
import projectIssueMilestoneMutation from './queries/project_issue_milestone.mutation.graphql';
import projectIssueMilestoneQuery from './queries/project_issue_milestone.query.graphql';
import projectMilestonesQuery from './queries/project_milestones.query.graphql';
@@ -241,10 +242,16 @@ export const issuableMilestoneQueries = {
export const milestonesQueries = {
[IssuableType.Issue]: {
- query: projectMilestonesQuery,
+ query: {
+ [WorkspaceType.group]: groupMilestonesQuery,
+ [WorkspaceType.project]: projectMilestonesQuery,
+ },
},
[IssuableType.MergeRequest]: {
- query: projectMilestonesQuery,
+ query: {
+ [WorkspaceType.group]: groupMilestonesQuery,
+ [WorkspaceType.project]: projectMilestonesQuery,
+ },
},
};
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 1d9ddfe6bfd..5f36486a22e 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -18,6 +18,7 @@ import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assi
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDueDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
+import BulkUpdateMilestoneDropdown from '~/sidebar/components/milestone/bulk_update_milestone_dropdown.vue';
import SidebarParticipantsWidget from '~/sidebar/components/participants/sidebar_participants_widget.vue';
import SidebarReferenceWidget from '~/sidebar/components/reference/sidebar_reference_widget.vue';
import SidebarDropdownWidget from '~/sidebar/components/sidebar_dropdown_widget.vue';
@@ -289,6 +290,31 @@ function mountMilestoneSelect() {
});
}
+export function mountMilestoneDropdown() {
+ const el = document.querySelector('.js-milestone-dropdown');
+
+ if (!el) {
+ return null;
+ }
+
+ Vue.use(VueApollo);
+
+ return new Vue({
+ el,
+ name: 'BulkUpdateMilestoneDropdownRoot',
+ apolloProvider,
+ render(createElement) {
+ return createElement(BulkUpdateMilestoneDropdown, {
+ props: {
+ attrWorkspacePath: el.dataset.fullPath,
+ issuableType: isInIssuePage() ? IssuableType.Issue : IssuableType.MergeRequest,
+ workspaceType: el.dataset.workspaceType,
+ },
+ });
+ },
+ });
+}
+
export function mountSidebarLabels() {
const el = document.querySelector('.js-sidebar-labels');
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 14b70df0feb..c5a24be26fb 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -7,6 +7,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:board_multi_select, group)
+ push_frontend_feature_flag(:apollo_boards, group)
push_frontend_feature_flag(:realtime_labels, group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 6a6701ead15..7af3cb9294a 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -7,6 +7,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :check_issues_available!
before_action do
push_frontend_feature_flag(:board_multi_select, project)
+ push_frontend_feature_flag(:apollo_boards, project)
push_frontend_feature_flag(:realtime_labels, project&.group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 01f7bb9e2cf..dd3e9886239 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -140,21 +140,13 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def builds
- if Feature.enabled?(:pipeline_tabs_vue, project)
- redirect_to pipeline_path(@pipeline, tab: 'builds')
- else
- render_show
- end
+ render_show
end
def dag
respond_to do |format|
format.html do
- if Feature.enabled?(:pipeline_tabs_vue, project)
- redirect_to pipeline_path(@pipeline, tab: 'dag')
- else
- render_show
- end
+ render_show
end
format.json do
render json: Ci::DagPipelineSerializer
@@ -165,9 +157,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def failures
- if Feature.enabled?(:pipeline_tabs_vue, project)
- redirect_to pipeline_path(@pipeline, tab: 'failures')
- elsif @pipeline.failed_builds.present?
+ if @pipeline.failed_builds.present?
render_show
else
redirect_to pipeline_path(@pipeline)
@@ -222,11 +212,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def test_report
respond_to do |format|
format.html do
- if Feature.enabled?(:pipeline_tabs_vue, project)
- redirect_to pipeline_path(@pipeline, tab: 'test_report')
- else
- render_show
- end
+ render_show
end
format.json do
render json: TestReportSerializer
diff --git a/app/helpers/projects/pipeline_helper.rb b/app/helpers/projects/pipeline_helper.rb
index c72beb4d722..edbdb9d4adf 100644
--- a/app/helpers/projects/pipeline_helper.rb
+++ b/app/helpers/projects/pipeline_helper.rb
@@ -19,6 +19,7 @@ module Projects
blob_path: project_blob_path(project, pipeline.sha),
has_test_report: pipeline.has_test_reports?,
empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg'),
+ empty_dag_svg_path: image_path('illustrations/empty-state/empty-dag-md.svg'),
artifacts_expired_image_path: image_path('illustrations/pipeline.svg'),
tests_count: pipeline.test_report_summary.total[:count]
}
diff --git a/app/models/alert_management/http_integration.rb b/app/models/alert_management/http_integration.rb
index 0c3b1679dc3..b2686924363 100644
--- a/app/models/alert_management/http_integration.rb
+++ b/app/models/alert_management/http_integration.rb
@@ -13,8 +13,7 @@ module AlertManagement
key: Settings.attr_encrypted_db_key_base_32,
algorithm: 'aes-256-gcm'
- default_value_for(:endpoint_identifier, allows_nil: false) { SecureRandom.hex(8) }
- default_value_for(:token) { generate_token }
+ attribute :endpoint_identifier, default: -> { SecureRandom.hex(8) }
validates :project, presence: true
validates :active, inclusion: { in: [true, false] }
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 4450d6d2158..e1aa0040ade 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -639,14 +639,6 @@ class ApplicationSetting < ApplicationRecord
validates :inactive_projects_send_warning_email_after_months,
numericality: { only_integer: true, greater_than: 0, less_than: :inactive_projects_delete_after_months }
- validates :cube_api_base_url,
- addressable_url: { allow_localhost: true, allow_local_network: false },
- allow_blank: true
-
- validates :product_analytics_enabled,
- presence: true,
- allow_blank: true
-
attr_encrypted :asset_proxy_secret_key,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
diff --git a/app/serializers/project_import_entity.rb b/app/serializers/project_import_entity.rb
index 9b51af685e7..a3dbff3dc0b 100644
--- a/app/serializers/project_import_entity.rb
+++ b/app/serializers/project_import_entity.rb
@@ -3,11 +3,13 @@
class ProjectImportEntity < ProjectEntity
include ImportHelper
- expose :import_source
- expose :import_status
- expose :human_import_status_name
+ expose :import_source, documentation: { type: 'string', example: 'source/source-repo' }
+ expose :import_status, documentation: {
+ type: 'string', example: 'scheduled', values: %w[scheduled started finished failed canceled]
+ }
+ expose :human_import_status_name, documentation: { type: 'string', example: 'canceled' }
- expose :provider_link do |project, options|
+ expose :provider_link, documentation: { type: 'string', example: '/source/source-repo' } do |project, options|
provider_project_link_url(options[:provider_url], project[:import_source])
end
end
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 99abddac41b..4f836789389 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -119,6 +119,7 @@
= render_if_exists 'admin/application_settings/feishu_integration'
= render 'admin/application_settings/third_party_offers'
= render 'admin/application_settings/snowplow'
+= render_if_exists 'admin/application_settings/product_analytics' if Feature.enabled?(:cube_api_proxy)
= render 'admin/application_settings/error_tracking' if Feature.enabled?(:gitlab_error_tracking)
= render 'admin/application_settings/eks'
= render 'admin/application_settings/floc'
diff --git a/app/views/admin/application_settings/service_usage_data.html.haml b/app/views/admin/application_settings/service_usage_data.html.haml
index 06bb9df84c4..82b627e1805 100644
--- a/app/views/admin/application_settings/service_usage_data.html.haml
+++ b/app/views/admin/application_settings/service_usage_data.html.haml
@@ -24,7 +24,7 @@
= c.body do
- enable_service_ping_link_url = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'enable-or-disable-usage-statistics')
- enable_service_ping_link = ''.html_safe % { url: enable_service_ping_link_url }
- - generate_manually_link_url = help_page_path('administration/troubleshooting/gitlab_rails_cheat_sheet', anchor: 'generate-service-ping')
+ - generate_manually_link_url = help_page_path('development/service_ping/troubleshooting', anchor: 'generate-service-ping')
- generate_manually_link = ''.html_safe % { url: generate_manually_link_url }
= html_escape(s_('%{enable_service_ping_link_start}Enable%{link_end} or %{generate_manually_link_start}generate%{link_end} Service Ping to preview and download service usage data payload.')) % { enable_service_ping_link_start: enable_service_ping_link, generate_manually_link_start: generate_manually_link, link_end: ''.html_safe }
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index f053ce9ced3..82276938d45 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -7,7 +7,7 @@
= gl_tab_link_to s_('Branches|Overview'), project_branches_path(@project), { item_active: @mode == 'overview', title: s_('Branches|Show overview of the branches') }
= gl_tab_link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), { title: s_('Branches|Show active branches') }
= gl_tab_link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), { title: s_('Branches|Show stale branches') }
- = gl_tab_link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), { item_active: !%w[overview active stale].include?(@mode), title: s_('Branches|Show all branches') }
+ = gl_tab_link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), { item_active: %w[overview active stale].exclude?(@mode), title: s_('Branches|Show all branches') }
.nav-controls
#js-branches-sort-dropdown{ data: { project_branches_filtered_path: project_branches_path(@project, state: 'all'), sort_options: branches_sort_options_hash.to_json, mode: @mode } }
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index c9eb2e92193..4531bb2d0a9 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -30,4 +30,4 @@
#js-pipeline-tabs{ data: js_pipeline_tabs_data(@project, @pipeline, @current_user) }
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline, stages: @stages, pipeline_has_errors: pipeline_has_errors
-.js-pipeline-details-vue{ data: { metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline) } }
+.js-pipeline-details-vue{ data: { metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline), pipeline_path: pipeline_path(@pipeline) } }
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index beb5b527669..843d2462851 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -34,7 +34,7 @@
.title
= _('Milestone')
.filter-item
- = dropdown_tag(_("Select milestone"), options: { title: _("Assign milestone"), toggle_class: "js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: _("Search milestones"), data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, use_id: true, default_label: _("Milestone") } })
+ .js-milestone-dropdown{ data: { full_path: @project.full_path, workspace_type: Namespaces::ProjectNamespace.sti_name.downcase } }
- if is_issue
= render_if_exists 'shared/issuable/iterations_dropdown', parent: @project.group
- if is_issue
diff --git a/config/feature_flags/development/apollo_boards.yml b/config/feature_flags/development/apollo_boards.yml
new file mode 100644
index 00000000000..d95e60bf4ea
--- /dev/null
+++ b/config/feature_flags/development/apollo_boards.yml
@@ -0,0 +1,8 @@
+---
+name: apollo_boards
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102719
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381210
+milestone: '15.6'
+type: development
+group: group::product planning
+default_enabled: false
diff --git a/config/feature_flags/development/enable_new_sentry_clientside_integration.yml b/config/feature_flags/development/enable_new_sentry_clientside_integration.yml
new file mode 100644
index 00000000000..338fd2b1e25
--- /dev/null
+++ b/config/feature_flags/development/enable_new_sentry_clientside_integration.yml
@@ -0,0 +1,8 @@
+---
+name: enable_new_sentry_clientside_integration
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102650
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344832
+milestone: '15.6'
+type: development
+group: group::runner
+default_enabled: false
diff --git a/config/feature_flags/development/enable_old_sentry_clientside_integration.yml b/config/feature_flags/development/enable_old_sentry_clientside_integration.yml
new file mode 100644
index 00000000000..0507d7cc559
--- /dev/null
+++ b/config/feature_flags/development/enable_old_sentry_clientside_integration.yml
@@ -0,0 +1,8 @@
+---
+name: enable_old_sentry_clientside_integration
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102650
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/344832
+milestone: '15.6'
+type: development
+group: group::runner
+default_enabled: true
diff --git a/config/feature_flags/development/search_index_curation.yml b/config/feature_flags/development/search_index_curation.yml
new file mode 100644
index 00000000000..ddcc243c60f
--- /dev/null
+++ b/config/feature_flags/development/search_index_curation.yml
@@ -0,0 +1,8 @@
+---
+name: search_index_curation
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98809
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375274
+milestone: '15.6'
+type: development
+group: group::global search
+default_enabled: false
diff --git a/config/open_api.yml b/config/open_api.yml
index 5376c69cdb1..811801584ac 100644
--- a/config/open_api.yml
+++ b/config/open_api.yml
@@ -45,6 +45,8 @@ metadata:
description: Operations related to project hooks
- name: project_import_bitbucket
description: Operations related to import BitBucket projects
+ - name: project_import_github
+ description: Operations related to import GitHub projects
- name: release_links
description: Operations related to release assets (links)
- name: releases
diff --git a/db/migrate/20221104094042_remove_users_foreign_key_to_projects.rb b/db/migrate/20221104094042_remove_users_foreign_key_to_projects.rb
new file mode 100644
index 00000000000..19497c80b8e
--- /dev/null
+++ b/db/migrate/20221104094042_remove_users_foreign_key_to_projects.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class RemoveUsersForeignKeyToProjects < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists :projects, column: :creator_id
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key :projects, :users, column: :creator_id, on_delete: :nullify, validate: false
+ end
+end
diff --git a/db/schema_migrations/20221104094042 b/db/schema_migrations/20221104094042
new file mode 100644
index 00000000000..08694063f07
--- /dev/null
+++ b/db/schema_migrations/20221104094042
@@ -0,0 +1 @@
+7ddb85c1acfd3fbeddbe96857d329ad09cd21210e6765ff36d4b9f516a7c10be
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 57b94946643..b328dd7aa94 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -32626,9 +32626,6 @@ ALTER TABLE ONLY service_desk_settings
ALTER TABLE ONLY design_management_designs_versions
ADD CONSTRAINT fk_03c671965c FOREIGN KEY (design_id) REFERENCES design_management_designs(id) ON DELETE CASCADE;
-ALTER TABLE ONLY projects
- ADD CONSTRAINT fk_03ec10b0d3 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL NOT VALID;
-
ALTER TABLE ONLY issues
ADD CONSTRAINT fk_05f1e72feb FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 7d684daf5a6..aa0339db3e5 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -232,12 +232,9 @@ who are aware of the risks.
- [Troubleshooting Kubernetes](https://docs.gitlab.com/charts/troubleshooting/kubernetes_cheat_sheet.html)
- [Troubleshooting PostgreSQL](troubleshooting/postgresql.md)
- [Guide to test environments](troubleshooting/test_environments.md) (for Support Engineers)
-- [GitLab Rails console commands](troubleshooting/gitlab_rails_cheat_sheet.md) (for Support Engineers)
- [Troubleshooting SSL](troubleshooting/ssl.md)
- Related links:
- [GitLab Developer Documentation](../development/index.md)
- [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html)
- [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/testing-with-openssl/index.html)
- [`strace` zine](https://wizardzines.com/zines/strace/)
-- GitLab.com-specific resources:
- - [Example group SAML and SCIM configurations](../user/group/saml_sso/example_saml_config.md)
diff --git a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
index 2800a70a0f1..6993d48b450 100644
--- a/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
+++ b/doc/administration/troubleshooting/gitlab_rails_cheat_sheet.md
@@ -1,91 +1,11 @@
---
-stage: Systems
-group: Distribution
-info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+redirect_to: 'index.md'
+remove_date: '2023-02-01'
---
-# GitLab Rails Console Cheat Sheet **(FREE SELF)**
+This document was moved to [another location](index.md).
-This is the GitLab Support Team's collection of information regarding the GitLab Rails
-console, for use while troubleshooting. It is listed here for transparency,
-and for users with experience with these tools. If you are currently
-having an issue with GitLab, it is highly recommended that you first check
-our guide on [our Rails console](../operations/rails_console.md),
-and your [support options](https://about.gitlab.com/support/), before attempting to use
-this information.
-
-WARNING:
-Some of these scripts could be damaging if not run correctly,
-or under the right conditions. We highly recommend running them under the
-guidance of a Support Engineer, or running them in a test environment with a
-backup of the instance ready to be restored, just in case.
-
-WARNING:
-As GitLab changes, changes to the code are inevitable,
-and so some scripts may not work as they once used to. These are not kept
-up-to-date as these scripts/commands were added as they were found/needed. As
-mentioned above, we recommend running these scripts under the supervision of a
-Support Engineer, who can also verify that they continue to work as they
-should and, if needed, update the script for the latest version of GitLab.
-
-## Mirrors
-
-### Find mirrors with "bad decrypt" errors
-
-This content has been converted to a Rake task, see [verify database values can be decrypted using the current secrets](../raketasks/check.md#verify-database-values-can-be-decrypted-using-the-current-secrets).
-
-### Transfer mirror users and tokens to a single service account
-
-This content has been moved to [Troubleshooting Repository mirroring](../../user/project/repository/mirror/index.md#transfer-mirror-users-and-tokens-to-a-single-service-account-in-rails-console).
-
-## Merge requests
-
-## CI
-
-This content has been moved to [Troubleshooting CI/CD](../../ci/troubleshooting.md).
-
-## License
-
-This content has been moved to [Activate GitLab EE with a license file or key](../../user/admin_area/license_file.md).
-
-## Registry
-
-### Registry Disk Space Usage by Project
-
-Find this content in the [Container Registry troubleshooting documentation](../packages/container_registry.md#registry-disk-space-usage-by-project).
-
-### Run the Cleanup policy now
-
-Find this content in the [Container Registry troubleshooting documentation](../packages/container_registry.md#run-the-cleanup-policy-now).
-
-## Sidekiq
-
-This content has been moved to [Troubleshooting Sidekiq](../sidekiq/sidekiq_troubleshooting.md).
-
-## Geo
-
-### Reverify all uploads (or any SSF data type which is verified)
-
-Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#reverify-all-uploads-or-any-ssf-data-type-which-is-verified).
-
-### Artifacts
-
-Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#find-failed-artifacts).
-
-### Repository verification failures
-
-Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#find-repository-verification-failures).
-
-### Resync repositories
-
-Moved to [Geo replication troubleshooting - Resync repository types except for project or project wiki repositories](../geo/replication/troubleshooting.md#repository-types-except-for-project-or-project-wiki-repositories).
-
-Moved to [Geo replication troubleshooting - Resync project and project wiki repositories](../geo/replication/troubleshooting.md#resync-project-and-project-wiki-repositories).
-
-### Blob types
-
-Moved to [Geo replication troubleshooting](../geo/replication/troubleshooting.md#blob-types).
-
-## Generate Service Ping
-
-This content has been moved to [Service Ping Troubleshooting](../../development/service_ping/troubleshooting.md).
+
+
+
+
diff --git a/doc/administration/troubleshooting/index.md b/doc/administration/troubleshooting/index.md
index ce548f9e100..db572f202ea 100644
--- a/doc/administration/troubleshooting/index.md
+++ b/doc/administration/troubleshooting/index.md
@@ -9,19 +9,21 @@ info: To determine the technical writer assigned to the Stage/Group associated w
This page documents a collection of resources to help you troubleshoot a GitLab
installation.
+This list is not necessarily comprehensive. If you don't find what you're looking
+for in this list, you should search the documentation.
+
## Troubleshooting guides
- [SSL](ssl.md)
- [Geo](../geo/replication/troubleshooting.md)
-- [GitLab Rails console cheat sheet](gitlab_rails_cheat_sheet.md)
-- [Example group SAML and SCIM configurations](../../user/group/saml_sso/example_saml_config.md)
+- [SAML](../../user/group/saml_sso/troubleshooting.md)
- [Kubernetes cheat sheet](https://docs.gitlab.com/charts/troubleshooting/kubernetes_cheat_sheet.html)
- [Linux cheat sheet](linux_cheat_sheet.md)
- [Parsing GitLab logs with `jq`](../logs/log_parsing.md)
- [Diagnostics tools](diagnostics_tools.md)
Some feature documentation pages also have a troubleshooting section at the end
-that you can check for feature-specific help.
+that you can check for feature-specific help, including helpful Rails commands.
If you need a testing environment to troubleshoot, see the
[apps for a testing environment](test_environments.md).
diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md
index a1ae355a738..d44dda7d5f7 100644
--- a/doc/subscriptions/gitlab_com/index.md
+++ b/doc/subscriptions/gitlab_com/index.md
@@ -190,6 +190,30 @@ If you add a member to a group by using the [share a group with another group](.
- Remove the member from the shared group. You must be a group owner to do this.
- From the group's membership page, remove access from the entire shared group.
+## Seat usage alerts
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348481) in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `seat_flag_alerts`.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/362041) in GitLab 15.4. Feature flag `seat_flag_alerts` removed.
+
+If you have the Owner role of the top-level group, an alert notifies you
+of your total seat usage.
+
+The alert displays on group, subgroup, and project
+pages, and only for top-level groups linked to subscriptions enrolled
+in [quarterly subscription reconciliations](../quarterly_reconciliation.md).
+After you dismiss the alert, it doesn't display until another seat is used.
+
+The alert displays based on the following seat usage. You cannot configure the
+amounts at which the alert displays.
+
+| Seats in subscription | Seat usage |
+|-----------------------|------------------------------------------------------------------------|
+| 0-15 | One seat remaining in the subscription. |
+| 16-25 | Two seats remaining in the subscription. |
+| 26-99 | 10% of seats have been used. |
+| 100-999 | 8% of seats have been used. |
+| 1000+ | 5% of seats have been used |
+
## Upgrade your GitLab SaaS subscription tier
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/):
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 38a07f2ee9f..f0194f5dabd 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -187,6 +187,7 @@ module API
mount ::API::FreezePeriods
mount ::API::Keys
mount ::API::ImportBitbucketServer
+ mount ::API::ImportGithub
mount ::API::Metadata
mount ::API::MergeRequestDiffs
mount ::API::ProjectHooks
@@ -261,7 +262,6 @@ module API
mount ::API::GroupVariables
mount ::API::Groups
mount ::API::HelmPackages
- mount ::API::ImportGithub
mount ::API::Integrations
mount ::API::Integrations::JiraConnect::Subscriptions
mount ::API::Invitations
diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb
index e96504db53e..b3ae5885aef 100644
--- a/lib/api/entities/basic_project_details.rb
+++ b/lib/api/entities/basic_project_details.rb
@@ -6,15 +6,18 @@ module API
include ::API::ProjectsRelationBuilder
include Gitlab::Utils::StrongMemoize
- expose :default_branch_or_main, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
+ expose :default_branch_or_main, documentation: { type: 'string', example: 'main' }, as: :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
- expose :topic_names, as: :tag_list
- expose :topic_names, as: :topics
+ expose :topic_names, as: :tag_list, documentation: { type: 'string', is_array: true, example: 'tag' }
+ expose :topic_names, as: :topics, documentation: { type: 'string', is_array: true, example: 'topic' }
- expose :ssh_url_to_repo, :http_url_to_repo, :web_url, :readme_url
+ expose :ssh_url_to_repo, documentation: { type: 'string', example: 'git@gitlab.example.com:gitlab/gitlab.git' }
+ expose :http_url_to_repo, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab.git' }
+ expose :web_url, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab' }
+ expose :readme_url, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab/blob/master/README.md' }
- expose :license_url, if: :license do |project|
+ expose :license_url, if: :license, documentation: { type: 'string', example: 'https://gitlab.example.com/gitlab/gitlab/blob/master/LICENCE' } do |project|
license = project.repository.license_blob
if license
@@ -26,13 +29,13 @@ module API
project.repository.license
end
- expose :avatar_url do |project, options|
+ expose :avatar_url, documentation: { type: 'string', example: 'http://example.com/uploads/project/avatar/3/uploads/avatar.png' } do |project, options|
project.avatar_url(only_path: false)
end
- expose :forks_count
- expose :star_count
- expose :last_activity_at
+ expose :forks_count, documentation: { type: 'integer', example: 1 }
+ expose :star_count, documentation: { type: 'integer', example: 1 }
+ expose :last_activity_at, documentation: { type: 'dateTime', example: '2013-09-30T13:46:02Z' }
expose :namespace, using: 'API::Entities::NamespaceBasic'
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
diff --git a/lib/api/entities/basic_repository_storage_move.rb b/lib/api/entities/basic_repository_storage_move.rb
index 3ee112fb9a2..83b4f428a56 100644
--- a/lib/api/entities/basic_repository_storage_move.rb
+++ b/lib/api/entities/basic_repository_storage_move.rb
@@ -3,11 +3,11 @@
module API
module Entities
class BasicRepositoryStorageMove < Grape::Entity
- expose :id
- expose :created_at
- expose :human_state_name, as: :state
- expose :source_storage_name
- expose :destination_storage_name
+ expose :id, documentation: { type: 'integer', example: 1 }
+ expose :created_at, documentation: { type: 'dateTime', example: '2020-05-07T04:27:17.234Z' }
+ expose :human_state_name, as: :state, documentation: { type: 'string', example: 'scheduled' }
+ expose :source_storage_name, documentation: { type: 'string', example: 'default' }
+ expose :destination_storage_name, documentation: { type: 'string', example: 'storage1' }
end
end
end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index 9b0a472400d..947b0a3c0c1 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -5,148 +5,148 @@ module API
class Project < BasicProjectDetails
include ::API::Helpers::RelatedResourcesHelpers
- expose :container_registry_url, as: :container_registry_image_prefix, if: -> (_, _) { Gitlab.config.registry.enabled }
+ expose :container_registry_url, as: :container_registry_image_prefix, documentation: { type: 'string', example: 'registry.gitlab.example.com/gitlab/gitlab-client' }, if: -> (_, _) { Gitlab.config.registry.enabled }
expose :_links do
- expose :self do |project|
+ expose :self, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4' } do |project|
expose_url(api_v4_projects_path(id: project.id))
end
- expose :issues, if: -> (project, options) { issues_available?(project, options) } do |project|
+ expose :issues, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/issues' }, if: -> (project, options) { issues_available?(project, options) } do |project|
expose_url(api_v4_projects_issues_path(id: project.id))
end
- expose :merge_requests, if: -> (project, options) { mrs_available?(project, options) } do |project|
+ expose :merge_requests, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/merge_requests' }, if: -> (project, options) { mrs_available?(project, options) } do |project|
expose_url(api_v4_projects_merge_requests_path(id: project.id))
end
- expose :repo_branches do |project|
+ expose :repo_branches, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/repository/branches' } do |project|
expose_url(api_v4_projects_repository_branches_path(id: project.id))
end
- expose :labels do |project|
+ expose :labels, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/labels' } do |project|
expose_url(api_v4_projects_labels_path(id: project.id))
end
- expose :events do |project|
+ expose :events, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/events' } do |project|
expose_url(api_v4_projects_events_path(id: project.id))
end
- expose :members do |project|
+ expose :members, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/members' } do |project|
expose_url(api_v4_projects_members_path(id: project.id))
end
- expose :cluster_agents do |project|
+ expose :cluster_agents, documentation: { type: 'string', example: 'https://gitlab.example.com/api/v4/projects/4/cluster_agents' } do |project|
expose_url(api_v4_projects_cluster_agents_path(id: project.id))
end
end
- expose :packages_enabled
- expose :empty_repo?, as: :empty_repo
- expose :archived?, as: :archived
- expose :visibility
+ expose :packages_enabled, documentation: { type: 'boolean' }
+ expose :empty_repo?, as: :empty_repo, documentation: { type: 'boolean' }
+ expose :archived?, as: :archived, documentation: { type: 'boolean' }
+ expose :visibility, documentation: { type: 'string', example: 'public' }
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
- expose :resolve_outdated_diff_discussions
+ expose :resolve_outdated_diff_discussions, documentation: { type: 'boolean' }
expose :container_expiration_policy,
using: Entities::ContainerExpirationPolicy,
if: -> (project, _) { project.container_expiration_policy }
# Expose old field names with the new permissions methods to keep API compatible
# TODO: remove in API v5, replaced by *_access_level
- expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
- expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
- expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
- expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
- expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
- expose(:container_registry_enabled) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
- expose :service_desk_enabled
- expose :service_desk_address, if: -> (project, options) do
+ expose(:issues_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:issues, options[:current_user]) }
+ expose(:merge_requests_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
+ expose(:wiki_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
+ expose(:jobs_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:builds, options[:current_user]) }
+ expose(:snippets_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
+ expose(:container_registry_enabled, documentation: { type: 'boolean' }) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
+ expose :service_desk_enabled, documentation: { type: 'boolean' }
+ expose :service_desk_address, documentation: { type: 'string', example: 'address@example.com' }, if: -> (project, options) do
Ability.allowed?(options[:current_user], :admin_issue, project)
end
- expose(:can_create_merge_request_in) do |project, options|
+ expose(:can_create_merge_request_in, documentation: { type: 'boolean' }) do |project, options|
Ability.allowed?(options[:current_user], :create_merge_request_in, project)
end
- expose(:issues_access_level) { |project, options| project_feature_string_access_level(project, :issues) }
- expose(:repository_access_level) { |project, options| project_feature_string_access_level(project, :repository) }
- expose(:merge_requests_access_level) { |project, options| project_feature_string_access_level(project, :merge_requests) }
- expose(:forking_access_level) { |project, options| project_feature_string_access_level(project, :forking) }
- expose(:wiki_access_level) { |project, options| project_feature_string_access_level(project, :wiki) }
- expose(:builds_access_level) { |project, options| project_feature_string_access_level(project, :builds) }
- expose(:snippets_access_level) { |project, options| project_feature_string_access_level(project, :snippets) }
- expose(:pages_access_level) { |project, options| project_feature_string_access_level(project, :pages) }
- expose(:operations_access_level) { |project, options| project_feature_string_access_level(project, :operations) }
- expose(:analytics_access_level) { |project, options| project_feature_string_access_level(project, :analytics) }
- expose(:container_registry_access_level) { |project, options| project_feature_string_access_level(project, :container_registry) }
- expose(:security_and_compliance_access_level) { |project, options| project_feature_string_access_level(project, :security_and_compliance) }
- expose(:releases_access_level) { |project, options| project_feature_string_access_level(project, :releases) }
+ expose(:issues_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :issues) }
+ expose(:repository_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :repository) }
+ expose(:merge_requests_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :merge_requests) }
+ expose(:forking_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :forking) }
+ expose(:wiki_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :wiki) }
+ expose(:builds_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :builds) }
+ expose(:snippets_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :snippets) }
+ expose(:pages_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :pages) }
+ expose(:operations_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :operations) }
+ expose(:analytics_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :analytics) }
+ expose(:container_registry_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :container_registry) }
+ expose(:security_and_compliance_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :security_and_compliance) }
+ expose(:releases_access_level, documentation: { type: 'string', example: 'enabled' }) { |project, options| project_feature_string_access_level(project, :releases) }
- expose :emails_disabled
- expose :shared_runners_enabled
- expose :lfs_enabled?, as: :lfs_enabled
- expose :creator_id
+ expose :emails_disabled, documentation: { type: 'boolean' }
+ expose :shared_runners_enabled, documentation: { type: 'boolean' }
+ expose :lfs_enabled?, as: :lfs_enabled, documentation: { type: 'boolean' }
+ expose :creator_id, documentation: { type: 'integer', example: 1 }
expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do
project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project)
end
- expose :mr_default_target_self, if: -> (project) { project.forked? }
+ expose :mr_default_target_self, if: -> (project) { project.forked? }, documentation: { type: 'boolean' }
- expose :import_url, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project|
+ expose :import_url, documentation: { type: 'string', example: 'https://gitlab.com/gitlab/gitlab.git' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project|
project[:import_url]
end
- expose :import_type, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) }
- expose :import_status
- expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
+ expose :import_type, documentation: { type: 'string', example: 'git' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) }
+ expose :import_status, documentation: { type: 'string', example: 'none' }
+ expose :import_error, documentation: { type: 'string', example: 'Import error' }, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
project.import_state&.last_error
end
- expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
- expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
- expose :ci_default_git_depth
- expose :ci_forward_deployment_enabled
- expose(:ci_job_token_scope_enabled) { |p, _| p.ci_outbound_job_token_scope_enabled? }
- expose :ci_separated_caches
- expose :ci_opt_in_jwt
- expose :ci_allow_fork_pipelines_to_run_in_parent_project
- expose :public_builds, as: :public_jobs
- expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
+ expose :open_issues_count, documentation: { type: 'integer', example: 1 }, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
+ expose :runners_token, documentation: { type: 'string', example: 'b8547b1dc37721d05889db52fa2f02' }, if: lambda { |_project, options| options[:user_can_admin_project] }
+ expose :ci_default_git_depth, documentation: { type: 'integer', example: 20 }
+ expose :ci_forward_deployment_enabled, documentation: { type: 'boolean' }
+ expose(:ci_job_token_scope_enabled, documentation: { type: 'boolean' }) { |p, _| p.ci_outbound_job_token_scope_enabled? }
+ expose :ci_separated_caches, documentation: { type: 'boolean' }
+ expose :ci_opt_in_jwt, documentation: { type: 'boolean' }
+ expose :ci_allow_fork_pipelines_to_run_in_parent_project, documentation: { type: 'boolean' }
+ expose :public_builds, as: :public_jobs, documentation: { type: 'boolean' }
+ expose :build_git_strategy, documentation: { type: 'string', example: 'fetch' }, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
project.build_allow_git_fetch ? 'fetch' : 'clone'
end
- expose :build_timeout
- expose :auto_cancel_pending_pipelines
- expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
- expose :shared_with_groups do |project, options|
+ expose :build_timeout, documentation: { type: 'integer', example: 3600 }
+ expose :auto_cancel_pending_pipelines, documentation: { type: 'string', example: 'enabled' }
+ expose :ci_config_path, documentation: { type: 'string', example: '' }, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
+ expose :shared_with_groups, documentation: { is_array: true } do |project, options|
user = options[:current_user]
SharedGroupWithProject.represent(project.visible_group_links(for_user: user), options)
end
- expose :only_allow_merge_if_pipeline_succeeds
- expose :allow_merge_on_skipped_pipeline
- expose :restrict_user_defined_variables
- expose :request_access_enabled
- expose :only_allow_merge_if_all_discussions_are_resolved
- expose :remove_source_branch_after_merge
- expose :printing_merge_request_link_enabled
- expose :merge_method
- expose :squash_option
- expose :enforce_auth_checks_on_uploads
- expose :suggestion_commit_message
- expose :merge_commit_template
- expose :squash_commit_template
- expose :issue_branch_template
+ expose :only_allow_merge_if_pipeline_succeeds, documentation: { type: 'boolean' }
+ expose :allow_merge_on_skipped_pipeline, documentation: { type: 'boolean' }
+ expose :restrict_user_defined_variables, documentation: { type: 'boolean' }
+ expose :request_access_enabled, documentation: { type: 'boolean' }
+ expose :only_allow_merge_if_all_discussions_are_resolved, documentation: { type: 'boolean' }
+ expose :remove_source_branch_after_merge, documentation: { type: 'boolean' }
+ expose :printing_merge_request_link_enabled, documentation: { type: 'boolean' }
+ expose :merge_method, documentation: { type: 'string', example: 'merge' }
+ expose :squash_option, documentation: { type: 'string', example: 'default_off' }
+ expose :enforce_auth_checks_on_uploads, documentation: { type: 'boolean' }
+ expose :suggestion_commit_message, documentation: { type: 'string', example: 'Suggestion message' }
+ expose :merge_commit_template, documentation: { type: 'string', example: '%(title)' }
+ expose :squash_commit_template, documentation: { type: 'string', example: '%(source_branch)' }
+ expose :issue_branch_template, documentation: { type: 'string', example: '%(title)' }
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
- expose :auto_devops_enabled?, as: :auto_devops_enabled
- expose :auto_devops_deploy_strategy do |project, options|
+ expose :auto_devops_enabled?, as: :auto_devops_enabled, documentation: { type: 'boolean' }
+ expose :auto_devops_deploy_strategy, documentation: { type: 'string', example: 'continuous' } do |project, options|
project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
end
- expose :autoclose_referenced_issues
- expose :repository_storage, if: ->(project, options) {
+ expose :autoclose_referenced_issues, documentation: { type: 'boolean' }
+ expose :repository_storage, documentation: { type: 'string', example: 'default' }, if: ->(project, options) {
Ability.allowed?(options[:current_user], :change_repository_storage, project)
}
- expose :keep_latest_artifacts_available?, as: :keep_latest_artifact
- expose :runner_token_expiration_interval
+ expose :keep_latest_artifacts_available?, as: :keep_latest_artifact, documentation: { type: 'boolean' }
+ expose :runner_token_expiration_interval, documentation: { type: 'integer', example: 3600 }
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_resource(project)
diff --git a/lib/api/entities/project_group_link.rb b/lib/api/entities/project_group_link.rb
index 89138854e67..b5d5991ec7f 100644
--- a/lib/api/entities/project_group_link.rb
+++ b/lib/api/entities/project_group_link.rb
@@ -3,7 +3,11 @@
module API
module Entities
class ProjectGroupLink < Grape::Entity
- expose :id, :project_id, :group_id, :group_access, :expires_at
+ expose :id, documentation: { type: 'integer', example: 1 }
+ expose :project_id, documentation: { type: 'integer', example: 1 }
+ expose :group_id, documentation: { type: 'integer', example: 1 }
+ expose :group_access, documentation: { type: 'integer', example: 10 }
+ expose :expires_at, documentation: { type: 'date', example: '2016-09-26' }
end
end
end
diff --git a/lib/api/entities/project_identity.rb b/lib/api/entities/project_identity.rb
index 2055195eea0..14aef05b95e 100644
--- a/lib/api/entities/project_identity.rb
+++ b/lib/api/entities/project_identity.rb
@@ -3,10 +3,13 @@
module API
module Entities
class ProjectIdentity < Grape::Entity
- expose :id, :description
- expose :name, :name_with_namespace
- expose :path, :path_with_namespace
- expose :created_at
+ expose :id, documentation: { type: 'integer', example: 1 }
+ expose :description, documentation: { type: 'string', example: 'desc' }
+ expose :name, documentation: { type: 'string', example: 'project1' }
+ expose :name_with_namespace, documentation: { type: 'string', example: 'John Doe / project1' }
+ expose :path, documentation: { type: 'string', example: 'project1' }
+ expose :path_with_namespace, documentation: { type: 'string', example: 'namespace1/project1' }
+ expose :created_at, documentation: { type: 'dateTime', example: '2020-05-07T04:27:17.016Z' }
end
end
end
diff --git a/lib/api/entities/project_repository_storage.rb b/lib/api/entities/project_repository_storage.rb
index 0816bebde2c..ae5039601d7 100644
--- a/lib/api/entities/project_repository_storage.rb
+++ b/lib/api/entities/project_repository_storage.rb
@@ -5,12 +5,16 @@ module API
class ProjectRepositoryStorage < Grape::Entity
include Gitlab::Routing
- expose :disk_path do |project|
+ expose :disk_path, documentation: {
+ type: 'string',
+ example: '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b'
+ } do |project|
project.repository.disk_path
end
- expose :id, as: :project_id
- expose :repository_storage, :created_at
+ expose :id, as: :project_id, documentation: { type: 'integer', example: 1 }
+ expose :repository_storage, documentation: { type: 'string', example: 'default' }
+ expose :created_at, documentation: { type: 'dateTime', example: '2012-10-12T17:04:47Z' }
end
end
end
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index ee678ee1981..d742e3732a8 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -37,7 +37,15 @@ module API
desc 'Import a GitHub project' do
detail 'This feature was introduced in GitLab 11.3.4.'
- success ::ProjectEntity
+ success code: 201, model: ::ProjectEntity
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 422, message: 'Unprocessable entity' },
+ { code: 503, message: 'Service unavailable' }
+ ]
+ tags ['project_import_github']
end
params do
requires :personal_access_token, type: String, desc: 'GitHub personal access token'
@@ -58,6 +66,18 @@ module API
end
end
+ desc 'Cancel GitHub project import' do
+ detail 'This feature was introduced in GitLab 15.5'
+ success code: 200, model: ProjectImportEntity
+ failure [
+ { code: 400, message: 'Bad request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not found' },
+ { code: 503, message: 'Service unavailable' }
+ ]
+ tags ['project_import_github']
+ end
params do
requires :project_id, type: Integer, desc: 'ID of importing project to be canceled'
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 58cdff2e977..88d34189edc 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -147,6 +147,12 @@ module Gitlab
'in the body of your migration class'
end
+ if !options.delete(:allow_partition) && partition?(table_name)
+ raise ArgumentError, 'add_concurrent_index can not be used on a partitioned ' \
+ 'table. Please use add_concurrent_partitioned_index on the partitioned table ' \
+ 'as we need to create indexes on each partition and an index on the parent table'
+ end
+
options = options.merge({ algorithm: :concurrently })
if index_exists?(table_name, column_name, **options)
@@ -1284,6 +1290,14 @@ into similar problems in the future (e.g. when new tables are created).
end
# rubocop:enable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
+ def partition?(table_name)
+ if view_exists?(:postgres_partitions)
+ Gitlab::Database::PostgresPartition.partition_exists?(table_name)
+ else
+ Gitlab::Database::PostgresPartition.legacy_partition_exists?(table_name)
+ end
+ end
+
private
def multiple_columns(columns, separator: ', ')
diff --git a/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb
index eb7f62395d0..62f33bb56bc 100644
--- a/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb
+++ b/lib/gitlab/database/partitioning_migration_helpers/index_helpers.rb
@@ -40,7 +40,7 @@ module Gitlab
partitioned_table.postgres_partitions.order(:name).each do |partition|
partition_index_name = generated_index_name(partition.identifier, options[:name])
- partition_options = options.merge(name: partition_index_name)
+ partition_options = options.merge(name: partition_index_name, allow_partition: true)
add_concurrent_index(partition.identifier, column_names, partition_options)
end
diff --git a/lib/gitlab/database/postgres_partition.rb b/lib/gitlab/database/postgres_partition.rb
index eb080904f73..eda11fd8382 100644
--- a/lib/gitlab/database/postgres_partition.rb
+++ b/lib/gitlab/database/postgres_partition.rb
@@ -19,6 +19,20 @@ module Gitlab
scope :for_parent_table, ->(name) { where("parent_identifier = concat(current_schema(), '.', ?)", name).order(:name) }
+ def self.partition_exists?(table_name)
+ where("identifier = concat(current_schema(), '.', ?)", table_name).exists?
+ end
+
+ def self.legacy_partition_exists?(table_name)
+ result = connection.select_value(<<~SQL)
+ SELECT true FROM pg_class
+ WHERE relname = '#{table_name}'
+ AND relispartition = true;
+ SQL
+
+ !!result
+ end
+
def to_s
name
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index bdb7484f3d6..bf111957a32 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -17,11 +17,18 @@ module Gitlab
gon.markdown_surround_selection = current_user&.markdown_surround_selection
gon.markdown_automatic_lists = current_user&.markdown_automatic_lists
- if Gitlab.config.sentry.enabled
+ # Support for Sentry setup via configuration will be removed in 16.0
+ # in favor of Gitlab::CurrentSettings.
+ if Feature.enabled?(:enable_old_sentry_clientside_integration) && Gitlab.config.sentry.enabled
gon.sentry_dsn = Gitlab.config.sentry.clientside_dsn
gon.sentry_environment = Gitlab.config.sentry.environment
end
+ if Feature.enabled?(:enable_new_sentry_clientside_integration) && Gitlab::CurrentSettings.sentry_enabled
+ gon.sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn
+ gon.sentry_environment = Gitlab::CurrentSettings.sentry_environment
+ end
+
gon.recaptcha_api_server_url = ::Recaptcha.configuration.api_server_url
gon.recaptcha_sitekey = Gitlab::CurrentSettings.recaptcha_site_key
gon.gitlab_url = Gitlab.config.gitlab.url
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9d1d1f3e49d..041ff029a4b 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2734,15 +2734,27 @@ msgstr ""
msgid "AdminSettings|CI/CD limits"
msgstr ""
+msgid "AdminSettings|Clickhouse URL"
+msgstr ""
+
msgid "AdminSettings|Configure Let's Encrypt"
msgstr ""
msgid "AdminSettings|Configure limits on the number of repositories users can download in a given time."
msgstr ""
+msgid "AdminSettings|Configure product analytics to track events within your project applications."
+msgstr ""
+
msgid "AdminSettings|Configure when inactive projects should be automatically deleted. %{linkStart}What are inactive projects?%{linkEnd}"
msgstr ""
+msgid "AdminSettings|Cube API URL"
+msgstr ""
+
+msgid "AdminSettings|Cube API key"
+msgstr ""
+
msgid "AdminSettings|Delete inactive projects"
msgstr ""
@@ -2791,6 +2803,9 @@ msgstr ""
msgid "AdminSettings|Enable pipeline suggestion banner"
msgstr ""
+msgid "AdminSettings|Enable product analytics"
+msgstr ""
+
msgid "AdminSettings|Enable shared runners for new projects"
msgstr ""
@@ -2836,6 +2851,18 @@ msgstr ""
msgid "AdminSettings|Instance runners expiration"
msgstr ""
+msgid "AdminSettings|Jitsu administrator email"
+msgstr ""
+
+msgid "AdminSettings|Jitsu administrator password"
+msgstr ""
+
+msgid "AdminSettings|Jitsu host"
+msgstr ""
+
+msgid "AdminSettings|Jitsu project ID"
+msgstr ""
+
msgid "AdminSettings|Keep the latest artifacts for all jobs in the latest successful pipelines"
msgstr ""
@@ -2974,9 +3001,18 @@ msgstr ""
msgid "AdminSettings|Size and domain settings for Pages static sites."
msgstr ""
+msgid "AdminSettings|The ID of the project in Jitsu. The project contains all analytics instances."
+msgstr ""
+
+msgid "AdminSettings|The URL of your Cube instance."
+msgstr ""
+
msgid "AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects."
msgstr ""
+msgid "AdminSettings|The host of your Jitsu instance."
+msgstr ""
+
msgid "AdminSettings|The latest artifacts for all jobs in the most recent successful pipelines in each project are stored and do not expire."
msgstr ""
@@ -3001,6 +3037,15 @@ msgstr ""
msgid "AdminSettings|Use AWS OpenSearch Service with IAM credentials"
msgstr ""
+msgid "AdminSettings|Used to connect Jitsu to the Clickhouse instance."
+msgstr ""
+
+msgid "AdminSettings|Used to generate short-lived API access tokens."
+msgstr ""
+
+msgid "AdminSettings|Used to retrieve dashboard data from the Cube instance."
+msgstr ""
+
msgid "AdminSettings|Users and groups must accept the invitation before they're added to a group or project."
msgstr ""
@@ -5332,9 +5377,6 @@ msgstr ""
msgid "Assign labels"
msgstr ""
-msgid "Assign milestone"
-msgstr ""
-
msgid "Assign myself"
msgstr ""
@@ -30890,6 +30932,9 @@ msgstr ""
msgid "Proceed"
msgstr ""
+msgid "Product analytics"
+msgstr ""
+
msgid "ProductAnalytics|Audience"
msgstr ""
@@ -36144,10 +36189,10 @@ msgstr ""
msgid "SecurityOrchestration|%{agent} for %{namespaces}"
msgstr ""
-msgid "SecurityOrchestration|%{branches} %{plural}"
+msgid "SecurityOrchestration|%{branches} and %{lastBranch} branches"
msgstr ""
-msgid "SecurityOrchestration|%{branches} and %{lastBranch} %{plural}"
+msgid "SecurityOrchestration|%{branches} branch"
msgstr ""
msgid "SecurityOrchestration|%{scanners}"
@@ -36504,10 +36549,10 @@ msgstr ""
msgid "SecurityOrchestration|the %{branches}"
msgstr ""
-msgid "SecurityOrchestration|the %{namespaces} %{plural}"
+msgid "SecurityOrchestration|the %{namespaces} and %{lastNamespace} namespaces"
msgstr ""
-msgid "SecurityOrchestration|the %{namespaces} and %{lastNamespace} %{plural}"
+msgid "SecurityOrchestration|the %{namespaces} namespace"
msgstr ""
msgid "SecurityOrchestration|vulnerabilities"
@@ -48842,11 +48887,6 @@ msgstr ""
msgid "my-topic"
msgstr ""
-msgid "namespace"
-msgid_plural "namespaces"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "needs to be between 10 minutes and 1 month"
msgstr ""
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
index 5689fa169ce..116a00f8385 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
@@ -98,12 +98,17 @@ module QA
let(:mrs) { fetch_mrs(imported_project, target_api_client) }
let(:issues) { fetch_issues(imported_project, target_api_client) }
+ let(:import_failures) { imported_group.import_details.sum([]) { |details| details[:failures] } }
+
before do
destination_group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
end
# rubocop:disable RSpec/InstanceVariable
after do |example|
+ # Log failures for easier debugging
+ Runtime::Logger.error("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
+
next unless defined?(@import_time)
# save data for comparison notification creation
@@ -112,7 +117,7 @@ module QA
{
importer: :gitlab,
import_time: @import_time,
- errors: imported_group.import_details.sum([]) { |details| details[:failures] },
+ errors: import_failures,
source: {
name: "GitLab Source",
project_name: source_project.path_with_namespace,
@@ -154,7 +159,7 @@ module QA
end
# rubocop:enable RSpec/InstanceVariable
- it "migrates large gitlab group via api", testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358842' do
+ it "migrates large gitlab group via api", testcase: "https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/358842" do
start = Time.now
# trigger import and log imported group path
@@ -165,7 +170,11 @@ module QA
# wait for import to finish and save import time
logger.info("== Waiting for import to be finished ==")
- expect { imported_group.import_status }.to eventually_eq('finished').within(import_wait_duration)
+ expect { imported_group.import_status }.not_to eventually_eq("started").within(import_wait_duration)
+ # finished status actually means success, don't wait for finished status explicitly
+ # because test would wait full duration if returned status is "failed"
+ expect(imported_group.import_status).to eq("finished")
+
@import_time = Time.now - start
aggregate_failures do
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index b132c0b5a69..527cba9e618 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -20,23 +20,11 @@ RSpec.describe Projects::PipelinesController do
end
shared_examples 'the show page' do |param|
- it 'redirects to pipeline path with param' do
+ it 'renders the show template' do
get param, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
- expect(response).to redirect_to(pipeline_path(pipeline, tab: param))
- end
-
- context 'when the FF pipeline_tabs_vue is disabled' do
- before do
- stub_feature_flags(pipeline_tabs_vue: false)
- end
-
- it 'renders the show template' do
- get param, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template :show
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
end
end
@@ -710,37 +698,25 @@ RSpec.describe Projects::PipelinesController do
describe 'GET failures' do
let(:pipeline) { create(:ci_pipeline, project: project) }
- context 'with ff `pipeline_tabs_vue` disabled' do
+ context 'with failed jobs' do
before do
- stub_feature_flags(pipeline_tabs_vue: false)
+ create(:ci_build, :failed, pipeline: pipeline, name: 'hello')
end
- context 'with failed jobs' do
- before do
- create(:ci_build, :failed, pipeline: pipeline, name: 'hello')
- end
+ it 'shows the page' do
+ get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
- it 'shows the page' do
- get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template :show
- end
- end
-
- context 'without failed jobs' do
- it 'redirects to the main pipeline page' do
- get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
-
- expect(response).to redirect_to(pipeline_path(pipeline))
- end
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
end
end
- it 'redirects to the pipeline page with `failures` query param' do
- get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
+ context 'without failed jobs' do
+ it 'redirects to the main pipeline page' do
+ get :failures, params: { namespace_id: project.namespace, project_id: project, id: pipeline }
- expect(response).to redirect_to(pipeline_path(pipeline, tab: 'failures'))
+ expect(response).to redirect_to(pipeline_path(pipeline))
+ end
end
end
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 1c24d091649..c383c4b6f24 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe 'Database schema' do
project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id],
project_statistics: %w[namespace_id],
- projects: %w[ci_id mirror_user_id],
+ projects: %w[creator_id ci_id mirror_user_id],
redirect_routes: %w[source_id],
repository_languages: %w[programming_language_id],
routes: %w[source_id],
diff --git a/spec/features/boards/board_filters_spec.rb b/spec/features/boards/board_filters_spec.rb
index 1184aea1114..eab92de7e8a 100644
--- a/spec/features/boards/board_filters_spec.rb
+++ b/spec/features/boards/board_filters_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe 'Issue board filters', :js do
let(:filter_submit) { find('.gl-search-box-by-click-search-button') }
before do
+ stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index a09c9d258dc..fee9b5b378e 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe 'Project issue boards', :js do
context 'signed in user' do
before do
+ stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
project.add_maintainer(user2)
@@ -29,7 +30,7 @@ RSpec.describe 'Project issue boards', :js do
context 'no lists' do
before do
- visit_project_board_path_without_query_limit(project, board)
+ visit_project_board(project, board)
end
it 'creates default lists' do
@@ -73,7 +74,7 @@ RSpec.describe 'Project issue boards', :js do
let_it_be(:issue10) { create(:labeled_issue, project: project, title: 'issue +', description: 'A+ great issue', labels: [a_plus]) }
before do
- visit_project_board_path_without_query_limit(project, board)
+ visit_project_board(project, board)
end
it 'shows description tooltip on list title', :quarantine do
@@ -124,7 +125,7 @@ RSpec.describe 'Project issue boards', :js do
it 'infinite scrolls list' do
create_list(:labeled_issue, 30, project: project, labels: [planning])
- visit_project_board_path_without_query_limit(project, board)
+ visit_project_board(project, board)
page.within(find('.board:nth-child(2)')) do
expect(page.find('.board-header')).to have_content('38')
@@ -203,7 +204,7 @@ RSpec.describe 'Project issue boards', :js do
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
# Make sure list positions are preserved after a reload
- visit_project_board_path_without_query_limit(project, board)
+ visit_project_board(project, board)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(3) [data-testid="board-list-header"]')).to have_content(planning.title)
@@ -215,15 +216,19 @@ RSpec.describe 'Project issue boards', :js do
let_it_be(:list2) { create(:list, board: board, label: development, position: 1) }
it 'changes position of list' do
- visit_project_board_path_without_query_limit(project, board)
+ inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
+ visit_project_board(project, board)
+ end
drag(list_from_index: 0, list_to_index: 1, selector: '.board-header')
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
- # Make sure list positions are preserved after a reload
- visit_project_board_path_without_query_limit(project, board)
+ inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
+ # Make sure list positions are preserved after a reload
+ visit_project_board(project, board)
+ end
expect(find('.board:nth-child(1) [data-testid="board-list-header"]')).to have_content(development.title)
expect(find('.board:nth-child(2) [data-testid="board-list-header"]')).to have_content(planning.title)
@@ -234,7 +239,9 @@ RSpec.describe 'Project issue boards', :js do
selector = '.board:not(.is-ghost) .board-header'
expect(page).to have_selector(selector, text: development.title, count: 1)
- drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
+ inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
+ drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
+ end
expect(page).to have_selector(selector, text: development.title, count: 1)
end
@@ -492,7 +499,7 @@ RSpec.describe 'Project issue boards', :js do
context 'keyboard shortcuts' do
before do
- visit_project_board_path_without_query_limit(project, board)
+ visit_project_board(project, board)
wait_for_requests
end
@@ -505,6 +512,7 @@ RSpec.describe 'Project issue boards', :js do
context 'signed out user' do
before do
+ stub_feature_flags(apollo_boards: false)
visit project_board_path(project, board)
wait_for_requests
end
@@ -526,6 +534,7 @@ RSpec.describe 'Project issue boards', :js do
let_it_be(:user_guest) { create(:user) }
before do
+ stub_feature_flags(apollo_boards: false)
project.add_guest(user_guest)
sign_in(user_guest)
visit project_board_path(project, board)
@@ -587,11 +596,9 @@ RSpec.describe 'Project issue boards', :js do
end
end
- def visit_project_board_path_without_query_limit(project, board)
- inspect_requests(inject_headers: { 'X-GITLAB-DISABLE-SQL-QUERY-LIMIT' => 'https://gitlab.com/gitlab-org/gitlab/-/issues/323426' }) do
- visit project_board_path(project, board)
+ def visit_project_board(project, board)
+ visit project_board_path(project, board)
- wait_for_requests
- end
+ wait_for_requests
end
end
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index 57f2bf26752..a3dda3b9d2f 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe 'Issue Boards', :js do
let!(:issue3) { create(:labeled_issue, project: project, title: 'testing 3', labels: [label], relative_position: 1) }
before do
+ stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/boards/sidebar_labels_spec.rb b/spec/features/boards/sidebar_labels_spec.rb
index 511233b50c0..12d91e9c5a8 100644
--- a/spec/features/boards/sidebar_labels_spec.rb
+++ b/spec/features/boards/sidebar_labels_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe 'Project issue boards sidebar labels', :js do
let(:card) { find('.board:nth-child(2)').first('.board-card') }
before do
+ stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 0e914ae19d1..2b2a412194a 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -15,6 +15,7 @@ RSpec.describe 'Project issue boards sidebar', :js do
let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) }
before do
+ stub_feature_flags(apollo_boards: false)
project.add_maintainer(user)
sign_in(user)
diff --git a/spec/features/boards/user_adds_lists_to_board_spec.rb b/spec/features/boards/user_adds_lists_to_board_spec.rb
index a1728d066cd..480a88a6b84 100644
--- a/spec/features/boards/user_adds_lists_to_board_spec.rb
+++ b/spec/features/boards/user_adds_lists_to_board_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe 'User adds lists', :js do
with_them do
before do
+ stub_feature_flags(apollo_boards: false)
sign_in(user)
set_cookie('sidebar_collapsed', 'true')
diff --git a/spec/features/boards/user_visits_board_spec.rb b/spec/features/boards/user_visits_board_spec.rb
index 5587d9560a2..c386477fa9d 100644
--- a/spec/features/boards/user_visits_board_spec.rb
+++ b/spec/features/boards/user_visits_board_spec.rb
@@ -44,6 +44,7 @@ RSpec.describe 'User visits issue boards', :js do
with_them do
before do
+ stub_feature_flags(apollo_boards: false)
visit board_path
wait_for_requests
@@ -59,6 +60,7 @@ RSpec.describe 'User visits issue boards', :js do
end
context "project boards" do
+ stub_feature_flags(apollo_boards: false)
let_it_be(:board) { create_default(:board, project: project) }
let_it_be(:backlog_list) { create_default(:backlog_list, board: board) }
@@ -68,6 +70,7 @@ RSpec.describe 'User visits issue boards', :js do
end
context "group boards" do
+ stub_feature_flags(apollo_boards: false)
let_it_be(:board) { create_default(:board, group: group) }
let_it_be(:backlog_list) { create_default(:backlog_list, board: board) }
diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb
index aa42b29b3ac..10ef28f3fbc 100644
--- a/spec/features/groups/board_sidebar_spec.rb
+++ b/spec/features/groups/board_sidebar_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe 'Group Issue Boards', :js do
let(:card) { find('.board:nth-child(1)').first('.board-card') }
before do
+ stub_feature_flags(apollo_boards: false)
sign_in(user)
visit group_board_path(group, board)
diff --git a/spec/features/incidents/user_views_incident_spec.rb b/spec/features/incidents/user_views_incident_spec.rb
index a669966502e..054a084ea9c 100644
--- a/spec/features/incidents/user_views_incident_spec.rb
+++ b/spec/features/incidents/user_views_incident_spec.rb
@@ -4,12 +4,16 @@ require "spec_helper"
RSpec.describe "User views incident" do
let_it_be(:project) { create(:project_empty_repo, :public) }
- let_it_be(:user) { create(:user) }
- let_it_be(:incident) { create(:incident, project: project, description: "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)", author: user) }
- let_it_be(:note) { create(:note, noteable: incident, project: project, author: user) }
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:user) { developer }
+ let(:author) { developer }
+ let(:description) { "# Description header\n\n**Lorem** _ipsum_ dolor sit [amet](https://example.com)" }
+ let(:incident) { create(:incident, project: project, description: description, author: author) }
before_all do
- project.add_developer(user)
+ project.add_developer(developer)
+ project.add_guest(guest)
end
before do
@@ -18,57 +22,61 @@ RSpec.describe "User views incident" do
visit(project_issues_incident_path(project, incident))
end
- it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
+ specify do
+ expect(page).to have_header_with_correct_id_and_link(1, 'Description header', 'description-header')
+ end
it_behaves_like 'page meta description', ' Description header Lorem ipsum dolor sit amet'
describe 'user actions' do
it 'shows the merge request and incident actions', :js, :aggregate_failures do
+ expected_href = new_project_issue_path(project,
+ issuable_template: 'incident',
+ issue: { issue_type: 'incident' },
+ add_related_issue: incident.iid)
+
click_button 'Incident actions'
- expect(page).to have_link('New related incident', href: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' }, add_related_issue: incident.iid }))
+ expect(page).to have_link('New related incident', href: expected_href)
expect(page).to have_button('Create merge request')
expect(page).to have_button('Close incident')
end
- context 'when user is a guest' do
- before do
- project.add_guest(user)
+ context 'when user is guest' do
+ let(:user) { guest }
- login_as(user)
+ context 'and author' do
+ let(:author) { guest }
- visit(project_issues_incident_path(project, incident))
+ it 'does not show the incident actions', :js do
+ expect(page).not_to have_button('Incident actions')
+ end
end
- it 'does not show the incident actions', :js, :aggregate_failures do
- expect(page).not_to have_button('Incident actions')
+ context 'and not author' do
+ it 'shows incident actions', :js do
+ click_button 'Incident actions'
+
+ expect(page).to have_link 'Report abuse'
+ end
end
end
end
context 'when the project is archived' do
- before do
+ before_all do
project.update!(archived: true)
- visit(project_issues_incident_path(project, incident))
end
- it 'hides the merge request and incident actions', :aggregate_failures do
- expect(page).not_to have_link('New incident')
- expect(page).not_to have_button('Create merge request')
- expect(page).not_to have_link('Close incident')
+ it 'does not show the incident actions', :js do
+ expect(page).not_to have_button('Incident actions')
end
end
describe 'user status' do
- subject { visit(project_issues_incident_path(project, incident)) }
-
context 'when showing status of the author of the incident' do
- it_behaves_like 'showing user status' do
- let(:user_with_status) { user }
- end
- end
+ subject { visit(project_issues_incident_path(project, incident)) }
- context 'when showing status of a user who commented on an incident', :js do
it_behaves_like 'showing user status' do
let(:user_with_status) { user }
end
diff --git a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb
index 4837d13574c..2a201e0bc23 100644
--- a/spec/features/issues/user_bulk_edits_issues_labels_spec.rb
+++ b/spec/features/issues/user_bulk_edits_issues_labels_spec.rb
@@ -417,7 +417,7 @@ RSpec.describe 'Issues > Labels bulk assignment' do
click_button 'Select milestone'
wait_for_requests
items.map do |item|
- click_link item
+ click_button item
end
end
diff --git a/spec/features/issues/user_bulk_edits_issues_spec.rb b/spec/features/issues/user_bulk_edits_issues_spec.rb
index 1ef2918adec..d7fad355cb4 100644
--- a/spec/features/issues/user_bulk_edits_issues_spec.rb
+++ b/spec/features/issues/user_bulk_edits_issues_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js do
click_button 'Edit issues'
check 'Select all'
click_button 'Select milestone'
- click_link milestone.title
+ click_button milestone.title
click_update_issues_button
expect(page.find('.issue')).to have_content milestone.title
@@ -97,7 +97,7 @@ RSpec.describe 'Multiple issue updating from issues#index', :js do
click_button 'Edit issues'
check 'Select all'
click_button 'Select milestone'
- click_link 'No milestone'
+ click_button 'No milestone'
click_update_issues_button
expect(find('.issue:first-of-type')).not_to have_text milestone.title
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index cf9760bcd7f..5c3cb098e28 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -130,7 +130,7 @@ RSpec.describe 'Merge requests > User mass updates', :js do
click_button 'Edit merge requests'
check 'Select all'
click_button 'Select milestone'
- click_link text
+ click_button text
click_update_merge_requests_button
end
diff --git a/spec/frontend/boards/components/board_app_spec.js b/spec/frontend/boards/components/board_app_spec.js
index dee097bfb08..c209f2f82e6 100644
--- a/spec/frontend/boards/components/board_app_spec.js
+++ b/spec/frontend/boards/components/board_app_spec.js
@@ -28,6 +28,7 @@ describe('BoardApp', () => {
store,
provide: {
...provide,
+ fullBoardId: 'gid://gitlab/Board/1',
},
});
};
diff --git a/spec/frontend/boards/components/board_content_spec.js b/spec/frontend/boards/components/board_content_spec.js
index bb1650cd2cc..bf491029c41 100644
--- a/spec/frontend/boards/components/board_content_spec.js
+++ b/spec/frontend/boards/components/board_content_spec.js
@@ -1,15 +1,20 @@
import { GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
import Draggable from 'vuedraggable';
import Vuex from 'vuex';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
import getters from 'ee_else_ce/boards/stores/getters';
+import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql';
import BoardColumn from '~/boards/components/board_column.vue';
import BoardContent from '~/boards/components/board_content.vue';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
-import { mockLists } from '../mock_data';
+import { mockLists, boardListsQueryResponse } from '../mock_data';
+Vue.use(VueApollo);
Vue.use(Vuex);
const actions = {
@@ -18,6 +23,7 @@ const actions = {
describe('BoardContent', () => {
let wrapper;
+ let fakeApollo;
window.gon = {};
const defaultState = {
@@ -35,19 +41,33 @@ describe('BoardContent', () => {
});
};
- const createComponent = ({ state, props = {}, canAdminList = true } = {}) => {
+ const createComponent = ({
+ state,
+ props = {},
+ canAdminList = true,
+ isApolloBoard = false,
+ issuableType = 'issue',
+ boardListQueryHandler = jest.fn().mockResolvedValue(boardListsQueryResponse),
+ } = {}) => {
+ fakeApollo = createMockApollo([[boardListsQuery, boardListQueryHandler]]);
+
const store = createStore({
...defaultState,
...state,
});
wrapper = shallowMount(BoardContent, {
+ apolloProvider: fakeApollo,
propsData: {
- lists: mockLists,
disabled: false,
+ boardId: 'gid://gitlab/Board/1',
...props,
},
provide: {
canAdminList,
+ boardType: 'group',
+ fullPath: 'gitlab-org/gitlab',
+ issuableType,
+ isApolloBoard,
},
store,
});
@@ -78,6 +98,7 @@ describe('BoardContent', () => {
afterEach(() => {
wrapper.destroy();
+ fakeApollo = null;
});
describe('default', () => {
@@ -112,7 +133,7 @@ describe('BoardContent', () => {
describe('when issuableType is not issue', () => {
beforeEach(() => {
- createComponent({ state: { issuableType: 'foo' } });
+ createComponent({ issuableType: 'foo' });
});
it('does not render BoardContentSidebar', () => {
@@ -139,4 +160,19 @@ describe('BoardContent', () => {
expect(wrapper.findComponent(Draggable).exists()).toBe(false);
});
});
+
+ describe('when Apollo boards FF is on', () => {
+ beforeEach(async () => {
+ createComponent({ isApolloBoard: true });
+ await waitForPromises();
+ });
+
+ it('renders a BoardColumn component per list', () => {
+ expect(wrapper.findAllComponents(BoardColumn)).toHaveLength(mockLists.length);
+ });
+
+ it('renders BoardContentSidebar', () => {
+ expect(wrapper.findComponent(BoardContentSidebar).exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index dc1f3246be0..17dcf5868a2 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -433,8 +433,11 @@ export const mockList = {
label: null,
assignee: null,
milestone: null,
+ iteration: null,
loading: false,
issuesCount: 1,
+ maxIssueCount: 0,
+ __typename: 'BoardList',
};
export const mockLabelList = {
@@ -449,11 +452,15 @@ export const mockLabelList = {
color: '#F0AD4E',
textColor: '#FFFFFF',
description: null,
+ descriptionHtml: null,
},
assignee: null,
milestone: null,
+ iteration: null,
loading: false,
issuesCount: 0,
+ maxIssueCount: 0,
+ __typename: 'BoardList',
};
export const mockMilestoneList = {
@@ -844,6 +851,22 @@ export const mockGroupLabelsResponse = {
},
};
+export const boardListsQueryResponse = {
+ data: {
+ group: {
+ id: 'gid://gitlab/Group/1',
+ board: {
+ id: 'gid://gitlab/Board/1',
+ hideBacklogList: false,
+ lists: {
+ nodes: mockLists,
+ },
+ },
+ __typename: 'Group',
+ },
+ },
+};
+
export const boardListQueryResponse = (issuesCount = 20) => ({
data: {
boardList: {
diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
index 3680d9d62c7..c2cb95d4320 100644
--- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
@@ -2,10 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
-import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
-import Dag from '~/pipelines/components/dag/dag.vue';
-import JobsApp from '~/pipelines/components/jobs/jobs_app.vue';
-import TestReports from '~/pipelines/components/test_reports/test_reports.vue';
describe('The Pipeline Tabs', () => {
let wrapper;
@@ -16,12 +12,6 @@ describe('The Pipeline Tabs', () => {
const findPipelineTab = () => wrapper.findByTestId('pipeline-tab');
const findTestsTab = () => wrapper.findByTestId('tests-tab');
- const findDagApp = () => wrapper.findComponent(Dag);
- const findFailedJobsApp = () => wrapper.findComponent(JobsApp);
- const findJobsApp = () => wrapper.findComponent(JobsApp);
- const findPipelineApp = () => wrapper.findComponent(PipelineGraphWrapper);
- const findTestsApp = () => wrapper.findComponent(TestReports);
-
const findFailedJobsBadge = () => wrapper.findByTestId('failed-builds-counter');
const findJobsBadge = () => wrapper.findByTestId('builds-counter');
const findTestsBadge = () => wrapper.findByTestId('tests-counter');
@@ -43,6 +33,7 @@ describe('The Pipeline Tabs', () => {
},
stubs: {
GlTab,
+ RouterView: true,
},
}),
);
@@ -54,17 +45,16 @@ describe('The Pipeline Tabs', () => {
describe('Tabs', () => {
it.each`
- tabName | tabComponent | appComponent
- ${'Pipeline'} | ${findPipelineTab} | ${findPipelineApp}
- ${'Dag'} | ${findDagTab} | ${findDagApp}
- ${'Jobs'} | ${findJobsTab} | ${findJobsApp}
- ${'Failed Jobs'} | ${findFailedJobsTab} | ${findFailedJobsApp}
- ${'Tests'} | ${findTestsTab} | ${findTestsApp}
- `('shows $tabName tab with its associated component', ({ appComponent, tabComponent }) => {
+ tabName | tabComponent
+ ${'Pipeline'} | ${findPipelineTab}
+ ${'Dag'} | ${findDagTab}
+ ${'Jobs'} | ${findJobsTab}
+ ${'Failed Jobs'} | ${findFailedJobsTab}
+ ${'Tests'} | ${findTestsTab}
+ `('shows $tabName tab', ({ tabComponent }) => {
createComponent();
expect(tabComponent().exists()).toBe(true);
- expect(appComponent().exists()).toBe(true);
});
describe('with no failed jobs', () => {
diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
index d6b13da3c3a..41b020189d0 100644
--- a/spec/frontend/pipelines/pipeline_graph/utils_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
@@ -1,5 +1,5 @@
import { createJobsHash, generateJobNeedsDict, getPipelineDefaultTab } from '~/pipelines/utils';
-import { TAB_QUERY_PARAM, validPipelineTabNames } from '~/pipelines/constants';
+import { validPipelineTabNames } from '~/pipelines/constants';
describe('utils functions', () => {
const jobName1 = 'build_1';
@@ -173,18 +173,25 @@ describe('utils functions', () => {
describe('getPipelineDefaultTab', () => {
const baseUrl = 'http://gitlab.com/user/multi-projects-small/-/pipelines/332/';
- it('returns null if there was no `tab` params', () => {
+ it('returns null if there is only the base url', () => {
expect(getPipelineDefaultTab(baseUrl)).toBe(null);
});
- it('returns null if there was no valid tab param', () => {
- expect(getPipelineDefaultTab(`${baseUrl}?${TAB_QUERY_PARAM}=invalid`)).toBe(null);
+ it('returns null if there was no valid last url part', () => {
+ expect(getPipelineDefaultTab(`${baseUrl}something`)).toBe(null);
});
it('returns the correct tab name if present', () => {
validPipelineTabNames.forEach((tabName) => {
- expect(getPipelineDefaultTab(`${baseUrl}?${TAB_QUERY_PARAM}=${tabName}`)).toBe(tabName);
+ expect(getPipelineDefaultTab(`${baseUrl}${tabName}`)).toBe(tabName);
});
});
+
+ it('returns the right value even with query params', () => {
+ const [tabName] = validPipelineTabNames;
+ expect(getPipelineDefaultTab(`${baseUrl}${tabName}?query="something"&query2="else"`)).toBe(
+ tabName,
+ );
+ });
});
});
diff --git a/spec/frontend/pipelines/pipeline_tabs_spec.js b/spec/frontend/pipelines/pipeline_tabs_spec.js
index b184ce31d20..099748a5cca 100644
--- a/spec/frontend/pipelines/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/pipeline_tabs_spec.js
@@ -1,9 +1,7 @@
-import { createAppOptions, createPipelineTabs } from '~/pipelines/pipeline_tabs';
-import { updateHistory } from '~/lib/utils/url_utility';
+import { createAppOptions } from '~/pipelines/pipeline_tabs';
jest.mock('~/lib/utils/url_utility', () => ({
removeParams: () => 'gitlab.com',
- updateHistory: jest.fn(),
joinPaths: () => {},
setUrlFragment: () => {},
}));
@@ -64,32 +62,4 @@ describe('~/pipelines/pipeline_tabs.js', () => {
expect(createAppOptions('foo', null)).toBe(null);
});
});
-
- describe('createPipelineTabs', () => {
- const title = 'Pipeline Tabs';
-
- beforeAll(() => {
- document.title = title;
- });
-
- afterAll(() => {
- document.title = '';
- });
-
- it('calls `updateHistory` with correct params', () => {
- createPipelineTabs({});
-
- expect(updateHistory).toHaveBeenCalledWith({
- title,
- url: 'gitlab.com',
- replace: true,
- });
- });
-
- it("returns early if options aren't provided", () => {
- createPipelineTabs();
-
- expect(updateHistory).not.toHaveBeenCalled();
- });
- });
});
diff --git a/spec/frontend/sidebar/components/milestone/bulk_update_milestone_dropdown_spec.js b/spec/frontend/sidebar/components/milestone/bulk_update_milestone_dropdown_spec.js
new file mode 100644
index 00000000000..03b2a3ca728
--- /dev/null
+++ b/spec/frontend/sidebar/components/milestone/bulk_update_milestone_dropdown_spec.js
@@ -0,0 +1,74 @@
+import { shallowMount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { IssuableType, WorkspaceType } from '~/issues/constants';
+import { __ } from '~/locale';
+import BulkUpdateMilestoneDropdown from '~/sidebar/components/milestone/bulk_update_milestone_dropdown.vue';
+import SidebarDropdown from '~/sidebar/components/sidebar_dropdown.vue';
+
+describe('BulkUpdateMilestoneDropdown component', () => {
+ let wrapper;
+
+ const propsData = {
+ attrWorkspacePath: 'full/path',
+ issuableType: IssuableType.Issue,
+ workspaceType: WorkspaceType.project,
+ };
+
+ const findHiddenInput = () => wrapper.find('input');
+ const findSidebarDropdown = () => wrapper.findComponent(SidebarDropdown);
+
+ const createComponent = () => {
+ wrapper = shallowMount(BulkUpdateMilestoneDropdown, { propsData });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders SidebarDropdown', () => {
+ expect(findSidebarDropdown().props()).toMatchObject({
+ attrWorkspacePath: propsData.attrWorkspacePath,
+ issuableAttribute: BulkUpdateMilestoneDropdown.issuableAttribute,
+ issuableType: propsData.issuableType,
+ workspaceType: propsData.workspaceType,
+ });
+ });
+
+ it('renders hidden input', () => {
+ expect(findHiddenInput().attributes()).toEqual({
+ type: 'hidden',
+ name: 'update[milestone_id]',
+ value: undefined,
+ });
+ });
+
+ describe('when SidebarDropdown emits `change` event', () => {
+ describe('when valid milestone is emitted', () => {
+ it('updates the hidden input value', async () => {
+ const milestone = {
+ id: 'gid://gitlab/Milestone/52',
+ title: __('Milestone 52'),
+ };
+
+ findSidebarDropdown().vm.$emit('change', milestone);
+ await nextTick();
+
+ expect(findHiddenInput().attributes('value')).toBe(
+ getIdFromGraphQLId(milestone.id).toString(),
+ );
+ });
+ });
+
+ describe('when null milestone is emitted', () => {
+ it('updates the hidden input value to `0`', async () => {
+ const milestone = { id: null };
+
+ findSidebarDropdown().vm.$emit('change', milestone);
+ await nextTick();
+
+ expect(findHiddenInput().attributes('value')).toBe('0');
+ });
+ });
+ });
+});
diff --git a/spec/helpers/projects/pipeline_helper_spec.rb b/spec/helpers/projects/pipeline_helper_spec.rb
index a70544ace1a..0d3466d6ed2 100644
--- a/spec/helpers/projects/pipeline_helper_spec.rb
+++ b/spec/helpers/projects/pipeline_helper_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe Projects::PipelineHelper do
suite_endpoint: project_pipeline_test_path(project, pipeline, suite_name: 'suite', format: :json),
blob_path: project_blob_path(project, pipeline.sha),
has_test_report: pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)),
+ empty_dag_svg_path: match_asset_path('illustrations/empty-state/empty-dag-md.svg'),
empty_state_image_path: match_asset_path('illustrations/empty-state/empty-test-cases-lg.svg'),
artifacts_expired_image_path: match_asset_path('illustrations/pipeline.svg'),
tests_count: pipeline.test_report_summary.total[:count]
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 5bc56f11425..a12cab11293 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -389,6 +389,40 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
model.add_concurrent_index(:users, :foo)
end
+
+ context 'when targeting a partition table' do
+ let(:schema) { 'public' }
+ let(:name) { '_test_partition_01' }
+ let(:identifier) { "#{schema}.#{name}" }
+
+ before do
+ model.execute(<<~SQL)
+ CREATE TABLE public._test_partitioned_table (
+ id serial NOT NULL,
+ partition_id serial NOT NULL,
+ PRIMARY KEY (id, partition_id)
+ ) PARTITION BY LIST(partition_id);
+
+ CREATE TABLE #{identifier} PARTITION OF public._test_partitioned_table
+ FOR VALUES IN (1);
+ SQL
+ end
+
+ context 'when allow_partition is true' do
+ it 'creates the index concurrently' do
+ expect(model).to receive(:add_index).with(:_test_partition_01, :foo, algorithm: :concurrently)
+
+ model.add_concurrent_index(:_test_partition_01, :foo, allow_partition: true)
+ end
+ end
+
+ context 'when allow_partition is not provided' do
+ it 'raises ArgumentError' do
+ expect { model.add_concurrent_index(:_test_partition_01, :foo) }
+ .to raise_error(ArgumentError, /use add_concurrent_partitioned_index/)
+ end
+ end
+ end
end
context 'inside a transaction' do
@@ -2889,4 +2923,36 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
model.add_sequence(:test_table, :test_column, :test_table_id_seq, 1)
end
end
+
+ describe "#partition?" do
+ subject { model.partition?(table_name) }
+
+ let(:table_name) { 'ci_builds_metadata' }
+
+ context "when a partition table exist" do
+ context 'when the view postgres_partitions exists' do
+ it 'calls the view', :aggregate_failures do
+ expect(Gitlab::Database::PostgresPartition).to receive(:partition_exists?).with(table_name).and_call_original
+ expect(subject).to be_truthy
+ end
+ end
+
+ context 'when the view postgres_partitions does not exist' do
+ before do
+ allow(model).to receive(:view_exists?).and_return(false)
+ end
+
+ it 'does not call the view', :aggregate_failures do
+ expect(Gitlab::Database::PostgresPartition).to receive(:legacy_partition_exists?).with(table_name).and_call_original
+ expect(subject).to be_truthy
+ end
+ end
+ end
+
+ context "when a partition table does not exist" do
+ let(:table_name) { 'partition_does_not_exist' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
index 5d02266e8a2..a81c8a5a49c 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/index_helpers_spec.rb
@@ -65,8 +65,11 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
end
def expect_add_concurrent_index_and_call_original(table, column, index)
- expect(migration).to receive(:add_concurrent_index).ordered.with(table, column, { name: index })
- .and_wrap_original { |_, table, column, options| connection.add_index(table, column, **options) }
+ expect(migration).to receive(:add_concurrent_index).ordered.with(table, column, { name: index, allow_partition: true })
+ .and_wrap_original do |_, table, column, options|
+ options.delete(:allow_partition)
+ connection.add_index(table, column, **options)
+ end
end
end
@@ -91,7 +94,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::IndexHelpers do
it 'forwards them to the index helper methods', :aggregate_failures do
expect(migration).to receive(:add_concurrent_index)
- .with(partition1_identifier, column_name, { name: partition1_index, where: 'x > 0', unique: true })
+ .with(partition1_identifier, column_name, { name: partition1_index, where: 'x > 0', unique: true, allow_partition: true })
expect(migration).to receive(:add_index)
.with(table_name, column_name, { name: index_name, where: 'x > 0', unique: true })
diff --git a/spec/lib/gitlab/database/postgres_partition_spec.rb b/spec/lib/gitlab/database/postgres_partition_spec.rb
index 5a44090d5ae..14a4d405621 100644
--- a/spec/lib/gitlab/database/postgres_partition_spec.rb
+++ b/spec/lib/gitlab/database/postgres_partition_spec.rb
@@ -72,4 +72,36 @@ RSpec.describe Gitlab::Database::PostgresPartition, type: :model do
expect(find(identifier).condition).to eq("FOR VALUES FROM ('2020-01-01 00:00:00+00') TO ('2020-02-01 00:00:00+00')")
end
end
+
+ describe '.partition_exists?' do
+ subject { described_class.partition_exists?(table_name) }
+
+ context 'when the partition exists' do
+ let(:table_name) { "ci_builds_metadata" }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the partition does not exist' do
+ let(:table_name) { 'partition_does_not_exist' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '.legacy_partition_exists?' do
+ subject { described_class.legacy_partition_exists?(table_name) }
+
+ context 'when the partition exists' do
+ let(:table_name) { "ci_builds_metadata" }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the partition does not exist' do
+ let(:table_name) { 'partition_does_not_exist' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
index dd4dcca809b..94192a9257c 100644
--- a/spec/lib/gitlab/gon_helper_spec.rb
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -39,6 +39,72 @@ RSpec.describe Gitlab::GonHelper do
helper.add_gon_variables
end
end
+
+ describe 'sentry configuration' do
+ let(:legacy_clientside_dsn) { 'https://xxx@sentry-legacy.example.com/1' }
+ let(:clientside_dsn) { 'https://xxx@sentry.example.com/1' }
+ let(:environment) { 'production' }
+
+ context 'with enable_old_sentry_clientside_integration enabled' do
+ before do
+ stub_feature_flags(
+ enable_old_sentry_clientside_integration: true,
+ enable_new_sentry_clientside_integration: false
+ )
+
+ stub_config(sentry: { enabled: true, clientside_dsn: legacy_clientside_dsn, environment: environment })
+ end
+
+ it 'sets sentry dsn and environment from config' do
+ expect(gon).to receive(:sentry_dsn=).with(legacy_clientside_dsn)
+ expect(gon).to receive(:sentry_environment=).with(environment)
+
+ helper.add_gon_variables
+ end
+ end
+
+ context 'with enable_new_sentry_clientside_integration enabled' do
+ before do
+ stub_feature_flags(
+ enable_old_sentry_clientside_integration: false,
+ enable_new_sentry_clientside_integration: true
+ )
+
+ stub_application_setting(sentry_enabled: true)
+ stub_application_setting(sentry_clientside_dsn: clientside_dsn)
+ stub_application_setting(sentry_environment: environment)
+ end
+
+ it 'sets sentry dsn and environment from application settings' do
+ expect(gon).to receive(:sentry_dsn=).with(clientside_dsn)
+ expect(gon).to receive(:sentry_environment=).with(environment)
+
+ helper.add_gon_variables
+ end
+ end
+
+ context 'with enable_old_sentry_clientside_integration and enable_new_sentry_clientside_integration enabled' do
+ before do
+ stub_feature_flags(
+ enable_old_sentry_clientside_integration: true,
+ enable_new_sentry_clientside_integration: true
+ )
+
+ stub_config(sentry: { enabled: true, clientside_dsn: legacy_clientside_dsn, environment: environment })
+
+ stub_application_setting(sentry_enabled: true)
+ stub_application_setting(sentry_clientside_dsn: clientside_dsn)
+ stub_application_setting(sentry_environment: environment)
+ end
+
+ it 'sets sentry dsn and environment from application settings' do
+ expect(gon).to receive(:sentry_dsn=).with(clientside_dsn)
+ expect(gon).to receive(:sentry_environment=).with(environment)
+
+ helper.add_gon_variables
+ end
+ end
+ end
end
describe '#push_frontend_feature_flag' do
diff --git a/spec/models/alert_management/http_integration_spec.rb b/spec/models/alert_management/http_integration_spec.rb
index f88a66a7c27..b453b3a82e0 100644
--- a/spec/models/alert_management/http_integration_spec.rb
+++ b/spec/models/alert_management/http_integration_spec.rb
@@ -13,6 +13,11 @@ RSpec.describe AlertManagement::HttpIntegration do
it { is_expected.to belong_to(:project) }
end
+ describe 'default values' do
+ it { expect(described_class.new.endpoint_identifier).to be_present }
+ it { expect(described_class.new(endpoint_identifier: 'test').endpoint_identifier).to eq('test') }
+ end
+
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
@@ -124,10 +129,6 @@ RSpec.describe AlertManagement::HttpIntegration do
end
context 'when unsaved' do
- context 'when unassigned' do
- it_behaves_like 'valid token'
- end
-
context 'when assigned' do
include_context 'assign token', 'random_token'
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index b8fe8412d55..77d36cbee62 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -4089,7 +4089,6 @@
- './spec/features/incidents/user_creates_new_incident_spec.rb'
- './spec/features/incidents/user_filters_incidents_by_status_spec.rb'
- './spec/features/incidents/user_searches_incidents_spec.rb'
-- './spec/features/incidents/user_views_incident_spec.rb'
- './spec/features/invites_spec.rb'
- './spec/features/issuables/issuable_list_spec.rb'
- './spec/features/issuables/markdown_references/internal_references_spec.rb'
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
index fa048b76e18..7df4b7635d3 100644
--- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -3,6 +3,7 @@
RSpec.shared_examples 'multiple issue boards' do
context 'authorized user' do
before do
+ stub_feature_flags(apollo_boards: false)
parent.add_maintainer(user)
login_as(user)
@@ -121,6 +122,7 @@ RSpec.shared_examples 'multiple issue boards' do
context 'unauthorized user' do
before do
+ stub_feature_flags(apollo_boards: false)
visit boards_path
wait_for_requests
end
diff --git a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
index fa111ca5811..d749479544d 100644
--- a/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb
@@ -5,6 +5,7 @@ RSpec.shared_examples 'multiple and scoped issue boards' do |route_definition|
context 'multiple issue boards' do
before do
+ stub_feature_flags(apollo_boards: false)
board_parent.add_reporter(user)
stub_licensed_features(multiple_group_issue_boards: true)
end
diff --git a/workhorse/internal/upstream/routes.go b/workhorse/internal/upstream/routes.go
index 08e3ef8c9f1..c47053ad682 100644
--- a/workhorse/internal/upstream/routes.go
+++ b/workhorse/internal/upstream/routes.go
@@ -52,6 +52,7 @@ const (
geoGitProjectPattern = `^/[^-].+\.git/` // Prevent matching routes like /-/push_from_secondary
projectPattern = `^/([^/]+/){1,}[^/]+/`
apiProjectPattern = apiPattern + `v4/projects/[^/]+` // API: Projects can be encoded via group%2Fsubgroup%2Fproject
+ apiGroupPattern = apiPattern + `v4/groups/[^/]+`
apiTopicPattern = apiPattern + `v4/topics`
snippetUploadPattern = `^/uploads/personal_snippet`
userUploadPattern = `^/uploads/user`
@@ -303,6 +304,7 @@ func configureRoutes(u *upstream) {
// we need to declare each routes until we have fixed all the routes on the rails codebase.
// Overall status can be seen at https://gitlab.com/groups/gitlab-org/-/epics/1802#current-status
u.route("POST", apiProjectPattern+`/wikis/attachments\z`, tempfileMultipartProxy),
+ u.route("POST", apiGroupPattern+`/wikis/attachments\z`, tempfileMultipartProxy),
u.route("POST", apiPattern+`graphql\z`, tempfileMultipartProxy),
u.route("POST", apiTopicPattern, tempfileMultipartProxy),
u.route("PUT", apiTopicPattern, tempfileMultipartProxy),
diff --git a/workhorse/upload_test.go b/workhorse/upload_test.go
index c7e5888cec9..22abed25928 100644
--- a/workhorse/upload_test.go
+++ b/workhorse/upload_test.go
@@ -138,6 +138,8 @@ func TestAcceleratedUpload(t *testing.T) {
{"POST", `/api/v4/groups`, false},
{"PUT", `/api/v4/groups/5`, false},
{"PUT", `/api/v4/groups/group%2Fsubgroup`, false},
+ {"POST", `/api/v4/groups/1/wikis/attachments`, false},
+ {"POST", `/api/v4/groups/my%2Fsubgroup/wikis/attachments`, false},
{"POST", `/api/v4/users`, false},
{"PUT", `/api/v4/users/42`, false},
{"PUT", "/api/v4/projects/9001/packages/nuget/v1/files", true},