Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8bdb37de3a
commit
634e9bccc6
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1.51.0
|
||||
1.52.0
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
class MoveContainerRegistryEnabledToProjectFeatures3 < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
include Gitlab::Database::DynamicModelHelpers
|
||||
|
||||
BATCH_SIZE = 21_000
|
||||
MIGRATION = 'MoveContainerRegistryEnabledToProjectFeature'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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`:
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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/)
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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
|
||||
|
||||

|
||||
|
||||
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).
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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"}`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue