Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-31 18:18:10 +00:00
parent 8bdb37de3a
commit 634e9bccc6
114 changed files with 2114 additions and 438 deletions

View File

@ -160,6 +160,7 @@ Lint/MixedRegexpCaptureTypes:
- 'lib/gitlab/diff/suggestions_parser.rb'
- 'lib/gitlab/github_import/representation/note.rb'
- 'lib/gitlab/metrics/system.rb'
- 'lib/gitlab/request_profiler/profile.rb'
- 'lib/gitlab/slash_commands/issue_move.rb'
- 'lib/gitlab/slash_commands/issue_new.rb'
- 'lib/gitlab/slash_commands/run.rb'

View File

@ -875,6 +875,7 @@ Gitlab/NamespacedClass:
- app/workers/repository_import_worker.rb
- app/workers/repository_remove_remote_worker.rb
- app/workers/repository_update_remote_mirror_worker.rb
- app/workers/requests_profiles_worker.rb
- app/workers/run_pipeline_schedule_worker.rb
- app/workers/schedule_merge_request_cleanup_refs_worker.rb
- app/workers/schedule_migrate_external_diffs_worker.rb

View File

@ -2,16 +2,3 @@
Rails/SaveBang:
Exclude:
- ee/spec/lib/analytics/merge_request_metrics_calculator_spec.rb
- spec/lib/backup/manager_spec.rb
- spec/lib/gitlab/alerting/alert_spec.rb
- spec/lib/gitlab/analytics/cycle_analytics/records_fetcher_spec.rb
- spec/lib/gitlab/auth/ldap/user_spec.rb
- spec/lib/gitlab/auth/o_auth/user_spec.rb
- spec/lib/gitlab/auth/saml/user_spec.rb
- spec/lib/gitlab/auth_spec.rb
- spec/lib/gitlab/authorized_keys_spec.rb
- spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
- spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
- spec/lib/gitlab/database/custom_structure_spec.rb
- spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
- spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb

View File

@ -1 +1 @@
1.51.0
1.52.0

View File

@ -1,11 +1,29 @@
<script>
import { GlAlert, GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
import { MAX_LIST_COUNT, ACTIVE_CONNECTION_TIME } from '../constants';
import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlBanner } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
MAX_LIST_COUNT,
ACTIVE_CONNECTION_TIME,
AGENT_FEEDBACK_ISSUE,
AGENT_FEEDBACK_KEY,
} from '../constants';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import AgentEmptyState from './agent_empty_state.vue';
import AgentTable from './agent_table.vue';
export default {
i18n: {
feedbackBannerTitle: s__('ClusterAgents|Tell us what you think'),
feedbackBannerText: s__(
'ClusterAgents|We would love to learn more about your experience with the GitLab Agent.',
),
feedbackBannerButton: s__('ClusterAgents|Give feedback'),
error: s__('ClusterAgents|An error occurred while loading your Agents'),
},
AGENT_FEEDBACK_ISSUE,
AGENT_FEEDBACK_KEY,
apollo: {
agents: {
query: getAgentsQuery,
@ -31,7 +49,10 @@ export default {
GlAlert,
GlKeysetPagination,
GlLoadingIcon,
GlBanner,
LocalStorageSync,
},
mixins: [glFeatureFlagMixin()],
inject: ['projectPath'],
props: {
defaultBranchName: {
@ -57,6 +78,7 @@ export default {
last: null,
},
folderList: {},
feedbackBannerDismissed: false,
};
},
computed: {
@ -86,6 +108,12 @@ export default {
treePageInfo() {
return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
},
feedbackBannerEnabled() {
return this.glFeatures.showGitlabAgentFeedback;
},
feedbackBannerClasses() {
return this.isChildComponent ? 'gl-my-2' : 'gl-mb-4';
},
},
methods: {
reloadAgents() {
@ -142,6 +170,9 @@ export default {
const count = this.agents?.project?.clusterAgents?.count;
this.$emit('onAgentsLoad', count);
},
handleBannerClose() {
this.feedbackBannerDismissed = true;
},
},
};
</script>
@ -151,6 +182,24 @@ export default {
<section v-else-if="agentList">
<div v-if="agentList.length">
<local-storage-sync
v-if="feedbackBannerEnabled"
v-model="feedbackBannerDismissed"
:storage-key="$options.AGENT_FEEDBACK_KEY"
>
<gl-banner
v-if="!feedbackBannerDismissed"
variant="introduction"
:class="feedbackBannerClasses"
:title="$options.i18n.feedbackBannerTitle"
:button-text="$options.i18n.feedbackBannerButton"
:button-link="$options.AGENT_FEEDBACK_ISSUE"
@close="handleBannerClose"
>
<p>{{ $options.i18n.feedbackBannerText }}</p>
</gl-banner>
</local-storage-sync>
<agent-table
:agents="agentList"
:default-branch-name="defaultBranchName"
@ -166,6 +215,6 @@ export default {
</section>
<gl-alert v-else variant="danger" :dismissible="false">
{{ s__('ClusterAgents|An error occurred while loading your GitLab Agents') }}
{{ $options.i18n.error }}
</gl-alert>
</template>

View File

@ -123,7 +123,7 @@ export default {
<div v-show="!isLoading" data-testid="clusters-cards-container">
<gl-card
header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between gl-py-4"
body-class="gl-pb-0"
body-class="gl-pb-0 cluster-card-item"
footer-class="gl-text-right"
>
<template #header>
@ -198,7 +198,7 @@ export default {
<gl-card
class="gl-mt-6"
header-class="gl-bg-white gl-display-flex gl-align-items-center gl-justify-content-space-between"
body-class="gl-pb-0"
body-class="gl-pb-0 cluster-card-item"
footer-class="gl-text-right"
>
<template #header>

View File

@ -267,3 +267,6 @@ export const MODAL_TYPE_EMPTY = 'empty_state';
export const MODAL_TYPE_REGISTER = 'agent_registration';
export const DELETE_AGENT_MODAL_ID = 'delete-agent-modal-%{agentName}';
export const AGENT_FEEDBACK_ISSUE = 'https://gitlab.com/gitlab-org/gitlab/-/issues/342696';
export const AGENT_FEEDBACK_KEY = 'agent_feedback_banner';

View File

@ -38,6 +38,10 @@ export default {
required: false,
default: null,
},
invalidGroups: {
type: Array,
required: true,
},
},
data() {
return {
@ -75,18 +79,26 @@ export default {
this.isFetching = true;
return this.fetchGroups()
.then((response) => {
this.groups = response.map((group) => ({
id: group.id,
name: group.full_name,
path: group.path,
avatarUrl: group.avatar_url,
}));
this.groups = this.processGroups(response);
this.isFetching = false;
})
.catch(() => {
this.isFetching = false;
});
}, SEARCH_DELAY),
processGroups(response) {
const rawGroups = response.map((group) => ({
id: group.id,
name: group.full_name,
path: group.path,
avatarUrl: group.avatar_url,
}));
return this.filterOutInvalidGroups(rawGroups);
},
filterOutInvalidGroups(groups) {
return groups.filter((group) => this.invalidGroups.indexOf(group.id) === -1);
},
selectGroup(group) {
this.selectedGroup = group;

View File

@ -107,6 +107,10 @@ export default {
type: Array,
required: true,
},
invalidGroups: {
type: Array,
required: true,
},
},
data() {
return {
@ -431,6 +435,7 @@ export default {
:access-levels="accessLevels"
:groups-filter="groupSelectFilter"
:parent-group-id="groupSelectParentId"
:invalid-groups="invalidGroups"
@input="handleMembersTokenSelectClear"
/>
</gl-form-group>

View File

@ -40,6 +40,7 @@ export default function initInviteMembersModal() {
defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
groupSelectFilter: el.dataset.groupsFilter,
groupSelectParentId: parseInt(el.dataset.parentId, 10),
invalidGroups: JSON.parse(el.dataset.invalidGroups || '[]'),
tasksToBeDoneOptions: JSON.parse(el.dataset.tasksToBeDoneOptions || '[]'),
projects: JSON.parse(el.dataset.projects || '[]'),
usersFilter: el.dataset.usersFilter,

View File

@ -280,7 +280,7 @@ export default {
}
return s__(
'ProjectSettings|View and edit files in this project. Non-project members will only have read access.',
'ProjectSettings|View and edit files in this project. Non-project members have only read access.',
);
},
cveIdRequestIsDisabled() {

View File

@ -18,7 +18,6 @@
line-height: 28px;
color: $gl-text-color-secondary;
border: 0;
border-bottom: 2px solid transparent;
white-space: nowrap;
&:hover,
@ -26,7 +25,7 @@
&:focus {
text-decoration: none;
color: $black;
border-bottom: 2px solid $gray-darkest;
box-shadow: inset 0 -2px 0 0 $gray-darkest;
}
}
@ -40,7 +39,7 @@
a.active {
color: $black;
font-weight: $gl-font-weight-bold;
border-bottom: 2px solid var(--gl-theme-accent, $theme-indigo-500);
box-shadow: inset 0 -2px 0 0 var(--gl-theme-accent, $theme-indigo-500);
.badge.badge-pill {
color: $black;

View File

@ -7,13 +7,6 @@
}
}
.gl-card-body {
@include media-breakpoint-up(sm) {
@include gl-pt-2;
min-height: 372px;
}
}
@include media-breakpoint-down(xs) {
.nav-controls {
@include gl-w-full;
@ -27,6 +20,13 @@
}
}
.cluster-card-item {
@include media-breakpoint-up(sm) {
@include gl-pt-2;
min-height: 372px;
}
}
.agent-activity-list {
.system-note .timeline-entry-inner {
.timeline-icon {

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class Admin::RequestsProfilesController < Admin::ApplicationController
feature_category :not_owned
def index
@profile_token = Gitlab::RequestProfiler.profile_token
@profiles = Gitlab::RequestProfiler.all.group_by(&:request_path)
end
def show
clean_name = Rack::Utils.clean_path_info(params[:name])
profile = Gitlab::RequestProfiler.find(clean_name)
unless profile && profile.content_type
return redirect_to admin_requests_profiles_path, alert: 'Profile not found'
end
send_file profile.file_path, type: "#{profile.content_type}; charset=utf-8", disposition: 'inline'
end
end

View File

@ -6,6 +6,7 @@ class Projects::ClustersController < Clusters::ClustersController
before_action do
push_frontend_feature_flag(:prometheus_computed_alerts)
push_frontend_feature_flag(:show_gitlab_agent_feedback, type: :ops, default_enabled: :yaml)
end
layout 'project'

View File

@ -67,11 +67,11 @@ class Projects::CommitsController < Projects::ApplicationController
def set_commits
render_404 unless @path.empty? || request.format == :atom || @repository.blob_at(@commit.id, @path) || @repository.tree(@commit.id, @path).entries.present?
limit = params[:limit].to_i
limit = permitted_params[:limit].to_i
@limit = limit > 0 ? limit : COMMITS_DEFAULT_LIMIT # limit can only ever be a positive number
@offset = (params[:offset] || 0).to_i
search = params[:search]
author = params[:author]
@offset = (permitted_params[:offset] || 0).to_i
search = permitted_params[:search]
author = permitted_params[:author]
@commits =
if search.present?
@ -87,4 +87,8 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @commits.with_latest_pipeline(@ref)
@commits = set_commits_for_rendering(@commits)
end
def permitted_params
params.permit(:limit, :offset, :search, :author)
end
end

View File

@ -13,8 +13,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
def index
@sort = params[:sort].presence || sort_value_name
@skip_groups = @project.invited_group_ids
@skip_groups += @project.group.self_and_ancestors_ids if @project.group
@skip_groups = @project.related_group_ids
@group_links = @project.project_group_links
@group_links = @group_links.search(params[:search_groups]) if params[:search_groups].present?

View File

@ -25,7 +25,7 @@ module Projects
if result[:status] == :success
flash[:notice] = _('Repository cleanup has started. You will receive an email once the cleanup operation is complete.')
else
flash[:alert] = status.fetch(:message, _('Failed to upload object map file'))
flash[:alert] = result.fetch(:message, _('Failed to upload object map file'))
end
redirect_to project_settings_repository_path(project)

View File

@ -21,6 +21,11 @@ module InviteMembersHelper
end
def group_select_data(group)
# This should only be used for groups to load the invite group modal.
# For instance the invite groups modal should not call this from a project scope
# this is only to be called in scope of a group context as noted in this thread
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79036#note_821465513
# the group sharing in projects disabling is explained there as well
if group.root_ancestor.namespace_settings.prevent_sharing_groups_outside_hierarchy
{ groups_filter: 'descendant_groups', parent_id: group.root_ancestor.id }
else
@ -32,7 +37,8 @@ module InviteMembersHelper
dataset = {
id: source.id,
name: source.name,
default_access_level: Gitlab::Access::GUEST
default_access_level: Gitlab::Access::GUEST,
invalid_groups: source.related_group_ids
}
if show_invite_members_for_task?(source)

View File

@ -56,7 +56,7 @@ module NavHelper
end
def admin_monitoring_nav_links
%w(system_info background_migrations background_jobs health_check)
%w(system_info background_migrations background_jobs health_check requests_profiles)
end
def admin_analytics_nav_links

View File

@ -2618,6 +2618,14 @@ class Project < ApplicationRecord
[project&.id, root_group&.id]
end
def related_group_ids
ids = invited_group_ids
ids += group.self_and_ancestors_ids if group
ids
end
def package_already_taken?(package_name, package_version, package_type:)
Packages::Package.with_name(package_name)
.with_version(package_version)

View File

@ -40,8 +40,9 @@ module Users
profile_personal_access_token_expiry: 37, # EE-only
terraform_notification_dismissed: 38,
security_newsletter_callout: 39,
verification_reminder: 40, # EE-only
ci_deprecation_warning_for_types_keyword: 41
verification_reminder: 40, # EE-only
ci_deprecation_warning_for_types_keyword: 41,
security_training_feature_promotion: 42 # EE-only
}
validates :feature_name,

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Security
module CiConfiguration
class ContainerScanningCreateService < ::Security::CiConfiguration::BaseCreateService
private
def action
Security::CiConfiguration::ContainerScanningBuildAction.new(project.auto_devops_enabled?, existing_gitlab_ci_content).generate
end
def next_branch
'set-container-scanning-config'
end
def message
_('Configure Container Scanning in `.gitlab-ci.yml`, creating this file if it does not already exist')
end
def description
_('Configure Container Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings) to customize Container Scanning settings.')
end
end
end
end

View File

@ -0,0 +1,22 @@
- page_title _('Requests Profiles')
%h3.page-title
= page_title
.bs-callout.clearfix
= html_escape(_('Pass the header %{codeOpen} X-Profile-Token: %{profile_token} %{codeClose} to profile the request')) % { profile_token: @profile_token, codeOpen: '<code>'.html_safe, codeClose: '</code>'.html_safe }
- if @profiles.present?
.gl-mt-3
- @profiles.each do |path, profiles|
.card
.card-header
%code= path
%ul.content-list
- profiles.each do |profile|
%li
= link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize,
admin_requests_profile_path(profile)
- else
%p
= _('No profiles found')

View File

@ -103,6 +103,10 @@
= link_to admin_health_check_path, title: _('Health Check') do
%span
= _('Health Check')
= nav_link(controller: :requests_profiles) do
= link_to admin_requests_profiles_path, title: _('Requests Profiles') do
%span
= _('Requests Profiles')
- if Gitlab::CurrentSettings.current_application_settings.grafana_enabled?
= nav_link do
= link_to Gitlab::CurrentSettings.current_application_settings.grafana_url, target: '_blank', title: _('Metrics Dashboard'), rel: 'noopener noreferrer' do

View File

@ -597,6 +597,15 @@
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:requests_profiles
:worker_name: RequestsProfilesWorker
:feature_category: :source_code_management
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
:tags: []
- :name: cronjob:schedule_merge_request_cleanup_refs
:worker_name: ScheduleMergeRequestCleanupRefsWorker
:feature_category: :code_review

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class RequestsProfilesWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
data_consistency :always
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
include CronjobQueue
# rubocop:enable Scalability/CronWorkerContext
feature_category :source_code_management
def perform
Gitlab::RequestProfiler.remove_all_profiles
end
end

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346124
milestone: '14.6'
type: development
group: group::pipeline execution
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,8 @@
---
name: show_gitlab_agent_feedback
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78567
rollout_issue_url:
milestone: '14.8'
type: ops
group: group::configure
default_enabled: true

View File

@ -479,6 +479,9 @@ Settings.cron_jobs['import_export_project_cleanup_worker']['job_class'] = 'Impor
Settings.cron_jobs['ci_archive_traces_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ci_archive_traces_cron_worker']['cron'] ||= '17 * * * *'
Settings.cron_jobs['ci_archive_traces_cron_worker']['job_class'] = 'Ci::ArchiveTracesCronWorker'
Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
Settings.cron_jobs['remove_expired_members_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['remove_expired_members_worker']['cron'] ||= '10 0 * * *'
Settings.cron_jobs['remove_expired_members_worker']['job_class'] = 'RemoveExpiredMembersWorker'

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
Rails.application.configure do |config|
config.middleware.use(Gitlab::RequestProfiler::Middleware)
config.middleware.use(Gitlab::Middleware::Speedscope)
end

View File

@ -1,23 +0,0 @@
---
data_category: operational
key_path: usage_activity_by_stage_monthly.secure.user_sast_jobs
description: Users who run a SAST job
product_section: sec
product_stage: secure
product_group: group::static analysis
product_category: static_application_security_testing
value_type: number
status: active
time_frame: 28d
data_source: database
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
performance_indicator_type:
- gmau
- paid_gmau
milestone: "<13.9"

View File

@ -1,23 +0,0 @@
---
data_category: operational
key_path: usage_activity_by_stage_monthly.secure.user_secret_detection_jobs
description: Users who run a Secret Detection job
product_section: sec
product_stage: secure
product_group: group::static analysis
product_category: secret_detection
value_type: number
status: active
time_frame: 28d
data_source: database
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
performance_indicator_type:
- gmau
- paid_gmau
milestone: "<13.9"

View File

@ -1,20 +0,0 @@
---
data_category: operational
key_path: usage_activity_by_stage.secure.user_sast_jobs
description: Count of SAST jobs per user
product_section: sec
product_stage: secure
product_group: group::static analysis
product_category: static_application_security_testing
value_type: number
status: active
time_frame: all
data_source: database
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
milestone: "<13.9"

View File

@ -1,20 +0,0 @@
---
data_category: operational
key_path: usage_activity_by_stage.secure.user_secret_detection_jobs
description: Count of Secret Detection Jobs per user
product_section: sec
product_stage: secure
product_group: group::static analysis
product_category: secret_detection
value_type: number
status: active
time_frame: all
data_source: database
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
milestone: "<13.9"

View File

@ -100,6 +100,7 @@ namespace :admin do
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.(html|txt)/ }
resources :projects, only: [:index]

View File

@ -0,0 +1,18 @@
- name: "REST API Runner will not accept `status` filter values of `active` or `paused`"
announcement_milestone: "14.8" # The milestone when this feature was first announced as deprecated.
announcement_date: "2022-02-22"
removal_milestone: "15.0" # the milestone when this feature is planned to be removed
removal_date: "2022-05-22" # the date of the milestone release when this feature is planned to be removed
breaking_change: true
body: | # Do not modify this line, instead modify the lines below.
The GitLab Runner REST endpoints will stop accepting `paused` or `active` as a status value in GitLab 15.0.
A runner's status will only relate to runner contact status, such as: `online`, `offline`.
Status values `paused` or `active` will no longer be accepted and will be replaced by the `paused` query parameter.
When checking for paused runners, API users are advised to specify `paused=true` as the query parameter.
When checking for active runners, specify `paused=false`.
stage: Verify
tiers: [Core, Premium, Ultimate]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/351109
documentation_url: https://docs.gitlab.com/ee/api/runners.html

View File

@ -2,7 +2,6 @@
class MoveContainerRegistryEnabledToProjectFeatures3 < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::DynamicModelHelpers
BATCH_SIZE = 21_000
MIGRATION = 'MoveContainerRegistryEnabledToProjectFeature'

View File

@ -10,7 +10,7 @@ class CleanUpPendingBuildsTable < ActiveRecord::Migration[6.0]
def up
return unless Gitlab.dev_or_test_env? || Gitlab.com?
each_batch_range('ci_pending_builds', of: BATCH_SIZE) do |min, max|
each_batch_range('ci_pending_builds', connection: connection, of: BATCH_SIZE) do |min, max|
execute <<~SQL
DELETE FROM ci_pending_builds
USING ci_builds

View File

@ -8,7 +8,7 @@ class MigrateProtectedAttributeToPendingBuilds < ActiveRecord::Migration[6.1]
def up
return unless Gitlab.dev_or_test_env? || Gitlab.com?
each_batch_range('ci_pending_builds', of: 1000) do |min, max|
each_batch_range('ci_pending_builds', connection: connection, of: 1000) do |min, max|
execute <<~SQL
UPDATE ci_pending_builds
SET protected = true

View File

@ -1029,7 +1029,7 @@ Considering these, you should carefully plan your PostgreSQL upgrade:
```
NOTE:
`gitlab-ctl pg-upgrade` tries to detect the role of the node. If for any reason the auto-detection does not work or you believe it did not detect the role correctly, you can use the `--leader` or `--replica` arguments to manually override it.
On a Geo secondary site, the Patroni leader node is called `standby leader`.
1. Stop Patroni **only on replicas**.
@ -1049,6 +1049,11 @@ Considering these, you should carefully plan your PostgreSQL upgrade:
sudo gitlab-ctl pg-upgrade -V 12
```
NOTE:
`gitlab-ctl pg-upgrade` tries to detect the role of the node. If for any reason the auto-detection
does not work or you believe it did not detect the role correctly, you can use the `--leader` or
`--replica` arguments to manually override it.
1. Check the status of the leader and cluster. You can proceed only if you have a healthy leader:
```shell

View File

@ -1018,6 +1018,30 @@ Input type: `CommitCreateInput`
| <a id="mutationcommitcreatecontent"></a>`content` | [`[String!]`](#string) | Contents of the commit. |
| <a id="mutationcommitcreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.configureContainerScanning`
Configure Container Scanning for a project by enabling Container Scanning in a new or modified
`.gitlab-ci.yml` file in a new branch. The new branch and a URL to
create a merge request are part of the response.
Input type: `ConfigureContainerScanningInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationconfigurecontainerscanningclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationconfigurecontainerscanningprojectpath"></a>`projectPath` | [`ID!`](#id) | Full path of the project. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationconfigurecontainerscanningbranch"></a>`branch` | [`String`](#string) | Branch that has the new/modified `.gitlab-ci.yml` file. |
| <a id="mutationconfigurecontainerscanningclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationconfigurecontainerscanningerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationconfigurecontainerscanningsuccesspath"></a>`successPath` | [`String`](#string) | Redirect path to use when the response is successful. |
### `Mutation.configureDependencyScanning`
Configure Dependency Scanning for a project by enabling Dependency Scanning in a new or modified
@ -17834,6 +17858,7 @@ Name of the feature that the callout is for.
| <a id="usercalloutfeaturenameenumsecurity_configuration_devops_alert"></a>`SECURITY_CONFIGURATION_DEVOPS_ALERT` | Callout feature name for security_configuration_devops_alert. |
| <a id="usercalloutfeaturenameenumsecurity_configuration_upgrade_banner"></a>`SECURITY_CONFIGURATION_UPGRADE_BANNER` | Callout feature name for security_configuration_upgrade_banner. |
| <a id="usercalloutfeaturenameenumsecurity_newsletter_callout"></a>`SECURITY_NEWSLETTER_CALLOUT` | Callout feature name for security_newsletter_callout. |
| <a id="usercalloutfeaturenameenumsecurity_training_feature_promotion"></a>`SECURITY_TRAINING_FEATURE_PROMOTION` | Callout feature name for security_training_feature_promotion. |
| <a id="usercalloutfeaturenameenumsuggest_pipeline"></a>`SUGGEST_PIPELINE` | Callout feature name for suggest_pipeline. |
| <a id="usercalloutfeaturenameenumsuggest_popover_dismissed"></a>`SUGGEST_POPOVER_DISMISSED` | Callout feature name for suggest_popover_dismissed. |
| <a id="usercalloutfeaturenameenumtabs_position_highlight"></a>`TABS_POSITION_HIGHLIGHT` | Callout feature name for tabs_position_highlight. |

View File

@ -44,7 +44,7 @@ placed on database-backed diffs. [Limits inherent to Gitaly](../development/diff
still apply.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/349031) in GitLab 14.7,
field `merge_user` can be either user who merged this merge request,
field `merge_user` can be either user who merged this merge request,
user who set it to merge when pipeline succeeds or `null`.
Field `merged_by` (user who merged this merge request or `null`) has been deprecated.
@ -1116,7 +1116,7 @@ POST /projects/:id/merge_requests
| `milestone_id` | integer | no | The global ID of a milestone. |
| `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging. |
| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch. |
| `allow_maintainer_to_push` | boolean | no | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/22665), see `allow_collaboration`. |
| `allow_maintainer_to_push` | boolean | no | Alias of `allow_collaboration`. |
| `squash` | boolean | no | Squash commits into a single commit when merging. |
```json
@ -1278,7 +1278,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid
| `squash` | boolean | no | Squash commits into a single commit when merging. |
| `discussion_locked` | boolean | no | Flag indicating if the merge request's discussion is locked. If the discussion is locked only project members can add, edit or resolve comments. |
| `allow_collaboration` | boolean | no | Allow commits from members who can merge to the target branch. |
| `allow_maintainer_to_push` | boolean | no | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/22665), see `allow_collaboration`. |
| `allow_maintainer_to_push` | boolean | no | Alias of `allow_collaboration`. |
Must include at least one non-required attribute from above.

View File

@ -39,21 +39,27 @@ Get a list of specific runners available to the user.
GET /runners
GET /runners?scope=active
GET /runners?type=project_type
GET /runners?status=active
GET /runners?status=online
GET /runners?paused=true
GET /runners?tag_list=tag1,tag2
```
| Attribute | Type | Required | Description |
|-------------|----------------|----------|---------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
| `tag_list` | string array | no | List of the runner's tags |
| Attribute | Type | Required | Description |
|------------|--------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 15.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners"
```
NOTE:
The `active` and `paused` values in the `status` query parameter were deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). They are replaced by the `paused` query parameter.
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
@ -98,21 +104,27 @@ is restricted to users with the administrator role.
GET /runners/all
GET /runners/all?scope=online
GET /runners/all?type=project_type
GET /runners/all?status=active
GET /runners/all?status=online
GET /runners/all?paused=true
GET /runners/all?tag_list=tag1,tag2
```
| Attribute | Type | Required | Description |
|-------------|----------------|----------|---------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online`, `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
| `tag_list` | string array | no | List of the runner's tags |
| Attribute | Type | Required | Description |
|------------|--------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of runners to show, one of: `specific`, `shared`, `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 15.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/runners/all"
```
NOTE:
The `active` and `paused` values in the `status` query parameter were deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). They are replaced by the `paused` query parameter.
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
@ -443,22 +455,28 @@ are listed if at least one shared runner is defined.
GET /projects/:id/runners
GET /projects/:id/runners?scope=active
GET /projects/:id/runners?type=project_type
GET /projects/:id/runners?status=active
GET /projects/:id/runners/all?status=online
GET /projects/:id/runners/all?paused=true
GET /projects/:id/runners?tag_list=tag1,tag2
```
| Attribute | Type | Required | Description |
|------------|----------------|----------|---------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online`, `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
| `tag_list` | string array | no | List of the runner's tags |
| Attribute | Type | Required | Description |
|------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user |
| `scope` | string | no | Deprecated: Use `type` or `status` instead. The scope of specific runners to show, one of: `active`, `paused`, `online` and `offline`; showing all runners if none provided |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 15.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/9/runners"
```
NOTE:
The `active` and `paused` values in the `status` query parameter were deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). They are replaced by the `paused` query parameter.
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.
@ -555,21 +573,27 @@ Shared runners are listed if at least one shared runner is defined.
```plaintext
GET /groups/:id/runners
GET /groups/:id/runners?type=group_type
GET /groups/:id/runners?status=active
GET /groups/:id/runners/all?status=online
GET /groups/:id/runners/all?paused=true
GET /groups/:id/runners?tag_list=tag1,tag2
```
| Attribute | Type | Required | Description |
|------------|----------------|----------|---------------------|
| `id` | integer | yes | The ID of the group owned by the authenticated user |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `active`, `paused`, `online`, `offline` |
| `tag_list` | string array | no | List of the runner's tags |
| Attribute | Type | Required | Description |
|------------|----------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `id` | integer | yes | The ID of the group owned by the authenticated user |
| `type` | string | no | The type of runners to show, one of: `instance_type`, `group_type`, `project_type` |
| `status` | string | no | The status of runners to show, one of: `online` and `offline`. `active` and `paused` are also possible values which were deprecated in GitLab 14.8 and will be removed in GitLab 15.0 |
| `paused` | boolean | no | Whether to include only runners that are accepting or ignoring new jobs |
| `tag_list` | string array | no | List of the runner's tags |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/9/runners"
```
NOTE:
The `active` and `paused` values in the `status` query parameter were deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). They are replaced by the `paused` query parameter.
NOTE:
The `active` attribute in the response was deprecated [in GitLab 14.8](https://gitlab.com/gitlab-org/gitlab/-/issues/347211).
and will be removed in [GitLab 15.0](https://gitlab.com/gitlab-org/gitlab/-/issues/351109). It is replaced by the `paused` attribute.

View File

@ -13,7 +13,7 @@ The Service Data API is associated with [Service Ping](../development/service_pi
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57270) in GitLab 13.11.
Export all metric definitions as a single YAML file, similar to the [Metrics Dictionary](https://metrics.gitlab.com/index.html), for easier importing.
Export all metric definitions as a single YAML file, similar to the [Metrics Dictionary](https://metrics.gitlab.com/), for easier importing.
```plaintext
GET /usage_data/metric_definitions

View File

@ -30,7 +30,7 @@ Include the following information:
After you create the identity provider, configure a [web identity role](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html) with conditions for limiting access to GitLab resources. Temporary credentials are obtained using [AWS Security Token Service](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html), so set the `Action` to [sts:AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html).
For the full list of supported filtering types, see [Connect to cloud services](../index.md).
For the full list of supported filtering types, see [Connect to cloud services](../index.md#configure-a-conditional-role-with-oidc-claims).
```json
{

View File

@ -194,7 +194,8 @@ artifacts:
### Add untracked files to artifacts
Use [`artifacts:untracked`](../yaml/index.md#artifactsuntracked) to add all Git untracked
files as artifacts (along with the paths defined in [`artifacts:paths`](../yaml/index.md#artifactspaths)).
files as artifacts (along with the paths defined in [`artifacts:paths`](../yaml/index.md#artifactspaths)). Untracked
files are those that haven't been added to the repository but exist in the repository checkout.
Save all Git untracked files and files in `binaries`:

View File

@ -939,7 +939,7 @@ rspec:
Use `artifacts:untracked` to add all Git untracked files as artifacts (along
with the paths defined in `artifacts:paths`). `artifacts:untracked` ignores configuration
in the repository's `.gitignore` file.
in the repository's `.gitignore`, so matching artifacts in `.gitignore` are included.
**Keyword type**: Job keyword. You can use it only as part of a job or in the
[`default` section](#default).

View File

@ -194,6 +194,10 @@ CI/CD is always uppercase. No need to spell it out on first use.
Use **CI/CD minutes** instead of **CI minutes**, **pipeline minutes**, **pipeline minutes quota**, or
**CI pipeline minutes**. This decision was made in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/342813).
## CI/CD tunnel
Use lowercase for **tunnel** in **CI/CD tunnel**.
## click
Do not use **click**. Instead, use **select** with buttons, links, menu items, and lists.

View File

@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Metrics Dictionary Guide
[Service Ping](index.md) metrics are defined in the
[Metrics Dictionary](https://metrics.gitlab.com/index.html).
[Metrics Dictionary](https://metrics.gitlab.com/).
This guide describes the dictionary and how it's implemented.
## Metrics Definition and validation

View File

@ -14,7 +14,7 @@ general best practices for code reviews, refer to our [code review guide](../cod
## Resources for reviewers
- [Service Ping Guide](index.md)
- [Metrics Dictionary](https://metrics.gitlab.com/index.html)
- [Metrics Dictionary](https://metrics.gitlab.com/)
## Review process

View File

@ -170,12 +170,12 @@ Snowplow JavaScript adds [web-specific parameters](https://docs.snowplowanalytic
For different stages in the processing pipeline, there are several tools that monitor Snowplow events tracking:
- [Product Intelligence Grafana dashboard](https://dashboards.gitlab.net/d/product-intelligence-main/product-intelligence-product-intelligence?orgId=1) monitors backend events sent from GitLab.com instance to collectors fleet. This dashboard provides information about:
- [Product Intelligence Grafana dashboard](https://dashboards.gitlab.net/d/product-intelligence-main/product-intelligence-product-intelligence?orgId=1) monitors backend events sent from GitLab.com instance to collectors fleet. This dashboard provides information about:
- The number of events that successfully reach Snowplow collectors.
- The number of events that failed to reach Snowplow collectors.
- The number of events that failed to reach Snowplow collectors.
- The number of backend events that were sent.
- [AWS CloudWatch dashboard](https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#dashboards:name=SnowPlow;start=P3D) monitors the state of the events processing pipeline. The pipeline starts from Snowplow collectors, through to enrichers and pseudonymization, and up to persistence on S3 bucket from which events are imported to Snowflake Data Warehouse. To view this dashboard AWS access is required, follow this [instruction](https://gitlab.com/gitlab-org/growth/product-intelligence/snowplow-pseudonymization#monitoring) if you are interested in getting one.
- [SiSense dashboard](https://app.periscopedata.com/app/gitlab/417669/Snowplow-Summary-Dashboard) provides information about the number of good and bad events imported into the Data Warehouse, in addition to the total number of imported Snowplow events.
- [SiSense dashboard](https://app.periscopedata.com/app/gitlab/417669/Snowplow-Summary-Dashboard) provides information about the number of good and bad events imported into the Data Warehouse, in addition to the total number of imported Snowplow events.
For more information, see this [video walk-through](https://www.youtube.com/watch?v=NxPS0aKa_oU).
@ -183,7 +183,7 @@ For more information, see this [video walk-through](https://www.youtube.com/watc
- [Snowplow data structure](https://docs.snowplowanalytics.com/docs/understanding-your-pipeline/canonical-event/)
- [Our Iglu schema registry](https://gitlab.com/gitlab-org/iglu)
- [List of events used in our codebase (Event Dictionary)](https://metrics.gitlab.com/snowplow.html)
- [List of events used in our codebase (Event Dictionary)](https://metrics.gitlab.com/snowplow/)
- [Product Intelligence Guide](https://about.gitlab.com/handbook/product/product-intelligence-guide/)
- [Service Ping Guide](../service_ping/index.md)
- [Product Intelligence Direction](https://about.gitlab.com/direction/product-intelligence/)

View File

@ -14,7 +14,7 @@ general best practices for code reviews, refer to our [code review guide](../cod
## Resources for reviewers
- [Snowplow Guide](index.md)
- [Event Dictionary](https://metrics.gitlab.com/snowplow.html)
- [Event Dictionary](https://metrics.gitlab.com/snowplow/)
## Review process

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,330 @@
---
stage: Manage
group: Optimize
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Aggregated Value Stream Analytics
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335391) in GitLab 14.7.
DISCLAIMER:
This page contains information related to upcoming products, features, and functionality.
It is important to note that the information presented is for informational purposes only.
Please do not rely on this information for purchasing or planning purposes.
As with all projects, the items mentioned on this page are subject to change or delay.
The development, release, and timing of any products, features, or functionality remain at the
sole discretion of GitLab Inc.
This page provides a high-level overview of the aggregated backend for
Value Stream Analytics (VSA).
## Current Status
As of 14.8 the aggregated VSA backend is used only in the `gitlab-org` group, for testing purposes
. We plan to gradually roll it out in the next major release (15.0) for the rest of the groups.
## Motivation
The aggregated backend aims to solve the performance limitations of the VSA feature and set it up
for long-term growth.
Our main database is not prepared for analytical workloads. Executing long-running queries can
affect the reliability of the application. For large groups, the current
implementation (old backend) is slow and, in some cases, doesn't even load due to the configured
statement timeout (15s).
The database queries in the old backend use the core domain models directly through
`IssuableFinders` classes: ([MergeRequestsFinder](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/finders/merge_requests_finder.rb) and [IssuesFinder](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/finders/issues_finder.rb)).
With the requested change of the [date range filters](https://gitlab.com/groups/gitlab-org/-/epics/6046),
this approach was no longer viable from the performance point of view.
Benefits of the aggregated VSA backend:
- Simpler database queries (fewer JOINs).
- Faster aggregations, only a single table is accessed.
- Possibility to introduce further aggregations for improving the first page load time.
- Better performance for large groups (with many sub-groups, projects, issues and, merge requests).
- Ready for database decomposition. The VSA related database tables could live in a separate
database with a minimal development effort.
- Ready for keyset pagination which can be useful for exporting the data.
- Possibility to implement more complex event definitions.
- For example, the start event can be two timestamp columns where the earliest value would be
used by the system.
- Example: `MIN(issues.created_at, issues.updated_at)`
## How does Value Stream Analytics work?
Value Stream Analytics calculates the duration between two timestamp columns or timestamp
expressions and runs various aggregations on the data.
Examples:
- Duration between the Merge Request creation time and Merge Request merge time.
- Duration between the Issue creation time and Issue close time.
This duration is exposed in various ways:
- Aggregation: median, average
- Listing: list the duration for individual Merge Request and Issue records
Apart from the durations, we expose the record count within a stage.
### Stages
A stage represents an event pair (start and end events) with additional metadata, such as the name
of the stage. Stages are configurable by the user within the pairing rules defined in the backend.
**Example stage: Code Review**
- Start event identifier: Merge Request creation time
- Start event column: uses the `merge_requests.created_at` timestamp column.
- End event identifier: Merge Request merge time
- End event column: uses the `merge_request_metrics.merged_at` timestamp column.
- Stage event hash ID: a calculated hash for the pair of start and end event identifiers.
- If two stages have the same configuration of start and end events, then their stage event hash
IDs are identical.
- The stage event hash ID is later used to store the aggregated data in partitioned database tables.
### Value streams
Value streams are container objects for the stages. There can be multiple value streams per group
or project focusing on different aspects of the Dev Ops lifecycle.
### Example configuration
![vsa object hierarchy example](img/object_hierarchy_example_V14_10.png)
In this example, there are two independent value streams set up for two teams that are using
different development workflows within the `Test Group` (top-level namespace).
The first value stream uses standard timestamp-based events for defining the stages. The second
value stream uses label events.
Each value stream and stage item from the example will be persisted in the database. Notice that
the `Deployment` stage is identical for both value streams; that means that the underlying
`stage_event_hash_id` is the same for both stages. The `stage_event_hash_id` reduces
the amount of data the backend collects and plays a vital role in database partitioning.
We expect value streams and stages to be rarely changed. When stages (start and end events) are
changed, the aggregated data gets stale. This is fixed by the periodical aggregation occurring
every day.
### Feature availability
The aggregated VSA feature is available on the group and project level however, the aggregated
backend is only available for Premium and Ultimate customers due to data storage and data
computation costs. Storing de-normalized, aggregated data requires significant disk space.
## Aggregated value stream analytics architecture
The main idea behind the aggregated VSA backend is separation: VSA database tables and queries do
not use the core domain models directly (Issue, MergeRequest). This allows us to scale and
optimize VSA independently from the other parts of the application.
The architecture consists of two main mechanisms:
- Periodical data collection and loading (happens in the background).
- Querying the collected data (invoked by the user).
### Data loading
The aggregated nature of VSA comes from the periodical data loading. The system queries the core
domain models to collect the stage and timestamp data. This data is periodically inserted into the
VSA database tables.
High-level overview for each top-level namespace with Premium or Ultimate license:
1. Load all stages in the group.
1. Iterate over the issues and merge requests records.
1. Based on the stage configurations (start and end event identifiers) collect the timestamp data.
1. `INSERT` or `UPDATE` the data into the VSA database tables.
The data loading is implemented within the [`Analytics::CycleAnalytics::DataLoaderService`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/services/analytics/cycle_analytics/data_loader_service.rb)
class. There are groups containing a lot of data, so to avoid overloading the primary database,
the service performs operations in batches and enforces strict application limits:
- Load records in batches.
- Insert records in batches.
- Stop processing when a limit is reached, schedule a background job to continue the processing
later.
- Continue processing data from a specific point.
As of GitLab 14.7, the data loading is done manually. Once the feature is ready, the service will
be invoked periodically by the system via a cron job (this part is not implemented yet).
#### Record iteration
The batched iteration is implemented with the
[efficient IN operator](../database/efficient_in_operator_queries.md). The background job scans
all issues and merge request records in the group hierarchy ordered by the `updated_at` and the
`id` columns. For already aggregated groups, the `DataLoaderService` continues the aggregation
from a specific point which saves time.
Collecting the timestamp data happens on every iteration. The `DataLoaderService` determines which
stage events are configured within the group hierarchy and builds a query that selects the
required timestamps. The stage record knows which events are configured and the events know how to
select the timestamp columns.
Example for collected stage events: merge request merged, merge request created, merge request
closed
Generated SQL query for loading the timestamps:
```sql
SELECT
-- the list of columns depends on the configured stages
"merge_request_metrics"."merged_at",
"merge_requests"."created_at",
"merge_request_metrics"."latest_closed_at"
FROM "merge_requests"
LEFT OUTER JOIN "merge_request_metrics" ON "merge_request_metrics"."merge_request_id" = "merge_requests"."id"
WHERE "merge_requests"."id" IN (1, 2, 3, 4) -- ids are coming from the batching query
```
The `merged_at` column is located in a separate table (`merge_request_metrics`). The
`Gitlab::Analytics::CycleAnalytics::StagEvents::MergeRequestMerged` class adds itself to a scope
for loading the timestamp data without affecting the number of rows (uses `LEFT JOIN`). This
behavior is implemented for each `StageEvent` class with the `include_in` method.
The data collection query works on the event level. It extracts the event timestamps from the
stages and ensures that we don't collect the same data multiple times. The events mentioned above
could come from the following stage configuration:
- merge request created - merge request merged
- merge request created - merge request closed
Other combinations might be also possible, but we prevent the ones that make no sense, for example:
- merge request merged - merge request created
Creation time always happens first, so this stage always reports negative duration.
#### Data scope
The data collection scans and processes all issues and merge requests records in the group
hierarchy, starting from the top-level group. This means that if a group only has one value stream
in a sub-group, we nevertheless collect data of all issues and merge requests in the hierarchy of
this group. This aims to simplify the data collection mechanism. Moreover, data research shows
that most group hierarchies have their stages configured on the top level.
During the data collection process, the collected timestamp data is transformed into rows. For
each configured stage, if the start event timestamp is present, the system inserts or updates one
event record. This allows us to determine the upper limit of the inserted rows per group by
counting all issues and merge requests and multiplying the sum by the stage count.
#### Data consistency concerns
Due to the async nature of the data collection, data consistency issues are bound to happen. This
is a trade-off that makes the query performance significantly faster. We think that for analytical
workload a slight lag in the data is acceptable.
Before the rollout we plan to implement some indicators on the VSA page that shows the most
recent backend activities. For example, indicators that show the last data collection timestamp
and the last consistency check timestamp.
#### Database structure
VSA collects data for the following domain models: `Issue` and `MergeRequest`. To keep the
aggregated data separated, we use two additional database tables:
- `analytics_cycle_analytics_issue_stage_events`
- `analytics_cycle_analytics_merge_request_stage_events`
Both tables are hash partitioned by the `stage_event_hash_id`. Each table uses 32 partitions. It's
an arbitrary number and it could be changed. Important is to keep the partitions under 100GB in
size (which gives the feature a lot of headroom).
|Column|Description|
|-|-|
|`stage_event_hash_id`|partitioning key|
|`merge_request_id` or `issue_id`|reference to the domain record (Issuable)|
|`group_id`|reference to the group (de-normalization)|
|`project_id`|reference to the project|
|`milestone_id`|duplicated data from the domain record table|
|`author_id`|duplicated data from the domain record table|
|`state_id`|duplicated data from the domain record table|
|`start_event_timestamp`|timestamp derived from the stage configuration|
|`end_event_timestamp`|timestamp derived from the stage configuration|
With accordance to the data separation requirements, the table doesn't have any foreign keys. The
consistency is ensured by a background job (eventually consistent).
### Data querying
The base query always includes the following filters:
- `stage_event_hash_id` - partition key
- `project_id` or `group_id` - depending if it's a project or group level query
- `end_event_timestamp` - date range filter (last 30 days)
Example: Selecting review stage duration for the GitLab project
```sql
SELECT end_event_timestamp - start_event_timestamp
FROM analytics_cycle_analytics_merge_request_stage_events
WHERE
stage_event_hash_id = 16 AND -- hits a specific partition
project_id = 278964 AND
end_event_timestamp > '2022-01-01' AND end_event_timestamp < '2022-01-30'
```
#### Query generation
The query backend is hidden behind the same interface that the old backend implementation uses.
Thanks to this, we can easily switch between the old and new query backends.
- `DataCollector`: entrypoint for querying VSA data
- `BaseQueryBuilder`: provides the base `ActiveRecord` scope (filters are applied here).
- `average`: average aggregation.
- `median`: median aggregation.
- `count`: row counting.
- `records`: list of issue or merge request records.
#### Filters
VSA supports various filters on the base query. Most of the filters require no additional JOINs:
|Filter name|Description|
|-|-|
|`milestone_title`|The backend translates it to `milestone_id` filter|
|`author_username`|The backend translates it to `author_id` filter|
|`project_ids`|Only used on the group-level|
Exceptions: these filters are applied on other tables which means we `JOIN` them.
|Filter name|Description|
|-|-|
|`label_name`|Array filter, using the `label_links` table|
|`assignee_username`|Array filter, using the `*_assignees` table|
To fully decompose the database, the required ID values would need to be replicated in the VSA
database tables. This change could be implemented using array columns.
### Endpoints
The feature uses private JSON APIs for delivering the data to the frontend. On the first page load
, the following requests are invoked:
- Initial HTML page load which is mostly empty. Some configuration data is exposed via `data`
attributes.
- `value_streams` - Load the available value streams for the given group.
- `stages` - Load the stages for the currently selected value stream.
- `median` - For each stage, request the median duration.
- `count` - For each stage, request the number of items in the stage (this is a
[limit count](../merge_request_performance_guidelines.md#badge-counters), maximum 1000 rows).
- `average_duration_chart` - Data for the duration chart.
- `summary`, `time_summary` - Top-level aggregations, most of the metrics are using different APIs/
finders and not invoking the aggregated backend.
When clicking on a specific stage, the `records` endpoint is invoked, which returns the related
records (paginated) for the chosen stage in a specific order.
### Database decomposition
By separating the query logic from the main application code, the feature is ready for database
decomposition. If we decide that VSA requires a separate database instance, then moving the
aggregated tables can be accomplished with little effort.
A different database technology could also be used to further improve the performance of the
feature, for example [Timescale DB](https://www.timescale.com).

View File

@ -31,7 +31,7 @@ Documentation for GitLab instance administrators is under [LFS administration do
- Git LFS is supported in GitLab starting with version 8.2
- Git LFS must be enabled under project settings
- [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up
- [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up must be installed
## Known limitations

View File

@ -733,6 +733,24 @@ The `instanceStatisticsMeasurements` GraphQL node has been renamed to `usageTren
**Planned removal milestone: 15.0 (2022-05-22)**
### REST API Runner will not accept `status` filter values of `active` or `paused`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The GitLab Runner REST endpoints will stop accepting `paused` or `active` as a status value in GitLab 15.0.
A runner's status will only relate to runner contact status, such as: `online`, `offline`.
Status values `paused` or `active` will no longer be accepted and will be replaced by the `paused` query parameter.
When checking for paused runners, API users are advised to specify `paused=true` as the query parameter.
When checking for active runners, specify `paused=false`.
**Planned removal milestone: 15.0 (2022-05-22)**
### REST and GraphQL API Runner usage of `active` replaced by `paused`
WARNING:

View File

@ -222,6 +222,7 @@ You can [configure](#customizing-the-container-scanning-settings) analyzers by u
| `CS_DISABLE_DEPENDENCY_LIST` | `"false"` | Disable Dependency Scanning for packages installed in the scanned image. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345434) in GitLab 14.6. | All |
| `CS_DISABLE_LANGUAGE_VULNERABILITY_SCAN` | `"true"` | Disable scanning for language-specific packages installed in the scanned image. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345434) in GitLab 14.6. | All |
| `CS_DOCKER_INSECURE` | `"false"` | Allow access to secure Docker registries using HTTPS without validating the certificates. | All |
| `CS_IGNORE_UNFIXED` | `"false"` | Ignore vulnerabilities that are not fixed. | All |
| `CS_REGISTRY_INSECURE` | `"false"` | Allow access to insecure registries (HTTP only). Should only be set to `true` when testing the image locally. Works with all scanners, but the registry must listen on port `80/tcp` for Trivy to work. | All |
| `CS_SEVERITY_THRESHOLD` | `UNKNOWN` | Severity level threshold. The scanner outputs vulnerabilities with severity level higher than or equal to this threshold. Supported levels are Unknown, Low, Medium, High, and Critical. | Trivy |
| `DOCKER_IMAGE` | `$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG` | The Docker image to be scanned. If set, this variable overrides the `$CI_APPLICATION_REPOSITORY` and `$CI_APPLICATION_TAG` variables. | All |

View File

@ -2,7 +2,6 @@
stage: Secure
group: Dynamic Analysis
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
type: reference, howto
---
# Coverage-guided fuzz testing **(ULTIMATE)**
@ -161,9 +160,9 @@ Example coverage-guided fuzzing report:
## Duration of coverage-guided fuzz testing
The available durations for coverage-guided fuzz testing are: 10 minutes (default) and 60 minutes.
The available durations for coverage-guided fuzz testing are:
- 10-minute duration: Recommended for the default branch.
- 10-minute duration (default): Recommended for the default branch.
- 60-minute duration: Recommended for the development branch and merge requests. The longer duration provides greater coverage.
In the `COVFUZZ_ADDITIONAL_ARGS` variable set the value `--regression=true`.

View File

@ -41,7 +41,7 @@ PUT /projects/:id/packages/terraform/modules/:module-name/:module-system/:module
Provide the file content in the request body.
Note that, in the following example, the request must end with `/file`.
As the following example shows, requests must end with `/file`.
If you send a request ending with something else, it results in a 404
error `{"error":"404 Not Found"}`.

View File

@ -18,6 +18,7 @@ module API
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
@ -26,9 +27,7 @@ module API
get do
runners = current_user.ci_owned_runners
runners = filter_runners(runners, params[:scope], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
@ -41,6 +40,7 @@ module API
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
@ -51,9 +51,7 @@ module API
runners = ::Ci::Runner.all
runners = filter_runners(runners, params[:scope])
runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
runners = apply_filter(runners, params)
present paginate(runners), with: Entities::Ci::Runner
end
@ -163,6 +161,7 @@ module API
desc: 'The scope of specific runners to show'
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
@ -226,6 +225,7 @@ module API
params do
optional :type, type: String, values: ::Ci::Runner::AVAILABLE_TYPES,
desc: 'The type of the runners to show'
optional :paused, type: Boolean, desc: 'Whether to include only runners that are accepting or ignoring new jobs'
optional :status, type: String, values: ::Ci::Runner::AVAILABLE_STATUSES,
desc: 'The status of the runners to show'
optional :tag_list, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'The tags of the runners to show'
@ -313,6 +313,7 @@ module API
def apply_filter(runners, params)
runners = filter_runners(runners, params[:type], allowed_scopes: ::Ci::Runner::AVAILABLE_TYPES)
runners = filter_runners(runners, params[:status], allowed_scopes: ::Ci::Runner::AVAILABLE_STATUSES)
runners = filter_runners(runners, params[:paused] ? 'paused' : 'active', allowed_scopes: %w[paused active]) if params.include?(:paused)
runners = runners.tagged_with(params[:tag_list]) if params[:tag_list]
runners

View File

@ -9,7 +9,7 @@ module Gitlab
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, stop_id, batch_table, batch_column, sub_batch_size, pause_ms)
parent_batch_relation = define_batchable_model(batch_table)
parent_batch_relation = define_batchable_model(batch_table, connection: connection)
.where(batch_column => start_id..stop_id)
parent_batch_relation.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|

View File

@ -29,7 +29,7 @@ module Gitlab
end
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
define_batchable_model(source_table)
define_batchable_model(source_table, connection: connection)
.where(source_key_column => start_id..stop_id)
.where(type: nil)
end

View File

@ -19,7 +19,7 @@ module Gitlab
# batch_size - The size of the next batch
# job_arguments - The migration job arguments
def next_batch(table_name, column_name, batch_min_value:, batch_size:, job_arguments:)
model_class = define_batchable_model(table_name)
model_class = define_batchable_model(table_name, connection: ActiveRecord::Base.connection)
quoted_column_name = model_class.connection.quote_column_name(column_name)
relation = model_class.where("#{quoted_column_name} >= ?", batch_min_value)

View File

@ -57,7 +57,7 @@ module Gitlab
end
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
define_batchable_model(source_table).where(source_key_column => start_id..stop_id)
define_batchable_model(source_table, connection: connection).where(source_key_column => start_id..stop_id)
end
def column_assignment_clauses(copy_from, copy_to)

View File

@ -15,7 +15,7 @@ module Gitlab
end
def orphaned_deployments
define_batchable_model('deployments')
define_batchable_model('deployments', connection: ActiveRecord::Base.connection)
.where('NOT EXISTS (SELECT 1 FROM environments WHERE deployments.environment_id = environments.id)')
end

View File

@ -10,7 +10,9 @@ module Gitlab
include Gitlab::Database::DynamicModelHelpers
def perform(start_id, stop_id)
define_batchable_model('vulnerability_finding_links').where(id: start_id..stop_id).delete_all
define_batchable_model('vulnerability_finding_links', connection: ActiveRecord::Base.connection)
.where(id: start_id..stop_id)
.delete_all
end
end
end

View File

@ -9,7 +9,9 @@ module Gitlab
BATCH_SIZE = 100
def perform(start_id, stop_id)
define_batchable_model('timelogs').where(spent_at: nil, id: start_id..stop_id).each_batch(of: 100) do |subbatch|
define_batchable_model('timelogs', connection: connection)
.where(spent_at: nil, id: start_id..stop_id)
.each_batch(of: 100) do |subbatch|
batch_start, batch_end = subbatch.pluck('min(id), max(id)').first
update_timelogs(batch_start, batch_end)
@ -25,9 +27,12 @@ module Gitlab
SQL
end
def execute(sql)
def connection
@connection ||= ::ActiveRecord::Base.connection
@connection.execute(sql)
end
def execute(sql)
connection.execute(sql)
end
end
end

View File

@ -17,6 +17,7 @@ module Gitlab
lib/gitlab/profiler.rb
lib/gitlab/query_limiting/
lib/gitlab/request_context.rb
lib/gitlab/request_profiler/
lib/gitlab/sidekiq_logging/
lib/gitlab/sidekiq_middleware/
lib/gitlab/sidekiq_status/

View File

@ -5,16 +5,19 @@ module Gitlab
module DynamicModelHelpers
BATCH_SIZE = 1_000
def define_batchable_model(table_name)
Class.new(ActiveRecord::Base) do
def define_batchable_model(table_name, connection:)
klass = Class.new(ActiveRecord::Base) do
include EachBatch
self.table_name = table_name
self.inheritance_column = :_type_disabled
end
klass.connection = connection
klass
end
def each_batch(table_name, scope: ->(table) { table.all }, of: BATCH_SIZE)
def each_batch(table_name, connection:, scope: ->(table) { table.all }, of: BATCH_SIZE)
if transaction_open?
raise <<~MSG.squish
each_batch should not run inside a transaction, you can disable
@ -23,12 +26,12 @@ module Gitlab
MSG
end
scope.call(define_batchable_model(table_name))
scope.call(define_batchable_model(table_name, connection: connection))
.each_batch(of: of) { |batch| yield batch }
end
def each_batch_range(table_name, scope: ->(table) { table.all }, of: BATCH_SIZE)
each_batch(table_name, scope: scope, of: of) do |batch|
def each_batch_range(table_name, connection:, scope: ->(table) { table.all }, of: BATCH_SIZE)
each_batch(table_name, connection: connection, scope: scope, of: of) do |batch|
yield batch.pluck('MIN(id), MAX(id)').first
end
end

View File

@ -9,6 +9,18 @@ module Gitlab
include RenameTableHelpers
include AsyncIndexes::MigrationHelpers
def define_batchable_model(table_name, connection: self.connection)
super(table_name, connection: connection)
end
def each_batch(table_name, connection: self.connection, **kwargs)
super(table_name, connection: connection, **kwargs)
end
def each_batch_range(table_name, connection: self.connection, **kwargs)
super(table_name, connection: connection, **kwargs)
end
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
MAX_IDENTIFIER_NAME_LENGTH = 63
DEFAULT_TIMESTAMP_COLUMNS = %i[created_at updated_at].freeze

View File

@ -49,7 +49,7 @@ module Gitlab
end
def relation_scoped_to_range(source_table, source_key_column, start_id, stop_id)
define_batchable_model(source_table)
define_batchable_model(source_table, connection: connection)
.where(source_key_column => start_id..stop_id)
end

View File

@ -5,7 +5,6 @@ module Gitlab
module PartitioningMigrationHelpers
module TableManagementHelpers
include ::Gitlab::Database::SchemaHelpers
include ::Gitlab::Database::DynamicModelHelpers
include ::Gitlab::Database::MigrationHelpers
include ::Gitlab::Database::Migrations::BackgroundMigrationHelpers

View File

@ -16,6 +16,7 @@ module Gitlab
lib/gitlab/middleware/
ee/lib/gitlab/middleware/
lib/gitlab/performance_bar/
lib/gitlab/request_profiler/
lib/gitlab/query_limiting/
lib/gitlab/tracing/
lib/gitlab/profiler.rb

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'fileutils'
module Gitlab
module RequestProfiler
PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
def all
Dir["#{PROFILES_DIR}/*.{html,txt}"].map do |path|
Profile.new(File.basename(path))
end.select(&:valid?)
end
module_function :all # rubocop: disable Style/AccessModifierDeclarations
def find(name)
file_path = File.join(PROFILES_DIR, name)
return unless File.exist?(file_path)
Profile.new(name)
end
module_function :find # rubocop: disable Style/AccessModifierDeclarations
def profile_token
Rails.cache.fetch('profile-token') do
Devise.friendly_token
end
end
module_function :profile_token # rubocop: disable Style/AccessModifierDeclarations
def remove_all_profiles
FileUtils.rm_rf(PROFILES_DIR)
end
module_function :remove_all_profiles # rubocop: disable Style/AccessModifierDeclarations
end
end

View File

@ -0,0 +1,107 @@
# frozen_string_literal: true
require 'ruby-prof'
require 'memory_profiler'
module Gitlab
module RequestProfiler
class Middleware
def initialize(app)
@app = app
end
def call(env)
if profile?(env)
call_with_profiling(env)
else
@app.call(env)
end
end
def profile?(env)
header_token = env['HTTP_X_PROFILE_TOKEN']
return unless header_token.present?
profile_token = Gitlab::RequestProfiler.profile_token
return unless profile_token.present?
header_token == profile_token
end
def call_with_profiling(env)
case env['HTTP_X_PROFILE_MODE']
when 'execution', nil
call_with_call_stack_profiling(env)
when 'memory'
call_with_memory_profiling(env)
else
raise ActionController::BadRequest, invalid_profile_mode(env)
end
end
def invalid_profile_mode(env)
<<~HEREDOC
Invalid X-Profile-Mode: #{env['HTTP_X_PROFILE_MODE']}.
Supported profile mode request header:
- X-Profile-Mode: execution
- X-Profile-Mode: memory
HEREDOC
end
def call_with_call_stack_profiling(env)
ret = nil
report = RubyProf::Profile.profile do
ret = catch(:warden) do # rubocop:disable Cop/BanCatchThrow
@app.call(env)
end
end
generate_report(env, 'execution', 'html') do |file|
printer = RubyProf::CallStackPrinter.new(report)
printer.print(file)
end
handle_request_ret(ret)
end
def call_with_memory_profiling(env)
ret = nil
report = MemoryProfiler.report do
ret = catch(:warden) do # rubocop:disable Cop/BanCatchThrow
@app.call(env)
end
end
generate_report(env, 'memory', 'txt') do |file|
report.pretty_print(to_file: file)
end
handle_request_ret(ret)
end
def generate_report(env, report_type, extension)
file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}"\
"_#{report_type}.#{extension}"
file_path = "#{PROFILES_DIR}/#{file_name}"
FileUtils.mkdir_p(PROFILES_DIR)
begin
File.open(file_path, 'wb') do |file|
yield(file)
end
rescue StandardError
FileUtils.rm(file_path)
end
end
def handle_request_ret(ret)
if ret.is_a?(Array)
ret
else
throw(:warden, ret) # rubocop:disable Cop/BanCatchThrow
end
end
end
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Gitlab
module RequestProfiler
class Profile
attr_reader :name, :time, :file_path, :request_path, :profile_mode, :type
alias_method :to_param, :name
def initialize(name)
@name = name
@file_path = File.join(PROFILES_DIR, name)
set_attributes
end
def valid?
@request_path.present?
end
def content_type
case type
when 'html'
'text/html'
when 'txt'
'text/plain'
end
end
private
def set_attributes
matches = name.match(/^(?<path>.*)_(?<timestamp>\d+)(_(?<profile_mode>\w+))?\.(?<type>html|txt)$/)
return unless matches
@request_path = matches[:path].tr('|', '/')
@time = Time.at(matches[:timestamp].to_i).utc
@profile_mode = matches[:profile_mode] || 'unknown'
@type = matches[:type]
end
end
end
end

View File

@ -42,6 +42,7 @@ module Security
# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings
# Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings
# Note that environment variables can be set in several places
# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
YAML

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Security
module CiConfiguration
class ContainerScanningBuildAction < BaseBuildAction
private
def update_existing_content!
@existing_gitlab_ci_content['include'] = generate_includes
end
def template
return 'Auto-DevOps.gitlab-ci.yml' if @auto_devops_enabled
'Security/Container-Scanning.gitlab-ci.yml'
end
def comment
<<~YAML
#{super}
# container_scanning:
# variables:
# DOCKER_IMAGE: ...
# DOCKER_USER: ...
# DOCKER_PASSWORD: ...
YAML
end
end
end
end

View File

@ -7587,7 +7587,7 @@ msgstr ""
msgid "ClusterAgents|All"
msgstr ""
msgid "ClusterAgents|An error occurred while loading your GitLab Agents"
msgid "ClusterAgents|An error occurred while loading your Agents"
msgstr ""
msgid "ClusterAgents|An error occurred while loading your agent"
@ -7674,6 +7674,9 @@ msgstr ""
msgid "ClusterAgents|GitLab Agent for Kubernetes"
msgstr ""
msgid "ClusterAgents|Give feedback"
msgstr ""
msgid "ClusterAgents|Go to the repository files"
msgstr ""
@ -7758,6 +7761,9 @@ msgstr ""
msgid "ClusterAgents|Select an agent to register with GitLab"
msgstr ""
msgid "ClusterAgents|Tell us what you think"
msgstr ""
msgid "ClusterAgents|The GitLab Agent provides an increased level of security when connecting Kubernetes clusters to GitLab. %{linkStart}Learn more about the GitLab Agent.%{linkEnd}"
msgstr ""
@ -7799,6 +7805,9 @@ msgstr ""
msgid "ClusterAgents|View all %{number} clusters"
msgstr ""
msgid "ClusterAgents|We would love to learn more about your experience with the GitLab Agent."
msgstr ""
msgid "ClusterAgents|What is GitLab Agent activity?"
msgstr ""
@ -9034,6 +9043,12 @@ msgstr ""
msgid "Configure CAPTCHAs, IP address limits, and other anti-spam measures."
msgstr ""
msgid "Configure Container Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings) to customize Container Scanning settings."
msgstr ""
msgid "Configure Container Scanning in `.gitlab-ci.yml`, creating this file if it does not already exist"
msgstr ""
msgid "Configure Dependency Scanning in `.gitlab-ci.yml` using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings) to customize Dependency Scanning settings."
msgstr ""
@ -10856,10 +10871,10 @@ msgstr ""
msgid "DORA4Metrics|Deployment frequency"
msgstr ""
msgid "DORA4Metrics|Lead time"
msgid "DORA4Metrics|Lead time for changes"
msgstr ""
msgid "DORA4Metrics|Median lead time"
msgid "DORA4Metrics|Median (last %{days}d)"
msgstr ""
msgid "DORA4Metrics|No merge requests were deployed during this period"
@ -24234,6 +24249,9 @@ msgstr ""
msgid "No prioritized labels with such name or description"
msgstr ""
msgid "No profiles found"
msgstr ""
msgid "No project subscribes to the pipelines in this project."
msgstr ""
@ -25781,6 +25799,9 @@ msgstr ""
msgid "Pass job variables"
msgstr ""
msgid "Pass the header %{codeOpen} X-Profile-Token: %{profile_token} %{codeClose} to profile the request"
msgstr ""
msgid "Passed"
msgstr ""
@ -28331,7 +28352,7 @@ msgstr ""
msgid "ProjectSettings|View and edit files in this project."
msgstr ""
msgid "ProjectSettings|View and edit files in this project. Non-project members will only have read access."
msgid "ProjectSettings|View and edit files in this project. Non-project members have only read access."
msgstr ""
msgid "ProjectSettings|View project analytics."
@ -30382,6 +30403,9 @@ msgstr ""
msgid "Requests"
msgstr ""
msgid "Requests Profiles"
msgstr ""
msgid "Requests for pages at %{code_start}%{help_text_url}%{code_end} redirect to the URL. The destination must meet certain requirements. %{docs_link_start}Learn more.%{docs_link_end}"
msgstr ""
@ -34786,9 +34810,6 @@ msgstr ""
msgid "Sunday"
msgstr ""
msgid "SuperSonics|Activate cloud license"
msgstr ""
msgid "SuperSonics|Activate subscription"
msgstr ""
@ -34813,6 +34834,9 @@ msgstr ""
msgid "SuperSonics|Cloud licensing is now available. It's an easier way to activate instances and manage subscriptions. Read more about it in our %{blogPostLinkStart}blog post%{blogPostLinkEnd}. Activation codes are available in the %{portalLinkStart}Customers Portal%{portalLinkEnd}."
msgstr ""
msgid "SuperSonics|Enter activation code"
msgstr ""
msgid "SuperSonics|Export license usage file"
msgstr ""

View File

@ -13,10 +13,10 @@ module QA
let(:package_version) { '1.3.7' }
let(:package_type) { 'maven_gradle' }
where(:authentication_token_type, :maven_header_name) do
:personal_access_token | 'Private-Token'
:ci_job_token | 'Job-Token'
:project_deploy_token | 'Deploy-Token'
where(:authentication_token_type, :maven_header_name, :testcase) do
:personal_access_token | 'Private-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347601'
:ci_job_token | 'Job-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347603'
:project_deploy_token | 'Deploy-Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347602'
end
with_them do
@ -31,7 +31,7 @@ module QA
end
end
it "pushes and pulls a maven package via gradle using #{params[:authentication_token_type]}" do
it "pushes and pulls a maven package via gradle using #{params[:authentication_token_type]}", testcase: params[:testcase] do
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
gradle_upload_yaml = ERB.new(read_fixture('package_managers/maven', 'gradle_upload_package.yaml.erb')).result(binding)

View File

@ -0,0 +1,72 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Admin::RequestsProfilesController do
let_it_be(:admin) { create(:admin) }
before do
sign_in(admin)
end
describe '#show' do
let(:tmpdir) { Dir.mktmpdir('profiler-test') }
let(:test_file) { File.join(tmpdir, basename) }
subject do
get :show, params: { name: basename }
end
before do
stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
File.write(test_file, sample_data)
end
after do
FileUtils.rm_rf(tmpdir)
end
context 'when loading HTML profile' do
let(:basename) { "profile_#{Time.current.to_i}_execution.html" }
let(:sample_data) do
'<html> <body> <h1>Heading</h1> <p>paragraph.</p> </body> </html>'
end
it 'renders the data' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(sample_data)
end
end
context 'when loading TXT profile' do
let(:basename) { "profile_#{Time.current.to_i}_memory.txt" }
let(:sample_data) do
<<~TXT
Total allocated: 112096396 bytes (1080431 objects)
Total retained: 10312598 bytes (53567 objects)
TXT
end
it 'renders the data' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(sample_data)
end
end
context 'when loading PDF profile' do
let(:basename) { "profile_#{Time.current.to_i}_anything.pdf" }
let(:sample_data) { 'mocked pdf content' }
it 'fails to render the data' do
expect { subject }.to raise_error(ActionController::UrlGenerationError, /No route matches.*unmatched constraints:/)
end
end
end
end

View File

@ -88,6 +88,26 @@ RSpec.describe Projects::CommitsController do
expect(response).to be_successful
end
context 'when limit is a hash' do
it 'uses the default limit' do
expect_any_instance_of(Repository).to receive(:commits).with(
"master",
path: "README.md",
limit: described_class::COMMITS_DEFAULT_LIMIT,
offset: 0
).and_call_original
get(:show, params: {
namespace_id: project.namespace,
project_id: project,
id: id,
limit: { 'broken' => 'value' }
})
expect(response).to be_successful
end
end
end
context "when the ref name ends in .atom" do

View File

@ -33,6 +33,20 @@ RSpec.describe Projects::Settings::RepositoryController do
expect(response).to redirect_to project_settings_repository_path(project)
end
context 'when project cleanup returns an error', :aggregate_failures do
it 'shows an error' do
expect(Projects::CleanupService)
.to receive(:enqueue)
.with(project, user, anything)
.and_return(status: :error, message: 'error message')
put :cleanup, params: { namespace_id: project.namespace, project_id: project, project: { bfg_object_map: object_map } }
expect(controller).to set_flash[:alert].to('error message')
expect(response).to redirect_to project_settings_repository_path(project)
end
end
end
describe 'POST create_deploy_token' do

View File

@ -0,0 +1,136 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Admin::RequestsProfilesController' do
let(:tmpdir) { Dir.mktmpdir('profiler-test') }
before do
stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
admin = create(:admin)
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end
after do
FileUtils.rm_rf(tmpdir)
end
describe 'GET /admin/requests_profiles' do
it 'shows the current profile token' do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
visit admin_requests_profiles_path
expect(page).to have_content("X-Profile-Token: #{Gitlab::RequestProfiler.profile_token}")
end
context 'when having multiple profiles' do
let(:time1) { 1.hour.ago }
let(:time2) { 2.hours.ago }
let(:profiles) do
[
{
request_path: '/gitlab-org/gitlab-foss',
name: "|gitlab-org|gitlab-foss_#{time1.to_i}_execution.html",
created: time1,
profile_mode: 'Execution'
},
{
request_path: '/gitlab-org/gitlab-foss',
name: "|gitlab-org|gitlab-foss_#{time2.to_i}_execution.html",
created: time2,
profile_mode: 'Execution'
},
{
request_path: '/gitlab-org/gitlab-foss',
name: "|gitlab-org|gitlab-foss_#{time1.to_i}_memory.html",
created: time1,
profile_mode: 'Memory'
},
{
request_path: '/gitlab-org/gitlab-foss',
name: "|gitlab-org|gitlab-foss_#{time2.to_i}_memory.html",
created: time2,
profile_mode: 'Memory'
},
{
request_path: '/gitlab-org/infrastructure',
name: "|gitlab-org|infrastructure_#{time1.to_i}_execution.html",
created: time1,
profile_mode: 'Execution'
},
{
request_path: '/gitlab-org/infrastructure',
name: "|gitlab-org|infrastructure_#{time2.to_i}_memory.html",
created: time2,
profile_mode: 'Memory'
},
{
request_path: '/gitlab-org/infrastructure',
name: "|gitlab-org|infrastructure_#{time2.to_i}.html",
created: time2,
profile_mode: 'Unknown'
}
]
end
before do
profiles.each do |profile|
FileUtils.touch(File.join(Gitlab::RequestProfiler::PROFILES_DIR, profile[:name]))
end
end
it 'lists all available profiles' do
visit admin_requests_profiles_path
profiles.each do |profile|
within('.card', text: profile[:request_path]) do
expect(page).to have_selector(
"a[href='#{admin_requests_profile_path(profile[:name])}']",
text: "#{profile[:created].to_s(:long)} #{profile[:profile_mode]}")
end
end
end
end
end
describe 'GET /admin/requests_profiles/:profile' do
context 'when a profile exists' do
before do
File.write("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile}", content)
end
context 'when is valid call stack profile' do
let(:content) { 'This is a call stack request profile' }
let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_execution.html" }
it 'displays the content' do
visit admin_requests_profile_path(profile)
expect(page).to have_content(content)
end
end
context 'when is valid memory profile' do
let(:content) { 'This is a memory request profile' }
let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_memory.txt" }
it 'displays the content' do
visit admin_requests_profile_path(profile)
expect(page).to have_content(content)
end
end
end
context 'when a profile does not exist' do
it 'shows an error message' do
visit admin_requests_profile_path('|non|existent_12345.html')
expect(page).to have_content('Profile not found')
end
end
end
end

View File

@ -156,6 +156,26 @@ RSpec.describe 'Groups > Members > Manage groups', :js do
group_outside_hierarchy.add_owner(user)
end
context 'when the invite members group modal is enabled' do
it 'does not show self or ancestors', :aggregate_failures do
group_sibbling = create(:group, parent: group)
group_sibbling.add_owner(user)
visit group_group_members_path(group_within_hierarchy)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
page.within('[data-testid="group-select-dropdown"]') do
expect(page).to have_selector("[entity-id='#{group_outside_hierarchy.id}']")
expect(page).to have_selector("[entity-id='#{group_sibbling.id}']")
expect(page).not_to have_selector("[entity-id='#{group.id}']")
expect(page).not_to have_selector("[entity-id='#{group_within_hierarchy.id}']")
end
end
end
context 'when sharing with groups outside the hierarchy is enabled' do
context 'when the invite members group modal is disabled' do
before do

View File

@ -8,7 +8,7 @@ RSpec.describe 'Project > Members > Invite group', :js do
include Spec::Support::Helpers::Features::MembersHelpers
include Spec::Support::Helpers::Features::InviteMembersModalHelper
let(:maintainer) { create(:user) }
let_it_be(:maintainer) { create(:user) }
using RSpec::Parameterized::TableSyntax
@ -190,17 +190,26 @@ RSpec.describe 'Project > Members > Invite group', :js do
end
describe 'the groups dropdown' do
context 'with multiple groups to choose from' do
let(:project) { create(:project) }
let_it_be(:parent_group) { create(:group, :public) }
let_it_be(:project_group) { create(:group, :public, parent: parent_group) }
let_it_be(:public_sub_subgroup) { create(:group, :public, parent: project_group) }
let_it_be(:public_sibbling_group) { create(:group, :public, parent: parent_group) }
let_it_be(:private_sibbling_group) { create(:group, :private, parent: parent_group) }
let_it_be(:private_membership_group) { create(:group, :private) }
let_it_be(:public_membership_group) { create(:group, :public) }
let_it_be(:project) { create(:project, group: project_group) }
it 'includes multiple groups' do
before do
private_membership_group.add_guest(maintainer)
public_membership_group.add_maintainer(maintainer)
sign_in(maintainer)
end
context 'for a project in a nested group' do
it 'does not show the groups inherited from projects' do
project.add_maintainer(maintainer)
sign_in(maintainer)
group1 = create(:group)
group1.add_owner(maintainer)
group2 = create(:group)
group2.add_owner(maintainer)
public_sibbling_group.add_maintainer(maintainer)
visit project_project_members_path(project)
@ -208,57 +217,36 @@ RSpec.describe 'Project > Members > Invite group', :js do
click_on 'Select a group'
wait_for_requests
expect(page).to have_button(group1.name)
expect(page).to have_button(group2.name)
end
end
page.within('[data-testid="group-select-dropdown"]') do
expect_to_have_group(public_membership_group)
expect_to_have_group(public_sibbling_group)
expect_to_have_group(private_membership_group)
context 'for a project in a nested group' do
let!(:parent_group) { create(:group, :public) }
let!(:public_subgroup) { create(:group, :public, parent: parent_group) }
let!(:public_sub_subgroup) { create(:group, :public, parent: public_subgroup) }
let!(:private_subgroup) { create(:group, :private, parent: parent_group) }
let!(:project) { create(:project, :public, namespace: public_subgroup) }
let!(:membership_group) { create(:group, :public) }
before do
project.add_maintainer(maintainer)
membership_group.add_guest(maintainer)
sign_in(maintainer)
end
context 'when invite_members_group_modal feature enabled' do
it 'does not show the groups inherited from projects' do
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
expect(page).to have_button(membership_group.name)
expect(page).not_to have_button(parent_group.name)
expect(page).not_to have_button(public_subgroup.name)
expect(page).not_to have_button(public_sub_subgroup.name)
expect(page).not_to have_button(private_subgroup.name)
expect_not_to_have_group(public_sub_subgroup)
expect_not_to_have_group(private_sibbling_group)
expect_not_to_have_group(parent_group)
expect_not_to_have_group(project_group)
end
end
# This behavior should be changed to exclude the ancestor and project
# group from the options once issue is fixed for the modal:
# https://gitlab.com/gitlab-org/gitlab/-/issues/329835
it 'does show ancestors and the project group' do
parent_group.add_maintainer(maintainer)
it 'does not show the ancestors or project group', :aggregate_failures do
parent_group.add_maintainer(maintainer)
visit project_project_members_path(project)
visit project_project_members_path(project)
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
click_on 'Invite a group'
click_on 'Select a group'
wait_for_requests
expect(page).to have_button(membership_group.name)
expect(page).to have_button(parent_group.name)
expect(page).to have_button(public_subgroup.name)
page.within('[data-testid="group-select-dropdown"]') do
expect_to_have_group(public_membership_group)
expect_to_have_group(public_sibbling_group)
expect_to_have_group(private_membership_group)
expect_to_have_group(public_sub_subgroup)
expect_to_have_group(private_sibbling_group)
expect_not_to_have_group(parent_group)
expect_not_to_have_group(project_group)
end
end
@ -269,21 +257,26 @@ RSpec.describe 'Project > Members > Invite group', :js do
stub_feature_flags(invite_members_group_modal: false)
end
it 'does not show the groups inherited from projects' do
it 'does not show the groups inherited from projects', :aggregate_failures do
project.add_maintainer(maintainer)
public_sibbling_group.add_maintainer(maintainer)
visit project_project_members_path(project)
click_on 'Invite group'
click_on 'Search for a group'
wait_for_requests
expect(group_invite_dropdown).to have_text(membership_group.name)
expect(group_invite_dropdown).not_to have_text(parent_group.name)
expect(group_invite_dropdown).not_to have_text(public_subgroup.name)
expect(group_invite_dropdown).not_to have_text(public_sub_subgroup.name)
expect(group_invite_dropdown).not_to have_text(private_subgroup.name)
expect(group_invite_dropdown).to have_text(public_membership_group.full_path)
expect(group_invite_dropdown).to have_text(public_sibbling_group.full_path)
expect(group_invite_dropdown).to have_text(private_membership_group.full_path)
expect(group_invite_dropdown).not_to have_text(public_sub_subgroup.full_path)
expect(group_invite_dropdown).not_to have_text(private_sibbling_group.full_path)
expect(group_invite_dropdown).not_to have_text(parent_group.full_path, exact: true)
expect(group_invite_dropdown).not_to have_text(project_group.full_path, exact: true)
end
it 'does not show ancestors and the project group' do
it 'does not show the ancestors or project group', :aggregate_failures do
parent_group.add_maintainer(maintainer)
visit project_project_members_path(project)
@ -292,11 +285,23 @@ RSpec.describe 'Project > Members > Invite group', :js do
click_on 'Search for a group'
wait_for_requests
expect(group_invite_dropdown).to have_text(membership_group.name)
expect(group_invite_dropdown).not_to have_text(parent_group.name, exact: true)
expect(group_invite_dropdown).not_to have_text(public_subgroup.name, exact: true)
expect(group_invite_dropdown).to have_text(public_membership_group.full_path)
expect(group_invite_dropdown).to have_text(public_sub_subgroup.full_path)
expect(group_invite_dropdown).to have_text(public_sibbling_group.full_path)
expect(group_invite_dropdown).to have_text(private_sibbling_group.full_path)
expect(group_invite_dropdown).to have_text(private_membership_group.full_path)
expect(group_invite_dropdown).not_to have_text(parent_group.full_path, exact: true)
expect(group_invite_dropdown).not_to have_text(project_group.full_path, exact: true)
end
end
def expect_to_have_group(group)
expect(page).to have_selector("[entity-id='#{group.id}']")
end
def expect_not_to_have_group(group)
expect(page).not_to have_selector("[entity-id='#{group.id}']")
end
end
end
end

View File

@ -1,13 +1,18 @@
import { GlAlert, GlKeysetPagination, GlLoadingIcon } from '@gitlab/ui';
import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlBanner } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
import AgentEmptyState from '~/clusters_list/components/agent_empty_state.vue';
import AgentTable from '~/clusters_list/components/agent_table.vue';
import Agents from '~/clusters_list/components/agents.vue';
import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants';
import {
ACTIVE_CONNECTION_TIME,
AGENT_FEEDBACK_KEY,
AGENT_FEEDBACK_ISSUE,
} from '~/clusters_list/constants';
import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
const localVue = createLocalVue();
localVue.use(VueApollo);
@ -24,6 +29,7 @@ describe('Agents', () => {
const createWrapper = async ({
props = {},
glFeatures = {},
agents = [],
pageInfo = null,
trees = [],
@ -51,20 +57,29 @@ describe('Agents', () => {
...defaultProps,
...props,
},
provide: provideData,
provide: {
...provideData,
glFeatures,
},
stubs: {
GlBanner,
LocalStorageSync,
},
});
await nextTick();
};
const findAgentTable = () => wrapper.find(AgentTable);
const findEmptyState = () => wrapper.find(AgentEmptyState);
const findPaginationButtons = () => wrapper.find(GlKeysetPagination);
const findAgentTable = () => wrapper.findComponent(AgentTable);
const findEmptyState = () => wrapper.findComponent(AgentEmptyState);
const findPaginationButtons = () => wrapper.findComponent(GlKeysetPagination);
const findAlert = () => wrapper.findComponent(GlAlert);
const findBanner = () => wrapper.findComponent(GlBanner);
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
wrapper.destroy();
localStorage.removeItem(AGENT_FEEDBACK_KEY);
});
describe('when there is a list of agents', () => {
@ -150,6 +165,49 @@ describe('Agents', () => {
expect(wrapper.emitted().onAgentsLoad).toEqual([[count]]);
});
describe.each`
featureFlagEnabled | localStorageItemExists | bannerShown
${true} | ${false} | ${true}
${true} | ${true} | ${false}
${false} | ${true} | ${false}
${false} | ${false} | ${false}
`(
'when the feature flag enabled is $featureFlagEnabled and dismissed localStorage item exists is $localStorageItemExists',
({ featureFlagEnabled, localStorageItemExists, bannerShown }) => {
const glFeatures = {
showGitlabAgentFeedback: featureFlagEnabled,
};
beforeEach(() => {
if (localStorageItemExists) {
localStorage.setItem(AGENT_FEEDBACK_KEY, true);
}
return createWrapper({ glFeatures, agents, count, trees });
});
it(`should ${bannerShown ? 'show' : 'hide'} the feedback banner`, () => {
expect(findBanner().exists()).toBe(bannerShown);
});
},
);
describe('when the agent feedback banner is present', () => {
const glFeatures = {
showGitlabAgentFeedback: true,
};
beforeEach(() => {
return createWrapper({ glFeatures, agents, count, trees });
});
it('should render the correct title', () => {
expect(findBanner().props('title')).toBe('Tell us what you think');
});
it('should render the correct issue link', () => {
expect(findBanner().props('buttonLink')).toBe(AGENT_FEEDBACK_ISSUE);
});
});
describe('when the agent has recently connected tokens', () => {
it('should set agent status to active', () => {
expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList);
@ -223,6 +281,10 @@ describe('Agents', () => {
expect(findAgentTable().exists()).toBe(false);
expect(findEmptyState().exists()).toBe(true);
});
it('should not show agent feedback alert', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe('when agents query has errored', () => {
@ -231,7 +293,7 @@ describe('Agents', () => {
});
it('displays an alert message', () => {
expect(wrapper.find(GlAlert).exists()).toBe(true);
expect(findAlert().text()).toBe('An error occurred while loading your Agents');
});
});

View File

@ -5,19 +5,20 @@ import * as groupsApi from '~/api/groups_api';
import GroupSelect from '~/invite_members/components/group_select.vue';
const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
const createComponent = () => {
return mount(GroupSelect, {
propsData: {
accessLevels,
},
});
};
const group1 = { id: 1, full_name: 'Group One', avatar_url: 'test' };
const group2 = { id: 2, full_name: 'Group Two', avatar_url: 'test' };
const allGroups = [group1, group2];
const createComponent = (props = {}) => {
return mount(GroupSelect, {
propsData: {
invalidGroups: [],
accessLevels,
...props,
},
});
};
describe('GroupSelect', () => {
let wrapper;
@ -90,6 +91,20 @@ describe('GroupSelect', () => {
size: '32',
});
});
describe('when filtering out the group from results', () => {
beforeEach(() => {
wrapper = createComponent({ invalidGroups: [group1.id] });
});
it('does not find an invalid group', () => {
expect(findAvatarByLabel(group1.full_name)).toBe(undefined);
});
it('finds a group that is valid', () => {
expect(findAvatarByLabel(group2.full_name).exists()).toBe(true);
});
});
});
describe('when group is selected from the dropdown', () => {

View File

@ -46,6 +46,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
const id = '1';
const name = 'test name';
const isProject = false;
const invalidGroups = [];
const inviteeType = 'members';
const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
const defaultAccessLevel = 10;
@ -93,6 +94,7 @@ const createComponent = (data = {}, props = {}) => {
tasksToBeDoneOptions,
projects,
helpLink,
invalidGroups,
...props,
},
data() {

View File

@ -244,7 +244,7 @@ describe('Settings Panel', () => {
wrapper = mountComponent({ currentSettings: { visibilityLevel: visibilityOptions.PUBLIC } });
expect(findRepositoryFeatureProjectRow().props('helpText')).toBe(
'View and edit files in this project. Non-project members will only have read access.',
'View and edit files in this project. Non-project members have only read access.',
);
});
});

View File

@ -20,7 +20,8 @@ RSpec.describe InviteMembersHelper do
attributes = {
id: project.id,
name: project.name,
default_access_level: Gitlab::Access::GUEST
default_access_level: Gitlab::Access::GUEST,
invalid_groups: project.related_group_ids
}
expect(helper.common_invite_modal_dataset(project)).to include(attributes)
@ -155,4 +156,28 @@ RSpec.describe InviteMembersHelper do
end
end
end
describe '#group_select_data' do
let_it_be(:group) { create(:group) }
context 'when sharing with groups outside the hierarchy is disabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: true)
end
it 'provides the correct attributes' do
expect(helper.group_select_data(group)).to eq({ groups_filter: 'descendant_groups', parent_id: group.id })
end
end
context 'when sharing with groups outside the hierarchy is enabled' do
before do
group.namespace_settings.update!(prevent_sharing_groups_outside_hierarchy: false)
end
it 'returns an empty hash' do
expect(helper.group_select_data(project.group)).to eq({})
end
end
end
end

View File

@ -409,7 +409,7 @@ RSpec.describe Backup::Manager do
# the Fog mock only knows about directories we create explicitly
connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) # rubocop:disable Rails/SaveBang
end
context 'target path' do
@ -455,7 +455,7 @@ RSpec.describe Backup::Manager do
}
)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) # rubocop:disable Rails/SaveBang
end
context 'with SSE-S3 without using storage_options' do
@ -521,7 +521,7 @@ RSpec.describe Backup::Manager do
)
connection = ::Fog::Storage.new(Gitlab.config.backup.upload.connection.symbolize_keys)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory)
connection.directories.create(key: Gitlab.config.backup.upload.remote_directory) # rubocop:disable Rails/SaveBang
end
it 'does not attempt to set ACL' do

View File

@ -43,8 +43,8 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
end
before do
issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue1.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue2.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
end
context 'when records are loaded by guest' do
@ -73,8 +73,8 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
end
before do
mr1.metrics.update(merged_at: 3.days.ago)
mr2.metrics.update(merged_at: 3.days.ago)
mr1.metrics.update!(merged_at: 3.days.ago)
mr2.metrics.update!(merged_at: 3.days.ago)
end
include_context 'when records are loaded by maintainer'
@ -95,9 +95,9 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
end
before(:all) do
issue1.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue2.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue3.metrics.update(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue1.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue2.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
issue3.metrics.update!(first_added_to_board_at: 3.days.ago, first_mentioned_in_commit_at: 2.days.ago)
end
before do

View File

@ -53,12 +53,12 @@ RSpec.describe Gitlab::Auth::Ldap::User do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
expect { ldap_user.save }.not_to change { User.count }
expect { ldap_user.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang
end
it "connects to existing non-ldap user if the email matches" do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
expect { ldap_user.save }.not_to change { User.count }
expect { ldap_user.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com'
@ -67,7 +67,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
it 'connects to existing ldap user if the extern_uid changes' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
expect { ldap_user.save }.not_to change { User.count }
expect { ldap_user.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com'
@ -77,7 +77,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
expect { ldap_user_upper_case.save }.not_to change { User.count }
expect { ldap_user_upper_case.save }.not_to change { User.count } # rubocop:disable Rails/SaveBang
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com'
@ -89,7 +89,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: 'twitter')
expect(existing_user.identities.count).to be(1)
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(ldap_user.gl_user.identities.count).to be(2)
# Expect that find_by provider only returns a single instance of an identity and not an Enumerable
@ -98,7 +98,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it "creates a new user if not found" do
expect { ldap_user.save }.to change { User.count }.by(1)
expect { ldap_user.save }.to change { User.count }.by(1) # rubocop:disable Rails/SaveBang
end
context 'when signup is disabled' do
@ -107,7 +107,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it 'creates the user' do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
end
@ -119,7 +119,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it 'creates and confirms the user anyway' do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
expect(gl_user).to be_confirmed
@ -132,7 +132,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it 'creates the user' do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
end
@ -189,7 +189,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -201,7 +201,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).to be_blocked
end
@ -210,7 +210,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
context 'sign-in' do
before do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
ldap_user.gl_user.activate
end
@ -220,7 +220,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -232,7 +232,7 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
it do
ldap_user.save
ldap_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end

View File

@ -67,7 +67,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
create(:omniauth_user, extern_uid: 'my-uid', provider: provider)
stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
@ -83,7 +83,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it 'creates the user' do
stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
end
@ -97,7 +97,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it 'creates and confirms the user anyway' do
stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
expect(gl_user).to be_confirmed
@ -112,7 +112,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it 'creates the user' do
stub_omniauth_config(allow_single_sign_on: [provider])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
end
@ -121,7 +121,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it 'marks user as having password_automatically_set' do
stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
expect(gl_user).to be_password_automatically_set
@ -131,7 +131,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
context 'provider is marked as external' do
it 'marks user as external' do
stub_omniauth_config(allow_single_sign_on: [provider], external_providers: [provider])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
@ -141,7 +141,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it 'does not mark external user as internal' do
create(:omniauth_user, extern_uid: 'my-uid', provider: provider, external: true)
stub_omniauth_config(allow_single_sign_on: [provider], external_providers: ['facebook'])
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
@ -151,9 +151,9 @@ RSpec.describe Gitlab::Auth::OAuth::User do
context 'when adding a new OAuth identity' do
it 'does not promote an external user to internal' do
user = create(:user, email: 'john@mail.com', external: true)
user.identities.create(provider: provider, extern_uid: uid)
user.identities.create!(provider: provider, extern_uid: uid)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
@ -166,7 +166,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it "creates a user from Omniauth" do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
identity = gl_user.identities.first
@ -181,7 +181,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it "creates a user from Omniauth" do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
identity = gl_user.identities.first
@ -196,7 +196,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it 'throws an error' do
expect { oauth_user.save }.to raise_error StandardError
expect { oauth_user.save }.to raise_error StandardError # rubocop:disable Rails/SaveBang
end
end
@ -206,7 +206,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it 'throws an error' do
expect { oauth_user.save }.to raise_error StandardError
expect { oauth_user.save }.to raise_error StandardError # rubocop:disable Rails/SaveBang
end
end
end
@ -228,7 +228,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
it "adds the OmniAuth identity to the GitLab user account" do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).not_to be_valid
end
@ -248,7 +248,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
it "adds the OmniAuth identity to the GitLab user account" do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
@ -277,7 +277,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
let!(:existing_user) { create(:user, email: 'john@mail.com', username: 'john') }
it "adds the OmniAuth identity to the GitLab user account" do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
@ -337,7 +337,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
before do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
end
it "creates a user with dual LDAP and omniauth identities" do
@ -376,7 +376,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with(uid, any_args).and_return(nil)
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).with(info_hash[:email], any_args).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
end
it 'creates the LDAP identity' do
@ -392,7 +392,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it "adds the omniauth identity to the LDAP account" do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
@ -414,7 +414,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(nil)
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array(result_identities(dn, uid))
@ -426,7 +426,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_email).and_return(nil)
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_dn).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array(result_identities(dn, uid))
@ -447,7 +447,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it 'does not save the identity' do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
expect(identities_as_hash).to match_array([{ provider: 'twitter', extern_uid: uid }])
@ -467,7 +467,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it 'creates a user favoring the LDAP username and strips email domain' do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'johndoe'
@ -510,7 +510,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
before do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
end
it "creates a user with dual LDAP and omniauth identities" do
@ -549,7 +549,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
it "adds the omniauth identity to the LDAP account" do
allow(Gitlab::Auth::Ldap::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
@ -584,7 +584,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -596,7 +596,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).to be_blocked
end
@ -622,7 +622,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -636,7 +636,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).to be_blocked
end
@ -654,7 +654,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -668,7 +668,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -678,7 +678,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
context 'sign-in' do
before do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
oauth_user.gl_user.activate
end
@ -688,7 +688,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -700,7 +700,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -714,7 +714,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -728,7 +728,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
end
it do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -791,7 +791,7 @@ RSpec.describe Gitlab::Auth::OAuth::User do
context 'when collision with existing user' do
it 'generates the username with a counter' do
oauth_user.save
oauth_user.save # rubocop:disable Rails/SaveBang
oauth_user2 = described_class.new(OmniAuth::AuthHash.new(uid: 'my-uid2', provider: provider, info: { nickname: 'johngitlab-ETC@othermail.com', email: 'john@othermail.com' }))
expect(oauth_user2.gl_user.username).to eq('johngitlab-ETC1')

View File

@ -36,7 +36,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'and should bind with SAML' do
it 'adds the SAML identity to the existing user' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).to eq existing_user
identity = gl_user.identities.first
@ -49,7 +49,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'are defined' do
it 'marks the user as external' do
stub_saml_group_config(%w(Freelancers))
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
@ -61,7 +61,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'are defined but the user does not belong there' do
it 'does not mark the user as external' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
end
@ -70,7 +70,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'user was external, now should not be' do
it 'makes user internal' do
existing_user.update_attribute('external', true)
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
end
@ -86,7 +86,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'creates a user from SAML' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
identity = gl_user.identities.first
@ -101,7 +101,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'does not throw an error' do
expect { saml_user.save }.not_to raise_error
expect { saml_user.save }.not_to raise_error # rubocop:disable Rails/SaveBang
end
end
@ -111,7 +111,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'throws an error' do
expect { saml_user.save }.to raise_error StandardError
expect { saml_user.save }.to raise_error StandardError # rubocop:disable Rails/SaveBang
end
end
end
@ -120,7 +120,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'are defined' do
it 'marks the user as external' do
stub_saml_group_config(%w(Freelancers))
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_truthy
end
@ -129,7 +129,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'are defined but the user does not belong there' do
it 'does not mark the user as external' do
stub_saml_group_config(%w(Interns))
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.external).to be_falsey
end
@ -170,7 +170,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'and no account for the LDAP user' do
it 'creates a user with dual LDAP and SAML identities' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
@ -230,7 +230,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
{ provider: id.provider, extern_uid: id.extern_uid }
end
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
@ -259,7 +259,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'adds the omniauth identity to the LDAP account' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
@ -271,9 +271,9 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'saves successfully on subsequent tries, when both identities are present' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
local_saml_user = described_class.new(auth_hash)
local_saml_user.save
local_saml_user.save # rubocop:disable Rails/SaveBang
expect(local_saml_user.gl_user).to be_valid
expect(local_saml_user.gl_user).to be_persisted
@ -289,7 +289,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
local_hash = OmniAuth::AuthHash.new(uid: dn, provider: provider, info: info_hash)
local_saml_user = described_class.new(local_hash)
local_saml_user.save
local_saml_user.save # rubocop:disable Rails/SaveBang
local_gl_user = local_saml_user.gl_user
expect(local_gl_user).to be_valid
@ -309,7 +309,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'creates the user' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
end
@ -321,7 +321,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'creates and confirms the user anyway' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
expect(gl_user).to be_confirmed
@ -334,7 +334,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'creates the user' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_persisted
end
@ -353,7 +353,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'does not block the user' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -365,7 +365,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it 'blocks user' do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).to be_blocked
end
@ -374,7 +374,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
context 'sign-in' do
before do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
saml_user.gl_user.activate
end
@ -384,7 +384,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end
@ -396,7 +396,7 @@ RSpec.describe Gitlab::Auth::Saml::User do
end
it do
saml_user.save
saml_user.save # rubocop:disable Rails/SaveBang
expect(gl_user).to be_valid
expect(gl_user).not_to be_blocked
end

View File

@ -165,27 +165,27 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end
it 'recognises user token' do
build.update(user: create(:user))
build.update!(user: create(:user))
expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities)
end
it 'recognises project level bot access token' do
build.update(user: create(:user, :project_bot))
build.update!(user: create(:user, :project_bot))
project.add_maintainer(build.user)
expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities)
end
it 'recognises group level bot access token' do
build.update(user: create(:user, :project_bot))
build.update!(user: create(:user, :project_bot))
group.add_maintainer(build.user)
expect(subject).to have_attributes(actor: build.user, project: build.project, type: :build, authentication_abilities: described_class.build_authentication_abilities)
end
it 'fails with blocked user token' do
build.update(user: create(:user, :blocked))
build.update!(user: create(:user, :blocked))
expect(subject).to have_attributes(auth_failure)
end
@ -213,7 +213,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'recognizes other ci services' do
project.create_drone_ci_integration(active: true)
project.drone_ci_integration.update(token: 'token')
project.drone_ci_integration.update!(token: 'token', drone_url: generate(:url))
expect(gl_auth.find_for_git_client('drone-ci-token', 'token', project: project, ip: 'ip')).to have_attributes(actor: nil, project: project, type: :ci, authentication_abilities: described_class.build_authentication_abilities)
end
@ -326,7 +326,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'orphaned token' do
before do
user.destroy
user.destroy!
end
it_behaves_like 'an oauth failure'
@ -903,7 +903,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'resets failed_attempts when true and password is correct' do
user.failed_attempts = 2
user.save
user.save!
expect do
gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)
@ -932,7 +932,7 @@ RSpec.describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'does not reset failed_attempts when true and password is correct' do
user.failed_attempts = 2
user.save
user.save!
expect do
gl_auth.find_with_user_password(username, password, increment_failed_attempts: true)

View File

@ -38,7 +38,7 @@ RSpec.describe Gitlab::AuthorizedKeys do
end
describe '#create' do
subject { authorized_keys.create }
subject { authorized_keys.create } # rubocop:disable Rails/SaveBang
context 'authorized_keys file exists' do
before do

View File

@ -25,6 +25,7 @@ RSpec.describe Gitlab::BacktraceCleaner do
"app/models/repository.rb:113:in `commit'",
"lib/gitlab/i18n.rb:50:in `with_locale'",
"lib/gitlab/middleware/multipart.rb:95:in `call'",
"lib/gitlab/request_profiler/middleware.rb:14:in `call'",
"ee/lib/gitlab/database/load_balancing/rack_middleware.rb:37:in `call'",
"ee/lib/gitlab/jira/middleware.rb:15:in `call'"
]

Some files were not shown because too many files have changed in this diff Show More