Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f2da46b705
commit
41533891fd
|
|
@ -6,7 +6,7 @@ include:
|
|||
inputs:
|
||||
cng_path: 'charts/components/images'
|
||||
- project: 'gitlab-org/quality/pipeline-common'
|
||||
ref: '9.7.1'
|
||||
ref: '9.8.0'
|
||||
file: ci/base.gitlab-ci.yml
|
||||
|
||||
stages:
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ Gitlab/AvoidGitlabInstanceChecks:
|
|||
- 'ee/app/models/ee/namespace.rb'
|
||||
- 'ee/app/models/ee/plan.rb'
|
||||
- 'ee/app/models/ee/preloaders/group_policy_preloader.rb'
|
||||
- 'ee/app/models/ee/preloaders/single_hierarchy_project_group_plans_preloader.rb'
|
||||
- 'ee/app/models/ee/project.rb'
|
||||
- 'ee/app/models/ee/project_statistics.rb'
|
||||
- 'ee/app/models/ee/user.rb'
|
||||
|
|
|
|||
|
|
@ -1141,7 +1141,6 @@ Gitlab/BoundedContexts:
|
|||
- 'app/models/preloaders/project_root_ancestor_preloader.rb'
|
||||
- 'app/models/preloaders/projects/notes_preloader.rb'
|
||||
- 'app/models/preloaders/runner_manager_policy_preloader.rb'
|
||||
- 'app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb'
|
||||
- 'app/models/preloaders/user_max_access_level_in_groups_preloader.rb'
|
||||
- 'app/models/preloaders/user_max_access_level_in_projects_preloader.rb'
|
||||
- 'app/models/preloaders/users_max_access_level_by_project_preloader.rb'
|
||||
|
|
@ -2823,7 +2822,6 @@ Gitlab/BoundedContexts:
|
|||
- 'ee/app/models/ee/preloaders/group_policy_preloader.rb'
|
||||
- 'ee/app/models/ee/preloaders/labels_preloader.rb'
|
||||
- 'ee/app/models/ee/preloaders/project_policy_preloader.rb'
|
||||
- 'ee/app/models/ee/preloaders/single_hierarchy_project_group_plans_preloader.rb'
|
||||
- 'ee/app/models/ee/preloaders/users_max_access_level_by_project_preloader.rb'
|
||||
- 'ee/app/models/ee/project.rb'
|
||||
- 'ee/app/models/ee/project_authorization.rb'
|
||||
|
|
|
|||
|
|
@ -189,7 +189,6 @@ Layout/LineLength:
|
|||
- 'app/models/pages_domain.rb'
|
||||
- 'app/models/personal_access_token.rb'
|
||||
- 'app/models/preloaders/environments/deployment_preloader.rb'
|
||||
- 'app/models/preloaders/single_hierarchy_project_group_plans_preloader.rb'
|
||||
- 'app/models/preloaders/user_max_access_level_in_groups_preloader.rb'
|
||||
- 'app/models/project.rb'
|
||||
- 'app/models/project_feature.rb'
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ Lint/AssignmentInCondition:
|
|||
- 'ee/app/graphql/mutations/incident_management/escalation_policy/base.rb'
|
||||
- 'ee/app/models/ee/ci/build.rb'
|
||||
- 'ee/app/models/ee/merge_request.rb'
|
||||
- 'ee/app/models/ee/preloaders/single_hierarchy_project_group_plans_preloader.rb'
|
||||
- 'ee/app/models/productivity_analytics.rb'
|
||||
- 'ee/app/presenters/ee/ci/pipeline_presenter.rb'
|
||||
- 'ee/app/services/app_sec/dast/profiles/create_associations_service.rb'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,3 @@ Performance/FlatMap:
|
|||
- 'lib/gitlab/diff/file_collection/base.rb'
|
||||
- 'lib/gitlab/instrumentation/redis_cluster_validator.rb'
|
||||
- 'lib/gitlab/testing/request_inspector_middleware.rb'
|
||||
- 'qa/qa/tools/ci/non_empty_suites.rb'
|
||||
|
|
|
|||
6
Gemfile
6
Gemfile
|
|
@ -389,9 +389,9 @@ gem 'gitlab-license', '~> 2.6', feature_category: :shared
|
|||
gem 'rack-attack', '~> 6.7.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-ruby', '~> 5.21.0', feature_category: :observability
|
||||
gem 'sentry-rails', '~> 5.21.0', feature_category: :observability
|
||||
gem 'sentry-sidekiq', '~> 5.21.0', feature_category: :observability
|
||||
gem 'sentry-ruby', '~> 5.22.0', feature_category: :observability
|
||||
gem 'sentry-rails', '~> 5.22.0', feature_category: :observability
|
||||
gem 'sentry-sidekiq', '~> 5.22.0', feature_category: :observability
|
||||
|
||||
# PostgreSQL query parsing
|
||||
#
|
||||
|
|
|
|||
|
|
@ -669,9 +669,9 @@
|
|||
{"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"},
|
||||
{"name":"selenium-webdriver","version":"4.27.0","platform":"ruby","checksum":"8821f4ad60b935cfcdc5954c0a6642d894e936250aece8bf37a6fcbebe5eb6e0"},
|
||||
{"name":"semver_dialects","version":"3.4.5","platform":"ruby","checksum":"7382ca351dc4796a8c824447a1ad87dfdea41f73b625cd2a5efabe54d11be63e"},
|
||||
{"name":"sentry-rails","version":"5.21.0","platform":"ruby","checksum":"b5a943d199aff0d3cb94dbac4eb3e00622dd0c55fd1be0cffd43a7e09f0ad602"},
|
||||
{"name":"sentry-ruby","version":"5.21.0","platform":"ruby","checksum":"294e0dd59afce7e08ba22a4e880924345c75c3e858dc8ee23553716793f78629"},
|
||||
{"name":"sentry-sidekiq","version":"5.21.0","platform":"ruby","checksum":"6df54ec79238f69d9d4b7647bcd2a192a4702f3a39edffd63a455203430e90e2"},
|
||||
{"name":"sentry-rails","version":"5.22.1","platform":"ruby","checksum":"23227608dc0e202de8cf96840a591e52bd7d6967ebaed6eb2da50a7d2a2d3fb7"},
|
||||
{"name":"sentry-ruby","version":"5.22.1","platform":"ruby","checksum":"ed77bdd76da7a4c6a3de43dc6d19d3c0412b2675b014a2654bc5bafd4d5b3289"},
|
||||
{"name":"sentry-sidekiq","version":"5.22.1","platform":"ruby","checksum":"bd7a3f915e58e13ea67251d9a458667fc4bee6dfbbd12614c47daa239e822a89"},
|
||||
{"name":"shellany","version":"0.0.1","platform":"ruby","checksum":"0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7"},
|
||||
{"name":"shoulda-matchers","version":"5.1.0","platform":"ruby","checksum":"a01d20589989e9653ab4a28c67d9db2b82bcf0a2496cf01d5e1a95a4aaaf5b07"},
|
||||
{"name":"sidekiq-cron","version":"1.12.0","platform":"ruby","checksum":"6663080a454088bd88773a0da3ae91e554b8a2e8b06cfc629529a83fd1a3096c"},
|
||||
|
|
|
|||
16
Gemfile.lock
16
Gemfile.lock
|
|
@ -1722,14 +1722,14 @@ GEM
|
|||
pastel (~> 0.8.0)
|
||||
thor (~> 1.3)
|
||||
tty-command (~> 0.10.1)
|
||||
sentry-rails (5.21.0)
|
||||
sentry-rails (5.22.1)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.21.0)
|
||||
sentry-ruby (5.21.0)
|
||||
sentry-ruby (~> 5.22.1)
|
||||
sentry-ruby (5.22.1)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sentry-sidekiq (5.21.0)
|
||||
sentry-ruby (~> 5.21.0)
|
||||
sentry-sidekiq (5.22.1)
|
||||
sentry-ruby (~> 5.22.1)
|
||||
sidekiq (>= 3.0)
|
||||
shellany (0.0.1)
|
||||
shoulda-matchers (5.1.0)
|
||||
|
|
@ -2290,9 +2290,9 @@ DEPENDENCIES
|
|||
seed-fu (~> 2.3.7)
|
||||
selenium-webdriver (~> 4.21, >= 4.21.1)
|
||||
semver_dialects (~> 3.0)
|
||||
sentry-rails (~> 5.21.0)
|
||||
sentry-ruby (~> 5.21.0)
|
||||
sentry-sidekiq (~> 5.21.0)
|
||||
sentry-rails (~> 5.22.0)
|
||||
sentry-ruby (~> 5.22.0)
|
||||
sentry-sidekiq (~> 5.22.0)
|
||||
shoulda-matchers (~> 5.1.0)
|
||||
sidekiq!
|
||||
sidekiq-cron (~> 1.12.0)
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@
|
|||
{"name":"io-event","version":"1.6.5","platform":"ruby","checksum":"5da4c044ac5f411563da1a4743d28c8d30d7802e29370db42139a52b807b4ce2"},
|
||||
{"name":"ipaddr","version":"1.2.5","platform":"ruby","checksum":"4e679c71d6d8ed99f925487082f70f9a958de155591caa0e7f6cef9aa160f17a"},
|
||||
{"name":"ipaddress","version":"0.8.3","platform":"ruby","checksum":"85640c4f9194c26937afc8c78e3074a8e7c97d5d1210358d1440f01034d006f5"},
|
||||
{"name":"irb","version":"1.14.2","platform":"ruby","checksum":"243040f6419115beb2404380a62e41861ddb03c0792c5873dec2a69a542723c6"},
|
||||
{"name":"irb","version":"1.14.3","platform":"ruby","checksum":"c457f1f2f1438ae9ce5c5be3981ae2138dec7fb894c7d73777eeeb0a6c0d0752"},
|
||||
{"name":"jaeger-client","version":"1.1.0","platform":"ruby","checksum":"cb5e9b9bbee6ee8d6a82d03d947a5b04543d8c0a949c22e484254f18d8a458a8"},
|
||||
{"name":"jaro_winkler","version":"1.5.6","platform":"java","checksum":"3262aea433861fec3179184e9adc1933cca8bc15665957a143b56816f1a22f74"},
|
||||
{"name":"jaro_winkler","version":"1.5.6","platform":"ruby","checksum":"007db7805527ada1cc12f2547676181d63b0a504ec4dd7a9a2eb2424521ccd81"},
|
||||
|
|
@ -567,7 +567,7 @@
|
|||
{"name":"rbs","version":"3.6.1","platform":"ruby","checksum":"ed7273d018556844583d1785ac54194e67eec594d68e317d57fa90ad035532c0"},
|
||||
{"name":"rbtrace","version":"0.5.1","platform":"ruby","checksum":"e8cba64d462bfb8ba102d7be2ecaacc789247d52ac587d8003549d909cb9c5dc"},
|
||||
{"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"},
|
||||
{"name":"rdoc","version":"6.9.1","platform":"ruby","checksum":"3344bf498a46b701aba70ccdd5cdfa8be37e68493984c1bf8c579f06c3442c9f"},
|
||||
{"name":"rdoc","version":"6.10.0","platform":"ruby","checksum":"db665021883ac9df3ba29cdf71aece960749888db1bf9615b4a584cfa3fa3eda"},
|
||||
{"name":"re2","version":"2.7.0","platform":"aarch64-linux","checksum":"778921298b6e8aba26a6230dd298c9b361b92e45024f81fa6aee788060fa307c"},
|
||||
{"name":"re2","version":"2.7.0","platform":"arm-linux","checksum":"d328b5286d83ae265e13b855da8e348a976f80f91b748045b52073a570577954"},
|
||||
{"name":"re2","version":"2.7.0","platform":"arm64-darwin","checksum":"7d993f27a1afac4001c539a829e2af211ced62604930c90df32a307cf74cb4a4"},
|
||||
|
|
@ -679,9 +679,9 @@
|
|||
{"name":"seed-fu","version":"2.3.7","platform":"ruby","checksum":"f19673443e9af799b730e3d4eca6a89b39e5a36825015dffd00d02ea3365cf74"},
|
||||
{"name":"selenium-webdriver","version":"4.27.0","platform":"ruby","checksum":"8821f4ad60b935cfcdc5954c0a6642d894e936250aece8bf37a6fcbebe5eb6e0"},
|
||||
{"name":"semver_dialects","version":"3.4.5","platform":"ruby","checksum":"7382ca351dc4796a8c824447a1ad87dfdea41f73b625cd2a5efabe54d11be63e"},
|
||||
{"name":"sentry-rails","version":"5.21.0","platform":"ruby","checksum":"b5a943d199aff0d3cb94dbac4eb3e00622dd0c55fd1be0cffd43a7e09f0ad602"},
|
||||
{"name":"sentry-ruby","version":"5.21.0","platform":"ruby","checksum":"294e0dd59afce7e08ba22a4e880924345c75c3e858dc8ee23553716793f78629"},
|
||||
{"name":"sentry-sidekiq","version":"5.21.0","platform":"ruby","checksum":"6df54ec79238f69d9d4b7647bcd2a192a4702f3a39edffd63a455203430e90e2"},
|
||||
{"name":"sentry-rails","version":"5.22.1","platform":"ruby","checksum":"23227608dc0e202de8cf96840a591e52bd7d6967ebaed6eb2da50a7d2a2d3fb7"},
|
||||
{"name":"sentry-ruby","version":"5.22.1","platform":"ruby","checksum":"ed77bdd76da7a4c6a3de43dc6d19d3c0412b2675b014a2654bc5bafd4d5b3289"},
|
||||
{"name":"sentry-sidekiq","version":"5.22.1","platform":"ruby","checksum":"bd7a3f915e58e13ea67251d9a458667fc4bee6dfbbd12614c47daa239e822a89"},
|
||||
{"name":"shellany","version":"0.0.1","platform":"ruby","checksum":"0e127a9132698766d7e752e82cdac8250b6adbd09e6c0a7fbbb6f61964fedee7"},
|
||||
{"name":"shoulda-matchers","version":"5.1.0","platform":"ruby","checksum":"a01d20589989e9653ab4a28c67d9db2b82bcf0a2496cf01d5e1a95a4aaaf5b07"},
|
||||
{"name":"sidekiq-cron","version":"1.12.0","platform":"ruby","checksum":"6663080a454088bd88773a0da3ae91e554b8a2e8b06cfc629529a83fd1a3096c"},
|
||||
|
|
|
|||
|
|
@ -1038,7 +1038,7 @@ GEM
|
|||
io-event (1.6.5)
|
||||
ipaddr (1.2.5)
|
||||
ipaddress (0.8.3)
|
||||
irb (1.14.2)
|
||||
irb (1.14.3)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jaeger-client (1.1.0)
|
||||
|
|
@ -1563,7 +1563,7 @@ GEM
|
|||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rchardet (1.8.0)
|
||||
rdoc (6.9.1)
|
||||
rdoc (6.10.0)
|
||||
psych (>= 4.0.0)
|
||||
re2 (2.7.0)
|
||||
mini_portile2 (~> 2.8.5)
|
||||
|
|
@ -1749,14 +1749,14 @@ GEM
|
|||
pastel (~> 0.8.0)
|
||||
thor (~> 1.3)
|
||||
tty-command (~> 0.10.1)
|
||||
sentry-rails (5.21.0)
|
||||
sentry-rails (5.22.1)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.21.0)
|
||||
sentry-ruby (5.21.0)
|
||||
sentry-ruby (~> 5.22.1)
|
||||
sentry-ruby (5.22.1)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sentry-sidekiq (5.21.0)
|
||||
sentry-ruby (~> 5.21.0)
|
||||
sentry-sidekiq (5.22.1)
|
||||
sentry-ruby (~> 5.22.1)
|
||||
sidekiq (>= 3.0)
|
||||
shellany (0.0.1)
|
||||
shoulda-matchers (5.1.0)
|
||||
|
|
@ -2318,9 +2318,9 @@ DEPENDENCIES
|
|||
seed-fu (~> 2.3.7)
|
||||
selenium-webdriver (~> 4.21, >= 4.21.1)
|
||||
semver_dialects (~> 3.0)
|
||||
sentry-rails (~> 5.21.0)
|
||||
sentry-ruby (~> 5.21.0)
|
||||
sentry-sidekiq (~> 5.21.0)
|
||||
sentry-rails (~> 5.22.0)
|
||||
sentry-ruby (~> 5.22.0)
|
||||
sentry-sidekiq (~> 5.22.0)
|
||||
shoulda-matchers (~> 5.1.0)
|
||||
sidekiq!
|
||||
sidekiq-cron (~> 1.12.0)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<span class="gl-flex gl-max-w-full gl-grow gl-items-center">
|
||||
<span class="gl-flex gl-items-center">
|
||||
<ci-icon
|
||||
:size="iconSize"
|
||||
:status="status"
|
||||
|
|
@ -37,7 +37,7 @@ export default {
|
|||
:use-link="false"
|
||||
class="gl-leading-0"
|
||||
/>
|
||||
<span class="gl-inline-block gl-max-w-7/10 gl-truncate gl-pl-3" :title="name">
|
||||
<span class="gl-line-clamp-2 gl-pl-3 gl-wrap-anywhere" :title="name">
|
||||
{{ name }}
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -51,10 +51,9 @@ export default {
|
|||
<template>
|
||||
<gl-disclosure-dropdown-item :item="item" class="ci-job-component" data-testid="job-item">
|
||||
<template #list-item>
|
||||
<div class="-gl-my-2 gl-flex gl-h-6">
|
||||
<div class="-gl-my-2 gl-flex gl-items-center gl-justify-between">
|
||||
<job-name-component
|
||||
v-gl-tooltip.viewport.left
|
||||
class="-gl-my-2 gl-min-w-0"
|
||||
:title="tooltipText"
|
||||
:name="job.name"
|
||||
:status="status"
|
||||
|
|
@ -62,6 +61,7 @@ export default {
|
|||
/>
|
||||
<job-action-button
|
||||
v-if="hasJobAction"
|
||||
class="gl-ml-6"
|
||||
:job-id="job.id"
|
||||
:job-action="status.action"
|
||||
:job-name="job.name"
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ export default {
|
|||
<gl-disclosure-dropdown
|
||||
data-testid="pipeline-mini-graph-dropdown"
|
||||
:aria-label="stageAriaLabel(stage.name)"
|
||||
fluid-width
|
||||
@hidden="onHideDropdown"
|
||||
@shown="onShowDropdown"
|
||||
>
|
||||
|
|
@ -145,7 +146,7 @@ export default {
|
|||
</div>
|
||||
<ul
|
||||
v-else
|
||||
class="gl-m-0 gl-overflow-y-auto gl-p-0"
|
||||
class="gl-m-0 gl-w-34 gl-overflow-y-auto gl-p-0"
|
||||
data-testid="pipeline-mini-graph-dropdown-menu-list"
|
||||
@click.stop
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
<script>
|
||||
import { GlFormGroup, GlButton, GlFormInput } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import addNamespaceMutation from '../graphql/mutations/inbound_add_group_or_project_ci_job_token_scope.mutation.graphql';
|
||||
import PoliciesSelector from './policies_selector.vue';
|
||||
|
||||
export default {
|
||||
components: { GlFormGroup, GlButton, GlFormInput },
|
||||
components: { GlFormGroup, GlButton, GlFormInput, PoliciesSelector },
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
inject: ['fullPath'],
|
||||
data() {
|
||||
return {
|
||||
namespacePath: '',
|
||||
targetPath: '',
|
||||
defaultPermissions: true,
|
||||
jobTokenPolicies: [],
|
||||
errorMessage: '',
|
||||
isSaving: false,
|
||||
};
|
||||
|
|
@ -19,10 +24,14 @@ export default {
|
|||
this.isSaving = true;
|
||||
this.errorMessage = '';
|
||||
|
||||
const response = await this.$apollo.mutate({
|
||||
mutation: addNamespaceMutation,
|
||||
variables: { projectPath: this.fullPath, targetPath: this.namespacePath },
|
||||
});
|
||||
const variables = { projectPath: this.fullPath, targetPath: this.targetPath };
|
||||
|
||||
if (this.glFeatures.addPoliciesToCiJobToken) {
|
||||
variables.defaultPermissions = this.defaultPermissions;
|
||||
variables.jobTokenPolicies = this.defaultPermissions ? [] : this.jobTokenPolicies;
|
||||
}
|
||||
|
||||
const response = await this.$apollo.mutate({ mutation: addNamespaceMutation, variables });
|
||||
|
||||
const error = response.data.ciJobTokenScopeAddGroupOrProject.errors[0];
|
||||
if (error) {
|
||||
|
|
@ -57,7 +66,7 @@ export default {
|
|||
>
|
||||
<gl-form-input
|
||||
id="namespace-input"
|
||||
v-model.trim="namespacePath"
|
||||
v-model.trim="targetPath"
|
||||
autofocus
|
||||
:state="!errorMessage"
|
||||
:placeholder="fullPath"
|
||||
|
|
@ -66,9 +75,19 @@ export default {
|
|||
/>
|
||||
</gl-form-group>
|
||||
|
||||
<policies-selector
|
||||
v-if="glFeatures.addPoliciesToCiJobToken"
|
||||
:is-default-permissions-selected="defaultPermissions"
|
||||
:job-token-policies="jobTokenPolicies"
|
||||
:disabled="isSaving"
|
||||
class="gl-mb-6"
|
||||
@update:isDefaultPermissionsSelected="defaultPermissions = $event"
|
||||
@update:jobTokenPolicies="jobTokenPolicies = $event"
|
||||
/>
|
||||
|
||||
<gl-button
|
||||
variant="confirm"
|
||||
:disabled="!namespacePath"
|
||||
:disabled="!targetPath"
|
||||
:loading="isSaving"
|
||||
data-testid="add-button"
|
||||
@click="saveNamespace"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
<script>
|
||||
import { GlCollapsibleListbox, GlFormRadioGroup, GlFormRadio, GlTableLite } from '@gitlab/ui';
|
||||
import { s__, __ } from '~/locale';
|
||||
import {
|
||||
POLICIES_BY_RESOURCE,
|
||||
JOB_TOKEN_RESOURCES,
|
||||
JOB_TOKEN_POLICIES,
|
||||
POLICY_NONE,
|
||||
} from '../constants';
|
||||
|
||||
export const TABLE_FIELDS = [
|
||||
{
|
||||
key: 'resource.text',
|
||||
label: s__('JobToken|Resource'),
|
||||
class: '!gl-border-none !gl-py-3 !gl-pl-0 !gl-align-middle gl-w-28',
|
||||
},
|
||||
{
|
||||
key: 'policies',
|
||||
label: __('Permissions'),
|
||||
class: '!gl-border-none !gl-py-3',
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
components: { GlCollapsibleListbox, GlFormRadioGroup, GlFormRadio, GlTableLite },
|
||||
props: {
|
||||
isDefaultPermissionsSelected: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
jobTokenPolicies: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
// Create an object where the key is the resource key and the value is the None option.
|
||||
const selected = Object.keys(JOB_TOKEN_RESOURCES).reduce((acc, resourceKey) => {
|
||||
acc[resourceKey] = POLICY_NONE.value;
|
||||
return acc;
|
||||
}, {});
|
||||
// Then use the values in jobTokenPolicies to set the selected options.
|
||||
this.jobTokenPolicies.forEach((policyValue) => {
|
||||
const policy = JOB_TOKEN_POLICIES[policyValue];
|
||||
selected[policy.resource.value] = policyValue;
|
||||
});
|
||||
|
||||
return selected;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
emitPermissionTypeChange(value) {
|
||||
this.$emit('update:isDefaultPermissionsSelected', value);
|
||||
},
|
||||
emitPoliciesChange(value, item) {
|
||||
const policies = { ...this.selected, [item.resource.value]: value };
|
||||
// Remove any '' values from the None policy.
|
||||
this.$emit('update:jobTokenPolicies', Object.values(policies).filter(Boolean));
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
defaultPermissions: s__(
|
||||
'JobToken|Use the standard permissions model based on user membership and roles.',
|
||||
),
|
||||
fineGrainedPermissions: s__(
|
||||
'JobToken|Apply permissions that grant access to individual resources.',
|
||||
),
|
||||
},
|
||||
TABLE_FIELDS,
|
||||
POLICIES_BY_RESOURCE,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<label>{{ __('Permissions') }}</label>
|
||||
<gl-form-radio-group
|
||||
:checked="isDefaultPermissionsSelected"
|
||||
:disabled="disabled"
|
||||
class="gl-mb-6"
|
||||
@change="emitPermissionTypeChange"
|
||||
>
|
||||
<gl-form-radio :value="true" data-testid="default-radio">
|
||||
{{ s__('JobToken|Default permissions') }}
|
||||
<template #help>{{ $options.i18n.defaultPermissions }}</template>
|
||||
</gl-form-radio>
|
||||
<gl-form-radio :value="false" data-testid="fine-grained-radio">
|
||||
{{ s__('JobToken|Fine-grained permissions') }}
|
||||
<template #help>{{ $options.i18n.fineGrainedPermissions }}</template>
|
||||
</gl-form-radio>
|
||||
</gl-form-radio-group>
|
||||
|
||||
<gl-table-lite
|
||||
v-if="!isDefaultPermissionsSelected"
|
||||
:fields="$options.TABLE_FIELDS"
|
||||
:items="$options.POLICIES_BY_RESOURCE"
|
||||
fixed
|
||||
>
|
||||
<template #cell(policies)="{ item, value: policies }">
|
||||
<gl-collapsible-listbox
|
||||
:items="policies"
|
||||
:disabled="disabled"
|
||||
:selected="selected[item.resource.value]"
|
||||
block
|
||||
class="gl-w-20"
|
||||
@select="emitPoliciesChange($event, item)"
|
||||
/>
|
||||
</template>
|
||||
</gl-table-lite>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -9,13 +9,13 @@ export default {
|
|||
{
|
||||
key: 'fullPath',
|
||||
label: s__('CICD|Group or project'),
|
||||
tdClass: 'gl-w-3/4',
|
||||
tdClass: 'gl-w-full',
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
label: __('Actions'),
|
||||
class: 'gl-text-right',
|
||||
tdClass: '!gl-py-0 !gl-pl-0 gl-w-0 !gl-align-middle',
|
||||
class: 'gl-text-right !gl-pl-0',
|
||||
tdClass: '!gl-py-0 !gl-align-middle',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
import { keyBy } from 'lodash';
|
||||
import { s__, __ } from '~/locale';
|
||||
|
||||
export const RESOURCE_CONTAINERS = { value: 'CONTAINERS', text: s__('JobToken|Containers') };
|
||||
export const RESOURCE_DEPLOYMENTS = { value: 'DEPLOYMENTS', text: s__('JobToken|Deployments') };
|
||||
export const RESOURCE_ENVIRONMENTS = { value: 'ENVIRONMENTS', text: s__('JobToken|Environments') };
|
||||
export const RESOURCE_JOBS = { value: 'JOBS', text: s__('JobToken|Jobs') };
|
||||
export const RESOURCE_PACKAGES = { value: 'PACKAGES', text: s__('JobToken|Packages') };
|
||||
export const RESOURCE_RELEASES = { value: 'RELEASES', text: s__('JobToken|Releases') };
|
||||
export const RESOURCE_SECURE_FILES = { value: 'SECURE_FILES', text: s__('JobToken|Secure files') };
|
||||
export const RESOURCE_TERRAFORM_STATE = {
|
||||
value: 'TERRAFORM_STATE',
|
||||
text: s__('JobToken|Terraform state'),
|
||||
};
|
||||
|
||||
const READ = s__('JobToken|Read');
|
||||
const READ_AND_WRITE = s__('JobToken|Read and write');
|
||||
|
||||
export const POLICY_READ_CONTAINERS = {
|
||||
value: 'READ_CONTAINERS',
|
||||
text: READ,
|
||||
resource: RESOURCE_CONTAINERS,
|
||||
};
|
||||
export const POLICY_ADMIN_CONTAINERS = {
|
||||
value: 'ADMIN_CONTAINERS',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_CONTAINERS,
|
||||
};
|
||||
export const POLICY_READ_DEPLOYMENTS = {
|
||||
value: 'READ_DEPLOYMENTS',
|
||||
text: READ,
|
||||
resource: RESOURCE_DEPLOYMENTS,
|
||||
};
|
||||
export const POLICY_ADMIN_DEPLOYMENTS = {
|
||||
value: 'ADMIN_DEPLOYMENTS',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_DEPLOYMENTS,
|
||||
};
|
||||
export const POLICY_READ_ENVIRONMENTS = {
|
||||
value: 'READ_ENVIRONMENTS',
|
||||
text: READ,
|
||||
resource: RESOURCE_ENVIRONMENTS,
|
||||
};
|
||||
export const POLICY_ADMIN_ENVIRONMENTS = {
|
||||
value: 'ADMIN_ENVIRONMENTS',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_ENVIRONMENTS,
|
||||
};
|
||||
export const POLICY_READ_JOBS = {
|
||||
value: 'READ_JOBS',
|
||||
text: READ,
|
||||
resource: RESOURCE_JOBS,
|
||||
};
|
||||
export const POLICY_ADMIN_JOBS = {
|
||||
value: 'ADMIN_JOBS',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_JOBS,
|
||||
};
|
||||
export const POLICY_READ_PACKAGES = {
|
||||
value: 'READ_PACKAGES',
|
||||
text: READ,
|
||||
resource: RESOURCE_PACKAGES,
|
||||
};
|
||||
export const POLICY_ADMIN_PACKAGES = {
|
||||
value: 'ADMIN_PACKAGES',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_PACKAGES,
|
||||
};
|
||||
export const POLICY_READ_RELEASES = {
|
||||
value: 'READ_RELEASES',
|
||||
text: READ,
|
||||
resource: RESOURCE_RELEASES,
|
||||
};
|
||||
export const POLICY_ADMIN_RELEASES = {
|
||||
value: 'ADMIN_RELEASES',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_RELEASES,
|
||||
};
|
||||
export const POLICY_READ_SECURE_FILES = {
|
||||
value: 'READ_SECURE_FILES',
|
||||
text: READ,
|
||||
resource: RESOURCE_SECURE_FILES,
|
||||
};
|
||||
export const POLICY_ADMIN_SECURE_FILES = {
|
||||
value: 'ADMIN_SECURE_FILES',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_SECURE_FILES,
|
||||
};
|
||||
export const POLICY_READ_TERRAFORM_STATE = {
|
||||
value: 'READ_TERRAFORM_STATE',
|
||||
text: READ,
|
||||
resource: RESOURCE_TERRAFORM_STATE,
|
||||
};
|
||||
export const POLICY_ADMIN_TERRAFORM_STATE = {
|
||||
value: 'ADMIN_TERRAFORM_STATE',
|
||||
text: READ_AND_WRITE,
|
||||
resource: RESOURCE_TERRAFORM_STATE,
|
||||
};
|
||||
export const POLICY_NONE = { value: '', text: __('None') };
|
||||
|
||||
export const POLICIES_BY_RESOURCE = [
|
||||
{
|
||||
resource: RESOURCE_CONTAINERS,
|
||||
policies: [POLICY_NONE, POLICY_READ_CONTAINERS, POLICY_ADMIN_CONTAINERS],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_DEPLOYMENTS,
|
||||
policies: [POLICY_NONE, POLICY_READ_DEPLOYMENTS, POLICY_ADMIN_DEPLOYMENTS],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_ENVIRONMENTS,
|
||||
policies: [POLICY_NONE, POLICY_READ_ENVIRONMENTS, POLICY_ADMIN_ENVIRONMENTS],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_JOBS,
|
||||
policies: [POLICY_NONE, POLICY_READ_JOBS, POLICY_ADMIN_JOBS],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_PACKAGES,
|
||||
policies: [POLICY_NONE, POLICY_READ_PACKAGES, POLICY_ADMIN_PACKAGES],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_RELEASES,
|
||||
policies: [POLICY_NONE, POLICY_READ_RELEASES, POLICY_ADMIN_RELEASES],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_SECURE_FILES,
|
||||
policies: [POLICY_NONE, POLICY_READ_SECURE_FILES, POLICY_ADMIN_SECURE_FILES],
|
||||
},
|
||||
{
|
||||
resource: RESOURCE_TERRAFORM_STATE,
|
||||
policies: [POLICY_NONE, POLICY_READ_TERRAFORM_STATE, POLICY_ADMIN_TERRAFORM_STATE],
|
||||
},
|
||||
];
|
||||
|
||||
// Create an object where the key is the resource value string and the value is the resource object. Used to look up
|
||||
// a resource by its value string.
|
||||
export const JOB_TOKEN_RESOURCES = keyBy(
|
||||
POLICIES_BY_RESOURCE.map(({ resource }) => resource),
|
||||
({ value }) => value,
|
||||
);
|
||||
// Create an object where the key is the policy value string and the value is the policy object. Used to look up a
|
||||
// policy by its value string.
|
||||
export const JOB_TOKEN_POLICIES = keyBy(
|
||||
POLICIES_BY_RESOURCE.flatMap(({ policies }) => policies),
|
||||
({ value }) => value,
|
||||
);
|
||||
|
|
@ -1,5 +1,17 @@
|
|||
mutation inboundAddGroupOrProjectCIJobTokenScope($projectPath: ID!, $targetPath: ID!) {
|
||||
ciJobTokenScopeAddGroupOrProject(input: { projectPath: $projectPath, targetPath: $targetPath }) {
|
||||
mutation inboundAddGroupOrProjectCIJobTokenScope(
|
||||
$projectPath: ID!
|
||||
$targetPath: ID!
|
||||
$defaultPermissions: Boolean
|
||||
$jobTokenPolicies: [CiJobTokenScopePolicies!]
|
||||
) {
|
||||
ciJobTokenScopeAddGroupOrProject(
|
||||
input: {
|
||||
projectPath: $projectPath
|
||||
targetPath: $targetPath
|
||||
defaultPermissions: $defaultPermissions
|
||||
jobTokenPolicies: $jobTokenPolicies
|
||||
}
|
||||
) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ export default {
|
|||
type="danger"
|
||||
dismissible
|
||||
data-testid="merge-error"
|
||||
class="mr-widget-section"
|
||||
class="mr-widget-section gl-rounded-b-none gl-border-b-section"
|
||||
>
|
||||
<span v-safe-html="mergeError"></span>
|
||||
</mr-widget-alert-message>
|
||||
|
|
@ -552,7 +552,7 @@ export default {
|
|||
v-if="showMergePipelineForkWarning"
|
||||
type="warning"
|
||||
:help-path="mr.mergeRequestPipelinesHelpPath"
|
||||
class="mr-widget-section"
|
||||
class="mr-widget-section gl-rounded-b-none gl-border-b-section"
|
||||
data-testid="merge-pipeline-fork-warning"
|
||||
>
|
||||
{{
|
||||
|
|
|
|||
|
|
@ -265,6 +265,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
persist_accepted_terms_if_required(@user) if new_user
|
||||
|
||||
perform_registration_tasks(@user, oauth['provider']) if new_user
|
||||
|
||||
enqueue_after_sign_in_workers(@user)
|
||||
|
||||
sign_in_and_redirect_or_verify_identity(@user, auth_user, new_user)
|
||||
end
|
||||
else
|
||||
|
|
@ -438,6 +441,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
sign_in_and_redirect(user, event: :authentication)
|
||||
end
|
||||
|
||||
# overridden in specific EE class
|
||||
def enqueue_after_sign_in_workers(_user)
|
||||
true
|
||||
end
|
||||
|
||||
def store_idp_two_factor_status(bypass_2fa)
|
||||
if Feature.enabled?(:by_pass_two_factor_for_current_session)
|
||||
session[:provider_2FA] = true if bypass_2fa
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ module Projects
|
|||
before_action do
|
||||
push_frontend_feature_flag(:ci_variables_pages, current_user)
|
||||
push_frontend_feature_flag(:allow_push_repository_for_job_token, @project)
|
||||
push_frontend_feature_flag(:add_policies_to_ci_job_token, @project)
|
||||
|
||||
push_frontend_ability(ability: :admin_project, resource: @project, user: current_user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Preloaders
|
||||
class SingleHierarchyProjectGroupPlansPreloader
|
||||
attr_reader :projects
|
||||
|
||||
def initialize(projects_relation)
|
||||
@projects = projects_relation
|
||||
end
|
||||
|
||||
def execute
|
||||
# no-op in FOSS
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Preloaders::SingleHierarchyProjectGroupPlansPreloader.prepend_mod_with('Preloaders::SingleHierarchyProjectGroupPlansPreloader')
|
||||
|
|
@ -925,6 +925,12 @@ production: &base
|
|||
#
|
||||
admin_group: ''
|
||||
|
||||
# LDAP group of users who should have Duo Add-on seats assigned in GitLab
|
||||
#
|
||||
# Ex. ['DuoGroup1', 'DuoGroup2']
|
||||
#
|
||||
duo_add_on_groups: []
|
||||
|
||||
# LDAP group of users who should be marked as external users in GitLab
|
||||
#
|
||||
# Ex. ['Contractors', 'Interns']
|
||||
|
|
|
|||
|
|
@ -385,6 +385,8 @@
|
|||
- 1
|
||||
- - gitlab_subscriptions_add_on_purchases_email_on_duo_bulk_user_assignments
|
||||
- 1
|
||||
- - gitlab_subscriptions_add_on_purchases_ldap_add_on_seat_sync
|
||||
- 1
|
||||
- - gitlab_subscriptions_add_on_purchases_refresh_user_assignments
|
||||
- 1
|
||||
- - gitlab_subscriptions_member_management_apply_pending_member_approvals
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
migration_job_name: BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId
|
||||
description: Backfills sharding key `protected_branch_unprotect_access_levels.protected_branch_namespace_id` from `protected_branches`.
|
||||
feature_category: source_code_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175718
|
||||
milestone: '17.8'
|
||||
queued_migration_version: 20241213141747
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
migration_job_name: BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId
|
||||
description: Backfills sharding key `protected_branch_unprotect_access_levels.protected_branch_project_id` from `protected_branches`.
|
||||
feature_category: source_code_management
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175718
|
||||
milestone: '17.8'
|
||||
queued_migration_version: 20241213141742
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -26,3 +26,6 @@ desired_sharding_key:
|
|||
sharding_key: namespace_id
|
||||
belongs_to: protected_branch
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name:
|
||||
- BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId
|
||||
- BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProtectedBranchProjectIdToProtectedBranchUnprotectAccessLevels < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
|
||||
def change
|
||||
add_column :protected_branch_unprotect_access_levels, :protected_branch_project_id, :bigint
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProtectedBranchNamespaceIdToProtectedBranchUnprotectAccessLevels < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
|
||||
def change
|
||||
add_column :protected_branch_unprotect_access_levels, :protected_branch_namespace_id, :bigint
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexProtectedBranchUnprotectAccessLevelsOnProtectedBranchProjectId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'i_protected_branch_unprotect_access_levels_protected_branch_pro'
|
||||
|
||||
def up
|
||||
add_concurrent_index :protected_branch_unprotect_access_levels, :protected_branch_project_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :protected_branch_unprotect_access_levels, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchProjectIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :protected_branch_unprotect_access_levels, :projects,
|
||||
column: :protected_branch_project_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :protected_branch_unprotect_access_levels, column: :protected_branch_project_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchProjectIdTrigger < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
|
||||
def up
|
||||
install_sharding_key_assignment_trigger(
|
||||
table: :protected_branch_unprotect_access_levels,
|
||||
sharding_key: :protected_branch_project_id,
|
||||
parent_table: :protected_branches,
|
||||
parent_sharding_key: :project_id,
|
||||
foreign_key: :protected_branch_id
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_sharding_key_assignment_trigger(
|
||||
table: :protected_branch_unprotect_access_levels,
|
||||
sharding_key: :protected_branch_project_id,
|
||||
parent_table: :protected_branches,
|
||||
parent_sharding_key: :project_id,
|
||||
foreign_key: :protected_branch_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillProtectedBranchUnprotectAccessLevelsProjectId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
MIGRATION = "BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:protected_branch_unprotect_access_levels,
|
||||
:id,
|
||||
:protected_branch_project_id,
|
||||
:protected_branches,
|
||||
:project_id,
|
||||
:protected_branch_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
:protected_branch_unprotect_access_levels,
|
||||
:id,
|
||||
[
|
||||
:protected_branch_project_id,
|
||||
:protected_branches,
|
||||
:project_id,
|
||||
:protected_branch_id
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexProtectedBranchUnprotectAccessLevelsOnProtectedBranchNamespaceId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'i_protected_branch_unprotect_access_levels_protected_branch_nam'
|
||||
|
||||
def up
|
||||
add_concurrent_index :protected_branch_unprotect_access_levels, :protected_branch_namespace_id, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :protected_branch_unprotect_access_levels, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceIdFk < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_foreign_key :protected_branch_unprotect_access_levels, :namespaces,
|
||||
column: :protected_branch_namespace_id, on_delete: :cascade
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_foreign_key :protected_branch_unprotect_access_levels, column: :protected_branch_namespace_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceIdTrigger < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
|
||||
def up
|
||||
install_sharding_key_assignment_trigger(
|
||||
table: :protected_branch_unprotect_access_levels,
|
||||
sharding_key: :protected_branch_namespace_id,
|
||||
parent_table: :protected_branches,
|
||||
parent_sharding_key: :namespace_id,
|
||||
foreign_key: :protected_branch_id
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_sharding_key_assignment_trigger(
|
||||
table: :protected_branch_unprotect_access_levels,
|
||||
sharding_key: :protected_branch_namespace_id,
|
||||
parent_table: :protected_branches,
|
||||
parent_sharding_key: :namespace_id,
|
||||
foreign_key: :protected_branch_id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillProtectedBranchUnprotectAccessLevelsNamespaceId < Gitlab::Database::Migration[2.2]
|
||||
milestone '17.8'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
MIGRATION = "BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 1000
|
||||
SUB_BATCH_SIZE = 100
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:protected_branch_unprotect_access_levels,
|
||||
:id,
|
||||
:protected_branch_namespace_id,
|
||||
:protected_branches,
|
||||
:namespace_id,
|
||||
:protected_branch_id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(
|
||||
MIGRATION,
|
||||
:protected_branch_unprotect_access_levels,
|
||||
:id,
|
||||
[
|
||||
:protected_branch_namespace_id,
|
||||
:protected_branches,
|
||||
:namespace_id,
|
||||
:protected_branch_id
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
a083f15fc20f7f3d6501e5ea5fd11ba7b8dd9c7e6d86216f83e5214fc5fa9f9f
|
||||
|
|
@ -0,0 +1 @@
|
|||
d10e72b8cf31f8ad24d4f2c3e7ed89533677c73d7367da9f896f9740b3c5a22d
|
||||
|
|
@ -0,0 +1 @@
|
|||
b005f824cd7cb6542305413209e1c71e70e102eb73a35628ed09e328ee373929
|
||||
|
|
@ -0,0 +1 @@
|
|||
3b2a59e3b874d57a98d8a029c26ca96fe18f6364b24e9b61b8e831c54a93b2c3
|
||||
|
|
@ -0,0 +1 @@
|
|||
b06ddae59bae1e4db5229521bf7a17530b50550543a282821b7d4084d8784907
|
||||
|
|
@ -0,0 +1 @@
|
|||
f600fb78971583654009b0c3312b50559da80c51ef8f4cdb52a4cc5916d6e1e8
|
||||
|
|
@ -0,0 +1 @@
|
|||
f0b00d1e6904ea0381c25e4470066631ae0e0d64c0e7884a9595cd20b32d7171
|
||||
|
|
@ -0,0 +1 @@
|
|||
e54c68567a9cd1b9740f1c5436fb2c956a073890e513e2c8630caf3e2ad27c41
|
||||
|
|
@ -0,0 +1 @@
|
|||
c9388208f640459b3dfb2bc72901375911d897c4bb679784bc750836e290b45b
|
||||
|
|
@ -0,0 +1 @@
|
|||
9a6b9a01e0d5c8c0cd2f124822c92d9a878f13d24bad3fe58c5c9d5c789cdf58
|
||||
|
|
@ -2512,6 +2512,22 @@ RETURN NEW;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_96298f7da5d3() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW."protected_branch_project_id" IS NULL THEN
|
||||
SELECT "project_id"
|
||||
INTO NEW."protected_branch_project_id"
|
||||
FROM "protected_branches"
|
||||
WHERE "protected_branches"."id" = NEW."protected_branch_id";
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_9699ea03bb37() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
|
@ -3200,6 +3216,22 @@ RETURN NEW;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_ed554313ca66() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW."protected_branch_namespace_id" IS NULL THEN
|
||||
SELECT "namespace_id"
|
||||
INTO NEW."protected_branch_namespace_id"
|
||||
FROM "protected_branches"
|
||||
WHERE "protected_branches"."id" = NEW."protected_branch_id";
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_efb9d354f05a() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
|
@ -18947,7 +18979,9 @@ CREATE TABLE protected_branch_unprotect_access_levels (
|
|||
protected_branch_id bigint NOT NULL,
|
||||
access_level integer DEFAULT 40,
|
||||
user_id bigint,
|
||||
group_id bigint
|
||||
group_id bigint,
|
||||
protected_branch_project_id bigint,
|
||||
protected_branch_namespace_id bigint
|
||||
);
|
||||
|
||||
CREATE SEQUENCE protected_branch_unprotect_access_levels_id_seq
|
||||
|
|
@ -28947,6 +28981,10 @@ CREATE UNIQUE INDEX i_pm_package_versions_on_package_id_and_version ON pm_packag
|
|||
|
||||
CREATE UNIQUE INDEX i_pm_packages_purl_type_and_name ON pm_packages USING btree (purl_type, name);
|
||||
|
||||
CREATE INDEX i_protected_branch_unprotect_access_levels_protected_branch_nam ON protected_branch_unprotect_access_levels USING btree (protected_branch_namespace_id);
|
||||
|
||||
CREATE INDEX i_protected_branch_unprotect_access_levels_protected_branch_pro ON protected_branch_unprotect_access_levels USING btree (protected_branch_project_id);
|
||||
|
||||
CREATE UNIQUE INDEX i_sbom_occurrences_vulnerabilities_on_occ_id_and_vuln_id ON sbom_occurrences_vulnerabilities USING btree (sbom_occurrence_id, vulnerability_id);
|
||||
|
||||
CREATE INDEX i_software_license_policies_on_custom_software_license_id ON software_license_policies USING btree (custom_software_license_id);
|
||||
|
|
@ -36019,6 +36057,8 @@ CREATE TRIGGER trigger_9259aae92378 BEFORE INSERT OR UPDATE ON packages_build_in
|
|||
|
||||
CREATE TRIGGER trigger_94514aeadc50 BEFORE INSERT OR UPDATE ON deployment_approvals FOR EACH ROW EXECUTE FUNCTION trigger_94514aeadc50();
|
||||
|
||||
CREATE TRIGGER trigger_96298f7da5d3 BEFORE INSERT OR UPDATE ON protected_branch_unprotect_access_levels FOR EACH ROW EXECUTE FUNCTION trigger_96298f7da5d3();
|
||||
|
||||
CREATE TRIGGER trigger_9699ea03bb37 BEFORE INSERT OR UPDATE ON related_epic_links FOR EACH ROW EXECUTE FUNCTION trigger_9699ea03bb37();
|
||||
|
||||
CREATE TRIGGER trigger_96a76ee9f147 BEFORE INSERT OR UPDATE ON design_management_versions FOR EACH ROW EXECUTE FUNCTION trigger_96a76ee9f147();
|
||||
|
|
@ -36107,6 +36147,8 @@ CREATE TRIGGER trigger_ebab34f83f1d BEFORE INSERT OR UPDATE ON packages_debian_p
|
|||
|
||||
CREATE TRIGGER trigger_ec1934755627 BEFORE INSERT OR UPDATE ON alert_management_alert_metric_images FOR EACH ROW EXECUTE FUNCTION trigger_ec1934755627();
|
||||
|
||||
CREATE TRIGGER trigger_ed554313ca66 BEFORE INSERT OR UPDATE ON protected_branch_unprotect_access_levels FOR EACH ROW EXECUTE FUNCTION trigger_ed554313ca66();
|
||||
|
||||
CREATE TRIGGER trigger_efb9d354f05a BEFORE INSERT OR UPDATE ON incident_management_issuable_escalation_statuses FOR EACH ROW EXECUTE FUNCTION trigger_efb9d354f05a();
|
||||
|
||||
CREATE TRIGGER trigger_eff80ead42ac BEFORE INSERT OR UPDATE ON ci_unit_test_failures FOR EACH ROW EXECUTE FUNCTION trigger_eff80ead42ac();
|
||||
|
|
@ -36536,6 +36578,9 @@ ALTER TABLE ONLY approvals
|
|||
ALTER TABLE ONLY namespaces
|
||||
ADD CONSTRAINT fk_319256d87a FOREIGN KEY (file_template_project_id) REFERENCES projects(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY protected_branch_unprotect_access_levels
|
||||
ADD CONSTRAINT fk_325cad614b FOREIGN KEY (protected_branch_project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY bulk_import_entities
|
||||
ADD CONSTRAINT fk_32782a175e FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
@ -36755,6 +36800,9 @@ ALTER TABLE ONLY status_check_responses
|
|||
ALTER TABLE ONLY merge_request_metrics
|
||||
ADD CONSTRAINT fk_56067dcb44 FOREIGN KEY (target_project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY protected_branch_unprotect_access_levels
|
||||
ADD CONSTRAINT fk_5632201009 FOREIGN KEY (protected_branch_namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY merge_request_diffs
|
||||
ADD CONSTRAINT fk_56ac6fc9c0 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ Allow enough time for downtime while step two is being executed.
|
|||
|
||||
Edit your `/etc/gitlab/gitlab.rb` and add the `maintenance` section to the `registry['storage']`
|
||||
configuration. For example, for a `gcs` backed registry using a `gs://my-company-container-registry`
|
||||
bucket , the configuration could be:
|
||||
bucket, the configuration could be:
|
||||
|
||||
```ruby
|
||||
## Object Storage - Container Registry
|
||||
|
|
|
|||
|
|
@ -308,8 +308,8 @@ the specific list of rules.
|
|||
If you want to force `e2e:test-on-omnibus` to run regardless of your changes, you can add the
|
||||
`pipeline:run-all-e2e` label to the merge request.
|
||||
|
||||
The [`e2e:test-on-gdk`](../testing_guide/end_to_end/index.md#using-the-test-on-gdk-job) child pipeline runs `:blocking`
|
||||
E2E specs automatically for all `code patterns changes`. See `.qa:rules:e2e-blocking-gdk` [`rules.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rules.gitlab-ci.yml) for specific set of rules.
|
||||
The [`e2e:test-on-gdk`](../testing_guide/end_to_end/index.md#using-the-test-on-gdk-job) child pipeline runs
|
||||
E2E specs automatically for all `code patterns changes`. See `.qa:rules:e2e:test-on-gdk` in [`rules.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rules.gitlab-ci.yml) for specific set of rules.
|
||||
|
||||
Consult the [End-to-end Testing](../testing_guide/end_to_end/index.md) dedicated page for more information.
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ This is a partial list of the [RSpec metadata](https://rspec.info/features/3-12/
|
|||
| `:product_group` | Specifies what product group the test belongs to. See [Product sections, stages, groups, and categories](https://handbook.gitlab.com/handbook/product/categories/) for the comprehensive groups list. |
|
||||
| `:quarantine` | The test has been [quarantined](https://handbook.gitlab.com/handbook/engineering/infrastructure/test-platform/debugging-qa-test-failures/#quarantining-tests), runs in a separate job that only includes quarantined tests, and is allowed to fail. The test is skipped in its regular job so that if it fails it doesn't hold up the pipeline. Note that you can also [quarantine a test only when it runs in a specific context](execution_context_selection.md#quarantine-a-test-for-a-specific-environment). |
|
||||
| `:relative_url` | The test requires a GitLab instance to be installed under a [relative URL](../../../../install/relative_url.md). |
|
||||
| `:blocking` | Tag for tests running in `e2e:test-on-gdk` pipeline and not allowed to fail. Only tests that pass consistently should have this tag. |
|
||||
| `:repository_storage` | The test requires a GitLab instance to be configured to use multiple [repository storage paths](../../../../administration/repository_storage_paths.md). Paired with the `:orchestrated` tag. |
|
||||
| `:requires_admin` | The test requires an administrator account. Tests with the tag are excluded when run against Canary and Production environments. |
|
||||
| `:requires_git_protocol_v2` | The test requires that Git protocol version 2 is enabled on the server. It's assumed to be enabled by default but if not the test can be skipped by setting `QA_CAN_TEST_GIT_PROTOCOL_V2` to `false`. |
|
||||
|
|
|
|||
|
|
@ -21,27 +21,7 @@ For any of the following scenarios, the `start-review-app-pipeline` job would be
|
|||
- for scheduled pipelines
|
||||
- the MR has the `pipeline:run-review-app` label set
|
||||
|
||||
## E2E test runs on review apps
|
||||
|
||||
On every pipeline in the `qa` stage (which comes after the `review` stage), the `review-qa-smoke` and `review-qa-blocking` jobs are automatically started.
|
||||
|
||||
`qa` stage consists of following jobs:
|
||||
|
||||
- `review-qa-smoke`: small and fast subset of tests to validate core functionality of GitLab.
|
||||
- `review-qa-blocking`: subset of tests that block the merge request. These tests are
|
||||
considered stable and are not allowed to fail.
|
||||
- `review-qa-non-blocking`: rest of the e2e tests that can be triggered manually.
|
||||
|
||||
`review-qa-*` jobs ensure that end-to-end tests for the changes in the merge request pass in a live environment. This shifts the identification of e2e failures from an environment
|
||||
on the path to production to the merge request to prevent breaking features on GitLab.com or costly GitLab.com deployment blockers. If needed, `review-qa-*` failures should be
|
||||
investigated with an SET (software engineer in test) counterpart to help determine the root cause of the error.
|
||||
|
||||
After the end-to-end test runs have finished, [Allure reports](https://github.com/allure-framework/allure2) are generated and published by
|
||||
the `e2e-test-report` job. A comment with links to the reports is added to the merge request.
|
||||
|
||||
Errors can be found in the `gitlab-review-apps` Sentry project and [filterable by review app URL](https://sentry.gitlab.net/gitlab/gitlab-review-apps/?query=url%3A%22https%3A%2F%2Fgitlab-review-require-ve-u92nn2.gitlab-review.app%2F%22) or [commit SHA](https://sentry.gitlab.net/gitlab/gitlab-review-apps/releases/6095b501da7/all-events/).
|
||||
|
||||
### Bypass failed review app deployment to merge a broken `master` fix
|
||||
## Bypass failed review app deployment to merge a broken `master` fix
|
||||
|
||||
Maintainers can elect to use the [process for merging during broken `master`](https://handbook.gitlab.com/handbook/engineering/workflow/#instructions-for-the-maintainer) if a customer-critical merge request is blocked by pipelines failing due to review app deployment failures.
|
||||
|
||||
|
|
@ -138,14 +118,12 @@ graph TD
|
|||
B[review-build-cng];
|
||||
C["review-deploy<br><br>Helm deploys the review app using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."];
|
||||
D[CNG-mirror];
|
||||
E[review-qa-smoke, review-qa-blocking, review-qa-non-blocking<br><br>gitlab-qa runs the e2e tests against the review app.];
|
||||
|
||||
A --> B1
|
||||
B1 --> B
|
||||
B -.->|triggers a CNG-mirror pipeline| D
|
||||
D -.->|depends on the multi-project pipeline| B
|
||||
B --> C
|
||||
C --> E
|
||||
|
||||
subgraph "1. gitlab-org/gitlab parent pipeline"
|
||||
A
|
||||
|
|
@ -155,7 +133,6 @@ subgraph "1. gitlab-org/gitlab parent pipeline"
|
|||
subgraph "2. gitlab-org/gitlab child pipeline"
|
||||
B
|
||||
C
|
||||
E
|
||||
end
|
||||
|
||||
subgraph "CNG-mirror pipeline"
|
||||
|
|
@ -207,13 +184,6 @@ subgraph "CNG-mirror pipeline"
|
|||
issue with a link to your merge request. The deployment failure can
|
||||
reveal an actual problem introduced in your merge request (that is, this isn't
|
||||
necessarily a transient failure)!
|
||||
- If the `review-qa-smoke` job keeps failing (we already retry them once),
|
||||
check the job's logs: you could discover an actual problem introduced in
|
||||
your merge request. You can also download the artifacts to see screenshots of
|
||||
the page at the time the failures occurred. If you don't find the cause of the
|
||||
failure or if it seems unrelated to your change, post a message in the
|
||||
`#test-platform` channel and/or create a ~Quality ~"type::bug" issue with a link to your
|
||||
merge request.
|
||||
- The manual `review-stop` can be used to
|
||||
stop a review app manually, and is also started by GitLab once a merge
|
||||
request's branch is deleted after being merged.
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ The following regions are verified for use:
|
|||
- US West (Oregon)
|
||||
- Middle East (Bahrain)
|
||||
|
||||
For more information about selecting low emission regions, see [Choose Region based on both business requirements and sustainability goals](https://docs.aws.amazon.com/wellarchitected/latest/sustainability-pillar/sus_sus_region_a2.html).
|
||||
|
||||
If you're interested in a region not listed here, contact your account representative or [GitLab Support](https://about.gitlab.com/support/) to inquire about availability.
|
||||
|
||||
## Data isolation
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ To improve your workflow across the entire software development lifecycle, try t
|
|||
|
||||
- [GitLab Duo Chat](../gitlab_duo_chat/index.md): Write and understand code, get up to speed on the status of projects,
|
||||
and learn about GitLab by asking your questions in a chat window.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ZQBAuf-CTAY&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=ZQBAuf-CTAY)
|
||||
- [Self-Hosted Models](../../administration/self_hosted_models/index.md): Host the language models that power AI features in GitLab.
|
||||
Code Suggestions and Duo Chat are supported. Use GitLab model vendors or self-host a supported language model.
|
||||
- [GitLab Duo Workflow](../duo_workflow/index.md): Automate tasks and help increase productivity in your development workflow.
|
||||
|
|
@ -41,8 +41,11 @@ To improve your workflow across the entire software development lifecycle, try t
|
|||
To improve your workflow while planning work, try these features:
|
||||
|
||||
- [Issue Description Generation](../project/issues/managing_issues.md#populate-an-issue-with-issue-description-generation): Generate a more in-depth issue description based on a short summary.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=-BWBQat7p5M)
|
||||
<!-- Video published on 2024-12-18 -->
|
||||
- [Discussion Summary](../discussions/index.md#summarize-issue-discussions-with-duo-chat): Summarize lengthy conversations in an issue.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=IcdxLfTIUgc)
|
||||
<!-- Video published on 2024-03-28 -->
|
||||
|
||||
## Authoring code
|
||||
|
||||
|
|
@ -56,11 +59,11 @@ To improve your workflow while authoring code, try these features:
|
|||
- [A file](../../user/project/repository/code_explain.md).
|
||||
- [A merge request](../../user/project/merge_requests/changes.md#explain-code-in-a-merge-request).
|
||||
- [Test Generation](../gitlab_duo_chat/examples.md#write-tests-in-the-ide): Test your code by generating tests.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
|
||||
- [Refactor Code](../gitlab_duo_chat/examples.md#refactor-code-in-the-ide): Improve or refactor the selected code.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
|
||||
- [Fix Code](../gitlab_duo_chat/examples.md#fix-code-in-the-ide): Fix quality problems, like bugs or typos, in the selected code.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
|
||||
- [GitLab Duo for the CLI](../../editor_extensions/gitlab_cli/index.md#gitlab-duo-for-the-cli): Discover or recall `git` commands.
|
||||
|
||||
## Reviewing code
|
||||
|
|
@ -71,7 +74,7 @@ To improve your workflow while reviewing code in merge requests, try these featu
|
|||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=CKjkVsfyFd8&list=PLFGfElNsQthZGazU1ZdfDpegu0HflunXW)
|
||||
- [Code Review](../project/merge_requests/duo_in_merge_requests.md#have-gitlab-duo-review-your-code): Review proposed code changes.
|
||||
- [Code Review Summary](../project/merge_requests/duo_in_merge_requests.md#summarize-a-code-review): Summarize all the comments in a review.
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=Bx6Zajyuy9k&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED)
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch overview](https://www.youtube.com/watch?v=Bx6Zajyuy9k)
|
||||
- [Merge Commit Message Generation](../project/merge_requests/duo_in_merge_requests.md#generate-a-merge-commit-message): Generate commit messages.
|
||||
|
||||
## Testing and deploying code
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ Project items that are migrated to the destination GitLab instance include:
|
|||
| Snippets | [GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/343438) |
|
||||
| Settings | [GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/339416) |
|
||||
| Uploads | [GitLab 14.5](https://gitlab.com/gitlab-org/gitlab/-/issues/339401) |
|
||||
| Vulnerability report | [GitLab 17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/501466) |
|
||||
| Wikis | [GitLab 14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/345923) |
|
||||
|
||||
<!-- vale gitlab_base.OutdatedVersions = YES -->
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ For a quick overview, items that are exported include:
|
|||
- Some merge request approval rules:
|
||||
- [Approvals for protected branches](../merge_requests/approvals/rules.md#approvals-for-protected-branches)
|
||||
- [Eligible approvers](../merge_requests/approvals/rules.md#eligible-approvers)
|
||||
- Vulnerability report ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/501466) in GitLab 17.7)
|
||||
|
||||
#### Project items that are not exported
|
||||
|
||||
|
|
|
|||
|
|
@ -114,11 +114,10 @@ module API
|
|||
paginate(projects)
|
||||
end
|
||||
|
||||
def present_projects(params, projects, single_hierarchy: false)
|
||||
def present_projects(params, projects)
|
||||
options = {
|
||||
with: params[:simple] ? Entities::BasicProjectDetails : Entities::Project,
|
||||
current_user: current_user,
|
||||
single_hierarchy: single_hierarchy
|
||||
current_user: current_user
|
||||
}
|
||||
|
||||
projects, options = with_custom_attributes(projects, options)
|
||||
|
|
@ -437,7 +436,7 @@ module API
|
|||
|
||||
projects = find_group_projects(params, finder_options)
|
||||
|
||||
present_projects(params, projects, single_hierarchy: true)
|
||||
present_projects(params, projects)
|
||||
end
|
||||
|
||||
desc 'Get a list of shared projects in this group' do
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ module API
|
|||
Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user]
|
||||
|
||||
preload_member_roles(projects_relation, options[:current_user]) if options[:current_user]
|
||||
Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy]
|
||||
preload_groups(projects_relation) if options[:with] == Entities::Project
|
||||
|
||||
projects_relation
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ module Gitlab
|
|||
options['admin_group']
|
||||
end
|
||||
|
||||
def duo_add_on_groups
|
||||
Array(options['duo_add_on_groups'])
|
||||
end
|
||||
|
||||
def active_directory
|
||||
options['active_directory']
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId < BackfillDesiredShardingKeyJob
|
||||
operation_name :backfill_protected_branch_unprotect_access_levels_protected_branch_namespace_id
|
||||
feature_category :source_code_management
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId < BackfillDesiredShardingKeyJob
|
||||
operation_name :backfill_protected_branch_unprotect_access_levels_protected_branch_project_id
|
||||
feature_category :source_code_management
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.114.0'
|
||||
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.115.0'
|
||||
|
||||
.dast-auto-deploy:
|
||||
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.114.0'
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.115.0'
|
||||
|
||||
.auto-deploy:
|
||||
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.114.0'
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.115.0'
|
||||
|
||||
.auto-deploy:
|
||||
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
|
||||
|
|
|
|||
|
|
@ -20634,10 +20634,13 @@ msgstr ""
|
|||
msgid "DuoCodeReview|Hey :wave: I'm starting to review your merge request and I will let you know when I'm finished."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoCodeReview|I encountered some problems while responding to your query. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoCodeReview|I finished my review and found nothing to comment on. Nice work! :tada:"
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoCodeReview|I have encountered some issues while I was reviewing. Please try again later."
|
||||
msgid "DuoCodeReview|I have encountered some problems while I was reviewing. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "DuoEnterpriseDiscover|AI Impact Dashboard measures the ROI of AI"
|
||||
|
|
@ -31535,6 +31538,51 @@ msgstr ""
|
|||
msgid "JobAssistant|week(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Apply permissions that grant access to individual resources."
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Containers"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Default permissions"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Deployments"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Fine-grained permissions"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Jobs"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Packages"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Read"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Read and write"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Releases"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Resource"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Secure files"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Terraform state"
|
||||
msgstr ""
|
||||
|
||||
msgid "JobToken|Use the standard permissions model based on user membership and roles."
|
||||
msgstr ""
|
||||
|
||||
msgid "Jobs"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ module QA
|
|||
end
|
||||
|
||||
it 'returns a custom server hook error', :skip_live_env,
|
||||
except: { job: 'review-qa-*' },
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/369053' do
|
||||
expect { project.create_repository_tag('v1.2.3') }
|
||||
.to raise_error.with_message(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module QA
|
||||
RSpec.describe 'Analytics' do
|
||||
describe 'Service ping default enabled', product_group: :analytics_instrumentation do
|
||||
context 'when using default enabled from gitlab.yml config', :requires_admin, except: { job: 'review-qa-*' } do
|
||||
context 'when using default enabled from gitlab.yml config', :requires_admin do
|
||||
before do
|
||||
Flow::Login.sign_in_as_admin
|
||||
|
||||
|
|
|
|||
|
|
@ -65,13 +65,9 @@ module QA
|
|||
Resource::Issue.fabricate_via_api_unless_fips! { |issue| issue.project = project }.visit!
|
||||
end
|
||||
|
||||
# The following example is excluded from running in `review-qa-smoke` job
|
||||
# as it proved to be flaky when running against Review App
|
||||
# See https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/11568#note_621999351
|
||||
it(
|
||||
'comments on an issue with an attachment',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946',
|
||||
except: { job: 'review-qa-*' }
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347946'
|
||||
) do
|
||||
Page::Project::Issue::Show.perform do |show|
|
||||
show.comment('See attached image for scale', attachment: file_to_attach)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ module QA
|
|||
RSpec.describe 'Plan',
|
||||
:gitlab_pages,
|
||||
:orchestrated,
|
||||
except: { job: 'review-qa-*' },
|
||||
quarantine: {
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/383215',
|
||||
type: :broken
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'open3'
|
||||
|
||||
module QA
|
||||
module Tools
|
||||
module Ci
|
||||
# Run count commands for scenarios and detect which ones have more than 0 examples to run
|
||||
#
|
||||
class NonEmptySuites
|
||||
include Helpers
|
||||
|
||||
# @return [Array] scenarios that never run in test-on-omnibus pipeline
|
||||
IGNORED_SCENARIOS = [
|
||||
"QA::EE::Scenario::Test::Geo",
|
||||
"QA::Scenario::Test::Instance::Airgapped"
|
||||
].freeze
|
||||
|
||||
def initialize(qa_tests)
|
||||
@qa_tests = qa_tests
|
||||
end
|
||||
|
||||
# Run counts and return runnable scenario list
|
||||
#
|
||||
# @return [String]
|
||||
def fetch
|
||||
logger.info("Checking for runnable suites")
|
||||
scenarios.each_with_object([]) do |scenario, runnable_scenarios|
|
||||
logger.info(" fetching runnable specs for '#{scenario}'")
|
||||
next logger.info(" scenario is in ignore list, skipping") if IGNORED_SCENARIOS.include?(scenario)
|
||||
|
||||
out, err, status = run_command(scenario)
|
||||
|
||||
unless status.success?
|
||||
logger.error(" example count failed!\n#{err}")
|
||||
next
|
||||
end
|
||||
|
||||
count = out.split("\n").last.to_i
|
||||
logger.info(" found #{count} examples to run")
|
||||
runnable_scenarios << scenario if count > 0
|
||||
end.join(",")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :qa_tests
|
||||
|
||||
# Get all defined scenarios
|
||||
#
|
||||
# @return [Array<String>]
|
||||
def scenarios
|
||||
foss_scenarios = scenario_classes(QA::Scenario::Test)
|
||||
return foss_scenarios unless QA.const_defined?("QA::EE")
|
||||
|
||||
foss_scenarios + scenario_classes(QA::EE::Scenario::Test)
|
||||
end
|
||||
|
||||
# Fetch scenario classes recursively
|
||||
#
|
||||
# @param [Module] mod
|
||||
# @return [Array<String>]
|
||||
def scenario_classes(mod)
|
||||
mod.constants.map do |const|
|
||||
c = mod.const_get(const, false)
|
||||
next c.to_s if c.is_a?(Class)
|
||||
|
||||
scenario_classes(c)
|
||||
end.flatten
|
||||
end
|
||||
|
||||
# Run scenario count command
|
||||
#
|
||||
# @param [String] klass
|
||||
# @return [String]
|
||||
def run_command(klass)
|
||||
cmd = ["bundle exec bin/qa"]
|
||||
cmd << klass
|
||||
cmd << "--count-examples-only --address http://dummy1.test"
|
||||
cmd << "-- #{qa_tests}" unless qa_tests.blank?
|
||||
|
||||
Open3.capture3(cmd.join(" "))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Tools
|
||||
module Ci
|
||||
# Execute rspec dry run to get list of executable specs for each scenario class
|
||||
#
|
||||
class RunnableSpecs
|
||||
include Helpers
|
||||
|
||||
# @return [Array] scenarios that never run in test-on-omnibus pipeline
|
||||
IGNORED_SCENARIOS = [
|
||||
QA::EE::Scenario::Test::Geo,
|
||||
QA::Scenario::Test::Instance::Airgapped,
|
||||
QA::Scenario::Test::Sanity::Selectors
|
||||
].freeze
|
||||
|
||||
def initialize(qa_tests)
|
||||
@qa_tests = qa_tests
|
||||
end
|
||||
|
||||
# Return list of executable spec files for each scenario class
|
||||
#
|
||||
# @return [Hash<Class, Array<String>>]
|
||||
def fetch
|
||||
logger.info("Checking for runnable suites")
|
||||
(scenarios - IGNORED_SCENARIOS).each_with_object({}) do |scenario, runnable_scenarios|
|
||||
specs = fetch_specs(scenario)
|
||||
|
||||
logger.info(" found #{specs.size} spec files to run")
|
||||
runnable_scenarios[scenario] = specs unless specs.empty?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :qa_tests
|
||||
|
||||
# Get all defined scenarios
|
||||
#
|
||||
# @return [Array<String>]
|
||||
def scenarios
|
||||
foss_scenarios = scenario_classes(QA::Scenario::Test)
|
||||
return foss_scenarios unless QA.const_defined?("QA::EE")
|
||||
|
||||
foss_scenarios + scenario_classes(QA::EE::Scenario::Test)
|
||||
end
|
||||
|
||||
# Fetch scenario classes recursively
|
||||
#
|
||||
# @param [Module] mod
|
||||
# @return [Array<Object>]
|
||||
def scenario_classes(mod)
|
||||
mod.constants.flat_map do |const|
|
||||
c = mod.const_get(const, false)
|
||||
next c if c.is_a?(Class)
|
||||
|
||||
scenario_classes(c)
|
||||
end
|
||||
end
|
||||
|
||||
# Fetch list of executable spec files for scenario class
|
||||
#
|
||||
# @param klass [Class]
|
||||
# @return [Array<String>]
|
||||
def fetch_specs(klass)
|
||||
logger.info(" fetching runnable spec files for '#{klass}'")
|
||||
Tempfile.open("test-metadata.json") do |file|
|
||||
Process.fork do
|
||||
err = StringIO.new
|
||||
tags = klass.focus.presence || Specs::Runner::DEFAULT_SKIPPED_TAGS.map { |tag| "~#{tag}" }
|
||||
args = ["--dry-run", *tags.flat_map { |tag| ["-t", tag.to_s] }]
|
||||
qa_tests.blank? ? args.push(*Specs::Runner::DEFAULT_TEST_PATH_ARGS) : args.push(*qa_tests)
|
||||
|
||||
# Clear variables that automatically add formatters in spec_helper
|
||||
%w[CI CI_SERVER COVERBAND_ENABLED].each { |var| ENV.delete(var) }
|
||||
RSpec.configure { |config| config.add_formatter(QA::Support::JsonFormatter, file.path) }
|
||||
|
||||
status = RSpec::Core::Runner.run(args, err, StringIO.new)
|
||||
logger.error(" subprocess failed! Err output: #{err.string}") if status.nonzero?
|
||||
Kernel.exit(status)
|
||||
end
|
||||
_pid, status = Process.wait2
|
||||
raise "Failed to fetch executable spec files for #{klass}" unless status.success?
|
||||
|
||||
JSON.load_file(file, symbolize_names: true)[:examples]
|
||||
.map { |example| example[:file_path].gsub("./", "") }
|
||||
.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe QA::Tools::Ci::NonEmptySuites do
|
||||
let(:non_empty_suites) { described_class.new(nil) }
|
||||
|
||||
let(:status) { instance_double(Process::Status, success?: true) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(Logger.new(StringIO.new))
|
||||
allow(Open3).to receive(:capture3).and_return(["output\n0", "", status])
|
||||
allow(Open3).to receive(:capture3)
|
||||
.with("bundle exec bin/qa QA::Scenario::Test::Instance::All --count-examples-only --address http://dummy1.test")
|
||||
.and_return(["output\n1", "", status])
|
||||
end
|
||||
|
||||
it "returns runnable test suites" do
|
||||
expect(non_empty_suites.fetch).to eq("QA::Scenario::Test::Instance::All")
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe QA::Tools::Ci::RunnableSpecs do
|
||||
let(:runnable_specs) { described_class.new([]).fetch }
|
||||
|
||||
let(:config_mock) { instance_double(RSpec::Core::Configuration, add_formatter: nil) }
|
||||
let(:run_result_file) { instance_double(File, path: "file_path") }
|
||||
let(:rspec_json) { { examples: [] } }
|
||||
let(:rspec_exit) { 0 }
|
||||
|
||||
before do
|
||||
allow(ENV).to receive(:delete)
|
||||
allow(Gitlab::QA::TestLogger).to receive(:logger).and_return(Logger.new(StringIO.new))
|
||||
allow(Tempfile).to receive(:open).with("test-metadata.json").and_yield(run_result_file)
|
||||
allow(JSON).to receive(:load_file).with(run_result_file, symbolize_names: true).and_return(rspec_json)
|
||||
allow(RSpec).to receive(:configure).and_yield(config_mock)
|
||||
allow(RSpec::Core::Runner).to receive(:run).and_return(rspec_exit)
|
||||
allow(Process).to receive(:fork).and_yield
|
||||
allow(Process).to receive(:wait2).and_return(
|
||||
[1, instance_double(Process::Status, success?: rspec_exit.nonzero? ? false : true)]
|
||||
)
|
||||
allow(Kernel).to receive(:exit)
|
||||
end
|
||||
|
||||
it "correctly configures formatter" do
|
||||
runnable_specs
|
||||
|
||||
expect(config_mock).to have_received(:add_formatter)
|
||||
.with(QA::Support::JsonFormatter, run_result_file.path)
|
||||
.at_least(:once)
|
||||
end
|
||||
|
||||
context "with rspec process failure" do
|
||||
let(:rspec_exit) { 1 }
|
||||
|
||||
before do
|
||||
# skip yielding fork block to correctly raise error
|
||||
allow(Process).to receive(:fork)
|
||||
end
|
||||
|
||||
it "raises error" do
|
||||
expect { runnable_specs }.to raise_error(RuntimeError, /Failed to fetch executable spec files for .*/)
|
||||
end
|
||||
end
|
||||
|
||||
context "with rspec returning runnable specs" do
|
||||
let(:rspec_json) do
|
||||
{
|
||||
examples: [
|
||||
{
|
||||
file_path: "./qa/specs/features/ee/test_spec.rb"
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it "returns runnable spec list" do
|
||||
expect(runnable_specs.all? { |_k, v| v == ["qa/specs/features/ee/test_spec.rb"] }).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context "with rspec returning no runnable specs" do
|
||||
it "returns empty spec list" do
|
||||
expect(runnable_specs.all? { |_k, v| v == [] }).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -71,7 +71,7 @@ namespace :ci do
|
|||
end
|
||||
|
||||
# always check all test suites in case a suite is defined but doesn't have any runnable specs
|
||||
suites = QA::Tools::Ci::NonEmptySuites.new(tests).fetch
|
||||
suites = QA::Tools::Ci::RunnableSpecs.new(tests).fetch.keys.join(",")
|
||||
append_to_file(env_file, <<~TXT)
|
||||
QA_SUITES='#{suites}'
|
||||
QA_TESTS='#{tests}'
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ ee/spec/frontend/vue_merge_request_widget/components/mr_widget_pipeline_containe
|
|||
ee/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js
|
||||
ee/spec/frontend/vue_shared/components/groups_list/groups_list_item_spec.js
|
||||
ee/spec/frontend/vue_shared/components/projects_list/projects_list_item_spec.js
|
||||
ee/spec/frontend/vue_shared/security_reports/components/security_training_promo_spec.js
|
||||
ee/spec/frontend/vulnerabilities/generic_report/types/list_graphql_spec.js
|
||||
ee/spec/frontend/vulnerabilities/related_issues_spec.js
|
||||
spec/frontend/__helpers__/vue_test_utils_helper_spec.js
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_category: :continuous_integration do
|
||||
RSpec.describe 'Merge request < User sees pipeline mini graph', :js, feature_category: :continuous_integration do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:user) { project.creator }
|
||||
let(:merge_request) { create(:merge_request, source_project: project, head_pipeline: pipeline) }
|
||||
|
|
@ -23,7 +23,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
|
|||
visit project_merge_request_path(project, merge_request, format: format, serializer: serializer)
|
||||
end
|
||||
|
||||
it 'displays a mini pipeline graph' do
|
||||
it 'displays a pipeline mini graph' do
|
||||
expect(page).to have_selector('[data-testid="pipeline-mini-graph"]')
|
||||
end
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
|
|||
end
|
||||
end
|
||||
|
||||
describe 'build list toggle' do
|
||||
describe 'stage dropdown toggle' do
|
||||
let(:toggle) do
|
||||
find(dropdown_selector)
|
||||
first(dropdown_selector)
|
||||
|
|
@ -69,7 +69,7 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
|
|||
end
|
||||
end
|
||||
|
||||
describe 'builds list menu' do
|
||||
describe 'stage dropdown' do
|
||||
let(:toggle) do
|
||||
find(dropdown_selector)
|
||||
first(dropdown_selector)
|
||||
|
|
@ -96,21 +96,20 @@ RSpec.describe 'Merge request < User sees mini pipeline graph', :js, feature_cat
|
|||
expect(toggle.find(:xpath, '..')).not_to have_selector('[data-testid="pipeline-mini-graph-dropdown-menu"]')
|
||||
end
|
||||
|
||||
describe 'build list build item' do
|
||||
let(:build_item) do
|
||||
find('.ci-job-component')
|
||||
first('.ci-job-component')
|
||||
describe 'job list job item' do
|
||||
let(:job_item) do
|
||||
first('[data-testid="job-name"]')
|
||||
end
|
||||
|
||||
it 'visits the build page when clicked' do
|
||||
build_item.click
|
||||
it 'visits the job page when clicked' do
|
||||
job_item.click
|
||||
find('.build-page')
|
||||
|
||||
expect(page).to have_current_path(project_job_path(project, build), ignore_query: true)
|
||||
end
|
||||
|
||||
it 'shows tooltip when hovered' do
|
||||
build_item.hover
|
||||
job_item.hover
|
||||
|
||||
expect(page).to have_selector('.tooltip')
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import NamespaceForm from '~/token_access/components/namespace_form.vue';
|
|||
import addNamespaceMutation from '~/token_access/graphql/mutations/inbound_add_group_or_project_ci_job_token_scope.mutation.graphql';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import PoliciesSelector from '~/token_access/components/policies_selector.vue';
|
||||
import { getAddNamespaceHandler } from './mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
|
@ -16,10 +17,13 @@ describe('Namespace form component', () => {
|
|||
|
||||
const defaultAddMutationHandler = getAddNamespaceHandler();
|
||||
|
||||
const createWrapper = ({ addMutationHandler = defaultAddMutationHandler } = {}) => {
|
||||
const createWrapper = ({
|
||||
addMutationHandler = defaultAddMutationHandler,
|
||||
addPoliciesToCiJobToken = true,
|
||||
} = {}) => {
|
||||
wrapper = shallowMountExtended(NamespaceForm, {
|
||||
apolloProvider: createMockApollo([[addNamespaceMutation, addMutationHandler]]),
|
||||
provide: { fullPath: 'full/path' },
|
||||
provide: { fullPath: 'full/path', glFeatures: { addPoliciesToCiJobToken } },
|
||||
stubs: {
|
||||
GlFormInput: stubComponent(GlFormInput, {
|
||||
props: ['autofocus', 'disabled', 'state', 'placeholder'],
|
||||
|
|
@ -32,6 +36,7 @@ describe('Namespace form component', () => {
|
|||
const findFormInput = () => wrapper.findComponent(GlFormInput);
|
||||
const findAddButton = () => wrapper.findByTestId('add-button');
|
||||
const findCancelButton = () => wrapper.findByTestId('cancel-button');
|
||||
const findPoliciesSelector = () => wrapper.findComponent(PoliciesSelector);
|
||||
|
||||
describe('on page load', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
|
|
@ -55,6 +60,30 @@ describe('Namespace form component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('policies selector', () => {
|
||||
it('shows policies selector', () => {
|
||||
expect(findPoliciesSelector().props()).toMatchObject({
|
||||
isDefaultPermissionsSelected: true,
|
||||
jobTokenPolicies: [],
|
||||
disabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('updates defaultPermissions when policies selector emits an update', async () => {
|
||||
findPoliciesSelector().vm.$emit('update:isDefaultPermissionsSelected', false);
|
||||
await nextTick();
|
||||
|
||||
expect(findPoliciesSelector().props('isDefaultPermissionsSelected')).toBe(false);
|
||||
});
|
||||
|
||||
it('updates jobTokenPolicies when policies selector emits an update', async () => {
|
||||
findPoliciesSelector().vm.$emit('update:jobTokenPolicies', ['ADMIN_JOB']);
|
||||
await nextTick();
|
||||
|
||||
expect(findPoliciesSelector().props('jobTokenPolicies')).toEqual(['ADMIN_JOB']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Add button', () => {
|
||||
it('shows button', () => {
|
||||
expect(findAddButton().text()).toBe('Add');
|
||||
|
|
@ -98,6 +127,8 @@ describe('Namespace form component', () => {
|
|||
expect(defaultAddMutationHandler).toHaveBeenCalledWith({
|
||||
projectPath: 'full/path',
|
||||
targetPath: 'gitlab',
|
||||
defaultPermissions: true,
|
||||
jobTokenPolicies: [],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -105,6 +136,10 @@ describe('Namespace form component', () => {
|
|||
expect(findFormInput().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('disables policies selector', () => {
|
||||
expect(findPoliciesSelector().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('disables Add button', () => {
|
||||
expect(findAddButton().props('loading')).toBe(true);
|
||||
});
|
||||
|
|
@ -125,6 +160,10 @@ describe('Namespace form component', () => {
|
|||
expect(findFormInput().props('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('enables policies selector', () => {
|
||||
expect(findPoliciesSelector().props('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('enables Add button', () => {
|
||||
expect(findAddButton().props('loading')).toBe(false);
|
||||
});
|
||||
|
|
@ -188,4 +227,24 @@ describe('Namespace form component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the addPoliciesToCiJobToken feature flag is disabled', () => {
|
||||
beforeEach(() => createWrapper({ addPoliciesToCiJobToken: false }));
|
||||
|
||||
it('does not show permissions selector', () => {
|
||||
expect(findPoliciesSelector().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('when namespace is saved', () => {
|
||||
it('calls mutation without defaultPermissions or jobTokenPolicies', () => {
|
||||
findFormInput().vm.$emit('input', 'gitlab');
|
||||
findAddButton().vm.$emit('click');
|
||||
|
||||
expect(defaultAddMutationHandler).toHaveBeenCalledWith({
|
||||
projectPath: 'full/path',
|
||||
targetPath: 'gitlab',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
import { GlTableLite, GlFormRadio, GlFormRadioGroup, GlCollapsibleListbox } from '@gitlab/ui';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import PoliciesSelector, { TABLE_FIELDS } from '~/token_access/components/policies_selector.vue';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { POLICIES_BY_RESOURCE, RESOURCE_JOBS, RESOURCE_RELEASES } from '~/token_access/constants';
|
||||
|
||||
describe('Policies selector component', () => {
|
||||
let wrapper;
|
||||
|
||||
const createWrapper = ({
|
||||
isDefaultPermissionsSelected = false,
|
||||
jobTokenPolicies = [],
|
||||
disabled = false,
|
||||
stubs = {},
|
||||
} = {}) => {
|
||||
wrapper = mountExtended(PoliciesSelector, {
|
||||
propsData: { isDefaultPermissionsSelected, jobTokenPolicies, disabled },
|
||||
stubs,
|
||||
});
|
||||
|
||||
return waitForPromises();
|
||||
};
|
||||
|
||||
const findTable = () => wrapper.findComponent(GlTableLite);
|
||||
const findRadioGroup = () => wrapper.findComponent(GlFormRadioGroup);
|
||||
const findDefaultRadio = () => wrapper.findByTestId('default-radio');
|
||||
const findFineGrainedRadio = () => wrapper.findByTestId('fine-grained-radio');
|
||||
|
||||
const getResourceIndex = (item) =>
|
||||
POLICIES_BY_RESOURCE.map(({ resource }) => resource).indexOf(item);
|
||||
|
||||
const findNameForResource = (resource) =>
|
||||
findTable().findAll('tbody tr').at(getResourceIndex(resource)).findAll('td').at(0);
|
||||
|
||||
const findPolicyDropdownForResource = (resource) =>
|
||||
wrapper.findAllComponents(GlCollapsibleListbox).at(getResourceIndex(resource));
|
||||
|
||||
describe('permission type radio options', () => {
|
||||
beforeEach(() =>
|
||||
createWrapper({
|
||||
stubs: {
|
||||
GlFormRadioGroup: stubComponent(GlFormRadioGroup, { props: ['checked'] }),
|
||||
GlFormRadio: stubComponent(GlFormRadio, {
|
||||
template: '<div><slot></slot><slot name="help"></slot></div>',
|
||||
props: ['value'],
|
||||
}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
describe('radio group', () => {
|
||||
it('shows radio group', () => {
|
||||
expect(findRadioGroup().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each`
|
||||
name | isDefaultPermissionsSelected
|
||||
${'default'} | ${true}
|
||||
${'fine-grained'} | ${false}
|
||||
`(
|
||||
'selects the $name option when the isDefaultPermissionsSelected prop is $isDefaultPermissionsSelected',
|
||||
async ({ isDefaultPermissionsSelected }) => {
|
||||
await wrapper.setProps({ isDefaultPermissionsSelected });
|
||||
|
||||
expect(findRadioGroup().props('checked')).toBe(isDefaultPermissionsSelected);
|
||||
},
|
||||
);
|
||||
|
||||
it.each`
|
||||
name | isDefaultPermissionsSelected
|
||||
${'default'} | ${true}
|
||||
${'fine-grained'} | ${false}
|
||||
`(
|
||||
'emits update:isDefaultPermissionsSelected event with $isDefaultPermissionsSelected when $name is selected',
|
||||
({ isDefaultPermissionsSelected }) => {
|
||||
findRadioGroup().vm.$emit('change', isDefaultPermissionsSelected);
|
||||
|
||||
expect(wrapper.emitted('update:isDefaultPermissionsSelected')[0][0]).toEqual(
|
||||
isDefaultPermissionsSelected,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it.each`
|
||||
name | value | findRadio | expectedText
|
||||
${'default'} | ${true} | ${findDefaultRadio} | ${'Default permissions Use the standard permissions model based on user membership and roles.'}
|
||||
${'fine-grained'} | ${false} | ${findFineGrainedRadio} | ${'Fine-grained permissions Apply permissions that grant access to individual resources.'}
|
||||
`('shows the $name radio', ({ value, findRadio, expectedText }) => {
|
||||
expect(findRadio().text()).toMatchInterpolatedText(expectedText);
|
||||
expect(findRadio().props('value')).toBe(value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Default permissions is selected', () => {
|
||||
beforeEach(() => createWrapper({ isDefaultPermissionsSelected: true }));
|
||||
|
||||
it('does not show policies table', () => {
|
||||
expect(findTable().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when Fine-grained permissions is selected', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
stubs: { GlTableLite: stubComponent(GlTableLite, { props: ['fields', 'items'] }) },
|
||||
});
|
||||
});
|
||||
|
||||
it('shows policies table', () => {
|
||||
expect(findTable().props()).toMatchObject({
|
||||
items: POLICIES_BY_RESOURCE,
|
||||
fields: TABLE_FIELDS,
|
||||
});
|
||||
});
|
||||
|
||||
describe('policies table', () => {
|
||||
beforeEach(() => createWrapper());
|
||||
|
||||
describe.each(POLICIES_BY_RESOURCE)(
|
||||
'for resource $resource.text',
|
||||
({ resource, policies }) => {
|
||||
it('shows the resource name', () => {
|
||||
expect(findNameForResource(resource).text()).toBe(resource.text);
|
||||
});
|
||||
|
||||
it('shows the resource dropdown', () => {
|
||||
expect(findPolicyDropdownForResource(resource).props('items')).toEqual(policies);
|
||||
});
|
||||
|
||||
it.each(policies)(`emits $value policy when it is selected`, (policy) => {
|
||||
const expected = policy.value ? [policy.value] : [];
|
||||
findPolicyDropdownForResource(resource).vm.$emit('select', policy.value);
|
||||
|
||||
expect(wrapper.emitted('update:jobTokenPolicies')[0][0]).toEqual(expected);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe('when multiple policies are selected across different resources', () => {
|
||||
const jobTokenPolicies = ['READ_JOBS', 'ADMIN_PACKAGES'];
|
||||
|
||||
beforeEach(() => wrapper.setProps({ jobTokenPolicies }));
|
||||
|
||||
it('adds the policy when there is no policy set for the resource', () => {
|
||||
findPolicyDropdownForResource(RESOURCE_RELEASES).vm.$emit('select', 'READ_RELEASES');
|
||||
|
||||
expect(wrapper.emitted('update:jobTokenPolicies')[0][0]).toEqual([
|
||||
...jobTokenPolicies,
|
||||
'READ_RELEASES',
|
||||
]);
|
||||
});
|
||||
|
||||
it('updates the policy when there is already a policy for the resource', () => {
|
||||
findPolicyDropdownForResource(RESOURCE_JOBS).vm.$emit('select', 'ADMIN_JOBS');
|
||||
|
||||
expect(wrapper.emitted('update:jobTokenPolicies')[0][0]).toEqual([
|
||||
'ADMIN_JOBS',
|
||||
'ADMIN_PACKAGES',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('disabled prop', () => {
|
||||
describe.each([true, false])('when disabled prop is %s', (disabled) => {
|
||||
beforeEach(() =>
|
||||
createWrapper({
|
||||
disabled,
|
||||
stubs: { GlFormRadioGroup: stubComponent(GlFormRadioGroup, { props: ['disabled'] }) },
|
||||
}),
|
||||
);
|
||||
|
||||
it(`sets radio group disabled to ${disabled}`, () => {
|
||||
expect(findRadioGroup().props('disabled')).toBe(disabled);
|
||||
});
|
||||
|
||||
it(`sets all policy dropdowns disabled to ${disabled}`, () => {
|
||||
wrapper.findAllComponents(GlCollapsibleListbox).wrappers.forEach((dropdown) => {
|
||||
expect(dropdown.props('disabled')).toBe(disabled);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -614,6 +614,22 @@ AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK
|
|||
end
|
||||
end
|
||||
|
||||
describe '#duo_add_on_groups' do
|
||||
it 'returns empty array when not set' do
|
||||
expect(config.duo_add_on_groups).to be_empty
|
||||
end
|
||||
|
||||
context 'when the config is set' do
|
||||
before do
|
||||
stub_ldap_config(options: { duo_add_on_groups: %w[duo_group_1 duo_group_2] })
|
||||
end
|
||||
|
||||
it 'returns configured duo_add_on_groups array' do
|
||||
expect(config.duo_add_on_groups).to match_array(%w[duo_group_1 duo_group_2])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'sign_in_enabled?' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchNamespaceId,
|
||||
feature_category: :source_code_management,
|
||||
schema: 20241213141743 do
|
||||
include_examples 'desired sharding key backfill job' do
|
||||
let(:batch_table) { :protected_branch_unprotect_access_levels }
|
||||
let(:backfill_column) { :protected_branch_namespace_id }
|
||||
let(:backfill_via_table) { :protected_branches }
|
||||
let(:backfill_via_column) { :namespace_id }
|
||||
let(:backfill_via_foreign_key) { :protected_branch_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillProtectedBranchUnprotectAccessLevelsProtectedBranchProjectId,
|
||||
feature_category: :source_code_management,
|
||||
schema: 20241213141738 do
|
||||
include_examples 'desired sharding key backfill job' do
|
||||
let(:batch_table) { :protected_branch_unprotect_access_levels }
|
||||
let(:backfill_column) { :protected_branch_project_id }
|
||||
let(:backfill_via_table) { :protected_branches }
|
||||
let(:backfill_via_column) { :project_id }
|
||||
let(:backfill_via_foreign_key) { :protected_branch_id }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillProtectedBranchUnprotectAccessLevelsProjectId, feature_category: :source_code_management do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :protected_branch_unprotect_access_levels,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main_cell,
|
||||
job_arguments: [
|
||||
:protected_branch_project_id,
|
||||
:protected_branches,
|
||||
:project_id,
|
||||
:protected_branch_id
|
||||
]
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillProtectedBranchUnprotectAccessLevelsNamespaceId, feature_category: :source_code_management do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :protected_branch_unprotect_access_levels,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main_cell,
|
||||
job_arguments: [
|
||||
:protected_branch_namespace_id,
|
||||
:protected_branches,
|
||||
:namespace_id,
|
||||
:protected_branch_id
|
||||
]
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue