Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
7186033c51
commit
61ebd57530
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 #
|
||||
#######################
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
2
Gemfile
2
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'
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<template><div></div></template>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -57,6 +57,7 @@ exceptions:
|
|||
- DHCP
|
||||
- DML
|
||||
- DNS
|
||||
- DSN
|
||||
- DOM
|
||||
- DORA
|
||||
- DSA
|
||||
|
|
|
|||
|
|
@ -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 | |
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||

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

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

|
||||

|
||||
|
||||
## Error tracking details
|
||||
|
||||

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

|
||||
|
||||
## 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 |
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -20933,6 +20933,9 @@ msgstr ""
|
|||
msgid "Gravatar enabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Grid"
|
||||
msgstr ""
|
||||
|
||||
msgid "Group"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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} ...)
|
||||
|
|
|
|||
|
|
@ -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$/) ...)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue