Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a628dedecf
commit
b9f27f110e
|
|
@ -1,19 +1,22 @@
|
|||
<script>
|
||||
import {
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
GlFormGroup,
|
||||
GlLink,
|
||||
GlIcon,
|
||||
GlModal,
|
||||
GlPopover,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { createAlert } from '~/alert';
|
||||
import { s__ } from '~/locale';
|
||||
import { s__, __ } from '~/locale';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
import { TYPENAME_CI_RUNNER } from '~/graphql_shared/constants';
|
||||
import runnerForRegistrationQuery from '../../graphql/register/runner_for_registration.query.graphql';
|
||||
import provisionGoogleCloudRunnerProject from '../../graphql/register/provision_google_cloud_runner_project.query.graphql';
|
||||
import {
|
||||
I18N_FETCH_ERROR,
|
||||
STATUS_ONLINE,
|
||||
|
|
@ -56,6 +59,33 @@ export default {
|
|||
'Runners|For most CI/CD jobs, use a %{linkStart}N2D standard machine type.%{linkEnd}',
|
||||
),
|
||||
runnerSetupBtnText: s__('Runners|Setup instructions'),
|
||||
modal: {
|
||||
subtitle: s__(
|
||||
'Runners|These setup instructions use your specifications and follow the best practices for performance and security',
|
||||
),
|
||||
step2_1Header: s__('Runners|Step 1: Configure Google Cloud project'),
|
||||
step2_1Body: s__(
|
||||
`Runners|If you haven't already configured your Google Cloud project, this step enables the required services and creates a service account with the required permissions. `,
|
||||
),
|
||||
step2_1Substep1: s__(
|
||||
'Runners|Run the following on your command line. You might be prompted to sign in to Google',
|
||||
),
|
||||
step2_2Header: s__('Runners|Step 2: Install and register GitLab Runner'),
|
||||
step2_2Body: s__(
|
||||
'Runners|This step creates the required infrastructure in Google Cloud, installs GitLab Runner, and registers it to this GitLab project. ',
|
||||
),
|
||||
step2_2Substep1: s__(
|
||||
'Runners|Use a text editor to create a main.tf file with the following Terraform configuration',
|
||||
),
|
||||
step2_2Substep2: s__(
|
||||
'Runners|In the directory with that Terraform configuration file, run the following on your command line.',
|
||||
),
|
||||
step2_2Substep3: s__(
|
||||
'Runners|After GitLab Runner is installed and registered, an autoscaling fleet of runners is available to execute your CI/CD jobs in Google Cloud',
|
||||
),
|
||||
},
|
||||
alertBody: s__('Runners|To view the setup instructions, complete the previous form'),
|
||||
invalidFormButton: s__('Runners|Go to first invalid form field'),
|
||||
},
|
||||
links: {
|
||||
projectIdLink:
|
||||
|
|
@ -67,11 +97,13 @@ export default {
|
|||
},
|
||||
components: {
|
||||
ClipboardButton,
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
GlFormGroup,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlModal,
|
||||
GlPopover,
|
||||
GlSprintf,
|
||||
},
|
||||
|
|
@ -80,6 +112,16 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
groupPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -89,6 +131,15 @@ export default {
|
|||
machineType: 'n2d-standard-2',
|
||||
token: '',
|
||||
runner: null,
|
||||
showInstructionsModal: false,
|
||||
validations: {
|
||||
projectId: false,
|
||||
region: false,
|
||||
zone: false,
|
||||
},
|
||||
provisioningSteps: [],
|
||||
setupBashScript: '',
|
||||
showAlert: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -122,6 +173,29 @@ export default {
|
|||
return RUNNER_REGISTRATION_POLLING_INTERVAL_MS;
|
||||
},
|
||||
},
|
||||
project: {
|
||||
query: provisionGoogleCloudRunnerProject,
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.projectPath,
|
||||
cloudProjectId: this.projectId,
|
||||
region: this.region,
|
||||
zone: this.zone,
|
||||
machineType: this.machineType,
|
||||
runnerToken: this.token,
|
||||
};
|
||||
},
|
||||
result({ data }) {
|
||||
this.provisioningSteps = data.project.runnerCloudProvisioning?.provisioningSteps;
|
||||
this.setupBashScript = data.project.runnerCloudProvisioning?.projectSetupShellScript;
|
||||
},
|
||||
error(error) {
|
||||
captureException({ error, component: this.$options.name });
|
||||
},
|
||||
skip() {
|
||||
return !this.projectPath || this.invalidFields.length > 0;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isRunnerOnline() {
|
||||
|
|
@ -137,6 +211,71 @@ export default {
|
|||
'Runners|The %{boldStart}runner authentication token%{boldEnd} is no longer visible, it is stored in the %{codeStart}config.toml%{codeEnd} if you have registered the runner.',
|
||||
);
|
||||
},
|
||||
invalidFields() {
|
||||
return Object.keys(this.validations).filter((field) => {
|
||||
return this.validations[field] === false;
|
||||
});
|
||||
},
|
||||
bashInstructions() {
|
||||
return this.setupBashScript.length > 0 ? this.setupBashScript : '';
|
||||
},
|
||||
terraformScriptInstructions() {
|
||||
return this.provisioningSteps.length > 0 ? this.provisioningSteps[0].instructions : '';
|
||||
},
|
||||
terraformApplyInstructions() {
|
||||
return this.provisioningSteps.length > 0 ? this.provisioningSteps[1].instructions : '';
|
||||
},
|
||||
codeStyles() {
|
||||
return {
|
||||
height: '300px',
|
||||
};
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
invalidFields() {
|
||||
if (this.invalidFields.length === 0) {
|
||||
this.showAlert = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showInstructions() {
|
||||
if (this.invalidFields.length > 0) {
|
||||
this.showAlert = true;
|
||||
} else {
|
||||
this.showAlert = false;
|
||||
this.showInstructionsModal = true;
|
||||
}
|
||||
},
|
||||
validateZone() {
|
||||
if (this.zone.length > 0) {
|
||||
this.validations.zone = true;
|
||||
} else {
|
||||
this.validations.zone = false;
|
||||
}
|
||||
},
|
||||
validateRegion() {
|
||||
if (this.region.length > 0) {
|
||||
this.validations.region = true;
|
||||
} else {
|
||||
this.validations.region = false;
|
||||
}
|
||||
},
|
||||
validateProjectId() {
|
||||
if (this.projectId.length > 0) {
|
||||
this.validations.projectId = true;
|
||||
} else {
|
||||
this.validations.projectId = false;
|
||||
}
|
||||
},
|
||||
goToFirstInvalidField() {
|
||||
if (this.invalidFields.length > 0) {
|
||||
this.$refs[this.invalidFields[0]].$el.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
cancelModalOptions: {
|
||||
text: __('Cancel'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -192,9 +331,11 @@ export default {
|
|||
</template>
|
||||
<gl-form-input
|
||||
id="project-id"
|
||||
ref="projectId"
|
||||
v-model="projectId"
|
||||
type="text"
|
||||
data-testid="project-id-input"
|
||||
@input="validateProjectId"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group label-for="region-id">
|
||||
|
|
@ -216,7 +357,13 @@ export default {
|
|||
</gl-popover>
|
||||
</div>
|
||||
</template>
|
||||
<gl-form-input id="region-id" v-model="region" data-testid="region-input" />
|
||||
<gl-form-input
|
||||
id="region-id"
|
||||
ref="region"
|
||||
v-model="region"
|
||||
data-testid="region-input"
|
||||
@input="validateRegion"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group label-for="zone-id">
|
||||
<template #label>
|
||||
|
|
@ -243,7 +390,13 @@ export default {
|
|||
<gl-icon name="external-link" />
|
||||
</gl-link>
|
||||
</template>
|
||||
<gl-form-input id="zone-id" v-model="zone" data-testid="zone-input" />
|
||||
<gl-form-input
|
||||
id="zone-id"
|
||||
ref="zone"
|
||||
v-model="zone"
|
||||
data-testid="zone-input"
|
||||
@input="validateZone"
|
||||
/>
|
||||
</gl-form-group>
|
||||
<gl-form-group label-for="machine-type-id">
|
||||
<template #label>
|
||||
|
|
@ -281,8 +434,62 @@ export default {
|
|||
<h2 class="gl-font-lg">{{ $options.i18n.stepTwoHeading }}</h2>
|
||||
<p>{{ $options.i18n.stepTwoDescription }}</p>
|
||||
</div>
|
||||
<gl-button>{{ $options.i18n.runnerSetupBtnText }}</gl-button>
|
||||
<gl-alert
|
||||
v-if="showAlert"
|
||||
:primary-button-text="$options.i18n.invalidFormButton"
|
||||
:dismissible="false"
|
||||
variant="danger"
|
||||
class="gl-mb-4"
|
||||
@primaryAction="goToFirstInvalidField"
|
||||
>
|
||||
{{ $options.i18n.alertBody }}
|
||||
</gl-alert>
|
||||
<gl-button data-testid="show-instructions-button" @click="showInstructions">{{
|
||||
$options.i18n.runnerSetupBtnText
|
||||
}}</gl-button>
|
||||
<gl-modal
|
||||
v-model="showInstructionsModal"
|
||||
cancel-variant="light"
|
||||
size="md"
|
||||
:scrollable="true"
|
||||
modal-id="setup-instructions"
|
||||
:action-cancel="$options.cancelModalOptions"
|
||||
:title="s__('Runners|Setup instructions')"
|
||||
>
|
||||
<p>{{ $options.i18n.modal.subtitle }}</p>
|
||||
<strong>{{ $options.i18n.modal.step2_1Header }}</strong>
|
||||
<p>{{ $options.i18n.modal.step2_1Body }}</p>
|
||||
<p>{{ $options.i18n.modal.step2_1Substep1 }}</p>
|
||||
<div class="gl-display-flex gl-my-4">
|
||||
<pre
|
||||
class="gl-w-full gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0"
|
||||
data-testid="bash-instructions"
|
||||
:style="codeStyles"
|
||||
>{{ bashInstructions }}</pre
|
||||
>
|
||||
</div>
|
||||
|
||||
<strong>{{ $options.i18n.modal.step2_2Header }}</strong>
|
||||
<p>{{ $options.i18n.modal.step2_2Body }}</p>
|
||||
<p>{{ $options.i18n.modal.step2_2Substep1 }}</p>
|
||||
<div class="gl-display-flex gl-my-4">
|
||||
<pre
|
||||
class="gl-w-full gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0"
|
||||
data-testid="terraform-script-instructions"
|
||||
:style="codeStyles"
|
||||
>{{ terraformScriptInstructions }}</pre
|
||||
>
|
||||
</div>
|
||||
<p>{{ $options.i18n.modal.step2_2Substep2 }}</p>
|
||||
<div class="gl-display-flex gl-my-4">
|
||||
<pre
|
||||
class="gl-w-full gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0"
|
||||
data-testid="terraform-apply-instructions"
|
||||
>{{ terraformApplyInstructions }}</pre
|
||||
>
|
||||
</div>
|
||||
<p>{{ $options.i18n.modal.step2_2Substep3 }}</p>
|
||||
</gl-modal>
|
||||
<hr />
|
||||
<!-- end: step two -->
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
query provisionGoogleCloudRunnerProject(
|
||||
$fullPath: ID!
|
||||
$cloudProjectId: GoogleCloudProject!
|
||||
$region: GoogleCloudRegion!
|
||||
$zone: GoogleCloudZone!
|
||||
$machineType: GoogleCloudMachineType!
|
||||
$runnerToken: String
|
||||
) {
|
||||
project(fullPath: $fullPath) {
|
||||
id
|
||||
runnerCloudProvisioning(provider: GOOGLE_CLOUD, cloudProjectId: $cloudProjectId) {
|
||||
... on CiRunnerGoogleCloudProvisioning {
|
||||
projectSetupShellScript
|
||||
provisioningSteps(
|
||||
region: $region
|
||||
zone: $zone
|
||||
ephemeralMachineType: $machineType
|
||||
runnerToken: $runnerToken
|
||||
) {
|
||||
title
|
||||
languageIdentifier
|
||||
instructions
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
groupPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -59,7 +63,7 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="showGoogleCloudRegistration">
|
||||
<google-cloud-registration-instructions :runner-id="runnerId" />
|
||||
<google-cloud-registration-instructions :runner-id="runnerId" :group-path="groupPath" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<registration-instructions
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const initGroupRegisterRunner = (selector = '#js-group-register-runner')
|
|||
return null;
|
||||
}
|
||||
|
||||
const { runnerId, runnersPath } = el.dataset;
|
||||
const { runnerId, runnersPath, groupPath } = el.dataset;
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
|
|
@ -29,6 +29,7 @@ export const initGroupRegisterRunner = (selector = '#js-group-register-runner')
|
|||
props: {
|
||||
runnerId,
|
||||
runnersPath,
|
||||
groupPath,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const initProjectRegisterRunner = (selector = '#js-project-register-runne
|
|||
return null;
|
||||
}
|
||||
|
||||
const { runnerId, runnersPath } = el.dataset;
|
||||
const { runnerId, runnersPath, projectPath } = el.dataset;
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
|
|
@ -29,6 +29,7 @@ export const initProjectRegisterRunner = (selector = '#js-project-register-runne
|
|||
props: {
|
||||
runnerId,
|
||||
runnersPath,
|
||||
projectPath,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -59,7 +63,7 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="showGoogleCloudRegistration">
|
||||
<google-cloud-registration-instructions :runner-id="runnerId" />
|
||||
<google-cloud-registration-instructions :runner-id="runnerId" :project-path="projectPath" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<registration-instructions
|
||||
|
|
|
|||
|
|
@ -122,8 +122,15 @@ module Pajamas
|
|||
attributes['disabled'] = 'disabled' if @disabled || @loading
|
||||
attributes['aria-disabled'] = true if @disabled || @loading
|
||||
attributes['type'] = @type unless @href
|
||||
attributes['rel'] = safe_rel_for_target_blank if link? && @target == '_blank'
|
||||
|
||||
attributes
|
||||
end
|
||||
|
||||
def safe_rel_for_target_blank
|
||||
(@button_options[:rel] || '').split(' ')
|
||||
.concat(%w[noopener noreferrer])
|
||||
.uniq.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ module Groups
|
|||
case user_or_deploy_token
|
||||
when User
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_deploy_token, nil, :user, [])
|
||||
sign_in(user_or_deploy_token) unless user_or_deploy_token.project_bot?
|
||||
sign_in(user_or_deploy_token) unless user_or_deploy_token.project_bot? ||
|
||||
user_or_deploy_token.service_account?
|
||||
when DeployToken
|
||||
@authentication_result = Gitlab::Auth::Result.new(user_or_deploy_token, nil, :deploy_token, [])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -374,6 +374,10 @@ class Group < Namespace
|
|||
left_joins(:group_members).group(:id).count(:members)
|
||||
end
|
||||
|
||||
def with_api_scopes
|
||||
preload(:namespace_settings, :group_feature, :parent)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def public_to_user_arel(user)
|
||||
|
|
|
|||
|
|
@ -1682,10 +1682,13 @@ class User < MainClusterwide::ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def assign_personal_namespace
|
||||
def assign_personal_namespace(organization = nil)
|
||||
return namespace if namespace
|
||||
|
||||
build_namespace(path: username, name: name)
|
||||
namespace_attributes = { path: username, name: name }
|
||||
namespace_attributes[:organization] = organization if organization
|
||||
|
||||
build_namespace(namespace_attributes)
|
||||
namespace.build_namespace_settings
|
||||
|
||||
namespace
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ module Users
|
|||
def initialize(current_user, params = {})
|
||||
@current_user = current_user
|
||||
@params = params.dup
|
||||
@organization_id = params.delete(:organization_id)
|
||||
@identity_params = params.slice(*identity_attributes)
|
||||
end
|
||||
|
||||
|
|
@ -37,7 +38,9 @@ module Users
|
|||
standard_build_user
|
||||
end
|
||||
|
||||
user.assign_personal_namespace
|
||||
user.assign_personal_namespace(
|
||||
Organizations::Organization.find_by_id(@organization_id)
|
||||
)
|
||||
end
|
||||
|
||||
def admin?
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
%p
|
||||
= s_('SlackIntegration|You must do this step only once.')
|
||||
%p
|
||||
= render Pajamas::ButtonComponent.new(href: slack_app_manifest_share_admin_application_settings_path, target: '_blank', button_options: { rel: 'noopener noreferrer' }) do
|
||||
= render Pajamas::ButtonComponent.new(href: slack_app_manifest_share_admin_application_settings_path, target: '_blank') do
|
||||
= s_("SlackIntegration|Create Slack app")
|
||||
%hr
|
||||
%h5
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@
|
|||
- c.with_body do
|
||||
= s_('ClusterIntegration|Every new Google Cloud Platform (GCP) account receives $300 in credit upon %{sign_up_link}. In partnership with Google, GitLab is able to offer an additional $200 for both new and existing GCP accounts to get started with GitLab\'s Google Kubernetes Engine Integration.').html_safe % { sign_up_link: link }
|
||||
- c.with_actions do
|
||||
= render Pajamas::ButtonComponent.new(variant: :confirm, href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', button_options: { rel: 'noopener noreferrer', data: { track_action: 'click_button', track_label: 'gcp_signup_offer_banner' } }) do
|
||||
= render Pajamas::ButtonComponent.new(variant: :confirm, href: 'https://cloud.google.com/partners/partnercredit/?pcn_code=0014M00001h35gDQAQ#contact-form', target: '_blank', button_options: { data: { track_action: 'click_button', track_label: 'gcp_signup_offer_banner' } }) do
|
||||
= s_("ClusterIntegration|Apply for credit")
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
|
||||
- add_to_breadcrumbs runner_name, register_group_runner_path(@group, @runner)
|
||||
|
||||
#js-group-register-runner{ data: { runner_id: @runner.id, runners_path: group_runners_path(@group) } }
|
||||
#js-group-register-runner{ data: { runner_id: @runner.id, runners_path: group_runners_path(@group), group_path: @group.full_path } }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
.text-muted
|
||||
= template.description
|
||||
.controls.d-flex.gl-align-items-center
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'gl-mr-3', data: { track_label: "template_preview", track_property: template.name, track_action: "click_button", track_value: "" }, rel: 'noopener noreferrer' }, href: template.preview, target: '_blank') do
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'gl-mr-3', data: { track_label: "template_preview", track_property: template.name, track_action: "click_button", track_value: "" } }, href: template.preview, target: '_blank') do
|
||||
= _("Preview")
|
||||
%label.btn.gl-button.btn-confirm.template-button.choose-template.gl-mb-0{ for: template.name,
|
||||
'data-testid': "use_template_#{template.name}" }
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@
|
|||
- breadcrumb_title s_('Runners|Register runner')
|
||||
- page_title s_('Runners|Register'), runner_name
|
||||
|
||||
#js-project-register-runner{ data: { runner_id: @runner.id, runners_path: project_runners_path(@project) } }
|
||||
#js-project-register-runner{ data: { runner_id: @runner.id, runners_path: project_runners_path(@project), project_path: @project.full_path } }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
breaking_change: true # (required) Change to false if this is not a breaking change.
|
||||
reporter: connorgilbert # (required) GitLab username of the person reporting the change
|
||||
stage: secure # (required) String value of the stage that the feature was created in. e.g., Growth
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431123 # (required) Link to the deprecation issue in GitLab
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412060 # (required) Link to the deprecation issue in GitLab
|
||||
body: | # (required) Do not modify this line, instead modify the lines below.
|
||||
We're reducing the number of supported [analyzers](https://docs.gitlab.com/ee/user/application_security/sast/analyzers/) used by default in GitLab SAST.
|
||||
This is part of our long-term strategy to deliver a faster, more consistent user experience across different programming languages.
|
||||
|
|
@ -27,4 +27,4 @@
|
|||
Findings that aren't migrated to the new analyzer will be [automatically resolved](https://docs.gitlab.com/ee/user/application_security/sast/#automatic-vulnerability-resolution).
|
||||
See [Vulnerability translation documentation](https://docs.gitlab.com/ee/user/application_security/sast/analyzers.html#vulnerability-translation) for further details.
|
||||
|
||||
If you applied customizations to the removed analyzers, or if you currently disable the Semgrep-based analyzer in your pipelines, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/431123#breaking-change).
|
||||
If you applied customizations to the removed analyzers, or if you currently disable the Semgrep-based analyzer in your pipelines, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/412060#action-required).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
migration_job_name: BackupAndRemoveNotesWithNullNoteableType
|
||||
description: Backs up (to guard against possible data loss) and removes orphaned notes
|
||||
feature_category: team-planning
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146023
|
||||
milestone: '16.10'
|
||||
queued_migration_version: 20240226143323
|
||||
finalize_after: '2024-03-15'
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
table_name: temp_notes_backup
|
||||
classes:
|
||||
feature_categories:
|
||||
- code_review_workflow
|
||||
- portfolio_management
|
||||
- service_desk
|
||||
- source_code_management
|
||||
- team_planning
|
||||
description: a temporary table to store the orphaned notes records that have been removed to insure against data loss
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146023
|
||||
milestone: "16.10"
|
||||
gitlab_schema: gitlab_main_cell
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdatePipelineTriggersPlanLimits < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.10'
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
return unless Gitlab.com?
|
||||
|
||||
create_or_update_plan_limit('pipeline_triggers', 'premium_trial', 25000)
|
||||
create_or_update_plan_limit('pipeline_triggers', 'ultimate_trial', 25000)
|
||||
create_or_update_plan_limit('pipeline_triggers', 'opensource', 25000)
|
||||
end
|
||||
|
||||
def down
|
||||
return unless Gitlab.com?
|
||||
|
||||
create_or_update_plan_limit('pipeline_triggers', 'premium_trial', 0)
|
||||
create_or_update_plan_limit('pipeline_triggers', 'ultimate_trial', 0)
|
||||
create_or_update_plan_limit('pipeline_triggers', 'opensource', 0)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTempNotesBackupTable < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.10'
|
||||
|
||||
TABLE_NAME = 'temp_notes_backup'
|
||||
|
||||
def up
|
||||
execute "CREATE TABLE IF NOT EXISTS #{TABLE_NAME} (LIKE notes);"
|
||||
execute "ALTER TABLE #{TABLE_NAME} ADD PRIMARY KEY (id);"
|
||||
end
|
||||
|
||||
def down
|
||||
execute "DROP TABLE IF EXISTS #{TABLE_NAME}"
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackupAndRemoveNotesWithNullNoteableType < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.10'
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
|
||||
|
||||
MIGRATION = "BackupAndRemoveNotesWithNullNoteableType"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 10_000
|
||||
SUB_BATCH_SIZE = 50
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:notes,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE,
|
||||
gitlab_schema: :gitlab_main_cell
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :notes, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
ecf1584417fd47c83ccba88e7cf8ce47d0b1325c7286cde293f75073c6ffccb3
|
||||
|
|
@ -0,0 +1 @@
|
|||
0ba71b425f973acc7343efcacae91c7c25917ee6c96ac88ede0f30ea2ceee9a9
|
||||
|
|
@ -0,0 +1 @@
|
|||
418490c7537189eba9a01940a8c905411765132d5c811ef836912537ba2c8b63
|
||||
|
|
@ -16213,6 +16213,38 @@ CREATE SEQUENCE target_branch_rules_id_seq
|
|||
|
||||
ALTER SEQUENCE target_branch_rules_id_seq OWNED BY target_branch_rules.id;
|
||||
|
||||
CREATE TABLE temp_notes_backup (
|
||||
note text,
|
||||
noteable_type character varying,
|
||||
author_id integer,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
project_id integer,
|
||||
attachment character varying,
|
||||
line_code character varying,
|
||||
commit_id character varying,
|
||||
noteable_id integer,
|
||||
system boolean NOT NULL,
|
||||
st_diff text,
|
||||
updated_by_id integer,
|
||||
type character varying,
|
||||
"position" text,
|
||||
original_position text,
|
||||
resolved_at timestamp without time zone,
|
||||
resolved_by_id integer,
|
||||
discussion_id character varying,
|
||||
note_html text,
|
||||
cached_markdown_version integer,
|
||||
change_position text,
|
||||
resolved_by_push boolean,
|
||||
review_id bigint,
|
||||
confidential boolean,
|
||||
last_edited_at timestamp with time zone,
|
||||
internal boolean NOT NULL,
|
||||
id bigint NOT NULL,
|
||||
namespace_id bigint
|
||||
);
|
||||
|
||||
CREATE TABLE term_agreements (
|
||||
id integer NOT NULL,
|
||||
term_id integer NOT NULL,
|
||||
|
|
@ -21995,6 +22027,9 @@ ALTER TABLE ONLY tags
|
|||
ALTER TABLE ONLY target_branch_rules
|
||||
ADD CONSTRAINT target_branch_rules_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY temp_notes_backup
|
||||
ADD CONSTRAINT temp_notes_backup_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY term_agreements
|
||||
ADD CONSTRAINT term_agreements_pkey PRIMARY KEY (id);
|
||||
|
||||
|
|
|
|||
|
|
@ -64,5 +64,6 @@ swap:
|
|||
sub-groups: "subgroups"
|
||||
timezone: "time zone"
|
||||
utiliz(?:es?|ing): "use"
|
||||
VSCode: "VS Code"
|
||||
we recommend: "you should"
|
||||
within: "in"
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ POST /import/github
|
|||
|----------------------------|---------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `personal_access_token` | string | yes | GitHub personal access token |
|
||||
| `repo_id` | integer | yes | GitHub repository ID |
|
||||
| `new_name` | string | no | New repository name |
|
||||
| `new_name` | string | no | Name of the new project. Also used as the new path so must not start or end with a special character and must not contain consecutive special characters. |
|
||||
| `target_namespace` | string | yes | Namespace to import repository into. Supports subgroups like `/namespace/subgroup`. In GitLab 15.8 and later, must not be blank |
|
||||
| `github_hostname` | string | no | Custom GitHub Enterprise hostname. Do not set for GitHub.com. |
|
||||
| `optional_stages` | object | no | [Additional items to import](../user/project/import/github.md#select-additional-items-to-import). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/373705) in GitLab 15.5 |
|
||||
|
|
@ -204,7 +204,7 @@ POST /import/bitbucket_server
|
|||
| `personal_access_token` | string | yes | Bitbucket Server personal access token/password |
|
||||
| `bitbucket_server_project` | string | yes | Bitbucket Project Key |
|
||||
| `bitbucket_server_repo` | string | yes | Bitbucket Repository Name |
|
||||
| `new_name` | string | no | New repository name |
|
||||
| `new_name` | string | no | Name of the new project. Also used as the new path so must not start or end with a special character and must not contain consecutive special characters. Between GitLab 15.1 and GitLab 16.9, the project path [was copied](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88845) from Bitbucket instead. In GitLab 16.10, the behavior was [changed back](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145793) to the original behavior. |
|
||||
| `target_namespace` | string | no | Namespace to import repository into. Supports subgroups like `/namespace/subgroup` |
|
||||
| `timeout_strategy` | string | no | Strategy for handling import timeouts. Valid values are `optimistic` (continue to next stage of import) or `pessimistic` (fail immediately). Defaults to `pessimistic`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/422979) in GitLab 16.5. |
|
||||
|
||||
|
|
@ -218,7 +218,8 @@ curl --request POST \
|
|||
"bitbucket_server_username": "root",
|
||||
"personal_access_token": "Nzk4MDcxODY4MDAyOiP8y410zF3tGAyLnHRv/E0+3xYs",
|
||||
"bitbucket_server_project": "NEW",
|
||||
"bitbucket_server_repo": "my-repo"
|
||||
"bitbucket_server_repo": "my-repo",
|
||||
"new_name": "NEW-NAME"
|
||||
}'
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -277,6 +277,7 @@ Example response:
|
|||
"projects_api_rate_limit_unauthenticated": 400,
|
||||
"silent_mode_enabled": false,
|
||||
"security_policy_global_group_approvers_enabled": true,
|
||||
"security_approval_policies_limit": 5,
|
||||
"package_registry_allow_anyone_to_pull_option": true,
|
||||
"bulk_import_max_download_file_size": 5120,
|
||||
"project_jobs_api_rate_limit": 600,
|
||||
|
|
@ -297,6 +298,7 @@ these parameters:
|
|||
- `deletion_adjourned_period`
|
||||
- `disable_personal_access_tokens`
|
||||
- `security_policy_global_group_approvers_enabled`
|
||||
- `security_approval_policies_limit`
|
||||
- `delete_unconfirmed_users`
|
||||
- `unconfirmed_users_delete_after_days`
|
||||
|
||||
|
|
@ -559,6 +561,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. |
|
||||
| `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes. |
|
||||
| `security_policy_global_group_approvers_enabled` | boolean | no | Whether to look up merge request approval policy approval groups globally or within project hierarchies. |
|
||||
| `security_approval_policies_limit` | integer | no | Maximum number of active merge request approval policies per security policy project. Default: 5. Maximum: 20 |
|
||||
| `security_txt_content` | string | no | [Public security contact information](../administration/settings/security_contact_information.md). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433210) in GitLab 16.7. |
|
||||
| `service_access_tokens_expiration_enforced` | boolean | no | Flag to indicate if token expiry date can be optional for service account users |
|
||||
| `shared_runners_enabled` | boolean | no | (**If enabled, requires:** `shared_runners_text` and `shared_runners_minutes`) Enable shared runners for new projects. |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Import and Integrate
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.ment-guidelines-review.ment-guidelines-review.ment-guidelines-review.
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
|
||||
---
|
||||
|
||||
# Export to CSV
|
||||
|
|
|
|||
|
|
@ -1654,7 +1654,7 @@ that is available now. We recommend this alternative solution because it provide
|
|||
<div class="deprecation-notes">
|
||||
- Announced in GitLab <span class="milestone">16.9</span>
|
||||
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/431123).
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/412060).
|
||||
</div>
|
||||
|
||||
We're reducing the number of supported [analyzers](https://docs.gitlab.com/ee/user/application_security/sast/analyzers/) used by default in GitLab SAST.
|
||||
|
|
@ -1678,7 +1678,7 @@ The vulnerability management system will update most existing findings so that t
|
|||
Findings that aren't migrated to the new analyzer will be [automatically resolved](https://docs.gitlab.com/ee/user/application_security/sast/#automatic-vulnerability-resolution).
|
||||
See [Vulnerability translation documentation](https://docs.gitlab.com/ee/user/application_security/sast/analyzers.html#vulnerability-translation) for further details.
|
||||
|
||||
If you applied customizations to the removed analyzers, or if you currently disable the Semgrep-based analyzer in your pipelines, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/431123#breaking-change).
|
||||
If you applied customizations to the removed analyzers, or if you currently disable the Semgrep-based analyzer in your pipelines, you must take action as detailed in the [deprecation issue for this change](https://gitlab.com/gitlab-org/gitlab/-/issues/412060#action-required).
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ the related documentation.
|
|||
| Scheduled Pipeline Cron | `*/5 * * * *` | See [Pipeline schedules advanced configuration](../../administration/cicd.md#change-maximum-scheduled-pipeline-frequency). |
|
||||
| Maximum jobs in active pipelines | `500` for Free tier, `1000` for all trial tiers, `20000` for Premium, and `100000` for Ultimate. | See [Number of jobs in active pipelines](../../administration/instance_limits.md#number-of-jobs-in-active-pipelines). |
|
||||
| Maximum CI/CD subscriptions to a project | `2` | See [Number of CI/CD subscriptions to a project](../../administration/instance_limits.md#number-of-cicd-subscriptions-to-a-project). |
|
||||
| Maximum number of pipeline triggers in a project | `25000` for Free tier, Unlimited for all paid tiers | See [Limit the number of pipeline triggers](../../administration/instance_limits.md#limit-the-number-of-pipeline-triggers). |
|
||||
| Maximum number of pipeline triggers in a project | `25000` for Free and trial tiers, Unlimited for all paid tiers | See [Limit the number of pipeline triggers](../../administration/instance_limits.md#limit-the-number-of-pipeline-triggers). |
|
||||
| Maximum pipeline schedules in projects | `10` for Free tier, `50` for all paid tiers | See [Number of pipeline schedules](../../administration/instance_limits.md#number-of-pipeline-schedules). |
|
||||
| Maximum pipelines per schedule | `24` for Free tier, `288` for all paid tiers | See [Limit the number of pipelines created by a pipeline schedule per day](../../administration/instance_limits.md#limit-the-number-of-pipelines-created-by-a-pipeline-schedule-per-day). |
|
||||
| Maximum number of schedule rules defined for each security policy project | Unlimited for all paid tiers | See [Number of schedule rules defined for each security policy project](../../administration/instance_limits.md#limit-the-number-of-schedule-rules-defined-for-security-policy-project). |
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ module API
|
|||
groups = GroupsFinder.new(current_user, find_params).execute
|
||||
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
|
||||
|
||||
order_groups(groups)
|
||||
order_groups(groups).with_api_scopes
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
class BackupAndRemoveNotesWithNullNoteableType < BatchedMigrationJob
|
||||
operation_name :delete_all
|
||||
scope_to ->(relation) { relation.where(noteable_type: nil) }
|
||||
feature_category :team_planning
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
Note.transaction do
|
||||
Note.connection.execute <<~SQL
|
||||
INSERT INTO temp_notes_backup #{sub_batch.to_sql}
|
||||
SQL
|
||||
|
||||
sub_batch.delete_all
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -75,6 +75,10 @@ module Gitlab
|
|||
return unless Feature.enabled?(:fetch_commits_for_bitbucket_server, project.group)
|
||||
|
||||
project.repository.fetch_remote(project.import_url, refmap: commits_to_fetch, prune: false)
|
||||
rescue Gitlab::Git::CommandError => e
|
||||
# When we try to fetch commit from the submodule, then the process might fail
|
||||
# with "unadvertised object" error. We are going to ignore it, to unblock the import
|
||||
track_import_failure!(project, exception: e) unless e.message.include?('unadvertised object')
|
||||
rescue StandardError => e
|
||||
track_import_failure!(project, exception: e)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def title
|
||||
_('Deployment Frequency')
|
||||
_('Deployment frequency')
|
||||
end
|
||||
|
||||
def value
|
||||
|
|
|
|||
|
|
@ -9893,9 +9893,6 @@ msgstr ""
|
|||
msgid "Certificate Subject"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change Failure Rate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change action"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9914,6 +9911,9 @@ msgstr ""
|
|||
msgid "Change confidentiality"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change failure rate"
|
||||
msgstr ""
|
||||
|
||||
msgid "Change label"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15421,7 +15421,7 @@ msgstr ""
|
|||
msgid "CycleAnalytics|Average time to completion (days)"
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|Change Failure Rate"
|
||||
msgid "CycleAnalytics|Change failure rate"
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|Create a custom value stream to view metrics about stages specific to your development process. Use your value stream to visualize your DevSecOps lifecycle, determine the velocity of your group, and identify inefficient processes."
|
||||
|
|
@ -15442,7 +15442,7 @@ msgstr ""
|
|||
msgid "CycleAnalytics|If you have recently upgraded your GitLab license from a tier without this feature, it can take up to 30 minutes for data to collect and display."
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|Lead Time for Changes"
|
||||
msgid "CycleAnalytics|Lead time for changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|New value stream…"
|
||||
|
|
@ -15486,7 +15486,7 @@ msgstr ""
|
|||
msgid "CycleAnalytics|There is no data for 'Total time' available. Adjust the current filters."
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|Time to Restore Service"
|
||||
msgid "CycleAnalytics|Time to restore service"
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalytics|Total time"
|
||||
|
|
@ -15536,22 +15536,19 @@ msgstr ""
|
|||
msgid "DORA4Metrics|By enabling this feature, you accept the %{url}"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Change Failure Rate"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Change Failure Rate (Quality)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Change failure rate"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Change failure rate (Quality)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Change failure rate (percentage)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Contributor count"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Critical Vulnerabilities over time"
|
||||
msgid "DORA4Metrics|Critical vulnerabilities over time"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Cycle time"
|
||||
|
|
@ -15569,15 +15566,12 @@ msgstr ""
|
|||
msgid "DORA4Metrics|Days from merge to deploy"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Deployment Frequency"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Deployment Frequency (Velocity)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Deployment frequency"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Deployment frequency (Velocity)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Deploys"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15625,7 +15619,7 @@ msgstr ""
|
|||
msgid "DORA4Metrics|High"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|High Vulnerabilities over time"
|
||||
msgid "DORA4Metrics|High vulnerabilities over time"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Issues closed"
|
||||
|
|
@ -15634,18 +15628,15 @@ msgstr ""
|
|||
msgid "DORA4Metrics|Issues created"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Lead Time for Changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Lead Time for Changes (Velocity)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Lead time"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Lead time for changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Lead time for changes (Velocity)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Lead time for changes (median days)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -15751,15 +15742,12 @@ msgstr ""
|
|||
msgid "DORA4Metrics|This visualization is not supported for project namespaces."
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Time to Restore Service"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Time to Restore Service (Quality)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Time to restore service"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Time to restore service (Quality)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Time to restore service (median days)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -17262,9 +17250,6 @@ msgstr ""
|
|||
msgid "Deployment #%{iid}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deployment Frequency"
|
||||
msgstr ""
|
||||
|
||||
msgid "Deployment Target|%{linkStart}How to provision or deploy to Kubernetes clusters from GitLab?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29085,10 +29070,10 @@ msgstr ""
|
|||
msgid "Layout|Fluid"
|
||||
msgstr ""
|
||||
|
||||
msgid "Lead Time for Changes"
|
||||
msgid "Lead time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Lead time"
|
||||
msgid "Lead time for changes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Learn GitLab"
|
||||
|
|
@ -42653,6 +42638,9 @@ msgstr ""
|
|||
msgid "Runners|Administrator"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|After GitLab Runner is installed and registered, an autoscaling fleet of runners is available to execute your CI/CD jobs in Google Cloud"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|After you complete the steps below, an autoscaling fleet of runners is available to execute your CI/CD jobs in Google Cloud. Based on demand, a runner manager automatically creates temporary runners."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -42880,6 +42868,9 @@ msgstr ""
|
|||
msgid "Runners|Go to %{groupLink} to enable them."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Go to first invalid form field"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Google Cloud project ID"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -42904,9 +42895,15 @@ msgstr ""
|
|||
msgid "Runners|If both settings are disabled, new runners cannot be registered."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|If you haven't already configured your Google Cloud project, this step enables the required services and creates a service account with the required permissions. "
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|In GitLab Runner 15.6, the use of registration tokens and runner parameters in the 'register' command was deprecated. They have been replaced by authentication tokens. %{linkStart}How does this impact my current registration workflow?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|In the directory with that Terraform configuration file, run the following on your command line."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Install GitLab Runner"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43148,6 +43145,9 @@ msgstr ""
|
|||
msgid "Runners|Revision"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Run the following on your command line. You might be prompted to sign in to Google"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Runner"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43304,12 +43304,18 @@ msgstr ""
|
|||
msgid "Runners|Step 1"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Step 1: Configure Google Cloud project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Step 1: Specify environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Step 2"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Step 2: Install and register GitLab Runner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Step 2: Set up GitLab Runner"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43375,6 +43381,9 @@ msgstr ""
|
|||
msgid "Runners|These runners are assigned to this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|These setup instructions use your specifications and follow the best practices for performance and security"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|This group currently has 1 stale runner."
|
||||
msgid_plural "Runners|This group currently has %d stale runners."
|
||||
msgstr[0] ""
|
||||
|
|
@ -43407,6 +43416,9 @@ msgstr ""
|
|||
msgid "Runners|This runner is outdated, an upgrade is recommended"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|This step creates the required infrastructure in Google Cloud, installs GitLab Runner, and registers it to this GitLab project. "
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|To improve security, use a dedicated project for CI/CD, separate from resources and identity management projects. %{linkStart}Where’s my project ID in Google Cloud?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43425,6 +43437,9 @@ msgstr ""
|
|||
msgid "Runners|To view the runner, go to %{runnerListName}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|To view the setup instructions, complete the previous form"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|To view the setup instructions, complete the previous form. The instructions help you set up an autoscaling fleet of runners to execute your CI/CD jobs in Google Cloud."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43461,6 +43476,9 @@ msgstr ""
|
|||
msgid "Runners|Use Group runners when you want all projects in a group to have access to a set of runners."
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Use a text editor to create a main.tf file with the following Terraform configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Runners|Use the dashboard to view performance statistics of your runner fleet."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -51706,10 +51724,10 @@ msgstr ""
|
|||
msgid "Time to Merge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Time to Restore Service"
|
||||
msgid "Time to merge"
|
||||
msgstr ""
|
||||
|
||||
msgid "Time to merge"
|
||||
msgid "Time to restore service"
|
||||
msgstr ""
|
||||
|
||||
msgid "Time to subtract exceeds the total time spent"
|
||||
|
|
|
|||
|
|
@ -253,11 +253,35 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
|
|||
end
|
||||
|
||||
context 'button component renders a link' do
|
||||
let(:options) { { href: 'https://gitlab.com', target: '_blank' } }
|
||||
let(:options) { { href: 'https://gitlab.com', target: '_self' } }
|
||||
|
||||
it "renders a link instead of the button" do
|
||||
expect(page).not_to have_css "button[type='button']"
|
||||
expect(page).to have_css "a[href='https://gitlab.com'][target='_blank']"
|
||||
expect(page).to have_css "a[href='https://gitlab.com'][target='_self']"
|
||||
end
|
||||
|
||||
context 'with target="_blank"' do
|
||||
let(:options) { { href: 'https://gitlab.com', target: '_blank' } }
|
||||
|
||||
it 'adds rel="noopener noreferrer"' do
|
||||
expect(page).to have_css "a[href='https://gitlab.com'][target='_blank'][rel='noopener noreferrer']"
|
||||
end
|
||||
|
||||
context 'with a value for "rel" already given' do
|
||||
let(:options) { { href: 'https://gitlab.com', target: '_blank', button_options: { rel: 'help next' } } }
|
||||
|
||||
it 'keeps given value and adds "noopener noreferrer"' do
|
||||
expect(page).to have_css "a[href='https://gitlab.com'][target='_blank'][rel='help next noopener noreferrer']"
|
||||
end
|
||||
end
|
||||
|
||||
context 'with "noopener noreferrer" for "rel" already given' do
|
||||
let(:options) { { href: 'https://gitlab.com', target: '_blank', button_options: { rel: 'noopener noreferrer' } } }
|
||||
|
||||
it 'does not duplicate "noopener noreferrer"' do
|
||||
expect(page).to have_css "a[href='https://gitlab.com'][target='_blank'][rel='noopener noreferrer']"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'basic button behavior'
|
||||
|
|
|
|||
|
|
@ -44,10 +44,13 @@ module Pajamas
|
|||
|
||||
# The component can also be used to create links that look and feel like buttons.
|
||||
# Just provide a `href` and optionally a `target` to create an `<a>` tag.
|
||||
def link
|
||||
# For links with target="_blank", the component automatically adds rel="noopener noreferrer".
|
||||
#
|
||||
# @param target select {{ Pajamas::ButtonComponent::TARGET_OPTIONS }}
|
||||
def link(target: nil)
|
||||
render(Pajamas::ButtonComponent.new(
|
||||
href: "https://gitlab.com",
|
||||
target: "_blank"
|
||||
target: target
|
||||
)) do
|
||||
"This is a link"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ RSpec.describe Groups::DependencyProxyAuthController do
|
|||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'service account user' do
|
||||
let_it_be(:user) { create(:user, :service_account) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
end
|
||||
|
||||
context 'deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token) }
|
||||
|
||||
|
|
@ -78,6 +84,14 @@ RSpec.describe Groups::DependencyProxyAuthController do
|
|||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'service account user from an expired token' do
|
||||
let_it_be(:user) { create(:user, :service_account) }
|
||||
|
||||
let(:jwt) { build_jwt(user, expire_time: Time.zone.now - 1.hour) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'expired deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :expired) }
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
subscriptions: %w[user_id subscribable_id],
|
||||
suggestions: %w[commit_id],
|
||||
taggings: %w[tag_id taggable_id tagger_id],
|
||||
# temp_notes_backup is a temporary table created to store orphaned records from the notes table to insure against data loss.
|
||||
temp_notes_backup: %w[author_id project_id commit_id noteable_id updated_by_id resolved_by_id discussion_id review_id namespace_id],
|
||||
timelogs: %w[user_id],
|
||||
todos: %w[target_id commit_id],
|
||||
uploads: %w[model_id],
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ RSpec.describe '.gitlab/ci/rules.gitlab-ci.yml', feature_category: :tooling do
|
|||
'.gitlab/CODEOWNERS',
|
||||
'.gitleaksignore',
|
||||
'.gitpod.yml',
|
||||
'.index.yml.example',
|
||||
'.license_encryption_key.pub',
|
||||
'.mailmap',
|
||||
'.prettierignore',
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ FactoryBot.define do
|
|||
true
|
||||
end
|
||||
|
||||
user.assign_personal_namespace if assign_ns
|
||||
user.assign_personal_namespace(create(:organization)) if assign_ns
|
||||
end
|
||||
|
||||
trait :without_default_org do
|
||||
|
|
@ -28,7 +28,9 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
trait :with_namespace do
|
||||
namespace { assign_personal_namespace }
|
||||
# rubocop: disable RSpec/FactoryBot/InlineAssociation -- We need to pass an Organization to this method
|
||||
namespace { assign_personal_namespace(create(:organization)) }
|
||||
# rubocop: enable RSpec/FactoryBot/InlineAssociation
|
||||
end
|
||||
|
||||
trait :admin do
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export const summary = [
|
|||
{ value: '20', title: 'New issues' },
|
||||
{ value: null, title: 'Commits' },
|
||||
{ value: null, title: 'Deploys' },
|
||||
{ value: null, title: 'Deployment Frequency', unit: '/day' },
|
||||
{ value: null, title: 'Deployment frequency', unit: '/day' },
|
||||
];
|
||||
|
||||
export const issueStage = {
|
||||
|
|
@ -112,7 +112,7 @@ export const convertedData = {
|
|||
{ value: '20', title: 'New issues' },
|
||||
{ value: '-', title: 'Commits' },
|
||||
{ value: '-', title: 'Deploys' },
|
||||
{ value: '-', title: 'Deployment Frequency', unit: '/day' },
|
||||
{ value: '-', title: 'Deployment frequency', unit: '/day' },
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { METRIC_POPOVER_LABEL } from '~/analytics/shared/constants';
|
|||
|
||||
const MOCK_METRIC = {
|
||||
key: 'deployment-frequency',
|
||||
label: 'Deployment Frequency',
|
||||
label: 'Deployment frequency',
|
||||
value: '10.0',
|
||||
unit: '/day',
|
||||
description: 'Average number of deployments to production per day.',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { GlAlert } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
|
@ -7,7 +8,12 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import GoogleCloudRegistrationInstructions from '~/ci/runner/components/registration/google_cloud_registration_instructions.vue';
|
||||
import runnerForRegistrationQuery from '~/ci/runner/graphql/register/runner_for_registration.query.graphql';
|
||||
import { runnerForRegistration, mockAuthenticationToken } from '../../mock_data';
|
||||
import provisionGoogleCloudRunnerQueryProject from '~/ci/runner/graphql/register/provision_google_cloud_runner_project.query.graphql';
|
||||
import {
|
||||
runnerForRegistration,
|
||||
mockAuthenticationToken,
|
||||
googleCloudRunnerProvisionResponse,
|
||||
} from '../../mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
@ -28,6 +34,14 @@ const mockRunnerWithoutTokenResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
const mockGoogleCloudRunnerProvisionResponse = {
|
||||
data: {
|
||||
project: {
|
||||
...googleCloudRunnerProvisionResponse,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockRunnerId = `${getIdFromGraphQLId(runnerForRegistration.data.runner.id)}`;
|
||||
|
||||
describe('GoogleCloudRegistrationInstructions', () => {
|
||||
|
|
@ -42,9 +56,19 @@ describe('GoogleCloudRegistrationInstructions', () => {
|
|||
const findMachineTypeLink = () => wrapper.findByTestId('machine-types-link');
|
||||
const findToken = () => wrapper.findByTestId('runner-token');
|
||||
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
|
||||
const findModalTerrarformInstructions = () =>
|
||||
wrapper.findByTestId('terraform-script-instructions');
|
||||
const findModalTerrarformApplyInstructions = () =>
|
||||
wrapper.findByTestId('terraform-apply-instructions');
|
||||
const findModalBashInstructions = () => wrapper.findByTestId('bash-instructions');
|
||||
const findInstructionsButton = () => wrapper.findByTestId('show-instructions-button');
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
const runnerWithTokenResolver = jest.fn().mockResolvedValue(mockRunnerResponse);
|
||||
const runnerWithoutTokenResolver = jest.fn().mockResolvedValue(mockRunnerWithoutTokenResponse);
|
||||
const googleCloudRunnerResolver = jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockGoogleCloudRunnerProvisionResponse);
|
||||
|
||||
const defaultHandlers = [[runnerForRegistrationQuery, runnerWithTokenResolver]];
|
||||
|
||||
|
|
@ -53,6 +77,7 @@ describe('GoogleCloudRegistrationInstructions', () => {
|
|||
apolloProvider: createMockApollo(handlers),
|
||||
propsData: {
|
||||
runnerId: mockRunnerId,
|
||||
projectPath: 'test/project',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -111,4 +136,51 @@ describe('GoogleCloudRegistrationInstructions', () => {
|
|||
expect(findToken().exists()).toBe(false);
|
||||
expect(findClipboardButton().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('Shows an alert when the form is not valid', async () => {
|
||||
createComponent();
|
||||
|
||||
findInstructionsButton().vm.$emit('click');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Hides an alert when the form is valid', async () => {
|
||||
createComponent(mountExtended, [
|
||||
[provisionGoogleCloudRunnerQueryProject, googleCloudRunnerResolver],
|
||||
]);
|
||||
|
||||
findProjectIdInput().vm.$emit('input', 'dev-gcp-xxx-integrati-xxxxxxxx');
|
||||
findRegionInput().vm.$emit('input', 'us-central1');
|
||||
findZoneInput().vm.$emit('input', 'us-central1');
|
||||
findMachineTypeInput().vm.$emit('input', 'n2d-standard-2');
|
||||
|
||||
findInstructionsButton().vm.$emit('click');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('Shows a modal with the correspondent scripts', async () => {
|
||||
createComponent(shallowMountExtended, [
|
||||
[provisionGoogleCloudRunnerQueryProject, googleCloudRunnerResolver],
|
||||
]);
|
||||
|
||||
findProjectIdInput().vm.$emit('input', 'dev-gcp-xxx-integrati-xxxxxxxx');
|
||||
findRegionInput().vm.$emit('input', 'us-central1');
|
||||
findZoneInput().vm.$emit('input', 'us-central1');
|
||||
|
||||
findInstructionsButton().vm.$emit('click');
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(googleCloudRunnerResolver).toHaveBeenCalled();
|
||||
|
||||
expect(findModalBashInstructions().text()).not.toBeNull();
|
||||
expect(findModalTerrarformInstructions().text()).not.toBeNull();
|
||||
expect(findModalTerrarformApplyInstructions().text).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -344,6 +344,31 @@ export const mockAuthenticationToken = 'MOCK_AUTHENTICATION_TOKEN';
|
|||
export const newRunnerPath = '/runners/new';
|
||||
export const runnerInstallHelpPage = 'https://docs.example.com/runner/install/';
|
||||
|
||||
export const googleCloudRunnerProvisionResponse = {
|
||||
__typename: 'Project',
|
||||
id: 'gid://gitlab/Project/23',
|
||||
runnerCloudProvisioning: {
|
||||
__typename: 'CiRunnerGoogleCloudProvisioning',
|
||||
projectSetupShellScript: '#!/bin/bash echo "hello world!"',
|
||||
provisioningSteps: [
|
||||
{
|
||||
__typename: 'CiRunnerCloudProvisioningStep',
|
||||
title: 'Save the Terraform script to a file',
|
||||
languageIdentifier: 'terraform',
|
||||
instructions:
|
||||
'terraform {\n required_version = "~> 1.5"\n\n required_providers {\n google = {\n source = "hashicorp/google"\n version = "~> 5.12"\n }\n\n tls = {\n source = "hashicorp/tls"\n version = "~> 4.0"\n }\n }\n}\n\nlocals {\n google_project = "dev-gcp-s3c-integrati-9abafed1"\n google_region = "us-central1"\n google_zone = "us-central1-a"\n}\n\nprovider "google" {\n project = local.google_project\n region = local.google_region\n zone = local.google_zone\n}\n\nvariable "runner_token" {\n type = string\n sensitive = true\n}\n\n# Added available customisation\nmodule "runner-deployment" {\n source = "git::https://gitlab.com/gitlab-org/ci-cd/runner-tools/grit.git//scenarios/google/linux/docker-autoscaler-default"\n\n google_project = local.google_project\n google_region = local.google_region\n google_zone = local.google_zone\n\n name = "grit-BQQkbs5X_"\n\n gitlab_url = "http://gdk.test:3000"\n\n runner_token = var.runner_token\n\n ephemeral_runner = {\n # disk_type = "pd-ssd"\n # disk_size = 50\n machine_type = "n2d-standard-2"\n # source_image = "projects/cos-cloud/global/images/family/cos-stable"\n }\n}\n\noutput "runner-manager-external-ip" {\n value = module.runner-deployment.runner_manager_external_ip\n}\n',
|
||||
},
|
||||
{
|
||||
__typename: 'CiRunnerCloudProvisioningStep',
|
||||
title: 'Apply the Terraform script',
|
||||
languageIdentifier: 'shell',
|
||||
instructions:
|
||||
'#!/bin/bash\n\nterraform plan -var gitlab_runner="glrt-BQQkbs5X_f-ys6wLEy2V" -out plan.out\nterraform apply plan.out\n',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
allRunnersData,
|
||||
allRunnersWithCreatorData,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackupAndRemoveNotesWithNullNoteableType, feature_category: :team_planning do
|
||||
let(:notes) { table(:notes) }
|
||||
let(:temp_notes_backup) { table(:temp_notes_backup) }
|
||||
let(:migration_args) do
|
||||
{
|
||||
start_id: notes.minimum(:id),
|
||||
end_id: notes.maximum(:id),
|
||||
batch_table: :notes,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 1,
|
||||
pause_ms: 0,
|
||||
connection: ApplicationRecord.connection
|
||||
}
|
||||
end
|
||||
|
||||
it 'backs up and removes orphaned notes records' do
|
||||
# Note:
|
||||
# This background migration attempts to remove the existing `notes` records with NULL noteable_type.
|
||||
# The removed records will be stored in the `temp_notes_back` table to be safe.
|
||||
#
|
||||
# The `notes` table received a new check constraint to prevent `null` noteable_type in an earlier migration.
|
||||
# To create records with NULL noteable_type, we need to drop the constraint.
|
||||
# (`schema:` cannot be used because the migration requires the `temp_notes_back` table to be present.)
|
||||
#
|
||||
# To keep the test database's schema valid, the dropping will be performing inside a transaction.
|
||||
ApplicationRecord.connection.transaction do
|
||||
ApplicationRecord.connection.execute("ALTER TABLE notes DROP CONSTRAINT check_1244cbd7d0;")
|
||||
|
||||
notes.create!(id: 1, noteable_type: 'a')
|
||||
notes.create!(id: 2, note: 'orphan 1', noteable_type: nil)
|
||||
notes.create!(id: 3, noteable_type: 'b')
|
||||
notes.create!(id: 4, note: 'orphan 2', noteable_type: nil)
|
||||
notes.create!(id: 5, noteable_type: 'c')
|
||||
|
||||
expect { described_class.new(**migration_args).perform }
|
||||
.to change { notes.count }
|
||||
.by(-2)
|
||||
.and change { notes.where(noteable_type: nil).count }
|
||||
.by(-2)
|
||||
.and change { temp_notes_backup.count }
|
||||
.by(2)
|
||||
|
||||
expect(temp_notes_backup.first.note).to eq('orphan 1')
|
||||
expect(temp_notes_backup.last.note).to eq('orphan 2')
|
||||
|
||||
ApplicationRecord.connection.execute("ABORT")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,9 +14,11 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
|
|||
)
|
||||
end
|
||||
|
||||
let_it_be(:repository) { project.repository }
|
||||
|
||||
subject(:importer) { described_class.new(project) }
|
||||
|
||||
describe '#execute', :clean_gitlab_redis_cache do
|
||||
describe '#execute', :clean_gitlab_redis_cache, :clean_gitlab_redis_shared_state do
|
||||
let(:commit_sha) { 'aaaa1' }
|
||||
|
||||
before do
|
||||
|
|
@ -88,7 +90,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
|
|||
'bbbb2:refs/keep-around/bbbb2'
|
||||
]
|
||||
|
||||
expect(project.repository).to receive(:fetch_remote).with(
|
||||
expect(repository).to receive(:fetch_remote).with(
|
||||
project.import_url,
|
||||
refmap: expected_refmap,
|
||||
prune: false
|
||||
|
|
@ -107,7 +109,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
|
|||
'bbbb2:refs/keep-around/bbbb2'
|
||||
]
|
||||
|
||||
expect(project.repository).to receive(:fetch_remote).with(
|
||||
expect(repository).to receive(:fetch_remote).with(
|
||||
project.import_url,
|
||||
refmap: expected_refmap,
|
||||
prune: false
|
||||
|
|
@ -123,7 +125,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
|
|||
end
|
||||
|
||||
it 'does not fetch anything' do
|
||||
expect(project.repository).not_to receive(:fetch_remote)
|
||||
expect(repository).not_to receive(:fetch_remote)
|
||||
importer.execute
|
||||
end
|
||||
end
|
||||
|
|
@ -135,7 +137,25 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
|
|||
end
|
||||
|
||||
it 'does not fetch anything' do
|
||||
expect(project.repository).not_to receive(:fetch_remote)
|
||||
expect(repository).not_to receive(:fetch_remote)
|
||||
|
||||
importer.execute
|
||||
end
|
||||
end
|
||||
|
||||
context 'when fetch causes an unadvertised object error' do
|
||||
let(:exception) do
|
||||
Gitlab::Git::CommandError.new(
|
||||
'Server does not allow request for unadvertised object 0731e4'
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(repository).to receive(:fetch_remote).and_raise(exception)
|
||||
end
|
||||
|
||||
it 'does not log the exception' do
|
||||
expect(Gitlab::Import::ImportFailureService).not_to receive(:track)
|
||||
|
||||
importer.execute
|
||||
end
|
||||
|
|
@ -145,7 +165,7 @@ RSpec.describe Gitlab::BitbucketServerImport::Importers::PullRequestsImporter, f
|
|||
let(:exception) { ArgumentError.new('blank or empty URL') }
|
||||
|
||||
before do
|
||||
allow(project.repository).to receive(:fetch_remote).and_raise(exception)
|
||||
allow(repository).to receive(:fetch_remote).and_raise(exception)
|
||||
end
|
||||
|
||||
it 'rescues and logs the exception' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackupAndRemoveNotesWithNullNoteableType, feature_category: :team_planning 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(
|
||||
gitlab_schema: :gitlab_main_cell,
|
||||
table_name: :notes,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe UpdatePipelineTriggersPlanLimits, feature_category: :pipeline_composition do
|
||||
let(:plans) { table(:plans) }
|
||||
let(:plan_limits) { table(:plan_limits) }
|
||||
|
||||
let!(:premium_trial_plan) { plans.create!(name: 'premium_trial') }
|
||||
let!(:ultimate_trial_plan) { plans.create!(name: 'ultimate_trial') }
|
||||
let!(:opensource_plan) { plans.create!(name: 'opensource') }
|
||||
let!(:default_plan) { plans.create!(name: 'default') }
|
||||
|
||||
before do
|
||||
plans.pluck(:id).each { |plan_id| plan_limits.create!(plan_id: plan_id) }
|
||||
plan_limits.update_all(pipeline_triggers: 0)
|
||||
end
|
||||
|
||||
context 'when on gitlab.com' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
end
|
||||
|
||||
it 'correctly migrates up and down' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(plan_limits.pluck(:plan_id, :pipeline_triggers))
|
||||
.to contain_exactly(
|
||||
[premium_trial_plan.id, 0], [ultimate_trial_plan.id, 0], [opensource_plan.id, 0], [default_plan.id, 0])
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(plan_limits.pluck(:plan_id, :pipeline_triggers))
|
||||
.to contain_exactly(
|
||||
[premium_trial_plan.id, 25000], [ultimate_trial_plan.id, 25000], [opensource_plan.id, 25000],
|
||||
[default_plan.id, 0])
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when on self-managed' do
|
||||
before do
|
||||
allow(Gitlab).to receive(:com?).and_return(false)
|
||||
end
|
||||
|
||||
it 'correctly migrates up and down' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(plan_limits.pluck(:plan_id, :pipeline_triggers))
|
||||
.to contain_exactly(
|
||||
[premium_trial_plan.id, 0], [ultimate_trial_plan.id, 0], [opensource_plan.id, 0], [default_plan.id, 0])
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(plan_limits.pluck(:plan_id, :pipeline_triggers))
|
||||
.to contain_exactly(
|
||||
[premium_trial_plan.id, 0], [ultimate_trial_plan.id, 0], [opensource_plan.id, 0], [default_plan.id, 0])
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6069,7 +6069,9 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
describe '#assign_personal_namespace' do
|
||||
subject(:personal_namespace) { user.assign_personal_namespace }
|
||||
let(:organization) { create(:organization) }
|
||||
|
||||
subject(:personal_namespace) { user.assign_personal_namespace(organization) }
|
||||
|
||||
context 'when namespace exists' do
|
||||
let(:user) { build(:user) }
|
||||
|
|
@ -6086,16 +6088,28 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
context 'when namespace does not exist' do
|
||||
let(:user) { described_class.new attributes_for(:user) }
|
||||
|
||||
it 'builds a new namespace' do
|
||||
it 'builds a new namespace using assigned organization' do
|
||||
subject
|
||||
|
||||
expect(user.namespace).to be_kind_of(Namespaces::UserNamespace)
|
||||
expect(user.namespace.namespace_settings).to be_present
|
||||
expect(user.namespace.organization).to eq(organization)
|
||||
end
|
||||
|
||||
it 'returns the personal namespace' do
|
||||
expect(personal_namespace).to eq(user.namespace)
|
||||
end
|
||||
|
||||
context 'when organization is nil' do
|
||||
let!(:default_organization) { create(:organization, :default) }
|
||||
|
||||
# This logic will be removed when organization becomes a required argument
|
||||
it 'builds a new namespace using default organization' do
|
||||
user.assign_personal_namespace
|
||||
|
||||
expect(user.namespace.organization).to eq(Organizations::Organization.default_organization)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,23 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
describe "GET /groups" do
|
||||
shared_examples 'groups list N+1' do
|
||||
it 'avoids N+1 queries', :use_sql_query_cache do
|
||||
# warm-up
|
||||
get api("/groups", user)
|
||||
|
||||
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
||||
get api("/groups", user)
|
||||
end
|
||||
|
||||
create(:group, :public)
|
||||
|
||||
expect do
|
||||
get api("/groups", user)
|
||||
end.not_to exceed_all_query_limit(control)
|
||||
end
|
||||
end
|
||||
|
||||
context "when unauthenticated" do
|
||||
it "returns public groups", :aggregate_failures do
|
||||
get api("/groups")
|
||||
|
|
@ -91,16 +108,8 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
.to satisfy_one { |group| group['name'] == group1.name }
|
||||
end
|
||||
|
||||
it 'avoids N+1 queries', :use_sql_query_cache do
|
||||
control = ActiveRecord::QueryRecorder.new(skip_cached: false) do
|
||||
get api("/groups")
|
||||
end
|
||||
|
||||
create(:group)
|
||||
|
||||
expect do
|
||||
get api("/groups")
|
||||
end.not_to exceed_all_query_limit(control)
|
||||
it_behaves_like 'groups list N+1' do
|
||||
let(:user) { nil }
|
||||
end
|
||||
|
||||
context 'when statistics are requested' do
|
||||
|
|
@ -116,6 +125,10 @@ RSpec.describe API::Groups, feature_category: :groups_and_projects do
|
|||
end
|
||||
|
||||
context "when authenticated as user" do
|
||||
it_behaves_like 'groups list N+1' do
|
||||
let(:user) { user1 }
|
||||
end
|
||||
|
||||
it "normal user: returns an array of groups of user1", :aggregate_failures do
|
||||
get api("/groups", user1)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,15 @@ require 'spec_helper'
|
|||
RSpec.describe Users::AuthorizedBuildService, feature_category: :user_management do
|
||||
describe '#execute' do
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
|
||||
let(:params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
|
||||
let(:base_params) do
|
||||
build_stubbed(:user)
|
||||
.slice(:first_name, :last_name, :name, :username, :email, :password)
|
||||
.merge(organization_id: organization.id)
|
||||
end
|
||||
|
||||
let(:params) { base_params }
|
||||
|
||||
subject(:user) { described_class.new(current_user, params).execute }
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,15 @@ RSpec.describe Users::BuildService, feature_category: :user_management do
|
|||
|
||||
describe '#execute' do
|
||||
let_it_be(:current_user) { nil }
|
||||
let_it_be(:organization) { create(:organization) }
|
||||
|
||||
let(:params) { build_stubbed(:user).slice(:first_name, :last_name, :username, :email, :password) }
|
||||
let(:base_params) do
|
||||
build_stubbed(:user)
|
||||
.slice(:first_name, :last_name, :name, :username, :email, :password)
|
||||
.merge(organization_id: organization.id)
|
||||
end
|
||||
|
||||
let(:params) { base_params }
|
||||
let(:service) { described_class.new(current_user, params) }
|
||||
|
||||
context 'with nil current_user' do
|
||||
|
|
@ -80,7 +87,6 @@ RSpec.describe Users::BuildService, feature_category: :user_management do
|
|||
context 'with an admin current_user' do
|
||||
let_it_be(:current_user) { create(:admin) }
|
||||
|
||||
let(:params) { build_stubbed(:user).slice(:name, :username, :email, :password) }
|
||||
let(:service) { described_class.new(current_user, ActionController::Parameters.new(params).permit!) }
|
||||
|
||||
subject(:user) { service.execute }
|
||||
|
|
@ -120,12 +126,15 @@ RSpec.describe Users::BuildService, feature_category: :user_management do
|
|||
public_email: 1,
|
||||
user_type: 'project_bot',
|
||||
note: 1,
|
||||
view_diffs_file_by_file: 1
|
||||
view_diffs_file_by_file: 1,
|
||||
organization_id: organization.id
|
||||
}
|
||||
end
|
||||
|
||||
let(:user_params) { params.except(:organization_id) }
|
||||
|
||||
it 'sets all allowed attributes' do
|
||||
expect(User).to receive(:new).with(hash_including(params)).and_call_original
|
||||
expect(User).to receive(:new).with(hash_including(user_params)).and_call_original
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,18 +31,18 @@ RSpec.shared_examples 'renders metrics comparison table' do
|
|||
let(:metric_table) { find_by_testid('panel-dora-chart') }
|
||||
|
||||
available_metrics = [
|
||||
{ name: "Deployment Frequency", values: ["0.0/d"] * 3, identifier: 'deployment-frequency' },
|
||||
{ name: "Lead Time for Changes", values: ["0.0 d"] * 3, identifier: 'lead-time-for-changes' },
|
||||
{ name: "Time to Restore Service", values: ["0.0 d"] * 3, identifier: 'time-to-restore-service' },
|
||||
{ name: "Change Failure Rate", values: ["0.0%"] * 3, identifier: 'change-failure-rate' },
|
||||
{ name: "Deployment frequency", values: ["0.0/d"] * 3, identifier: 'deployment-frequency' },
|
||||
{ name: "Lead time for changes", values: ["0.0 d"] * 3, identifier: 'lead-time-for-changes' },
|
||||
{ name: "Time to restore service", values: ["0.0 d"] * 3, identifier: 'time-to-restore-service' },
|
||||
{ name: "Change failure rate", values: ["0.0%"] * 3, identifier: 'change-failure-rate' },
|
||||
{ name: "Lead time", values: ["-"] * 3, identifier: 'lead-time' },
|
||||
{ name: "Cycle time", values: ["-"] * 3, identifier: 'cycle-time' },
|
||||
{ name: "Issues created", values: ["-"] * 3, identifier: 'issues' },
|
||||
{ name: "Issues closed", values: ["-"] * 3, identifier: 'issues-completed' },
|
||||
{ name: "Deploys", values: ["-"] * 3, identifier: 'deploys' },
|
||||
{ name: "Merge request throughput", values: ["-"] * 3, identifier: 'merge-request-throughput' },
|
||||
{ name: "Critical Vulnerabilities over time", values: ["-"] * 3, identifier: "vulnerability-critical" },
|
||||
{ name: "High Vulnerabilities over time", values: ["-"] * 3, identifier: 'vulnerability-high' }
|
||||
{ name: "Critical vulnerabilities over time", values: ["-"] * 3, identifier: "vulnerability-critical" },
|
||||
{ name: "High vulnerabilities over time", values: ["-"] * 3, identifier: 'vulnerability-high' }
|
||||
]
|
||||
|
||||
it 'renders the metrics comparison visualization' do
|
||||
|
|
|
|||
|
|
@ -13,6 +13,20 @@ RSpec.shared_examples 'common user build items' do
|
|||
user
|
||||
end
|
||||
|
||||
context 'when organization_id is in the params' do
|
||||
it 'creates personal namespace in specified organization' do
|
||||
expect(user.namespace.organization).to eq(organization)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when organization_id is not in the params' do
|
||||
let(:params) { base_params.except(:organization_id) }
|
||||
|
||||
it 'does not assign organization' do
|
||||
expect(user.namespace.organization).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user_type is provided' do
|
||||
context 'when project_bot' do
|
||||
before do
|
||||
|
|
@ -63,12 +77,15 @@ RSpec.shared_examples_for 'current user not admin build items' do
|
|||
password: 1,
|
||||
password_automatically_set: 1,
|
||||
username: 1,
|
||||
user_type: 'project_bot'
|
||||
user_type: 'project_bot',
|
||||
organization_id: organization.id
|
||||
}
|
||||
end
|
||||
|
||||
let(:user_params) { params.except(:organization_id) }
|
||||
|
||||
it 'sets all allowed attributes' do
|
||||
expect(User).to receive(:new).with(hash_including(params)).and_call_original
|
||||
expect(User).to receive(:new).with(hash_including(user_params)).and_call_original
|
||||
|
||||
user
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue