Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-05-24 15:08:38 +00:00
parent 7186033c51
commit 61ebd57530
59 changed files with 508 additions and 231 deletions

View File

@ -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

View File

@ -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
@ -2526,6 +2507,29 @@
- ".gitlab/ci/test-metadata.gitlab-ci.yml"
- "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 #
#######################

View File

@ -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'

View File

@ -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'

View File

@ -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"},

View File

@ -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)

View File

@ -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"

View File

@ -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 = {

View File

@ -0,0 +1 @@
<template><div></div></template>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -57,6 +57,7 @@ exceptions:
- DHCP
- DML
- DNS
- DSN
- DOM
- DORA
- DSA

View File

@ -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 | |

View File

@ -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"
```

View File

@ -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. |

View File

@ -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.

View File

@ -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.

View File

@ -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`

View File

@ -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).

View File

@ -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`.

View File

@ -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. 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. Enable Error Tracking with GitLab as backend:
![MonitorTabPostEnable](img/Monitor_tab-post-enable.png)
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. You need at
least Maintainer [permissions](../user/permissions.md) to enable the Sentry integration.
GitLab provides a way to connect Sentry to your project.
Prerequisites:
- 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 886 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -20933,6 +20933,9 @@ msgstr ""
msgid "Gravatar enabled"
msgstr ""
msgid "Grid"
msgstr ""
msgid "Group"
msgstr ""

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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} ...)

View File

@ -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$/) ...)

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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:

View File

@ -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))

View File

@ -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', () => {

View File

@ -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);
});
});
});

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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