Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/preflight.gitlab-ci.yml28
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml42
-rw-r--r--.rubocop_todo/style/mutable_constant.yml10
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue51
-rw-r--r--app/assets/javascripts/issues/list/constants.js6
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_grid.vue1
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue17
-rw-r--r--app/controllers/groups_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/graphql/mutations/environments/create.rb7
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/work_items/widgets/base.rb8
-rw-r--r--app/services/environments/create_service.rb21
-rw-r--r--app/services/issuable/callbacks/base.rb1
-rw-r--r--app/services/issuable_base_service.rb13
-rw-r--r--app/services/work_items/callbacks/award_emoji.rb33
-rw-r--r--app/services/work_items/callbacks/base.rb13
-rw-r--r--app/services/work_items/widgets/award_emoji_service/update_service.rb33
-rw-r--r--config/feature_flags/development/issues_grid_view.yml8
-rw-r--r--doc/.vale/gitlab/Uppercase.yml1
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md6
-rw-r--r--doc/api/environments.md2
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/development/database/adding_database_indexes.md2
-rw-r--r--doc/development/service_ping/implement.md2
-rw-r--r--doc/development/service_ping/metrics_dictionary.md20
-rw-r--r--doc/development/service_ping/review_guidelines.md2
-rw-r--r--doc/development/work_items_widgets.md12
-rw-r--r--doc/operations/error_tracking.md157
-rw-r--r--doc/operations/img/Monitor-detail_errors.pngbin906805 -> 0 bytes
-rw-r--r--doc/operations/img/Monitor-list_errors.pngbin990019 -> 0 bytes
-rw-r--r--doc/operations/img/Monitor_tab-post-enable.pngbin193554 -> 0 bytes
-rw-r--r--doc/operations/img/Monitor_tab-pre-enable.pngbin188656 -> 0 bytes
-rw-r--r--doc/operations/img/detail_errors_v16_0.pngbin0 -> 132852 bytes
-rw-r--r--doc/operations/img/list_errors_v16_0.pngbin0 -> 108829 bytes
-rw-r--r--doc/user/project/pages/index.md14
-rw-r--r--doc/user/project/pages/introduction.md19
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb18
-rw-r--r--locale/gitlab.pot3
-rw-r--r--rubocop/cop/background_migration/feature_category.rb2
-rw-r--r--rubocop/cop/filename_length.rb4
-rw-r--r--rubocop/cop/gitlab/event_store_subscriber.rb4
-rw-r--r--rubocop/cop/graphql/descriptions.rb9
-rw-r--r--rubocop/cop/graphql/enum_names.rb6
-rw-r--r--rubocop/cop/migration/prevent_index_creation.rb2
-rw-r--r--rubocop/cop/migration/versioned_migration_class.rb4
-rw-r--r--rubocop/cop/migration/with_lock_retries_disallowed_method.rb2
-rw-r--r--rubocop/cop/scalability/idempotent_worker.rb2
-rw-r--r--rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb4
-rw-r--r--spec/frontend/issues/list/components/issues_list_app_spec.js39
-rw-r--r--spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js16
-rw-r--r--spec/graphql/mutations/environments/create_spec.rb23
-rw-r--r--spec/lib/gitlab/import/errors_spec.rb1
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb8
-rw-r--r--spec/services/environments/create_service_spec.rb39
-rw-r--r--spec/services/work_items/callbacks/award_emoji_spec.rb (renamed from spec/services/work_items/widgets/award_emoji_service/update_service_spec.rb)11
59 files changed, 508 insertions, 231 deletions
diff --git a/.gitlab/ci/preflight.gitlab-ci.yml b/.gitlab/ci/preflight.gitlab-ci.yml
index e477466e5f3..968402b2ea5 100644
--- a/.gitlab/ci/preflight.gitlab-ci.yml
+++ b/.gitlab/ci/preflight.gitlab-ci.yml
@@ -16,20 +16,23 @@
- !reference [.default-before_script, before_script]
- cd qa && bundle install
-rails-production-server-boot:
+.rails-production-server-boot:
extends:
- .preflight-job-base
- .default-before_script
- .production
- .ruby-cache
- - .setup:rules:rails-production-server-boot
+ - .preflight:rules:rails-production-server-boot
- .use-pg13
variables:
BUNDLE_WITHOUT: "development:test"
BUNDLE_WITH: "production"
- needs: []
+
+# Test the puma configuration present in `config/puma.rb.example`
+rails-production-server-boot-puma-example:
+ extends:
+ - .rails-production-server-boot
script:
- - source scripts/utils.sh
- cp config/puma.rb.example config/puma.rb
- sed --in-place "s:/home/git/gitlab:${PWD}:" config/puma.rb
- echo 'bind "tcp://127.0.0.1:3000"' >> config/puma.rb
@@ -38,17 +41,30 @@ rails-production-server-boot:
- retry_times_sleep 10 5 "curl http://127.0.0.1:3000"
- kill $(jobs -p)
+# Test the puma configuration present in
+# https://gitlab.com/gitlab-org/build/CNG/-/raw/master/gitlab-webservice/configuration/puma.rb
+rails-production-server-boot-puma-cng:
+ extends:
+ - .rails-production-server-boot
+ script:
+ - curl --silent https://gitlab.com/gitlab-org/build/CNG/-/raw/master/gitlab-webservice/configuration/puma.rb > config/puma.rb
+ - sed --in-place "s:/srv/gitlab:${PWD}:" config/puma.rb
+ - bundle exec puma --environment production --config config/puma.rb &
+ - sleep 40 # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114124#note_1309506358
+ - retry_times_sleep 10 5 "curl http://127.0.0.1:8080"
+ - kill $(jobs -p)
+
no-ee-check:
extends:
- .preflight-job-base
- - .setup:rules:no-ee-check
+ - .preflight:rules:no-ee-check
script:
- scripts/no-dir-check ee
no-jh-check:
extends:
- .preflight-job-base
- - .setup:rules:no-jh-check
+ - .preflight:rules:no-jh-check
script:
- scripts/no-dir-check jh
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 9390d89212c..a37d0e2be37 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -2477,25 +2477,6 @@
- <<: *if-default-refs
changes: *code-backstage-patterns
-.setup:rules:rails-production-server-boot:
- rules:
- - <<: *if-default-refs
- changes: *code-patterns
-
-.setup:rules:no-ee-check:
- rules:
- - <<: *if-not-foss
- when: never
- - <<: *if-default-refs
- changes: *code-backstage-patterns
-
-.setup:rules:no-jh-check:
- rules:
- - <<: *if-jh
- when: never
- - <<: *if-default-refs
- changes: *code-backstage-patterns
-
.setup:rules:verify-ruby-3.0:
rules:
- <<: *if-merge-request-labels-run-in-ruby2
@@ -2527,6 +2508,29 @@
- "scripts/rspec_helpers.sh"
#######################
+# Preflight rules #
+#######################
+
+.preflight:rules:rails-production-server-boot:
+ rules:
+ - <<: *if-default-refs
+ changes: *code-patterns
+
+.preflight:rules:no-ee-check:
+ rules:
+ - <<: *if-not-foss
+ when: never
+ - <<: *if-default-refs
+ changes: *code-backstage-patterns
+
+.preflight:rules:no-jh-check:
+ rules:
+ - <<: *if-jh
+ when: never
+ - <<: *if-default-refs
+ changes: *code-backstage-patterns
+
+#######################
# Test metadata rules #
#######################
.test-metadata:rules:retrieve-tests-metadata:
diff --git a/.rubocop_todo/style/mutable_constant.yml b/.rubocop_todo/style/mutable_constant.yml
index 8e6a0a2335b..dd7fd259b2f 100644
--- a/.rubocop_todo/style/mutable_constant.yml
+++ b/.rubocop_todo/style/mutable_constant.yml
@@ -40,16 +40,6 @@ Style/MutableConstant:
- 'lib/gitlab/sidekiq_signals.rb'
- 'lib/gitlab/web_hooks/recursion_detection/uuid.rb'
- 'lib/tasks/gitlab/backup.rake'
- - 'rubocop/cop/background_migration/feature_category.rb'
- - 'rubocop/cop/filename_length.rb'
- - 'rubocop/cop/gitlab/event_store_subscriber.rb'
- - 'rubocop/cop/graphql/descriptions.rb'
- - 'rubocop/cop/graphql/enum_names.rb'
- - 'rubocop/cop/migration/prevent_index_creation.rb'
- - 'rubocop/cop/migration/versioned_migration_class.rb'
- - 'rubocop/cop/migration/with_lock_retries_disallowed_method.rb'
- - 'rubocop/cop/scalability/idempotent_worker.rb'
- - 'rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb'
- 'scripts/lib/glfm/constants.rb'
- 'scripts/lint-docs-blueprints.rb'
- 'scripts/perf/gc/collect_gc_stats.rb'
diff --git a/Gemfile b/Gemfile
index b50baa992a1..927a4e4958b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -287,7 +287,7 @@ gem 'circuitbox', '2.0.0'
# Sanitize user input
gem 'sanitize', '~> 6.0'
-gem 'babosa', '~> 1.0.4'
+gem 'babosa', '~> 2.0'
# Sanitizes SVG input
gem 'loofah', '~> 2.21.1'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index f6f50484232..cc3ed31f5af 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -46,7 +46,7 @@
{"name":"axiom-types","version":"0.1.1","platform":"ruby","checksum":"c1ff113f3de516fa195b2db7e0a9a95fd1b08475a502ff660d04507a09980383"},
{"name":"azure-storage-blob","version":"2.0.3","platform":"ruby","checksum":"61b76118843c91776bd24bee22c74adafeb7c4bb3a858a325047dae3b59d0363"},
{"name":"azure-storage-common","version":"2.0.4","platform":"ruby","checksum":"608f4daab0e06b583b73dcffd3246ea39e78056de31630286b0cf97af7d6956b"},
-{"name":"babosa","version":"1.0.4","platform":"ruby","checksum":"18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99"},
+{"name":"babosa","version":"2.0.0","platform":"ruby","checksum":"a6218db8a4dc8fd99260dde8bc3d5fa1a0c52178196e236ebb31e41fbdcdb8a6"},
{"name":"backport","version":"1.2.0","platform":"ruby","checksum":"912c7dfdd9ee4625d013ddfccb6205c3f92da69a8990f65c440e40f5b2fc7f75"},
{"name":"base32","version":"0.3.2","platform":"ruby","checksum":"532e9b19c5dd1fce281df67fc93a803ebd5d26426a93f6dda6612769bc46fe2c"},
{"name":"batch-loader","version":"2.0.1","platform":"ruby","checksum":"93f711df78d316ee0440a7a45daba4f5418d0ee2b5f58f60c9ea038424e7a89d"},
diff --git a/Gemfile.lock b/Gemfile.lock
index fe45768cdf9..3380ec995f6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -236,7 +236,7 @@ GEM
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8)
- babosa (1.0.4)
+ babosa (2.0.0)
backport (1.2.0)
base32 (0.3.2)
batch-loader (2.0.1)
@@ -1672,7 +1672,7 @@ DEPENDENCIES
aws-sdk-core (~> 3.173.0)
aws-sdk-s3 (~> 1.122.0)
axe-core-rspec
- babosa (~> 1.0.4)
+ babosa (~> 2.0)
base32 (~> 0.3.0)
batch-loader (~> 2.0.1)
bcrypt (~> 3.1, >= 3.1.14)
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 5fb83dfd1ab..2cb966cf1fd 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -1,6 +1,7 @@
<script>
import {
GlButton,
+ GlButtonGroup,
GlFilteredSearchToken,
GlTooltipDirective,
GlDropdown,
@@ -14,6 +15,7 @@ import IssueCardStatistics from 'ee_else_ce/issues/list/components/issue_card_st
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { createAlert, VARIANT_INFO } from '~/alert';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
@@ -78,6 +80,9 @@ import {
RELATIVE_POSITION_ASC,
UPDATED_DESC,
urlSortParams,
+ ISSUES_VIEW_TYPE_KEY,
+ ISSUES_LIST_VIEW_KEY,
+ ISSUES_GRID_VIEW_KEY,
} from '../constants';
import eventHub from '../eventhub';
import reorderIssuesMutation from '../queries/reorder_issues.mutation.graphql';
@@ -116,11 +121,15 @@ const CrmOrganizationToken = () =>
export default {
i18n,
issuableListTabs,
+ ISSUES_VIEW_TYPE_KEY,
+ ISSUES_GRID_VIEW_KEY,
+ ISSUES_LIST_VIEW_KEY,
components: {
CsvImportExportButtons,
EmptyStateWithAnyIssues,
EmptyStateWithoutAnyIssues,
GlButton,
+ GlButtonGroup,
GlDropdown,
GlDropdownDivider,
GlDropdownItem,
@@ -129,6 +138,7 @@ export default {
IssueCardStatistics,
IssueCardTimeInfo,
NewResourceDropdown,
+ LocalStorageSync,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -194,6 +204,7 @@ export default {
sortKey: CREATED_DESC,
state: STATUS_OPEN,
pageSize: DEFAULT_PAGE_SIZE,
+ viewType: ISSUES_LIST_VIEW_KEY,
};
},
apollo: {
@@ -504,6 +515,12 @@ export default {
})
);
},
+ gridViewFeatureEnabled() {
+ return Boolean(this.glFeatures?.issuesGridView);
+ },
+ isGridView() {
+ return this.viewType === ISSUES_GRID_VIEW_KEY;
+ },
},
watch: {
$route(newValue, oldValue) {
@@ -764,6 +781,15 @@ export default {
this.sortKey = sortKey;
this.state = state || STATUS_OPEN;
},
+ switchViewType(type) {
+ // Filter the wrong data from localStorage
+ if (type === ISSUES_GRID_VIEW_KEY) {
+ this.viewType = ISSUES_GRID_VIEW_KEY;
+ return;
+ }
+ // The default view is list view
+ this.viewType = ISSUES_LIST_VIEW_KEY;
+ },
},
};
</script>
@@ -798,6 +824,7 @@ export default {
:has-next-page="pageInfo.hasNextPage"
:has-previous-page="pageInfo.hasPreviousPage"
:show-filtered-search-friendly-text="hasOrFeature"
+ :is-grid-view="isGridView"
show-work-item-type-icon
@click-tab="handleClickTab"
@dismiss-alert="handleDismissAlert"
@@ -810,6 +837,30 @@ export default {
@page-size-change="handlePageSizeChange"
>
<template #nav-actions>
+ <local-storage-sync
+ v-if="gridViewFeatureEnabled"
+ :value="viewType"
+ :storage-key="$options.ISSUES_VIEW_TYPE_KEY"
+ @input="switchViewType"
+ >
+ <gl-button-group>
+ <gl-button
+ :variant="isGridView ? 'default' : 'confirm'"
+ data-testid="list-view-type"
+ @click="switchViewType($options.ISSUES_LIST_VIEW_KEY)"
+ >
+ {{ $options.i18n.listLabel }}
+ </gl-button>
+ <gl-button
+ :variant="isGridView ? 'confirm' : 'default'"
+ data-testid="grid-view-type"
+ @click="switchViewType($options.ISSUES_GRID_VIEW_KEY)"
+ >
+ {{ $options.i18n.gridLabel }}
+ </gl-button>
+ </gl-button-group>
+ </local-storage-sync>
+
<gl-button
v-if="canBulkUpdate"
:disabled="isBulkEditButtonDisabled"
diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js
index 56d3a57457b..1a3d97277c7 100644
--- a/app/assets/javascripts/issues/list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -75,6 +75,10 @@ export const NORMAL_FILTER = 'normalFilter';
export const SPECIAL_FILTER = 'specialFilter';
export const ALTERNATIVE_FILTER = 'alternativeFilter';
+export const ISSUES_VIEW_TYPE_KEY = 'issuesViewType';
+export const ISSUES_LIST_VIEW_KEY = 'List';
+export const ISSUES_GRID_VIEW_KEY = 'Grid';
+
export const i18n = {
actionsLabel: __('Actions'),
calendarLabel: __('Subscribe to calendar'),
@@ -116,6 +120,8 @@ export const i18n = {
upvotes: __('Upvotes'),
titles: __('Titles'),
descriptions: __('Descriptions'),
+ listLabel: __('List'),
+ gridLabel: __('Grid'),
};
export const urlSortParams = {
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_grid.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_grid.vue
new file mode 100644
index 00000000000..0ada1d8a6ae
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_grid.vue
@@ -0,0 +1 @@
+<template><div></div></template>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
index 95108933a0b..4023337a1cb 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue
@@ -6,12 +6,14 @@ import PageSizeSelector from '~/vue_shared/components/page_size_selector.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import issuableEventHub from '~/issues/list/eventhub';
import { DEFAULT_SKELETON_COUNT, PAGE_SIZE_STORAGE_KEY } from '../constants';
import IssuableBulkEditSidebar from './issuable_bulk_edit_sidebar.vue';
import IssuableItem from './issuable_item.vue';
import IssuableTabs from './issuable_tabs.vue';
+import IssuableGrid from './issuable_grid.vue';
const VueDraggable = () => import('vuedraggable');
@@ -30,12 +32,14 @@ export default {
IssuableTabs,
FilteredSearchBar,
IssuableItem,
+ IssuableGrid,
IssuableBulkEditSidebar,
GlPagination,
VueDraggable,
PageSizeSelector,
LocalStorageSync,
},
+ mixins: [glFeatureFlagMixin()],
props: {
namespace: {
type: String,
@@ -194,6 +198,11 @@ export default {
required: false,
default: false,
},
+ isGridView: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -229,6 +238,9 @@ export default {
issuablesWrapper() {
return this.isManualOrdering ? VueDraggable : 'ul';
},
+ gridViewFeatureEnabled() {
+ return Boolean(this.glFeatures?.issuesGridView);
+ },
},
watch: {
issuables(list) {
@@ -342,7 +354,7 @@ export default {
<template v-else>
<component
:is="issuablesWrapper"
- v-if="issuables.length > 0"
+ v-if="issuables.length > 0 && !isGridView"
class="content-list issuable-list issues-list"
:class="{ 'manual-ordering': isManualOrdering }"
v-bind="$options.vueDraggableAttributes"
@@ -382,6 +394,9 @@ export default {
</template>
</issuable-item>
</component>
+ <div v-else-if="issuables.length > 0 && isGridView">
+ <issuable-grid />
+ </div>
<slot v-else name="empty-state"></slot>
</template>
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index d2f65104d86..182d7aee060 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -36,6 +36,7 @@ class GroupsController < Groups::ApplicationController
push_frontend_feature_flag(:or_issuable_queries, group)
push_frontend_feature_flag(:frontend_caching, group)
push_force_frontend_feature_flag(:work_items, group.work_items_feature_flag_enabled?)
+ push_frontend_feature_flag(:issues_grid_view)
end
before_action only: :merge_requests do
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 642d5943854..ac12dcb6f49 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -50,6 +50,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_force_frontend_feature_flag(:content_editor_on_issues, project&.content_editor_on_issues_feature_flag_enabled?)
push_frontend_feature_flag(:service_desk_new_note_email_native_attachments, project)
push_frontend_feature_flag(:saved_replies, current_user)
+ push_frontend_feature_flag(:issues_grid_view)
end
before_action only: [:index, :show] do
diff --git a/app/graphql/mutations/environments/create.rb b/app/graphql/mutations/environments/create.rb
index a6386e3c0b1..271585eb06c 100644
--- a/app/graphql/mutations/environments/create.rb
+++ b/app/graphql/mutations/environments/create.rb
@@ -30,6 +30,11 @@ module Mutations
required: false,
description: 'Tier of the environment.'
+ argument :cluster_agent_id,
+ ::Types::GlobalIDType[::Clusters::Agent],
+ required: false,
+ description: 'Cluster agent of the environment.'
+
field :environment,
Types::EnvironmentType,
null: true,
@@ -38,6 +43,8 @@ module Mutations
def resolve(project_path:, **kwargs)
project = authorized_find!(project_path)
+ kwargs[:cluster_agent] = GitlabSchema.find_by_gid(kwargs.delete(:cluster_agent_id))&.sync
+
response = ::Environments::CreateService.new(project, current_user, kwargs).execute
if response.success?
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 1d0ce594f63..e830594af11 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -4,7 +4,7 @@ module Awardable
extend ActiveSupport::Concern
included do
- has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, inverse_of: :awardable, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
if self < Participable
# By default we always load award_emoji user association
diff --git a/app/models/work_items/widgets/base.rb b/app/models/work_items/widgets/base.rb
index b54b84f1e1b..a8b1b3f9a59 100644
--- a/app/models/work_items/widgets/base.rb
+++ b/app/models/work_items/widgets/base.rb
@@ -16,9 +16,13 @@ module WorkItems
end
def self.callback_class
- Issuable::Callbacks.const_get(name.demodulize, false)
+ WorkItems::Callbacks.const_get(name.demodulize, false)
rescue NameError
- nil
+ begin
+ Issuable::Callbacks.const_get(name.demodulize, false)
+ rescue NameError
+ nil
+ end
end
def type
diff --git a/app/services/environments/create_service.rb b/app/services/environments/create_service.rb
index 81fedf4b6d8..760c8a6e306 100644
--- a/app/services/environments/create_service.rb
+++ b/app/services/environments/create_service.rb
@@ -2,6 +2,8 @@
module Environments
class CreateService < BaseService
+ ALLOWED_ATTRIBUTES = %i[name external_url tier cluster_agent].freeze
+
def execute
unless can?(current_user, :create_environment, project)
return ServiceResponse.error(
@@ -10,7 +12,13 @@ module Environments
)
end
- environment = project.environments.create(**params)
+ if unauthorized_cluster_agent?
+ return ServiceResponse.error(
+ message: _('Unauthorized to access the cluster agent in this project'),
+ payload: { environment: nil })
+ end
+
+ environment = project.environments.create(**params.slice(*ALLOWED_ATTRIBUTES))
if environment.persisted?
ServiceResponse.success(payload: { environment: environment })
@@ -21,5 +29,16 @@ module Environments
)
end
end
+
+ private
+
+ def unauthorized_cluster_agent?
+ return false unless params[:cluster_agent]
+
+ ::Clusters::Agents::Authorizations::UserAccess::Finder
+ .new(current_user, agent: params[:cluster_agent], project: project)
+ .execute
+ .empty?
+ end
end
end
diff --git a/app/services/issuable/callbacks/base.rb b/app/services/issuable/callbacks/base.rb
index 3fabce2c949..368dd76c16c 100644
--- a/app/services/issuable/callbacks/base.rb
+++ b/app/services/issuable/callbacks/base.rb
@@ -12,6 +12,7 @@ module Issuable
end
def after_initialize; end
+ def before_update; end
def after_update_commit; end
def after_save_commit; end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e9312bd6b31..3b007d4dba7 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -314,16 +314,19 @@ class IssuableBaseService < ::BaseContainerService
before_update(issuable)
- # Do not touch when saving the issuable if only changes position within a list. We should call
- # this method at this point to capture all possible changes.
- should_touch = update_timestamp?(issuable)
-
- issuable.updated_by = current_user if should_touch
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
issuable_saved = issuable.with_transaction_returning_status do
+ @callbacks.each(&:before_update)
+
+ # Do not touch when saving the issuable if only changes position within a list. We should call
+ # this method at this point to capture all possible changes.
+ should_touch = update_timestamp?(issuable)
+
+ issuable.updated_by = current_user if should_touch
+
transaction_update(issuable, { save_with_touch: should_touch })
end
diff --git a/app/services/work_items/callbacks/award_emoji.rb b/app/services/work_items/callbacks/award_emoji.rb
new file mode 100644
index 00000000000..6344813d4b9
--- /dev/null
+++ b/app/services/work_items/callbacks/award_emoji.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Callbacks
+ class AwardEmoji < Base
+ def before_update
+ return unless params.present? && params.key?(:name) && params.key?(:action)
+ return unless has_permission?(:award_emoji)
+
+ execute_emoji_service(params[:action], params[:name])
+ end
+
+ private
+
+ def execute_emoji_service(action, name)
+ class_name = {
+ add: ::AwardEmojis::AddService,
+ remove: ::AwardEmojis::DestroyService
+ }
+
+ raise_error(invalid_action_error(action)) unless class_name.key?(action)
+
+ result = class_name[action].new(work_item, name, current_user).execute
+
+ raise_error(result[:message]) if result[:status] == :error
+ end
+
+ def invalid_action_error(key)
+ format(_("%{key} is not a valid action."), key: key)
+ end
+ end
+ end
+end
diff --git a/app/services/work_items/callbacks/base.rb b/app/services/work_items/callbacks/base.rb
new file mode 100644
index 00000000000..c91e2b37d10
--- /dev/null
+++ b/app/services/work_items/callbacks/base.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module WorkItems
+ module Callbacks
+ class Base < Issuable::Callbacks::Base
+ alias_method :work_item, :issuable
+
+ def raise_error(message)
+ raise ::WorkItems::Widgets::BaseService::WidgetError, message
+ end
+ end
+ end
+end
diff --git a/app/services/work_items/widgets/award_emoji_service/update_service.rb b/app/services/work_items/widgets/award_emoji_service/update_service.rb
deleted file mode 100644
index 7c58c0c9af9..00000000000
--- a/app/services/work_items/widgets/award_emoji_service/update_service.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-module WorkItems
- module Widgets
- module AwardEmojiService
- class UpdateService < WorkItems::Widgets::BaseService
- def before_update_in_transaction(params:)
- return unless params.present? && params.key?(:name) && params.key?(:action)
- return unless has_permission?(:award_emoji)
-
- service_response!(service_result(params[:action], params[:name]))
- end
-
- private
-
- def service_result(action, name)
- class_name = {
- add: ::AwardEmojis::AddService,
- remove: ::AwardEmojis::DestroyService
- }
-
- return invalid_action_error(action) unless class_name.key?(action)
-
- class_name[action].new(work_item, name, current_user).execute
- end
-
- def invalid_action_error(key)
- error(format(_("%{key} is not a valid action."), key: key))
- end
- end
- end
- end
-end
diff --git a/config/feature_flags/development/issues_grid_view.yml b/config/feature_flags/development/issues_grid_view.yml
new file mode 100644
index 00000000000..310b725d6c8
--- /dev/null
+++ b/config/feature_flags/development/issues_grid_view.yml
@@ -0,0 +1,8 @@
+---
+name: issues_grid_view
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113012
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/393152
+milestone: '16.0'
+type: development
+group: group::project management
+default_enabled: false \ No newline at end of file
diff --git a/doc/.vale/gitlab/Uppercase.yml b/doc/.vale/gitlab/Uppercase.yml
index 1948842d026..4730184b950 100644
--- a/doc/.vale/gitlab/Uppercase.yml
+++ b/doc/.vale/gitlab/Uppercase.yml
@@ -57,6 +57,7 @@ exceptions:
- DHCP
- DML
- DNS
+ - DSN
- DOM
- DORA
- DSA
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 3523721c342..f8cab0c605e 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -36,7 +36,7 @@ The following metrics are available:
| Metric | Type | Since | Description | Labels |
| :--------------------------------------------------------------- | :---------- | ------: | :-------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------- |
-| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action`, `store` |
+| `gitlab_cache_misses_total` | Counter | 10.2 | Cache read miss | `controller`, `action` |
| `gitlab_cache_operation_duration_seconds` | Histogram | 10.2 | Cache access time | `operation`, `store` |
| `gitlab_cache_operations_total` | Counter | 12.2 | Cache operations by controller or action | `controller`, `action`, `operation`, `store` |
| `gitlab_cache_read_multikey_count` | Histogram | 15.7 | Count of keys in multi-key cache read operations | `controller`, `action`, `store` |
@@ -63,8 +63,8 @@ The following metrics are available:
| `gitlab_transaction_cache_<key>_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (per key) | |
| `gitlab_transaction_cache_count_total` | Counter | 10.2 | Counter for total Rails cache calls (aggregate) | |
| `gitlab_transaction_cache_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (aggregate) | |
-| `gitlab_transaction_cache_read_hit_count_total` | Counter | 10.2 | Counter for cache hits for Rails cache calls | `controller`, `action`, `store` |
-| `gitlab_transaction_cache_read_miss_count_total` | Counter | 10.2 | Counter for cache misses for Rails cache calls | `controller`, `action`, `store` |
+| `gitlab_transaction_cache_read_hit_count_total` | Counter | 10.2 | Counter for cache hits for Rails cache calls | `controller`, `action` |
+| `gitlab_transaction_cache_read_miss_count_total` | Counter | 10.2 | Counter for cache misses for Rails cache calls | `controller`, `action` |
| `gitlab_transaction_duration_seconds` | Histogram | 10.2 | Duration for successful requests (`gitlab_transaction_*` metrics) | `controller`, `action` |
| `gitlab_transaction_event_build_found_total` | Counter | 9.4 | Counter for build found for API /jobs/request | |
| `gitlab_transaction_event_build_invalid_total` | Counter | 9.4 | Counter for build invalid due to concurrency conflict for API /jobs/request | |
diff --git a/doc/api/environments.md b/doc/api/environments.md
index 3cbb6076300..501fec70dd6 100644
--- a/doc/api/environments.md
+++ b/doc/api/environments.md
@@ -299,7 +299,7 @@ PUT /projects/:id/environments/:environments_id
| `tier` | string | no | The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`. |
```shell
-curl --request PUT --data "name=staging&external_url=https://staging.gitlab.example.com" \
+curl --request PUT --data "external_url=https://staging.gitlab.example.com" \
--header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/environments/1"
```
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 39ee028faa0..c27075a9a53 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -3077,6 +3077,7 @@ Input type: `EnvironmentCreateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationenvironmentcreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationenvironmentcreateclusteragentid"></a>`clusterAgentId` | [`ClustersAgentID`](#clustersagentid) | Cluster agent of the environment. |
| <a id="mutationenvironmentcreateexternalurl"></a>`externalUrl` | [`String`](#string) | External URL of the environment. |
| <a id="mutationenvironmentcreatename"></a>`name` | [`String!`](#string) | Name of the environment. |
| <a id="mutationenvironmentcreateprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
diff --git a/doc/development/database/adding_database_indexes.md b/doc/development/database/adding_database_indexes.md
index 7b29b1b14de..23a12413975 100644
--- a/doc/development/database/adding_database_indexes.md
+++ b/doc/development/database/adding_database_indexes.md
@@ -429,7 +429,7 @@ Use the asynchronous index helpers on your local environment to test changes for
For very large tables, index destruction can be a challenge to manage.
While `remove_concurrent_index` removes indexes in a way that does not block
ordinary traffic, it can still be problematic if index destruction runs for
-during `autovacuum`. Necessary database operations like `autovacuum` cannot run, and
+many hours. Necessary database operations like `autovacuum` cannot run, and
the deployment process on GitLab.com is blocked while waiting for index
destruction to finish.
diff --git a/doc/development/service_ping/implement.md b/doc/development/service_ping/implement.md
index 2cabd6c40e1..0677e8febf3 100644
--- a/doc/development/service_ping/implement.md
+++ b/doc/development/service_ping/implement.md
@@ -339,8 +339,6 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd/) and [P
Name format for Redis HLL events `{hll_counters}_<name>`
- [See Metric name](metrics_dictionary.md#metric-name) for a complete guide on metric naming suggestion.
-
Example names: `users_creating_epics`, `users_triggering_security_scans`.
- `aggregation`: may be set to a `:daily` or `:weekly` key. Defines how counting data is stored in Redis.
diff --git a/doc/development/service_ping/metrics_dictionary.md b/doc/development/service_ping/metrics_dictionary.md
index debe1cca37e..d53400276d0 100644
--- a/doc/development/service_ping/metrics_dictionary.md
+++ b/doc/development/service_ping/metrics_dictionary.md
@@ -32,7 +32,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields:
| Field | Required | Additional information |
|---------------------|----------|----------------------------------------------------------------|
| `key_path` | yes | JSON key path for the metric, location in Service Ping payload. |
-| `name` | no | Metric name suggestion. Can replace the last part of `key_path`. |
+| `name` (deprecated) | no | Metric name suggestion. Does not have any impact on the Service Ping payload, only serves as documentation. |
| `description` | yes | |
| `product_section` | yes | The [section](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/master/data/sections.yml). |
| `product_stage` | yes | The [stage](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) for the metric. |
@@ -71,7 +71,11 @@ NOTE:
We can't control what the metric's `key_path` is, because some of them are generated dynamically in `usage_data.rb`.
For example, see [Redis HLL metrics](implement.md#redis-hll-counters).
-### Metric name
+### Metric name (deprecated)
+
+WARNING:
+This feature was deprecated in GitLab 16.1
+and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/411602) in 16.2.
To improve metric discoverability by a wider audience, each metric with
instrumentation added at an appointed `key_path` receives a `name` attribute
@@ -86,7 +90,11 @@ Metric name suggestions can contain two types of elements:
For a metric name to be valid, it must not include any prompt, and fixed suggestions
must not be changed.
-#### Generate a metric name suggestion
+#### Generate a metric name suggestion (deprecated)
+
+WARNING:
+This feature was deprecated in GitLab 16.1
+and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/411602) in 16.2.
The metric YAML generator can suggest a metric name for you.
To generate a metric name suggestion, first instrument the metric at the provided `key_path`.
@@ -149,7 +157,11 @@ We use the following categories to classify a metric:
An aggregate metric is a metric that is the sum of two or more child metrics. Service Ping uses the data category of
the aggregate metric to determine whether or not the data is included in the reported Service Ping payload.
-### Metric name suggestion examples
+### Metric name suggestion examples (deprecated)
+
+WARNING:
+This feature was deprecated in GitLab 16.1
+and is planned for [removal](https://gitlab.com/gitlab-org/gitlab/-/issues/411602) in 16.2.
#### Metric with `data_source: database`
diff --git a/doc/development/service_ping/review_guidelines.md b/doc/development/service_ping/review_guidelines.md
index feb85a491ad..71c16820e23 100644
--- a/doc/development/service_ping/review_guidelines.md
+++ b/doc/development/service_ping/review_guidelines.md
@@ -53,8 +53,6 @@ are regular backend changes.
- Perform a first-pass review on the merge request and suggest improvements to the author.
- Check the [metrics location](metrics_dictionary.md#metric-key_path) in
the Service Ping JSON payload.
-- Suggest that the author checks the [naming suggestion](metrics_dictionary.md#generate-a-metric-name-suggestion) while
- generating the metric's YAML definition.
- Add the `~database` label and ask for a [database review](../database_review.md) for
metrics that are based on Database.
- Add `~Data Warehouse::Impact Check` for any database metric that has a query change. Changes in queries can affect [data operations](https://about.gitlab.com/handbook/business-technology/data-team/how-we-work/triage/#gitlabcom-db-structure-changes).
diff --git a/doc/development/work_items_widgets.md b/doc/development/work_items_widgets.md
index bafceccdafe..6c453f49db1 100644
--- a/doc/development/work_items_widgets.md
+++ b/doc/development/work_items_widgets.md
@@ -146,18 +146,24 @@ You can update widgets using custom fine-grained mutations (for example, `WorkIt
### Widget callbacks
When updating the widget together with the work item's mutation, backend code should be implemented using
-callback classes that inherit from `Issuable::Callbacks::Base`. These classes have callback methods
+callback classes that inherit from `WorkItems::Callbacks::Base`. These classes have callback methods
that are named similar to ActiveRecord callbacks and behave similarly.
-Callback classes with the same name as the widget are automatically used. For example, `Issuable::Callbacks::Milestone`
-is called when the work item has the milestone widget. To use a different class, you can override the `callback_class`
+Callback classes with the same name as the widget are automatically used. For example, `WorkItems::Callbacks::AwardEmoji`
+is called when the work item has the `AwardEmoji` widget. To use a different class, you can override the `callback_class`
class method.
+When a callback class is also used for other issuables like merge requests or epics, define the class under `Issuable::Callbacks`
+and add the class to the list in `IssuableBaseService#available_callbacks`. These are executed for both work item updates and
+legacy issue, merge request, or epic updates.
+
#### Available callbacks
- `after_initialize` is called after the work item is initialized by the `BuildService` and before
the work item is saved by the `CreateService` and `UpdateService`. This callback runs outside the
creation or update database transaction.
+- `before_update` is called before the work item is saved by the `UpdateService`. This callback runs
+ within the update database transaction.
- `after_update_commit` is called after the DB update transaction is committed by the `UpdateService`.
- `after_save_commit` is called after the creation or DB update transaction is committed by the
`CreateService` or `UpdateService`.
diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md
index d38210dab8d..224f04ad24f 100644
--- a/doc/operations/error_tracking.md
+++ b/doc/operations/error_tracking.md
@@ -26,7 +26,7 @@ For error tracking to work, you need:
Whatever backend you choose, the [error tracking UI](#error-tracking-list)
is the same.
-## Integrated error tracking
+## Integrated error tracking **(FREE SAAS)**
This guide provides you with basics of setting up error tracking for your project, using examples from different languages.
@@ -42,64 +42,59 @@ According to the Sentry [data model](https://develop.sentry.dev/sdk/envelopes/#d
- [User feedback](https://develop.sentry.dev/sdk/envelopes/#user-feedback) (also known as user report)
- [Client report](https://develop.sentry.dev/sdk/client-reports/)
-### Enable error tracking for your project
+### Enable error tracking for a project
Regardless of the programming language you use, you first need to enable error tracking for your GitLab project.
-The `gitlab.com` instance is used in this guide.
+The `GitLab.com` instance is used in this guide.
-> This guide assumes that you already have a project for which you want to enable error tracking. Refer to [the GitLab documentation](../user/project/index.md) if you need to create a new one.
+Prerequisites:
-To enable error tracking:
+- You have a project for which you want to enable error tracking. To learn how to create a new one, see [Create a project](../user/project/index.md).
-1. In your project, go to **Settings > Monitor**. Expand the `Error Tracking` tab:
+To enable error tracking with GitLab as the backend:
- ![MonitorTabPreEnable](img/Monitor_tab-pre-enable.png)
-
-1. Enable Error Tracking with GitLab as backend:
-
- ![MonitorTabPostEnable](img/Monitor_tab-post-enable.png)
-
-1. Select `Save Changes`.
+1. In your project, go to **Settings > Monitor**.
+1. Expand **Error Tracking**.
+1. Under **Enable error tracking**, select the **Active** checkbox.
+1. Under **Error tracking backend**, select **GitLab**.
+1. Select **Save changes**.
-1. Copy the DSN string. You need it for configuring your SDK implementation.
+1. Copy the Data Source Name (DSN) string. You need it for configuring your SDK implementation.
## Error tracking list
-Once your application has emitted errors to the Error Tracking API through the Sentry SDK,
+After your application has emitted errors to the Error Tracking API through the Sentry SDK,
they should be available under the **Monitor > Error Tracking** tab/section.
-![MonitorListErrors](img/Monitor-list_errors.png)
+![MonitorListErrors](img/list_errors_v16_0.png)
## Error tracking details
-![MonitorDetailErrors](img/Monitor-detail_errors.png)
+In the Error Details view you can see more details of the exception, including number of occurrences,
+users affected, first seen, and last seen dates.
-The Error Details view allows you to see more details of the Exception, including number of occurrences, users affected, first seen, and last seen.
+You can also review the stack trace.
-You can also review the Stack trace.
+![MonitorDetailErrors](img/detail_errors_v16_0.png)
## Emit errors
### Supported language SDKs & Sentry types
-In the following table, you can see a list of all event types available through Sentry SDK, and whether they are currently supported by GitLab Error Tracking.
+In the following table, you can see a list of all event types available through Sentry SDK, and whether they are supported by GitLab Error Tracking.
-<!-- markdownlint-disable MD044 -->
+| Language | Tested SDK client and version | Endpoint | Supported item types |
+| -------- | ------------------------------- | ---------- | --------------------------------- |
+| Go | `sentry-go/0.20.0` | `store` | `exception`, `message` |
+| Java | `sentry.java:6.18.1` | `envelope` | `exception`, `message` |
+| NodeJS | `sentry.javascript.node:7.38.0` | `envelope` | `exception`, `message` |
+| PHP | `sentry.php/3.18.0` | `store` | `exception`, `message` |
+| Python | `sentry.python/1.21.0` | `envelope` | `exception`, `message`, `session` |
+| Ruby | `sentry.ruby:5.9.0` | `envelope` | `exception`, `message` |
+| Rust | `sentry.rust/0.31.0` | `envelope` | `exception`, `message`, `session` |
-| LANGUAGE | TESTED SDK CLIENT/VERSION | ENDPOINT | SUPPORTED ITEM TYPES |
-| --- | --- | --- | --- |
-| Go | sentry-go/0.20.0 | store | exception, message |
-| Java | sentry.java:6.18.1 | envelope | exception, message |
-| NodeJS | sentry.javascript.node:7.38.0 | envelope | exception, message |
-| PHP | sentry.php/3.18.0 | store | exception, message |
-| Python | sentry.python/1.21.0 | envelope | exception, message, session |
-| Ruby | sentry.ruby:5.9.0 | envelope | exception, message |
-| Rust | sentry.rust/0.31.0 | envelope | exception, message, session |
-
-<!-- markdownlint-enable -->
-
-For a detailed version of this matrix, see [this issue](https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/1737).
+For a detailed version of this table, see [this issue](https://gitlab.com/gitlab-org/opstrace/opstrace/-/issues/1737).
## Usage examples
@@ -112,34 +107,34 @@ see [Sentry SDK's documentation](https://docs.sentry.io/) specific to the used l
## Rotate generated DSN
-The Sentry DSN (client key) is a secret and it should not be exposed to the public.
+The Sentry Data Source Name, or DSN, (client key) is a secret and it should not be exposed to the public.
In case of a leak, rotate the Sentry DSN by following these steps:
-1. [Create an access token](/ee/user/profile/personal_access_tokens.md#create-a-personal-access-token)
-by selecting your profile picture in GitLab.com.
-Then select Preferences, and then Access Token. Make sure you add API scope.
-1. Using the [error tracking API](/ee/api/error_tracking.md),
-create a new Sentry DSN:
+1. [Create an access token](../user/profile/personal_access_tokens.md#create-a-personal-access-token)
+ by selecting your profile picture in GitLab.com.
+ Then select Preferences, and then Access Token. Make sure you add API scope.
+1. Using the [error tracking API](../api/error_tracking.md),
+ create a new Sentry DSN:
-```shell
-curl --request POST --header "PRIVATE-TOKEN: <your_access_token>"
---header "Content-Type: application/json" \
- "https://gitlab.example.com/api/v4/projects/<your_project_number>/error_tracking/client_keys"
-```
+ ```shell
+ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>"
+ --header "Content-Type: application/json" \
+ "https://gitlab.example.com/api/v4/projects/<your_project_number>/error_tracking/client_keys"
+ ```
-- Get the available client keys (Sentry DSNs).
- Ensure that the newly created Sentry DSN is in place.
- Then note down the key ID of the old client key:
+1. Get the available client keys (Sentry DSNs).
+ Ensure that the newly created Sentry DSN is in place.
+ Then note down the key ID of the old client key:
-```shell
-curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<your_project_number>/error_tracking/client_keys"
-```
+ ```shell
+ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<your_project_number>/error_tracking/client_keys"
+ ```
-- Delete the old client key.
+1. Delete the old client key.
-```shell
-curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<your_project_number>/error_tracking/client_keys/<key_id>"
-```
+ ```shell
+ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<your_project_number>/error_tracking/client_keys/<key_id>"
+ ```
## Debug SDK issues
@@ -158,51 +153,53 @@ so users can view a list of Sentry errors in GitLab.
You can sign up to the cloud-hosted [Sentry](https://sentry.io) or deploy your own
[on-premise instance](https://github.com/getsentry/onpremise/).
-### Enabling Sentry
+### Enable Sentry integration for a project
+
+GitLab provides a way to connect Sentry to your project.
+
+Prerequisites:
-GitLab provides a way to connect Sentry to your project. You need at
-least Maintainer [permissions](../user/permissions.md) to enable the Sentry integration.
+- You must have at least the Developer role for the project.
+
+To enable the Sentry integration:
1. Sign up to Sentry.io or [deploy your own](#deploying-sentry) Sentry instance.
-1. [Create](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/)
-a new Sentry project. For each GitLab project that you want to integrate,
-you should create a new Sentry project.
+1. [Create a new Sentry project](https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/).
+ For each GitLab project that you want to integrate, you should create a new Sentry project.
1. Find or generate a [Sentry auth token](https://docs.sentry.io/api/auth/#auth-tokens).
For the SaaS version of Sentry, you can find or generate the auth token at [https://sentry.io/api/](https://sentry.io/api/).
You should give the token at least the following scopes: `project:read`,
`event:read`, and
`event:write` (for resolving events).
-1. In GitLab, enable error tracking:
+1. In GitLab, enable and configure Error Tracking:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Monitor > Error Tracking**.
- 1. Select **Enable error tracking**.
-1. In GitLab, ensure error tracking is active.
- 1. On the left sidebar, select **Settings > Monitor**.
- 1. Expand **Error Tracking**.
- 1. Ensure the **Active** checkbox is selected.
-1. In the **Sentry API URL** box, enter your Sentry hostname. For example,
-enter `https://sentry.example.com`.
-For the SaaS version of Sentry, the hostname is `https://sentry.io`.
-1. In the **Auth Token** box, enter the token you previously generated.
-1. To test the connection to Sentry and populate the **Project** dropdown list,
-select **Connect**.
-1. From the **Project** list, choose a Sentry project to link to your GitLab project.
-1. Select **Save changes**.
+ 1. Under **Enable error tracking**, select the **Active** checkbox.
+ 1. Under **Error tracking backend**, select **Sentry**.
+ 1. Under **Sentry API URL**, enter your Sentry hostname. For example,
+ enter `https://sentry.example.com`.
+ For the SaaS version of Sentry, the hostname is `https://sentry.io`.
+ 1. Under **Auth Token**, enter the token you previously generated.
+ 1. To test the connection to Sentry and populate the **Project** dropdown list,
+ select **Connect**.
+ 1. From the **Project** list, choose a Sentry project to link to your GitLab project.
+ 1. Select **Save changes**.
You can now visit **Monitor > Error Tracking** in your project's sidebar to
[view a list](#error-tracking-list) of Sentry errors.
-### Enabling GitLab issues links
+### Sentry's GitLab integration
You might also want to enable Sentry's GitLab integration by following the steps
-in the [Sentry documentation](https://docs.sentry.io/product/integrations/gitlab/)
+in the [Sentry documentation](https://docs.sentry.io/product/integrations/gitlab/).
### Enable GitLab Runner
To configure GitLab Runner with Sentry, you must add the value for `sentry_dsn`
-to your GitLab Runner's `config.toml` configuration file, as referenced in
-[GitLab Runner Advanced Configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
-While setting up Sentry, select **Go** if you're asked for the project type.
+to your runner's `config.toml` configuration file, as referenced in
+[Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html).
+
+If you're asked for the project type while setting up Sentry, select **Go**.
If you see the following error in your GitLab Runner logs, then you should
specify the deprecated
diff --git a/doc/operations/img/Monitor-detail_errors.png b/doc/operations/img/Monitor-detail_errors.png
deleted file mode 100644
index ac4234a3811..00000000000
--- a/doc/operations/img/Monitor-detail_errors.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/img/Monitor-list_errors.png b/doc/operations/img/Monitor-list_errors.png
deleted file mode 100644
index 0f1feca6329..00000000000
--- a/doc/operations/img/Monitor-list_errors.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/img/Monitor_tab-post-enable.png b/doc/operations/img/Monitor_tab-post-enable.png
deleted file mode 100644
index 91f0a0d8d27..00000000000
--- a/doc/operations/img/Monitor_tab-post-enable.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/img/Monitor_tab-pre-enable.png b/doc/operations/img/Monitor_tab-pre-enable.png
deleted file mode 100644
index 2984110a7d7..00000000000
--- a/doc/operations/img/Monitor_tab-pre-enable.png
+++ /dev/null
Binary files differ
diff --git a/doc/operations/img/detail_errors_v16_0.png b/doc/operations/img/detail_errors_v16_0.png
new file mode 100644
index 00000000000..3b63c565e56
--- /dev/null
+++ b/doc/operations/img/detail_errors_v16_0.png
Binary files differ
diff --git a/doc/operations/img/list_errors_v16_0.png b/doc/operations/img/list_errors_v16_0.png
new file mode 100644
index 00000000000..0ddf7f93616
--- /dev/null
+++ b/doc/operations/img/list_errors_v16_0.png
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index a68ad604989..9b299b46f75 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -129,12 +129,14 @@ If you are running a self-managed instance of GitLab,
### Configure GitLab Pages in a Helm Chart (Kubernetes) instance
To configure GitLab Pages on instances deployed via Helm chart (Kubernetes), use either:
-
-- [The `gitlab-pages` subchart](https://docs.gitlab.com/charts/charts/gitlab/gitlab-pages/).
-- [An external GitLab Pages instance](https://docs.gitlab.com/charts/advanced/external-gitlab-pages/).
+
+- [The `gitlab-pages` subchart](https://docs.gitlab.com/charts/charts/gitlab/gitlab-pages/).
+- [An external GitLab Pages instance](https://docs.gitlab.com/charts/advanced/external-gitlab-pages/).
## Security for GitLab Pages
+### Namespaces that contain `.`
+
If your username is `example`, your GitLab Pages website is located at `example.gitlab.io`.
GitLab allows usernames to contain a `.`, so a user named `bar.example` could create
a GitLab Pages website `bar.example.gitlab.io` that effectively is a subdomain of your
@@ -153,3 +155,9 @@ document.cookie = "key=value;domain=example.gitlab.io";
This issue doesn't affect users with a custom domain, or users who don't set any
cookies manually with JavaScript.
+
+### Shared cookies
+
+By default, every project in a group shares the same domain, for example, `group.gitlab.io`. This means that cookies are also shared for all projects in a group.
+
+To ensure each project uses different cookies, enable the Pages [unique domains](introduction.md#enable-unique-domains) feature for your project.
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 05d0b461fea..f7d273d198d 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -94,6 +94,25 @@ the group must be at the top level and not a subgroup.
For [project websites](../../project/pages/getting_started_part_one.md#project-website-examples),
you can create your project first and access it under `http(s)://namespace.example.io/projectname`.
+## Enable unique domains
+
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/9347) in GitLab 15.9 [with a flag](../../../administration/feature_flags.md) named `pages_unique_domain`. Disabled by default.
+> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/388151) in GitLab 15.11.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available,
+ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `pages_unique_domain`.
+On GitLab.com, by default this feature is available.
+
+By default, every project in a group shares the same domain, for example, `group.gitlab.io`. This means that cookies are also shared for all projects in a group.
+
+To ensure your project uses a unique Pages domain, enable the unique domains feature for the project:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Pages**.
+1. Select the **Use unique domain** checkbox.
+1. Select **Save changes**.
+
## Specific configuration options for Pages
Learn how to set up GitLab CI/CD for specific use cases.
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index 4639ea63622..b4e9e85a012 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -13,7 +13,7 @@ module Gitlab
return unless current_transaction
- labels = { store: extract_store_name(event) }
+ labels = { store: event.payload[:store].split('::').last }
current_transaction.observe(:gitlab_cache_read_multikey_count, event.payload[:key].size, labels) do
buckets [10, 50, 100, 1000]
docstring 'Number of keys for mget in read_multi/fetch_multi'
@@ -48,26 +48,23 @@ module Gitlab
def cache_fetch_hit(event)
return unless current_transaction
- labels = { store: extract_store_name(event) }
- current_transaction.increment(:gitlab_transaction_cache_read_hit_count_total, 1, labels)
+ current_transaction.increment(:gitlab_transaction_cache_read_hit_count_total, 1)
end
def cache_generate(event)
return unless current_transaction
- labels = { store: extract_store_name(event) }
-
- current_transaction.increment(:gitlab_cache_misses_total, 1, labels) do
+ current_transaction.increment(:gitlab_cache_misses_total, 1) do
docstring 'Cache read miss'
end
- current_transaction.increment(:gitlab_transaction_cache_read_miss_count_total, 1, labels)
+ current_transaction.increment(:gitlab_transaction_cache_read_miss_count_total, 1)
end
def observe(key, event)
return unless current_transaction
- labels = { operation: key, store: extract_store_name(event) }
+ labels = { operation: key, store: event.payload[:store].split('::').last }
current_transaction.increment(:gitlab_cache_operations_total, 1, labels) do
docstring 'Cache operations'
@@ -79,11 +76,6 @@ module Gitlab
private
- def extract_store_name(event)
- # see payload documentation in https://guides.rubyonrails.org/active_support_instrumentation.html#active-support
- event.payload[:store].to_s.split('::').last
- end
-
def current_transaction
::Gitlab::Metrics::WebTransaction.current
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0576cb19803..b5e6e8e2cde 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -20933,6 +20933,9 @@ msgstr ""
msgid "Gravatar enabled"
msgstr ""
+msgid "Grid"
+msgstr ""
+
msgid "Group"
msgstr ""
diff --git a/rubocop/cop/background_migration/feature_category.rb b/rubocop/cop/background_migration/feature_category.rb
index ec70b5baadf..c6611a65e49 100644
--- a/rubocop/cop/background_migration/feature_category.rb
+++ b/rubocop/cop/background_migration/feature_category.rb
@@ -17,7 +17,7 @@ module RuboCop
"https://docs.gitlab.com/ee/development/feature_categorization/#batched-background-migrations"
INVALID_FEATURE_CATEGORY_MSG = "'feature_category' is invalid. " \
- "List of valid ones can be found in #{FEATURE_CATEGORIES_FILE_PATH}"
+ "List of valid ones can be found in #{FEATURE_CATEGORIES_FILE_PATH}".freeze
RESTRICT_ON_SEND = [:feature_category].freeze
diff --git a/rubocop/cop/filename_length.rb b/rubocop/cop/filename_length.rb
index 948f69cc88c..4144337f00f 100644
--- a/rubocop/cop/filename_length.rb
+++ b/rubocop/cop/filename_length.rb
@@ -7,8 +7,8 @@ module RuboCop
FILEPATH_MAX_BYTES = 256
FILENAME_MAX_BYTES = 100
- MSG_FILEPATH_LEN = "This file path is too long. It should be #{FILEPATH_MAX_BYTES} or less"
- MSG_FILENAME_LEN = "This file name is too long. It should be #{FILENAME_MAX_BYTES} or less"
+ MSG_FILEPATH_LEN = "This file path is too long. It should be #{FILEPATH_MAX_BYTES} or less".freeze
+ MSG_FILENAME_LEN = "This file name is too long. It should be #{FILENAME_MAX_BYTES} or less".freeze
def on_new_investigation
file_path = processed_source.file_path
diff --git a/rubocop/cop/gitlab/event_store_subscriber.rb b/rubocop/cop/gitlab/event_store_subscriber.rb
index 7e4cc3e66cc..04dc29ec400 100644
--- a/rubocop/cop/gitlab/event_store_subscriber.rb
+++ b/rubocop/cop/gitlab/event_store_subscriber.rb
@@ -32,8 +32,8 @@ module RuboCop
#
class EventStoreSubscriber < RuboCop::Cop::Base
SUBSCRIBER_MODULE_NAME = 'Gitlab::EventStore::Subscriber'
- FORBID_PERFORM_OVERRIDE = "Do not override `perform` in a `#{SUBSCRIBER_MODULE_NAME}`."
- REQUIRE_HANDLE_EVENT = "A `#{SUBSCRIBER_MODULE_NAME}` must implement `#handle_event(event)`."
+ FORBID_PERFORM_OVERRIDE = "Do not override `perform` in a `#{SUBSCRIBER_MODULE_NAME}`.".freeze
+ REQUIRE_HANDLE_EVENT = "A `#{SUBSCRIBER_MODULE_NAME}` must implement `#handle_event(event)`.".freeze
def_node_matcher :includes_subscriber?, <<~PATTERN
(send nil? :include (const (const (const nil? :Gitlab) :EventStore) :Subscriber))
diff --git a/rubocop/cop/graphql/descriptions.rb b/rubocop/cop/graphql/descriptions.rb
index 239f5b966a4..b096dfb148e 100644
--- a/rubocop/cop/graphql/descriptions.rb
+++ b/rubocop/cop/graphql/descriptions.rb
@@ -51,11 +51,12 @@ module RuboCop
extend RuboCop::Cop::AutoCorrector
MSG_STYLE_GUIDE_LINK = 'See the description style guide: https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#description-style-guide'
- MSG_NO_DESCRIPTION = "Please add a `description` property. #{MSG_STYLE_GUIDE_LINK}"
- MSG_NO_PERIOD = "`description` strings must end with a `.`. #{MSG_STYLE_GUIDE_LINK}"
- MSG_BAD_START = "`description` strings should not start with \"A...\" or \"The...\". #{MSG_STYLE_GUIDE_LINK}"
+ MSG_NO_DESCRIPTION = "Please add a `description` property. #{MSG_STYLE_GUIDE_LINK}".freeze
+ MSG_NO_PERIOD = "`description` strings must end with a `.`. #{MSG_STYLE_GUIDE_LINK}".freeze
+ MSG_BAD_START = "`description` strings should not start with \"A...\" or \"The...\"."\
+ " #{MSG_STYLE_GUIDE_LINK}".freeze
MSG_CONTAINS_THIS = "`description` strings should not contain the demonstrative \"this\"."\
- " #{MSG_STYLE_GUIDE_LINK}"
+ " #{MSG_STYLE_GUIDE_LINK}".freeze
def_node_matcher :graphql_describable?, <<~PATTERN
(send nil? {:field :argument :value} ...)
diff --git a/rubocop/cop/graphql/enum_names.rb b/rubocop/cop/graphql/enum_names.rb
index 74847cb8d17..19ba426fdeb 100644
--- a/rubocop/cop/graphql/enum_names.rb
+++ b/rubocop/cop/graphql/enum_names.rb
@@ -34,9 +34,9 @@ module RuboCop
module Graphql
class EnumNames < RuboCop::Cop::Base
SEE_SG_MSG = "See https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#enums"
- CLASS_NAME_SUFFIX_MSG = "Enum class names must end with `Enum`. #{SEE_SG_MSG}"
- GRAPHQL_NAME_MISSING_MSG = "A `graphql_name` must be defined for a GraphQL enum. #{SEE_SG_MSG}"
- GRAPHQL_NAME_WITH_ENUM_MSG = "The `graphql_name` must not contain the string \"Enum\". #{SEE_SG_MSG}"
+ CLASS_NAME_SUFFIX_MSG = "Enum class names must end with `Enum`. #{SEE_SG_MSG}".freeze
+ GRAPHQL_NAME_MISSING_MSG = "A `graphql_name` must be defined for a GraphQL enum. #{SEE_SG_MSG}".freeze
+ GRAPHQL_NAME_WITH_ENUM_MSG = "The `graphql_name` must not contain the string \"Enum\". #{SEE_SG_MSG}".freeze
def_node_matcher :enum_subclass, <<~PATTERN
(class $(const nil? _) (const {nil? cbase} /.*Enum$/) ...)
diff --git a/rubocop/cop/migration/prevent_index_creation.rb b/rubocop/cop/migration/prevent_index_creation.rb
index 185f36cb24b..4c39032eb0f 100644
--- a/rubocop/cop/migration/prevent_index_creation.rb
+++ b/rubocop/cop/migration/prevent_index_creation.rb
@@ -10,7 +10,7 @@ module RuboCop
FORBIDDEN_TABLES = %i[ci_builds].freeze
- MSG = "Adding new index to #{FORBIDDEN_TABLES.join(", ")} is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886"
+ MSG = "Adding new index to #{FORBIDDEN_TABLES.join(", ")} is forbidden, see https://gitlab.com/gitlab-org/gitlab/-/issues/332886".freeze
def on_new_investigation
super
diff --git a/rubocop/cop/migration/versioned_migration_class.rb b/rubocop/cop/migration/versioned_migration_class.rb
index 27f1c0e16a1..d078acedb37 100644
--- a/rubocop/cop/migration/versioned_migration_class.rb
+++ b/rubocop/cop/migration/versioned_migration_class.rb
@@ -12,10 +12,10 @@ module RuboCop
DOC_LINK = "https://docs.gitlab.com/ee/development/migration_style_guide.html#migration-helpers-and-versioning"
MSG_INHERIT = "Don't inherit from ActiveRecord::Migration or old versions of Gitlab::Database::Migration. " \
- "Use Gitlab::Database::Migration[2.1] instead. See #{DOC_LINK}."
+ "Use Gitlab::Database::Migration[2.1] instead. See #{DOC_LINK}.".freeze
MSG_INCLUDE = "Don't include migration helper modules directly. " \
- "Inherit from Gitlab::Database::Migration[2.1] instead. See #{DOC_LINK}."
+ "Inherit from Gitlab::Database::Migration[2.1] instead. See #{DOC_LINK}.".freeze
GITLAB_MIGRATION_CLASS = 'Gitlab::Database::Migration'
ACTIVERECORD_MIGRATION_CLASS = 'ActiveRecord::Migration'
diff --git a/rubocop/cop/migration/with_lock_retries_disallowed_method.rb b/rubocop/cop/migration/with_lock_retries_disallowed_method.rb
index 5c96b38dcdc..1b0d5ed9324 100644
--- a/rubocop/cop/migration/with_lock_retries_disallowed_method.rb
+++ b/rubocop/cop/migration/with_lock_retries_disallowed_method.rb
@@ -31,7 +31,7 @@ module RuboCop
create_trigger_to_sync_tables
].sort.freeze
- MSG = "The method is not allowed to be called within the `with_lock_retries` block, the only allowed methods are: #{ALLOWED_MIGRATION_METHODS.join(', ')}"
+ MSG = "The method is not allowed to be called within the `with_lock_retries` block, the only allowed methods are: #{ALLOWED_MIGRATION_METHODS.join(', ')}".freeze
MSG_ONLY_ONE_FK_ALLOWED = "Avoid adding more than one foreign key within the `with_lock_retries`. See https://docs.gitlab.com/ee/development/migration_style_guide.html#examples"
def_node_matcher :send_node?, <<~PATTERN
diff --git a/rubocop/cop/scalability/idempotent_worker.rb b/rubocop/cop/scalability/idempotent_worker.rb
index 88b5d796def..fee9e952105 100644
--- a/rubocop/cop/scalability/idempotent_worker.rb
+++ b/rubocop/cop/scalability/idempotent_worker.rb
@@ -28,7 +28,7 @@ module RuboCop
HELP_LINK = 'https://github.com/mperham/sidekiq/wiki/Best-Practices#2-make-your-job-idempotent-and-transactional'
- MSG = <<~MSG
+ MSG = <<~MSG.freeze
Avoid adding not idempotent workers.
A worker is considered idempotent if:
diff --git a/rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb b/rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb
index badd81ff138..1d421a5d017 100644
--- a/rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb
+++ b/rubocop/cop/sidekiq_load_balancing/worker_data_consistency.rb
@@ -28,13 +28,13 @@ module RuboCop
HELP_LINK = 'https://docs.gitlab.com/ee/development/sidekiq/worker_attributes.html#job-data-consistency-strategies'
- MISSING_DATA_CONSISTENCY_MSG = <<~MSG
+ MISSING_DATA_CONSISTENCY_MSG = <<~MSG.freeze
Should define data_consistency expectation.
See #{HELP_LINK} for a more detailed explanation of these settings.
MSG
DISCOURAGE_ALWAYS_MSG = "Refrain from using `:always` if possible." \
- "See #{HELP_LINK} for a more detailed explanation of these settings."
+ "See #{HELP_LINK} for a more detailed explanation of these settings.".freeze
def_node_search :application_worker?, <<~PATTERN
`(send nil? :include (const nil? :ApplicationWorker))
diff --git a/spec/frontend/issues/list/components/issues_list_app_spec.js b/spec/frontend/issues/list/components/issues_list_app_spec.js
index af24b547545..4f37717c933 100644
--- a/spec/frontend/issues/list/components/issues_list_app_spec.js
+++ b/spec/frontend/issues/list/components/issues_list_app_spec.js
@@ -11,7 +11,8 @@ import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_coun
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import {
getIssuesCountsQueryResponse,
@@ -34,6 +35,7 @@ import { issuableListTabs } from '~/vue_shared/issuable/list/constants';
import EmptyStateWithAnyIssues from '~/issues/list/components/empty_state_with_any_issues.vue';
import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_without_any_issues.vue';
import IssuesListApp from '~/issues/list/components/issues_list_app.vue';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue';
import {
CREATED_DESC,
@@ -135,6 +137,9 @@ describe('CE IssuesListApp component', () => {
const findGlButton = () => wrapper.findComponent(GlButton);
const findGlButtons = () => wrapper.findAllComponents(GlButton);
const findIssuableList = () => wrapper.findComponent(IssuableList);
+ const findListViewTypeBtn = () => wrapper.findByTestId('list-view-type');
+ const findGridtViewTypeBtn = () => wrapper.findByTestId('grid-view-type');
+ const findViewTypeLocalStorageSync = () => wrapper.findAllComponents(LocalStorageSync).at(0);
const findNewResourceDropdown = () => wrapper.findComponent(NewResourceDropdown);
const findRssButton = () => wrapper.findByRole('menuitem', { name: IssuesListApp.i18n.rssLabel });
@@ -233,6 +238,7 @@ describe('CE IssuesListApp component', () => {
hasPreviousPage: getIssuesQueryResponse.data.project.issues.pageInfo.hasPreviousPage,
hasNextPage: getIssuesQueryResponse.data.project.issues.pageInfo.hasNextPage,
});
+ expect(findIssuableList().props('isGridView')).toBe(false);
});
});
@@ -354,6 +360,37 @@ describe('CE IssuesListApp component', () => {
});
});
+ describe('header action buttons with the grid view enabled', () => {
+ beforeEach(() => {
+ wrapper = mountComponent({
+ mountFn: shallowMountExtended,
+ provide: {
+ glFeatures: {
+ issuesGridView: true,
+ },
+ },
+ stubs: {
+ IssuableList: stubComponent(IssuableList, {
+ template: `<div><slot name="nav-actions" /></div>`,
+ }),
+ },
+ });
+ });
+
+ it('switch between list and grid', async () => {
+ findGridtViewTypeBtn().vm.$emit('click');
+ await nextTick();
+
+ expect(findIssuableList().props('isGridView')).toBe(true);
+ expect(findViewTypeLocalStorageSync().props('value')).toBe('Grid');
+
+ findListViewTypeBtn().vm.$emit('click');
+ await nextTick();
+ expect(findIssuableList().props('isGridView')).toBe(false);
+ expect(findViewTypeLocalStorageSync().props('value')).toBe('List');
+ });
+ });
+
describe('initial url params', () => {
describe('page', () => {
it('page_after is set from the url params', () => {
diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
index ec975dfdcb5..68904603f40 100644
--- a/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
+++ b/spec/frontend/vue_shared/issuable/list/components/issuable_list_root_spec.js
@@ -7,6 +7,7 @@ import { TEST_HOST } from 'helpers/test_constants';
import IssuableItem from '~/vue_shared/issuable/list/components/issuable_item.vue';
import IssuableListRoot from '~/vue_shared/issuable/list/components/issuable_list_root.vue';
+import issuableGrid from '~/vue_shared/issuable/list/components/issuable_grid.vue';
import IssuableTabs from '~/vue_shared/issuable/list/components/issuable_tabs.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import PageSizeSelector from '~/vue_shared/components/page_size_selector.vue';
@@ -43,6 +44,7 @@ describe('IssuableListRoot', () => {
const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination);
const findGlPagination = () => wrapper.findComponent(GlPagination);
const findIssuableItem = () => wrapper.findComponent(IssuableItem);
+ const findIssuableGrid = () => wrapper.findComponent(issuableGrid);
const findIssuableTabs = () => wrapper.findComponent(IssuableTabs);
const findVueDraggable = () => wrapper.findComponent(VueDraggable);
const findPageSizeSelector = () => wrapper.findComponent(PageSizeSelector);
@@ -514,4 +516,18 @@ describe('IssuableListRoot', () => {
expect(wrapper.emitted('page-size-change')).toEqual([[pageSize]]);
});
});
+
+ describe('grid view issue', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ props: {
+ isGridView: true,
+ },
+ });
+ });
+
+ it('renders issuableGrid', () => {
+ expect(findIssuableGrid().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/graphql/mutations/environments/create_spec.rb b/spec/graphql/mutations/environments/create_spec.rb
index 8b40ecd6c81..c15f5cacade 100644
--- a/spec/graphql/mutations/environments/create_spec.rb
+++ b/spec/graphql/mutations/environments/create_spec.rb
@@ -12,10 +12,9 @@ RSpec.describe Mutations::Environments::Create, feature_category: :environment_m
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
describe '#resolve' do
- subject { mutation.resolve(project_path: project.full_path, name: name, external_url: external_url) }
+ subject { mutation.resolve(project_path: project.full_path, **kwargs) }
- let(:name) { 'production' }
- let(:external_url) { 'https://gitlab.com/' }
+ let(:kwargs) { { name: 'production', external_url: 'https://gitlab.com/' } }
context 'when service execution succeeded' do
it 'returns no errors' do
@@ -23,13 +22,13 @@ RSpec.describe Mutations::Environments::Create, feature_category: :environment_m
end
it 'creates the environment' do
- expect(subject[:environment][:name]).to eq(name)
- expect(subject[:environment][:external_url]).to eq(external_url)
+ expect(subject[:environment][:name]).to eq('production')
+ expect(subject[:environment][:external_url]).to eq('https://gitlab.com/')
end
end
context 'when service cannot create the attribute' do
- let(:external_url) { 'http://${URL}' }
+ let(:kwargs) { { name: 'production', external_url: 'http://${URL}' } }
it 'returns an error' do
expect(subject)
@@ -40,6 +39,18 @@ RSpec.describe Mutations::Environments::Create, feature_category: :environment_m
end
end
+ context 'when setting cluster agent ID to the environment' do
+ let_it_be(:cluster_agent) { create(:cluster_agent, project: project) }
+
+ let!(:authorization) { create(:agent_user_access_project_authorization, project: project, agent: cluster_agent) }
+
+ let(:kwargs) { { name: 'production', cluster_agent_id: cluster_agent.to_global_id } }
+
+ it 'sets the cluster agent to the environment' do
+ expect(subject[:environment].cluster_agent).to eq(cluster_agent)
+ end
+ end
+
context 'when user is reporter who does not have permission to access the environment' do
let(:user) { reporter }
diff --git a/spec/lib/gitlab/import/errors_spec.rb b/spec/lib/gitlab/import/errors_spec.rb
index f89cb36bbb4..3b45af0618b 100644
--- a/spec/lib/gitlab/import/errors_spec.rb
+++ b/spec/lib/gitlab/import/errors_spec.rb
@@ -40,7 +40,6 @@ RSpec.describe Gitlab::Import::Errors, feature_category: :importers do
"Author can't be blank",
"Project does not match noteable project",
"User can't be blank",
- "Awardable can't be blank",
"Name is not a valid emoji name"
)
end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index 98f0fc5ea83..2d4c6d1cc56 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do
it 'does not increment cache read miss total' do
expect(transaction).not_to receive(:increment)
- .with(:gitlab_cache_misses_total, 1, { store: store_label })
+ .with(:gitlab_cache_misses_total, 1)
subscriber.cache_read(event)
end
@@ -145,7 +145,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do
it 'increments the cache_read_hit count' do
expect(transaction).to receive(:increment)
- .with(:gitlab_transaction_cache_read_hit_count_total, 1, { store: store_label })
+ .with(:gitlab_transaction_cache_read_hit_count_total, 1)
subscriber.cache_fetch_hit(event)
end
@@ -168,9 +168,9 @@ RSpec.describe Gitlab::Metrics::Subscribers::RailsCache do
end
it 'increments the cache_fetch_miss count and cache_read_miss total' do
- expect(transaction).to receive(:increment).with(:gitlab_cache_misses_total, 1, { store: store_label })
+ expect(transaction).to receive(:increment).with(:gitlab_cache_misses_total, 1)
expect(transaction).to receive(:increment)
- .with(:gitlab_transaction_cache_read_miss_count_total, 1, { store: store_label })
+ .with(:gitlab_transaction_cache_read_miss_count_total, 1)
subscriber.cache_generate(event)
end
diff --git a/spec/services/environments/create_service_spec.rb b/spec/services/environments/create_service_spec.rb
index e834e66dd9c..d7fdfd2a38e 100644
--- a/spec/services/environments/create_service_spec.rb
+++ b/spec/services/environments/create_service_spec.rb
@@ -29,6 +29,33 @@ RSpec.describe Environments::CreateService, feature_category: :environment_manag
expect(response.payload[:environment].tier).to eq('production')
end
+ context 'with a cluster agent' do
+ let_it_be(:agent_management_project) { create(:project) }
+ let_it_be(:cluster_agent) { create(:cluster_agent, project: agent_management_project) }
+
+ let!(:authorization) { create(:agent_user_access_project_authorization, project: project, agent: cluster_agent) }
+ let(:params) { { name: 'production', cluster_agent: cluster_agent } }
+
+ it 'returns successful response' do
+ response = subject
+
+ expect(response).to be_success
+ expect(response.payload[:environment].cluster_agent).to eq(cluster_agent)
+ end
+
+ context 'when user does not have permission to read the agent' do
+ let!(:authorization) { nil }
+
+ it 'returns an error' do
+ response = subject
+
+ expect(response).to be_error
+ expect(response.message).to eq('Unauthorized to access the cluster agent in this project')
+ expect(response.payload[:environment]).to be_nil
+ end
+ end
+ end
+
context 'when params contain invalid value' do
let(:params) { { name: 'production', external_url: 'http://${URL}' } }
@@ -45,6 +72,18 @@ RSpec.describe Environments::CreateService, feature_category: :environment_manag
end
end
+ context 'when disallowed parameter is passed' do
+ let(:params) { { name: 'production', slug: 'prod' } }
+
+ it 'ignores the parameter' do
+ response = subject
+
+ expect(response).to be_success
+ expect(response.payload[:environment].name).to eq('production')
+ expect(response.payload[:environment].slug).not_to eq('prod')
+ end
+ end
+
context 'when user is reporter' do
let(:current_user) { reporter }
diff --git a/spec/services/work_items/widgets/award_emoji_service/update_service_spec.rb b/spec/services/work_items/callbacks/award_emoji_spec.rb
index 186e4d56cc4..831604d73b1 100644
--- a/spec/services/work_items/widgets/award_emoji_service/update_service_spec.rb
+++ b/spec/services/work_items/callbacks/award_emoji_spec.rb
@@ -2,27 +2,26 @@
require 'spec_helper'
-RSpec.describe WorkItems::Widgets::AwardEmojiService::UpdateService, feature_category: :team_planning do
+RSpec.describe WorkItems::Callbacks::AwardEmoji, feature_category: :team_planning do
let_it_be(:reporter) { create(:user) }
let_it_be(:unauthorized_user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:work_item) { create(:work_item, project: project) }
let(:current_user) { reporter }
- let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::AwardEmoji) } }
before_all do
project.add_reporter(reporter)
end
- describe '#before_update_in_transaction' do
+ describe '#before_update' do
subject do
- described_class.new(widget: widget, current_user: current_user)
- .before_update_in_transaction(params: params)
+ described_class.new(issuable: work_item, current_user: current_user, params: params)
+ .before_update
end
shared_examples 'raises a WidgetError' do
- it { expect { subject }.to raise_error(described_class::WidgetError, message) }
+ it { expect { subject }.to raise_error(::WorkItems::Widgets::BaseService::WidgetError, message) }
end
context 'when awarding an emoji' do