Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-03-04 21:12:48 +00:00
parent 661b6b005b
commit 23a4cb9200
48 changed files with 700 additions and 442 deletions

View File

@ -333,9 +333,10 @@ Rails/InverseOf:
Rails/MigrationTimestamp:
Enabled: true
Include:
- 'db/migrate/*.rb'
- 'db/post_migrate/*.rb'
- 'ee/db/geo/migrate/*.rb'
- db/migrate/*.rb
- db/post_migrate/*.rb
- ee/db/geo/migrate/*.rb
- ee/db/geo/post_migrate/*.rb
# This is currently exiting with a rubocop exception error and should be
# resolved hopefully a future update

View File

@ -1,3 +1,6 @@
---
Rails/MigrationTimestamp:
Details: grace period
Exclude:
- 'db/migrate/20230414230535_add_external_identifiers_index_to_import_failures.rb.rb'
- 'ee/db/geo/migrate/20180402170913_add_missing_on_primary_to_job_artifact_registry..rb'

View File

@ -884,7 +884,6 @@ RSpec/FeatureCategory:
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/protected_environments_required_approvals_average_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/user_cap_setting_enabled_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage_data_counters/epic_activity_unique_counter_spec.rb'
- 'ee/spec/lib/gitlab/usage_data_counters/licenses_list_spec.rb'
- 'ee/spec/lib/gitlab/usage_data_counters/streaming_audit_event_type_counter_spec.rb'
- 'ee/spec/lib/gitlab/user_access_spec.rb'
- 'ee/spec/lib/gitlab/visibility_level_spec.rb'

View File

@ -1,7 +1,6 @@
<script>
import { GlTab, GlTabs } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ExperimentBadge from '~/vue_shared/components/badges/experiment_badge.vue';
import CiResourceComponents from './ci_resource_components.vue';
import CiResourceReadme from './ci_resource_readme.vue';
@ -14,7 +13,6 @@ export default {
GlTab,
GlTabs,
},
mixins: [glFeatureFlagsMixin()],
props: {
resourcePath: {
type: String,
@ -44,7 +42,7 @@ export default {
</template>
<ci-resource-readme :resource-path="resourcePath" />
</gl-tab>
<gl-tab v-if="glFeatures.ciCatalogComponentsTab" :title-link-class="$options.tabClass" lazy>
<gl-tab :title-link-class="$options.tabClass" lazy>
<template #title>
<div class="gl--flex-center gl-line-height-20">
{{ $options.i18n.tabs.components }}

View File

@ -13,6 +13,7 @@ import {
} from 'ee_else_ce/ci/runner/runner_search_utils';
import allRunnersQuery from 'ee_else_ce/ci/runner/graphql/list/all_runners.query.graphql';
import allRunnersCountQuery from 'ee_else_ce/ci/runner/graphql/list/all_runners_count.query.graphql';
import usersSearchAllQuery from '~/graphql_shared/queries/users_search_all.query.graphql';
import RunnerListHeader from '../components/runner_list_header.vue';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
@ -30,6 +31,7 @@ import { pausedTokenConfig } from '../components/search_tokens/paused_token_conf
import { statusTokenConfig } from '../components/search_tokens/status_token_config';
import { tagTokenConfig } from '../components/search_tokens/tag_token_config';
import { versionTokenConfig } from '../components/search_tokens/version_token_config';
import { creatorTokenConfig } from '../components/search_tokens/creator_token_config';
import {
ADMIN_FILTERED_SEARCH_NAMESPACE,
INSTANCE_TYPE,
@ -115,10 +117,33 @@ export default {
return !this.runnersLoading && !this.runners.items.length;
},
searchTokens() {
const preloadedUsers = [];
if (gon.current_user_id) {
preloadedUsers.push({
id: gon.current_user_id,
name: gon.current_user_fullname,
username: gon.current_username,
avatar_url: gon.current_user_avatar_url,
});
}
return [
pausedTokenConfig,
statusTokenConfig,
versionTokenConfig,
{
...creatorTokenConfig,
fetchUsers: (search) => {
return this.$apollo
.query({
query: usersSearchAllQuery,
variables: { search },
})
.then(({ data }) => data.users.nodes);
},
defaultUsers: [],
preloadedUsers,
},
{
...tagTokenConfig,
recentSuggestionsStorageKey: `${this.$options.filteredSearchNamespace}-recent-tags`,

View File

@ -66,35 +66,36 @@ export default {
'Runners|Machine type with preset amounts of virtual machines processors (vCPUs) and memory',
),
machineTypeDescription: s__(
'Runners|For most CI/CD jobs, use a %{linkStart}N2D standard machine type.%{linkEnd}',
'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',
'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_1Header: s__('Runners|Step 1: Configure your 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',
'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',
'Runners|Use a text editor to create a %{codeStart}main.tf%{codeEnd} 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',
'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'),
copyCommands: __('Copy commands'),
alertBody: s__('Runners|To view the setup instructions, complete the previous form.'),
invalidFormButton: s__('Runners|Go to first invalid form field'),
externalLink: __('(external link)'),
},
@ -108,7 +109,8 @@ export default {
'https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects',
regionAndZonesLink: 'https://cloud.google.com/compute/docs/regions-zones',
zonesLink: 'https://console.cloud.google.com/compute/zones?pli=1',
machineTypesLink:
machineTypesLink: 'https://cloud.google.com/compute/docs/machine-resource',
n2dMachineTypesLink:
'https://cloud.google.com/compute/docs/general-purpose-machines#n2d_machine_types',
},
components: {
@ -148,6 +150,7 @@ export default {
token: '',
runner: null,
showInstructionsModal: false,
showInstructionsButtonVariant: 'default',
validations: {
projectId: false,
region: false,
@ -243,7 +246,7 @@ export default {
},
codeStyles() {
return {
height: '300px',
maxHeight: '300px',
};
},
},
@ -251,6 +254,9 @@ export default {
invalidFields() {
if (this.invalidFields.length === 0) {
this.showAlert = false;
this.showInstructionsButtonVariant = 'confirm';
} else {
this.showInstructionsButtonVariant = 'default';
}
},
},
@ -291,15 +297,15 @@ export default {
},
},
cancelModalOptions: {
text: __('Cancel'),
text: __('Close'),
},
};
</script>
<template>
<div>
<div class="gl-mb-2">
<h1 class="gl-font-size-h1">{{ $options.i18n.heading }}</h1>
<div class="gl-mt-5">
<h1 class="gl-heading-1">{{ $options.i18n.heading }}</h1>
<p>
<gl-icon name="information-o" class="gl-text-blue-600!" />
<gl-sprintf :message="tokenMessage">
@ -327,7 +333,7 @@ export default {
<!-- start: before you begin -->
<div>
<h2 class="gl-font-lg">{{ $options.i18n.beforeHeading }}</h2>
<h2 class="gl-heading-2">{{ $options.i18n.beforeHeading }}</h2>
<ul>
<li>
<gl-sprintf :message="$options.i18n.permissionsText">
@ -371,10 +377,8 @@ export default {
<!-- end: before you begin -->
<!-- start: step one -->
<div class="gl-pb-4">
<h2 class="gl-font-lg">{{ $options.i18n.stepOneHeading }}</h2>
<p>{{ $options.i18n.stepOneDescription }}</p>
</div>
<h2 class="gl-heading-2">{{ $options.i18n.stepOneHeading }}</h2>
<p>{{ $options.i18n.stepOneDescription }}</p>
<gl-form-group :label="$options.i18n.projectIdLabel" label-for="project-id">
<template #description>
@ -385,7 +389,8 @@ export default {
target="_blank"
data-testid="project-id-link"
>
{{ content }} <gl-icon name="external-link" />
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
@ -403,14 +408,15 @@ export default {
<template #label>
<div>
{{ $options.i18n.regionLabel }}
<gl-icon id="region-popover" class="gl-ml-2" name="question-o" />
<gl-icon id="region-popover" name="question-o" class="gl-text-blue-600" />
<gl-popover triggers="hover" placement="top" target="region-popover">
<template #default>
<p>{{ $options.i18n.regionHelpText }}</p>
<gl-sprintf :message="$options.i18n.learnMore">
<template #link="{ content }">
<gl-link :href="$options.links.regionAndZonesLink" target="_blank">
{{ content }} <gl-icon name="external-link" />
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
@ -430,14 +436,15 @@ export default {
<template #label>
<div>
{{ $options.i18n.zoneLabel }}
<gl-icon id="zone-popover" class="gl-ml-2" name="question-o" />
<gl-icon id="zone-popover" name="question-o" class="gl-text-blue-600" />
<gl-popover triggers="hover" placement="top" target="zone-popover">
<template #default>
<p>{{ $options.i18n.zoneHelpText }}</p>
<gl-sprintf :message="$options.i18n.machineTypeDescription">
<gl-sprintf :message="$options.i18n.learnMore">
<template #link="{ content }">
<gl-link :href="$options.links.regionAndZonesLink" target="_blank">
{{ content }} <gl-icon name="external-link" />
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
@ -448,7 +455,7 @@ export default {
<template #description>
<gl-link :href="$options.links.zonesLink" target="_blank" data-testid="zone-link">
{{ $options.i18n.zonesLinkText }}
<gl-icon name="external-link" />
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
<gl-form-input
@ -463,10 +470,18 @@ export default {
<template #label>
<div>
{{ $options.i18n.machineTypeLabel }}
<gl-icon id="machine-type-popover" class="gl-ml-2" name="question-o" />
<gl-icon id="machine-type-popover" name="question-o" class="gl-text-blue-600" />
<gl-popover triggers="hover" placement="top" target="machine-type-popover">
<template #default>
{{ $options.i18n.machineTypeHelpText }}
<p>{{ $options.i18n.machineTypeHelpText }}</p>
<gl-sprintf :message="$options.i18n.learnMore">
<template #link="{ content }">
<gl-link :href="$options.links.machineTypesLink" target="_blank">
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
</template>
</gl-popover>
</div>
@ -475,11 +490,12 @@ export default {
<gl-sprintf :message="$options.i18n.machineTypeDescription">
<template #link="{ content }">
<gl-link
:href="$options.links.machineTypesLink"
:href="$options.links.n2dMachineTypesLink"
target="_blank"
data-testid="machine-types-link"
>
{{ content }} <gl-icon name="external-link" />
{{ content }}
<gl-icon name="external-link" :aria-label="$options.i18n.externalLink" />
</gl-link>
</template>
</gl-sprintf>
@ -491,10 +507,8 @@ export default {
<!-- end: step one -->
<!-- start: step two -->
<div class="gl-pb-4">
<h2 class="gl-font-lg">{{ $options.i18n.stepTwoHeading }}</h2>
<p>{{ $options.i18n.stepTwoDescription }}</p>
</div>
<h2 class="gl-heading-2">{{ $options.i18n.stepTwoHeading }}</h2>
<p>{{ $options.i18n.stepTwoDescription }}</p>
<gl-alert
v-if="showAlert"
:primary-button-text="$options.i18n.invalidFormButton"
@ -505,9 +519,13 @@ export default {
>
{{ $options.i18n.alertBody }}
</gl-alert>
<gl-button data-testid="show-instructions-button" @click="showInstructions">{{
$options.i18n.runnerSetupBtnText
}}</gl-button>
<gl-button
data-testid="show-instructions-button"
:variant="showInstructionsButtonVariant"
category="secondary"
@click="showInstructions"
>{{ $options.i18n.runnerSetupBtnText }}</gl-button
>
<gl-modal
v-model="showInstructionsModal"
cancel-variant="light"
@ -518,34 +536,38 @@ export default {
:title="s__('Runners|Setup instructions')"
>
<p>{{ $options.i18n.modal.subtitle }}</p>
<strong>{{ $options.i18n.modal.step2_1Header }}</strong>
<h3 class="gl-heading-4">{{ $options.i18n.modal.step2_1Header }}</h3>
<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 class="gl-display-flex gl-align-items-flex-start">
<pre class="gl-w-full gl-mb-5" data-testid="bash-instructions" :style="codeStyles">{{
bashInstructions
}}</pre>
</div>
<strong>{{ $options.i18n.modal.step2_2Header }}</strong>
<h3 class="gl-heading-4">{{ $options.i18n.modal.step2_2Header }}</h3>
<p>{{ $options.i18n.modal.step2_2Body }}</p>
<p>{{ $options.i18n.modal.step2_2Substep1 }}</p>
<div class="gl-display-flex gl-my-4">
<p>
<gl-sprintf :message="$options.i18n.modal.step2_2Substep1">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</p>
<div class="gl-display-flex gl-align-items-flex-start">
<pre
class="gl-w-full gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0"
class="gl-w-full gl-mb-5"
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">
<div class="gl-display-flex gl-align-items-flex-start">
<pre
class="gl-w-full gl-py-4 gl-display-flex gl-justify-content-space-between gl-m-0"
class="gl-w-full gl-mb-5"
data-testid="terraform-apply-instructions"
:style="codeStyles"
>{{ terraformApplyInstructions }}</pre
>
</div>

View File

@ -0,0 +1,12 @@
import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants';
import UserToken from '~/vue_shared/components/filtered_search_bar/tokens/user_token.vue';
import { PARAM_KEY_CREATOR, I18N_CREATOR } from '../../constants';
export const creatorTokenConfig = {
icon: 'user',
title: I18N_CREATOR,
type: PARAM_KEY_CREATOR,
token: UserToken,
dataType: 'user',
operators: OPERATORS_IS,
};

View File

@ -75,6 +75,7 @@ export const I18N_DELETE_RUNNER = s__('Runners|Delete runner');
export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
// List
export const I18N_CREATOR = s__('Runners|Creator');
export const I18N_LOCKED_RUNNER_DESCRIPTION = s__(
'Runners|Runner is locked and available for currently assigned projects only. Only administrators can change the assigned projects.',
);
@ -133,6 +134,7 @@ export const RUNNER_TAG_BG_CLASS = 'gl-bg-blue-100';
// - Used for URL params names
// - GlFilteredSearch tokens type
export const PARAM_KEY_CREATOR = 'creator';
export const PARAM_KEY_STATUS = 'status';
export const PARAM_KEY_PAUSED = 'paused';
export const PARAM_KEY_RUNNER_TYPE = 'runner_type';

View File

@ -11,6 +11,7 @@ query getAllRunners(
$tagList: [String!]
$search: String
$versionPrefix: String
$creator: String
$sort: CiRunnerSort
) {
runners(
@ -24,6 +25,7 @@ query getAllRunners(
tagList: $tagList
search: $search
versionPrefix: $versionPrefix
creatorUsername: $creator
sort: $sort
) {
...AllRunnersConnection

View File

@ -5,6 +5,7 @@ query getAllRunnersCount(
$tagList: [String!]
$search: String
$versionPrefix: String
$creator: String
) {
runners(
paused: $paused
@ -13,6 +14,7 @@ query getAllRunnersCount(
tagList: $tagList
search: $search
versionPrefix: $versionPrefix
creatorUsername: $creator
) {
count
}

View File

@ -14,6 +14,7 @@ import {
PARAM_KEY_TAG,
PARAM_KEY_VERSION,
PARAM_KEY_SEARCH,
PARAM_KEY_CREATOR,
PARAM_KEY_MEMBERSHIP,
PARAM_KEY_SORT,
PARAM_KEY_AFTER,
@ -157,6 +158,7 @@ export const fromUrlQueryToSearch = (query = window.location.search) => {
PARAM_KEY_STATUS,
PARAM_KEY_TAG,
PARAM_KEY_VERSION,
PARAM_KEY_CREATOR,
],
filteredSearchTermKey: PARAM_KEY_SEARCH,
}),
@ -185,6 +187,7 @@ export const fromSearchToUrl = (
[PARAM_KEY_TAG]: [],
[PARAM_KEY_PAUSED]: [],
[PARAM_KEY_VERSION]: [],
[PARAM_KEY_CREATOR]: [],
// Current filters
...filterToQueryObject(processFilters(filters), {
filteredSearchTermKey: PARAM_KEY_SEARCH,
@ -237,6 +240,7 @@ export const fromSearchToVariables = ({
filterVariables.search = queryObj[PARAM_KEY_SEARCH];
filterVariables.tagList = queryObj[PARAM_KEY_TAG];
[filterVariables.versionPrefix] = queryObj[PARAM_KEY_VERSION] || [];
[filterVariables.creator] = queryObj[PARAM_KEY_CREATOR] || [];
if (queryObj[PARAM_KEY_PAUSED]) {
filterVariables.paused = parseBoolean(queryObj[PARAM_KEY_PAUSED]);

View File

@ -56,7 +56,6 @@ export default {
},
data() {
return {
fetchSettingsError: false,
packageProtectionRules: [],
protectionRuleFormVisibility: false,
packageProtectionRulesQueryPayload: { nodes: [], pageInfo: {} },
@ -123,7 +122,7 @@ export default {
return data.project?.packagesProtectionRules ?? this.packageProtectionRulesQueryPayload;
},
error(e) {
this.fetchSettingsError = e;
this.alertErrorMessage = e.message;
},
},
},

View File

@ -7,9 +7,6 @@ module Explore
feature_category :pipeline_composition
before_action :check_resource_access, only: :show
track_internal_event :index, name: 'unique_users_visiting_ci_catalog', conditions: :current_user
before_action do
push_frontend_feature_flag(:ci_catalog_components_tab, current_user)
end
def show; end

View File

@ -13,12 +13,6 @@ module Resolvers
alias_method :organization, :object
def resolve_groups(**args)
return Group.none if Feature.disabled?(:resolve_organization_groups, current_user)
super
end
def finder_params(args)
extra_args = { organization: organization, include_ancestors: false, all_available: false }

View File

@ -11,7 +11,6 @@ module Issues
end
def execute
return error_feature_flag unless Feature.enabled?(:convert_to_ticket_quick_action, target.project, type: :beta)
return error_underprivileged unless current_user.can?(:"admin_#{target.to_ability_name}", target)
return error_already_ticket if ticket?
return error_invalid_email unless valid_email?
@ -68,11 +67,6 @@ module Issues
ServiceResponse.error(message: message)
end
def error_feature_flag
# Don't translate feature flag error because it's temporary.
error("Feature flag convert_to_ticket_quick_action is not enabled for this project.")
end
def error_underprivileged
error(_("You don't have permission to manage this issue."))
end

View File

@ -1,9 +0,0 @@
---
name: convert_to_ticket_quick_action
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433376
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139553
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17488
milestone: '16.9'
group: group::respond
type: beta
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: ci_catalog_components_tab
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132764
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/426443
milestone: '16.5'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: resolve_organization_groups
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128733
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/421673
milestone: '16.3'
type: development
group: group::tenant scale
default_enabled: true

View File

@ -8510,10 +8510,6 @@ Input type: `VulnerabilitiesDismissInput`
### `Mutation.vulnerabilitiesRemoveAllFromProject`
DETAILS:
**Introduced** in GitLab 16.7.
**Status**: Experiment.
Input type: `VulnerabilitiesRemoveAllFromProjectInput`
#### Arguments
@ -25063,9 +25059,20 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="pipelinesecurityreportfindingstate"></a>`state` | [`VulnerabilityState`](#vulnerabilitystate) | Finding status. |
| <a id="pipelinesecurityreportfindingstatecomment"></a>`stateComment` | [`String`](#string) | Comment for the state of the security report finding. |
| <a id="pipelinesecurityreportfindingtitle"></a>`title` | [`String`](#string) | Title of the vulnerability finding. |
| <a id="pipelinesecurityreportfindinguserpermissions"></a>`userPermissions` | [`PipelineSecurityReportFindingPermissions!`](#pipelinesecurityreportfindingpermissions) | Permissions for the current user on the resource. |
| <a id="pipelinesecurityreportfindinguuid"></a>`uuid` | [`String`](#string) | UUIDv5 digest based on the vulnerability's report type, primary identifier, location, fingerprint, project identifier. |
| <a id="pipelinesecurityreportfindingvulnerability"></a>`vulnerability` | [`Vulnerability`](#vulnerability) | Vulnerability related to the security report finding. |
### `PipelineSecurityReportFindingPermissions`
Check permissions for the current user on a vulnerability finding.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="pipelinesecurityreportfindingpermissionsadminvulnerability"></a>`adminVulnerability` | [`Boolean!`](#boolean) | If `true`, the user can perform `admin_vulnerability` on this resource. |
### `PipelineTrigger`
#### Fields

View File

@ -5,92 +5,269 @@ description: Runner integration for [CI Steps](index.md).
# Runner Integration
Steps are delivered to Step Runner as a YAML blob in the GitLab CI syntax.
Runner interacts with Step Runner over a gRPC service `StepRunner`
which is started on a local socket in the execution environment. This
is the same way that Nesting serves a gRPC service in a dedicated
Mac instance. The service has three RPCs, `run`, `follow` and `cancel`.
## Non goals
Run is the initial delivery of the steps. Follow requests a streaming
response to step traces. And Cancel stops execution and cleans up
resources as soon as possible.
This proposal does not address deployment of the Step Runner binary into
target environments, nor of starting the Step Runner gRPC service
described below. The rest of the proposal assumes both that the Step
Runner binary exists in the target environment and that the gRPC service
is running and listening on a local socket. Similarly this proposal does
not address the life-cycle of the `Step Runner` service, and how to handle
things like restarting the service if it dies, or upgrades.
Step Runner operating in gRPC mode will be able to executed multiple
step payloads at once. That is each call to `run` will start a new
goroutine and execute the steps until completion. Multiple calls to `run`
may be made simultaneously. This is also why components are cached by
`location`, `version` and `hash`. Because we cannot be changing which
ref we are on while multiple, concurrent executions are using the
underlying files.
See [Deployment and Lifecycle Management](service-deployment.md) for relevant blueprint.
## Steps Service gRPC Definition
The Step Runner service gRPC definition is as follows:
```proto
service StepRunner {
rpc Run(RunRequest) returns (RunResponse);
rpc Follow(FollowRequest) returns (stream FollowResponse);
rpc Cancel(CancelRequest) returns (CancelResponse);
rpc FollowSteps(FollowStepsRequest) returns (stream FollowStepsResponse);
rpc FollowLogs(FollowLogsRequest) returns (stream FollowLogsResponse);
rpc Finish(FinishRequest) returns (FinishResponse);
rpc Status(StatusRequest) returns (StatusResponse);
}
message Variable {
string key = 1;
string value = 2;
bool file = 3;
bool masked = 4;
}
message Job {
repeated Variable variables = 1;
string job_id = 2;
string pipeline_id = 3;
string build_dir = 4;
repeated string token_prefixes = 5;
}
message Masking {
repeated string phrases = 1;
repeated string token_prefixes = 2;
}
message RunRequest {
string id = 1;
oneof job_oneof {
string ci_job = 2;
Steps steps = 3;
}
string work_dir = 2;
map<string,string> env = 3;
Masking masking = 4;
Job job = 5;
string steps = 6;
}
message RunResponse {
}
message FollowRequest {
message FollowStepsRequest {
string id = 1;
}
message FollowResponse {
message FollowStepsResponse {
StepResult result = 1;
}
message CancelRequest {
message FollowLogsRequest {
string id = 1;
int32 offset = 2;
}
message FollowLogsResponse {
bytes data = 1;
}
message FinishRequest {
string id = 1;
}
message CancelResponse {
message FinishResponse {
}
message Status {
string id = 1;
bool finished = 2;
int32 exit_code = 3;
google.protobuf.Timestamp start_time = 4;
google.protobuf.Timestamp end_time = 5;
}
message StatusRequest {
string id = 1;
}
message StatusResponse {
repeated Status jobs = 1;
}
```
As steps are executed, traces are streamed back to GitLab Runner.
So execution can be followed at least at the step level. If a more
granular follow is required, we can introduce a gRPC step type which
can stream back logs as they are produced.
Runner interacts with Step Runner over the above gRPC service which is
started on a local socket in the execution environment. Runner accesses
the local socket by first connecting to the target environment via
executor-specific protocols, then use a provided `proxy` command to
connect to the `gRPC` service, and transparently tunnel `gRPC` requests
from the Runner to Step Runner (see[Proxy Command](#proxy-command)). This
is the same way that Nesting serves a gRPC service in a dedicated Mac
instance. The service has five RPCs, `Run`, `FollowSteps`, `FollowLogs`,
`Finish` and `Status`.
Here is how we will connect to Step Runner in each runner executor:
`Run` is the initial delivery of the steps. `FollowSteps` requests a
streaming response of step-result traces. `FollowLogs` similarly requests
a streaming response of output (`stdout`/`stderr`) written by processes
executed as part of running the steps, and logs produced by Step Runner
itself. `Finish` stops execution of the request (if still running) and
cleans up resources as soon as possible. `Status` lists the status of the
specified job, or if no job was specified, of all active jobs in the Step
Runner service (including completed but not `Finish`ed jobs). `Status` can
for example be used by a runner to recover after a crash.
## Instance
The Step Runner gRPC service will be able to execute multiple `Run`
payloads at once. That is, each call to `Run` will start a new goroutine
and execute the steps until completion. Multiple calls to `Run` may be
made simultaneously.
As steps are executed, step-result traces and sub-process logs can be
streamed back to GitLab Runner. This allows Runner (or any caller) to
follow execution, at the step level for step-result traces
(`FollowSteps`), and as written for sub-process and Step Runner logs
(`FollowLogs`). Logs will be written in a [specific format](#log-format),
and sensitive tokens will be [masked](#masking) by Step Runner before
being streamed to Runner.
All APIs excluding `Status` are idempotent, meaning that multiple calls to
the same API with the same parameters should return the same result. For
example, If `Run` is called multiple times with the same `id`, only the
first invocation should begin processing of the job request, and
subsequent invocations return a success status but otherwise do noting.
Similarly, multiple calls to `Finish` with the same `id` should finish and
remove the relevant job on the first call, and do nothing on subsequent
calls.
The service should not assume clients will be well-behaved, and should be
able to handle clients that never call or prematurely disconnect from
either of the `Follow` APIs, and also clients that never call `Finish` on
a corresponding `Run` request. To this end the `Step Runner` process
should periodically perform a scan to identify and prune stale or
runaway/stuck jobs. A stale job could be a job that has finished some
specified time ago (and has not been `Finish`ed). A runaway job is a job
that has been running some (long) specified amount of time, possibly
without producing output.
Finally, to facilitate integrating steps into the below runner executors,
it is recommended that steps provide a client library to coordinate
execution of the `Run`/`Follow*`/`Finish` APIs, and to handle reconnecting
to the step-runner service in the event that the `Follow*` calls loose
connectivity.
## RunRequest Parameters
Steps are delivered to Step Runner in the `RunRequest.Steps` field as a
JSON-serialized version of
[step.go](https://gitlab.com/gitlab-org/step-runner/-/blob/main/schema/v1/step.go),
with no processing of the step definition required by runner itself. The
`id` field uniquely identifies each request running on the `Step Runner`
service. The `RunRequest.Env` field holds environment variable that are to
be injected into the environment when each step is executed.
The optional `Job` parameter will include select parameters from the
corresponding CI job. `Job` will include the corresponding CI job's build
directory; `Job.BuildDir` should be copied to `RunRequest.WorkDir`, and
all steps in a request should be invoked in that directory to preserve
existing job script behavior. The `RunRequest` will also include the CI
job's environment variables (i.e. the `variables` defined at the job and
global levels in the CI configuration). When a `RunRequest` is made by
Runner, variables must be included in `Job.Variables`, and
`RunRequest.Env` should be left empty. When the run request is processed,
file-type variables will be written to file, variables will be expanded,
copied into `RunRequest.Env`, and the `Job` field will be discarded from
the remainder of the request. Variables should be expanded by the Step
Runner service since they may reference object in the execution
environment (like other environment variables or paths). This includes
file-type variables, which should be written to the same path as they
are be in traditional runner job execution. Similarly, from
`Job.Variables`, phrases to be masked should be extracted and used to
populate `Masking.Phrases`, and `Job.TokenPrefixes` should be copied into
`Masking.TokenPrefixes`.
Clients other than Runner wishing to run steps can omit the `Job` field,
and in this case the `Masking` and `Env` fields should be populated
directly by the caller.
## Log Format
Log lines emitted buy the `FollowLogs` API should have the format
```plaintext
<timestamp> <stream> <stdout/stderr> <append flag> <message>
```
This is the same log format introduce into runner in [this merge request](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4591).
The logging library used to produce this format should be shared between
`GitLab Runner` and `Step Runner`.
## Masking
`Step Runner` will be responsible for masking sensitive variables or
tokens. This should be done before the raw log message is formatted into
the above log format. The libraries used to mask variables should shared
between `GitLab Runner` and `Step Runner`. (See
[relevant](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/trace/internal/tokensanitizer/token_masker.go)
[modules](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/trace/internal/masker/masker.go)).
## Proxy Command
The `Step Runner` binary will include a command to proxy data from
(typically text-based) `stdin`/`stdout`/`stderr`-based protocols to the
gRPC service. This command will run in the same host as the gRPC service,
and will read input from `stdin`, forward it to the gRPC service over a
local socket, receive output from the gRPC service over same socket, and
forward it to the client via `stdout`/`stderr`. This command will enable
clients (like Runner) to transparently tunnel to the `gRPC` service via
`stdin`/`stderr`/`stdout`-based protocols like SSH or `docker exec`, which
will eliminate the need to expose the Step Runner service's gRPC port on
Docker images, or set up SSH port forwarding on VMs, and will allow runner
to interact with `Step Runner` using established protocols (i.e. SSH and
`docker exec`). `stdout` should be reserved for writing responses from the
`Step Runner` service, and `stderr` should be reserved for errors
originating in the `proxy` command itself.
## Executors
Here is how GitLab Runner will connect to Step Runner in each runner
executor:
### Instance
The Instance executor is accessed via SSH, the same as today. However
instead of starting a bash shell and piping in commands, it connects
to the Step Runner socket in a known location and makes gRPC
calls. This is the same as how Runner calls the Nesting server in
instead of starting a bash shell and piping in commands, it invokes the
[proxy command](#proxy-command), which in turn connects to the Step
Runner socket in a known location. Runner can then make `gRPC` calls
directly, and transparently tunnel through the `SSH` connection to the
`gRPC` service. This is the same as how Runner calls the Nesting server in
dedicated Mac instances to make VMs.
This requires that Step Runner is present and started in the job
execution environment.
## Docker
### Docker
The same requirement that Step Runner is present and started is true
for the Docker executor (and `docker-autoscaler`). However in order to
connect to the socket inside the container, we must `exec` a bridge
process in the container. This will be another command on the Step
Runner binary which proxies STDIN and STDOUT to the local socket in a
known location, allowing the caller of exec to make gRPC calls inside
the container.
The same requirement that Step Runner is present and the gRPC service is
running is true for the Docker executor (and `docker-autoscaler`). However
in order to connect to the gRPC service inside the container, Runner will
`docker exec` to the container and execute the proxy command to connect to
the gRPC service in the container. The client can then write to the
`docker exec`'s `stdin`, which will transparently be proxied to the gRPC
service, and read from its `stdout/stderr`, which will contain responses
from the gRPC service.
## Kubernetes
### Kubernetes
The Kubelet on Kubernetes Nodes exposes an exec API which will start a
process in a container of a running Pod. We will use this exec create
a bridge process that will allow the caller to make gRPC calls inside
the Pod. Same as the Docker executor.
process in a container of a running Pod. We will use this to `exec create`
a bridge process that will allow the caller to make `gRPC` calls inside
the Pod, similar to the Docker executor.
In order to access to this protected Kubelet API we must use the
Kubernetes API which provides an exec sub-resource on Pod. A caller

View File

@ -0,0 +1,18 @@
---
owning-stage: "~devops::verify"
description: Steps Runner Deployment and Lifecycle Management for [Runner Integration](runner-integration.md).
---
# Steps Runner Deployment and Lifecycle Management
This Blueprint is concerned with:
- The deployment or injection of the Step Runner binary into target
environments. This includes build containers for Docker, Kubernetes and
Instance executors.
- Startup of the Step Runner gRPC service in said environments.
- Any required install-time configuration.
- Service restart in the event of a crash.
- Step Runner binary upgrade for environments where the Step Runner service is long lived.
- Management of any resources used by the Step Runner service

View File

@ -190,7 +190,7 @@ DETAILS:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142189) in GitLab 16.9.
The Rake task for bulk user assignment is available in GitLab 16.9 and later. For GitLab 16.8, use the script [`bulk_user_assignment.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/services/gitlab_subscriptions/duo_pro/bulk_user_assignment.rb) instead.
The Rake task for bulk user assignment is available in GitLab 16.9 and later. For GitLab 16.8, use the script [`bulk_user_assignment.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/duo_pro/bulk_user_assignment.rb) instead.
To perform bulk user assignment for GitLab Duo Pro, you can use the following Rake task:

View File

@ -186,6 +186,9 @@ NOTE:
Projects that use the fast-forward merge strategy can't
[filter merge requests](../index.md#filter-the-list-of-merge-requests)
by deployment date, because no merge commit is created.
Features that rely on the deployment-MR relationship do not work when fast-forward merges are
enabled.
This bug is tracked in [issue 398611](https://gitlab.com/gitlab-org/gitlab/-/issues/398611).
When you visit the merge request page with `Fast-forward merge`
method selected, you can accept it **only if a fast-forward merge is possible**.

View File

@ -67,7 +67,7 @@ To auto-format this table, use the VS Code Markdown Table formatter: `https://do
| `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given. Copies as much data as possible as long as the target project contains equivalent objects like labels, milestones, or epics. Does not copy comments or system notes unless `--with_notes` is provided as an argument. |
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. |
| `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Mark issue or epic as confidential. Support for epics [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213741) in GitLab 15.6. |
| `/convert_to_ticket <email address>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | [Convert an issue into a Service Desk ticket](service_desk/using_service_desk.md#convert-a-regular-issue-to-a-service-desk-ticket). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433376) in GitLab 16.9 [with a flag](../../administration/feature_flags.md) named `convert_to_ticket_quick_action`. Disabled by default. |
| `/convert_to_ticket <email address>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | [Convert an issue into a Service Desk ticket](service_desk/using_service_desk.md#convert-a-regular-issue-to-a-service-desk-ticket). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433376) in GitLab 16.9 |
| `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project. |
| `/copy_metadata <#issue>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another issue in the project. |
| `/create_merge_request <branch name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Create a new merge request starting from the current issue. |

View File

@ -44,6 +44,19 @@ Any responses they send via email are displayed in the issue itself.
For information about headers used for treating email, see
[the incoming email documentation](../../../administration/incoming_email.md#accepted-headers).
### Create a Service Desk ticket in GitLab UI
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433376) in GitLab 16.9 [with a flag](../../../administration/feature_flags.md) named `convert_to_ticket_quick_action`. Disabled by default.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/433376) in GitLab 16.10. Feature flag `convert_to_ticket_quick_action` removed.
To create a Service Desk ticket from the UI:
1. [Create an issue](../issues/create_issues.md)
1. Add a comment that contains only the quick action `/convert_to_ticket user@example.com`.
You should see a comment from the [GitLab Support Bot](configure.md#support-bot-user).
1. Reload the page so the UI reflects the type change.
1. Optional. Add a comment on the ticket to send an initial Service Desk email to the external participant.
## As a responder to the issue
For responders to the issue, everything works just like other GitLab issues.
@ -148,12 +161,7 @@ In GitLab 15.9 and earlier, uploads to a comment are sent as links in the email.
## Convert a regular issue to a Service Desk ticket
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433376) in GitLab 16.9 [with a flag](../../../administration/feature_flags.md) named `convert_to_ticket_quick_action`. Disabled by default.
> - Enabled on GitLab.com in GitLab 16.10.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available per group,
an administrator can [enable the feature flag](../../../administration/feature_flags.md) named `convert_to_ticket_quick_action`.
On GitLab.com, this feature is available. On GitLab Dedicated, this feature is not available.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/433376) in GitLab 16.10. Feature flag `convert_to_ticket_quick_action` removed.
Use the quick action `/convert_to_ticket external-issue-author@example.com` to convert any regular issue
into a Service Desk ticket. This assigns the provided email address as the external author of the ticket
@ -163,19 +171,6 @@ comment on the ticket and can reply to these emails. Replies add a new comment o
GitLab doesn't send [the default `thank_you` email](configure.md#customize-emails-sent-to-the-requester).
You can add a public comment on the ticket to let the end user know that the ticket has been created.
<!--
When the feature flag convert_to_ticket_quick_action is removed, move the steps below
into a new topic called "### Create a Service Desk ticket in GitLab UI"
and nest it under "## As an end user (issue creator)".
-->
To create a Service Desk ticket from the UI:
1. [Create an issue](../issues/create_issues.md)
1. Add a comment that contains only the quick action `/convert_to_ticket user@example.com`.
You should see a comment from the [GitLab Support Bot](configure.md#support-bot-user).
1. Reload the page so the UI reflects the type change.
1. Optional. Add a comment on the ticket to send an initial Service Desk email to the external participant.
## Privacy considerations
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/108901) the minimum required role to view the creator's and participant's email in GitLab 15.9.

View File

@ -76,6 +76,10 @@ Naming/FileName:
Exclude:
- spec/**/*.rb
# Include behavior not honored. See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145865/diffs#note_1791901400
Rails/MigrationTimestamp:
Enabled: false
# This cop doesn't make sense in the context of gems
RSpec/AvoidConditionalStatements:
Enabled: false

View File

@ -130,7 +130,7 @@ module Gitlab
email = read_email_from_cache(username)
if email.blank? && !email_fetched_for_project?(username)
feature_flag_in_lock(lease_key, ttl: 3.minutes, sleep_sec: 1.second, retries: 30) do |retried|
feature_flag_in_lock(lease_key(username), sleep_sec: 0.2.seconds, retries: 30) do |retried|
# when retried, check the cache again as the other process that had the lease may have fetched the email
if retried
email = read_email_from_cache(username)
@ -227,8 +227,8 @@ module Gitlab
private
def lease_key
"gitlab:github_import:user_finder:#{project.id}"
def lease_key(username)
"gitlab:github_import:user_finder:#{username}"
end
# Retrieves the email associated with the given username from the cache.
@ -307,10 +307,10 @@ module Gitlab
)
end
def feature_flag_in_lock(lease_key, ttl: 3.minutes, sleep_sec: 1.second, retries: 30)
def feature_flag_in_lock(lease_key, sleep_sec:, retries:)
return yield(false) if Feature.disabled?(:github_import_lock_user_finder, project.creator)
in_lock(lease_key, ttl: ttl, sleep_sec: sleep_sec, retries: retries) do |retried|
in_lock(lease_key, sleep_sec: sleep_sec, retries: retries) do |retried|
yield(retried)
end
end

View File

@ -21,9 +21,7 @@ module Gitlab
def track_event(event_name, category: nil, send_snowplow_event: true, additional_properties: {}, **kwargs)
raise UnknownEventError, "Unknown event: #{event_name}" unless EventDefinitions.known_event?(event_name)
mapped_additional_properties = map_additional_properties(event_name, additional_properties)
validate_properties!(mapped_additional_properties, kwargs)
validate_properties!(additional_properties, kwargs)
project = kwargs[:project]
kwargs[:namespace] ||= project.namespace if project
@ -32,7 +30,7 @@ module Gitlab
increase_weekly_total_counter(event_name)
update_unique_counters(event_name, kwargs)
trigger_snowplow_event(event_name, category, mapped_additional_properties, kwargs) if send_snowplow_event
trigger_snowplow_event(event_name, category, additional_properties, kwargs) if send_snowplow_event
if Feature.enabled?(:internal_events_for_product_analytics)
send_application_instrumentation_event(event_name, additional_properties, kwargs)
@ -180,42 +178,6 @@ module Gitlab
GitlabSDK::Client.new(app_id: app_id, host: host, buffer_size: SNOWPLOW_EMITTER_BUFFER_SIZE)
end
strong_memoize_attr :gitlab_sdk_client
def map_additional_properties(event_name, additional_properties)
return {} if additional_properties.empty?
properties_mapping = additional_properties_mapping(event_name)
additional_properties.transform_keys do |key|
properties_mapping.fetch(key, key)
end
end
def additional_properties_mapping(event_name)
definition = event_definitions.find do |definition|
definition.attributes[:action] == event_name
end
return unless definition
mapping = definition.attributes.fetch(:additional_properties, {}).filter_map do |name, config|
external_key = config[:external_key]
next unless external_key
[external_key.to_sym, name]
end.to_h
base_property_mapping.merge(mapping).invert
end
def base_property_mapping
keys = ALLOWED_ADDITIONAL_PROPERTIES.keys
keys.zip(keys).to_h
end
def event_definitions
@_event_definitions ||= Gitlab::Tracking::EventDefinition.definitions
end
end
end
end

View File

@ -266,7 +266,6 @@ module Gitlab
types Issue
condition do
quick_action_target.persisted? &&
Feature.enabled?(:convert_to_ticket_quick_action, parent, type: :beta) &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target) &&
quick_action_target.respond_to?(:from_service_desk?) &&
!quick_action_target.from_service_desk?

View File

@ -7,5 +7,6 @@
# NEW_KEY_2: LEGACY_KEY_2
# ...
#
'{event_counters}_licenses_list_viewed': USAGE_LICENSES_LIST_VIEWS
'{event_counters}_usage_data_download_payload_clicked': USAGE_SERVICE_USAGE_DATA_DOWNLOAD_PAYLOAD_CLICK
'{event_counters}_web_ide_viewed': WEB_IDE_VIEWS_COUNT

View File

@ -42698,7 +42698,7 @@ 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"
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."
@ -42839,6 +42839,9 @@ msgstr ""
msgid "Runners|Created by %{user} %{timeAgo}"
msgstr ""
msgid "Runners|Creator"
msgstr ""
msgid "Runners|Delete"
msgstr ""
@ -42925,7 +42928,7 @@ msgstr ""
msgid "Runners|Fleet dashboard"
msgstr ""
msgid "Runners|For most CI/CD jobs, use a %{linkStart}N2D standard machine type.%{linkEnd}"
msgid "Runners|For most CI/CD jobs, use a %{linkStart}N2D standard machine type%{linkEnd}."
msgstr ""
msgid "Runners|Get started with runners"
@ -43214,7 +43217,7 @@ msgstr ""
msgid "Runners|Revision"
msgstr ""
msgid "Runners|Run the following on your command line. You might be prompted to sign in to Google"
msgid "Runners|Run the following on your command line. You might be prompted to sign in to Google."
msgstr ""
msgid "Runners|Runner"
@ -43373,7 +43376,7 @@ msgstr ""
msgid "Runners|Step 1"
msgstr ""
msgid "Runners|Step 1: Configure Google Cloud project"
msgid "Runners|Step 1: Configure your Google Cloud project"
msgstr ""
msgid "Runners|Step 1: Specify environment"
@ -43450,7 +43453,7 @@ 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"
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."
@ -43509,7 +43512,7 @@ msgstr ""
msgid "Runners|To view the runner, go to %{runnerListName}."
msgstr ""
msgid "Runners|To view the setup instructions, complete the previous form"
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."
@ -43548,7 +43551,7 @@ 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"
msgid "Runners|Use a text editor to create a %{codeStart}main.tf%{codeEnd} file with the following Terraform configuration."
msgstr ""
msgid "Runners|Use the dashboard to view performance statistics of your runner fleet."
@ -45458,6 +45461,15 @@ msgstr ""
msgid "SecurityOrchestration|You don't have any security policies yet"
msgstr ""
msgid "SecurityOrchestration|You must select one or more compliance frameworks to which this policy should apply."
msgstr ""
msgid "SecurityOrchestration|You must select one or more projects to be excluded from this policy."
msgstr ""
msgid "SecurityOrchestration|You must select one or more projects to which this policy should apply."
msgstr ""
msgid "SecurityOrchestration|a license scanner found license violations"
msgstr ""

View File

@ -2,7 +2,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa', '~> 14', '>= 14.2.1', require: 'gitlab/qa'
gem 'gitlab-qa', '~> 14', '>= 14.3.0', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.17.0', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8.1' # This should stay in sync with the root's Gemfile

View File

@ -119,7 +119,7 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
gitlab-qa (14.2.1)
gitlab-qa (14.3.0)
activesupport (>= 6.1, < 7.2)
gitlab (~> 4.19)
http (~> 5.0)
@ -360,7 +360,7 @@ DEPENDENCIES
faraday-retry (~> 2.2)
fog-core (= 2.1.0)
fog-google (~> 1.19)
gitlab-qa (~> 14, >= 14.2.1)
gitlab-qa (~> 14, >= 14.3.0)
gitlab-utils!
gitlab_quality-test_tooling (~> 1.17.0)
influxdb-client (~> 3.1)

View File

@ -27,32 +27,41 @@ module RuboCop
include RangeHelp
MSG = 'The date of this file (`%<basename>s`) must not be in the future.'
BAD_FORMAT_MSG = 'The filename format of (`%<basename>s`) must be of format: YYYYMMDDHHMMSS_some_name.rb.'
def on_new_investigation
file_path = processed_source.file_path
basename = File.basename(file_path)
return unless date_named_file?(basename)
for_bad_filename(basename) { |range, msg| add_offense(range, message: msg) }
end
private
DATE_LENGTH = 14
STARTS_WITH_DATE_REGEX = /^\d{#{DATE_LENGTH}}_/
def date_named_file?(basename)
basename.match?(STARTS_WITH_DATE_REGEX)
end
FILE_NAME_REGEX = /^\d{#{DATE_LENGTH}}_[a-z0-9]+(?:[0-9a-z_]*)[a-z0-9]+\.rb$/
def for_bad_filename(basename)
message = if incorrect_filename_format?(basename)
BAD_FORMAT_MSG
elsif future_date?(basename)
MSG
end
return unless message
yield source_range(processed_source.buffer, 1, 0), format(message, basename: basename)
end
def incorrect_filename_format?(basename)
!basename.match?(FILE_NAME_REGEX)
end
def future_date?(basename)
# match ActiveRecord https://api.rubyonrails.org/classes/ActiveRecord/Migration.html
now = Time.now.utc.strftime('%Y%m%d%H%M%S') # length is 14
return if basename.first(DATE_LENGTH) <= now
yield source_range(processed_source.buffer, 1, 0), format(MSG, basename: basename)
basename.first(DATE_LENGTH) > now
end
end
end

View File

@ -48,8 +48,9 @@ tests = [
},
{
explanation: 'EE usage counters map to usage data spec',
changed_file: 'ee/lib/gitlab/usage_data_counters/licenses_list.rb',
expected: ['ee/spec/lib/gitlab/usage_data_counters/licenses_list_spec.rb', 'spec/lib/gitlab/usage_data_spec.rb']
changed_file: 'ee/lib/gitlab/usage_data_counters/value_streams_dashboard_counter.rb',
expected: ['ee/spec/lib/gitlab/usage_data_counters/value_streams_dashboard_counter_spec.rb',
'spec/lib/gitlab/usage_data_spec.rb']
},
{

View File

@ -221,6 +221,41 @@ RSpec.describe "Admin Runners", feature_category: :fleet_visibility do
end
end
describe 'filter by creator' do
before_all do
create(:ci_runner, :instance, description: 'runner-creator-admin', creator: admin)
create(:ci_runner, :instance, description: 'runner-creator-user', creator: user)
end
before do
visit admin_runners_path
end
it 'shows all runners' do
expect(page).to have_link('All 2')
expect(page).to have_content 'runner-creator-admin'
expect(page).to have_content 'runner-creator-user'
end
it 'shows filtered runner based on creator' do
input_filtered_search_filter_is_only(s_('Runners|Creator'), admin.username)
expect(page).to have_link('All 1')
expect(page).to have_content 'runner-creator-admin'
expect(page).not_to have_content 'runner-creator-user'
end
it 'shows filtered search suggestions' do
open_filtered_search_suggestions(s_('Runners|Creator'))
page.within(search_bar_selector) do
expect(page).to have_content admin.username
expect(page).to have_content user.username
end
end
end
describe 'filter by status' do
let_it_be(:never_contacted) do
create(:ci_runner, :instance, description: 'runner-never-contacted', contacted_at: nil)

View File

@ -12,20 +12,13 @@ describe('CiResourceDetails', () => {
const defaultProps = {
resourcePath: 'twitter/project-1',
};
const defaultProvide = {
glFeatures: { ciCatalogComponentsTab: true },
};
const createComponent = ({ provide = {}, mountFn = shallowMount, props = {} } = {}) => {
const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
wrapper = mountFn(CiResourceDetails, {
propsData: {
...defaultProps,
...props,
},
provide: {
...defaultProvide,
...provide,
},
stubs: {
GlTabs,
},
@ -36,62 +29,42 @@ describe('CiResourceDetails', () => {
const findCiResourceComponents = () => wrapper.findComponent(CiResourceComponents);
const findExperimentBadge = () => wrapper.findComponent(ExperimentBadge);
describe('UI', () => {
beforeEach(() => {
createComponent();
});
it('passes the right props to the readme component', () => {
expect(findCiResourceReadme().props().resourceId).toBe(defaultProps.resourceId);
});
});
describe('tabs', () => {
describe('when feature flag `ci_catalog_components_tab` is enabled', () => {
beforeEach(() => {
createComponent();
});
beforeEach(() => {
createComponent();
});
it('renders the readme and components tab', () => {
expect(findAllTabs()).toHaveLength(2);
expect(findCiResourceComponents().exists()).toBe(true);
expect(findCiResourceReadme().exists()).toBe(true);
});
it('renders the readme and components tabs', () => {
expect(findAllTabs()).toHaveLength(2);
expect(findCiResourceComponents().exists()).toBe(true);
expect(findCiResourceReadme().exists()).toBe(true);
});
it('renders an Experiment Badge', async () => {
createComponent({ mountFn: mount });
await waitForPromises();
expect(findExperimentBadge().exists()).toBe(true);
it('passes lazy attribute to all tabs', () => {
findAllTabs().wrappers.forEach((tab) => {
expect(tab.attributes().lazy).not.toBeUndefined();
});
});
describe('when feature flag `ci_catalog_components_tab` is disabled', () => {
beforeEach(() => {
createComponent({
provide: { glFeatures: { ciCatalogComponentsTab: false } },
});
});
it('renders an Experiment Badge for the components tab', async () => {
createComponent({ mountFn: mount });
await waitForPromises();
it('renders only readme tab as default', () => {
expect(findCiResourceReadme().exists()).toBe(true);
expect(findCiResourceComponents().exists()).toBe(false);
expect(findAllTabs()).toHaveLength(1);
});
it('does not render an Experiment Badge', () => {
expect(findExperimentBadge().exists()).toBe(false);
});
expect(findExperimentBadge().exists()).toBe(true);
});
describe('UI', () => {
beforeEach(() => {
createComponent();
});
it('passes lazy attribute to all tabs', () => {
findAllTabs().wrappers.forEach((tab) => {
expect(tab.attributes().lazy).not.toBeUndefined();
});
});
it('passes the right props to the readme component', () => {
expect(findCiResourceReadme().props().resourceId).toBe(defaultProps.resourceId);
});
it('passes the right props to the components tab', () => {
expect(findCiResourceComponents().props().resourceId).toBe(defaultProps.resourceId);
});
it('passes the right props to the components tab', () => {
expect(findCiResourceComponents().props().resourceId).toBe(defaultProps.resourceId);
});
});
});

View File

@ -44,6 +44,7 @@ import {
PARAM_KEY_STATUS,
PARAM_KEY_TAG,
PARAM_KEY_VERSION,
PARAM_KEY_CREATOR,
STATUS_ONLINE,
DEFAULT_MEMBERSHIP,
RUNNER_PAGE_SIZE,
@ -51,6 +52,8 @@ import {
import allRunnersQuery from 'ee_else_ce/ci/runner/graphql/list/all_runners.query.graphql';
import allRunnersCountQuery from 'ee_else_ce/ci/runner/graphql/list/all_runners_count.query.graphql';
import runnerJobCountQuery from '~/ci/runner/graphql/list/runner_job_count.query.graphql';
import usersSearchAllQuery from '~/graphql_shared/queries/users_search_all.query.graphql';
import { captureException } from '~/ci/runner/sentry_utils';
import {
@ -61,6 +64,7 @@ import {
mockRegistrationToken,
newRunnerPath,
emptyPageInfo,
usersData,
} from '../mock_data';
const mockRunners = allRunnersData.data.runners.nodes;
@ -69,6 +73,7 @@ const mockRunnersCount = runnersCountData.data.runners.count;
const mockRunnersHandler = jest.fn();
const mockRunnersCountHandler = jest.fn();
const mockRunnerJobCountHandler = jest.fn();
const mockUsersSearchAllHandler = jest.fn();
jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
@ -110,6 +115,7 @@ describe('AdminRunnersApp', () => {
[allRunnersQuery, mockRunnersHandler],
[allRunnersCountQuery, mockRunnersCountHandler],
[runnerJobCountQuery, mockRunnerJobCountHandler],
[usersSearchAllQuery, mockUsersSearchAllHandler],
];
wrapper = mountFn(AdminRunnersApp, {
@ -138,12 +144,14 @@ describe('AdminRunnersApp', () => {
mockRunnersHandler.mockResolvedValue(allRunnersData);
mockRunnersCountHandler.mockResolvedValue(runnersCountData);
mockRunnerJobCountHandler.mockResolvedValue(runnerJobCountData);
mockUsersSearchAllHandler.mockResolvedValue(usersData);
});
afterEach(() => {
mockRunnersHandler.mockReset();
mockRunnersCountHandler.mockReset();
mockRunnerJobCountHandler.mockReset();
mockUsersSearchAllHandler.mockReset();
showToast.mockReset();
});
@ -245,28 +253,78 @@ describe('AdminRunnersApp', () => {
});
});
it('sets tokens in the filtered search', () => {
createComponent();
describe('filtered search configuration', () => {
it('sets tokens in the filtered search', () => {
createComponent();
expect(findRunnerFilteredSearchBar().props('tokens')).toEqual([
expect.objectContaining({
type: PARAM_KEY_PAUSED,
options: expect.any(Array),
}),
expect.objectContaining({
type: PARAM_KEY_STATUS,
options: expect.any(Array),
}),
expect.objectContaining({
type: PARAM_KEY_VERSION,
title: 'Version starts with',
}),
expect.objectContaining({
type: PARAM_KEY_TAG,
recentSuggestionsStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
}),
upgradeStatusTokenConfig,
]);
expect(findRunnerFilteredSearchBar().props('tokens')).toEqual([
expect.objectContaining({
type: PARAM_KEY_PAUSED,
options: expect.any(Array),
}),
expect.objectContaining({
type: PARAM_KEY_STATUS,
options: expect.any(Array),
}),
expect.objectContaining({
type: PARAM_KEY_VERSION,
title: 'Version starts with',
}),
expect.objectContaining({
type: PARAM_KEY_CREATOR,
title: 'Creator',
}),
expect.objectContaining({
type: PARAM_KEY_TAG,
recentSuggestionsStorageKey: `${ADMIN_FILTERED_SEARCH_NAMESPACE}-recent-tags`,
}),
upgradeStatusTokenConfig,
]);
});
describe('creator suggestions', () => {
const [loggedInUser, otherUser] = usersData.data.users.nodes;
const getCreatorToken = () =>
findRunnerFilteredSearchBar()
.props('tokens')
.filter((t) => t?.type === PARAM_KEY_CREATOR)[0];
beforeEach(() => {
// simulate logged in user
window.gon = {
current_user_id: loggedInUser.id,
current_user_fullname: loggedInUser.name,
current_username: loggedInUser.username,
current_user_avatar_url: loggedInUser.avatarUrl,
};
createComponent();
});
it('preloads logged in user', () => {
expect(getCreatorToken()).toMatchObject({
defaultUsers: [],
preloadedUsers: [
{
id: gon.current_user_id,
name: gon.current_user_fullname,
username: gon.current_username,
avatar_url: gon.current_user_avatar_url,
},
],
});
});
it('requests and shows creator suggestions', async () => {
const suggestions = await getCreatorToken().fetchUsers('search');
expect(mockUsersSearchAllHandler).toHaveBeenCalledTimes(1);
expect(mockUsersSearchAllHandler).toHaveBeenCalledWith({ first: null, search: 'search' });
expect(suggestions).toEqual([loggedInUser, otherUser]);
});
});
});
describe('Single runner row', () => {

View File

@ -52,6 +52,32 @@ const runnerJobCountData = {
},
};
const usersData = {
data: {
users: {
nodes: [
{
id: 'gid://gitlab/User/1',
avatarUrl: '/avatar.jpg',
webUrl: '/root',
name: 'Admin Istrator',
username: 'root',
__typename: 'UserCore',
},
{
id: 'gid://gitlab/User/2',
avatarUrl: '/root',
webUrl: '/user2',
name: 'Billy West',
username: 'user2',
__typename: 'UserCore',
},
],
__typename: 'UserCoreConnection',
},
},
};
// Other mock data
// Mock searches and their corresponding urls
@ -336,6 +362,23 @@ export const mockSearchExamples = [
first: RUNNER_PAGE_SIZE,
},
},
{
name: 'creator username',
urlQuery: '?creator[]=root',
search: {
runnerType: null,
membership: DEFAULT_MEMBERSHIP,
filters: [{ type: 'creator', value: { data: 'root', operator: '=' } }],
pagination: {},
sort: CREATED_DESC,
},
graphqlVariables: {
creator: 'root',
membership: DEFAULT_MEMBERSHIP,
sort: CREATED_DESC,
first: RUNNER_PAGE_SIZE,
},
},
];
export const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN';
@ -385,4 +428,5 @@ export {
runnerFormData,
runnerCreateResult,
runnerForRegistration,
usersData,
};

View File

@ -67,6 +67,7 @@ describe('search_params.js', () => {
'http://test.host/?runner_type[]=INSTANCE_TYPE',
'http://test.host/?paused[]=true',
'http://test.host/?search=my_text',
'http://test.host/?creator[]=root',
])('When a filter is removed, it is removed from the URL', (initialUrl) => {
const search = { filters: [], sort: DEFAULT_SORT };
const expectedUrl = `http://test.host/`;

View File

@ -166,6 +166,20 @@ describe('Packages protection rules project settings', () => {
);
});
it('show alert when GraphQL request fails', async () => {
const protectionRuleQueryResolverRejectedErrorMessage = 'Error protectionRuleQueryResolver';
const packagesProtectionRuleQueryResolver = jest
.fn()
.mockRejectedValue(new Error(protectionRuleQueryResolverRejectedErrorMessage));
createComponent({ packagesProtectionRuleQueryResolver });
await waitForPromises();
expect(findAlert().isVisible()).toBe(true);
expect(findAlert().text()).toBe(protectionRuleQueryResolverRejectedErrorMessage);
});
describe('when button "Previous" is clicked', () => {
const packagesProtectionRuleQueryResolver = jest
.fn()

View File

@ -211,7 +211,7 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
let(:username) { 'kittens' }
let(:user) { {} }
let(:etag) { 'etag' }
let(:lease_name) { "gitlab:github_import:user_finder:#{project.id}" }
let(:lease_name) { "gitlab:github_import:user_finder:#{username}" }
let(:cache_key) { described_class::EMAIL_FOR_USERNAME_CACHE_KEY % username }
let(:etag_cache_key) { described_class::USERNAME_ETAG_CACHE_KEY % username }
let(:email_fetched_for_project_key) do
@ -307,7 +307,7 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
it 'makes an API call' do
expect(client).to receive(:user).with(username, { headers: {} }).and_return({ email: email }).once
expect(finder).to receive(:in_lock).with(
lease_name, ttl: 3.minutes, sleep_sec: 1.second, retries: 30
lease_name, sleep_sec: 0.2.seconds, retries: 30
).and_call_original
email_for_github_username
@ -370,7 +370,7 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
it 'makes a non-rate-limited API call' do
expect(client).to receive(:user).with(username, { headers: { 'If-None-Match' => etag } }).once
expect(finder).to receive(:in_lock).with(
lease_name, ttl: 3.minutes, sleep_sec: 1.second, retries: 30
lease_name, sleep_sec: 0.2.seconds, retries: 30
).and_call_original
email_for_github_username
@ -442,7 +442,7 @@ RSpec.describe Gitlab::GithubImport::UserFinder, :clean_gitlab_redis_cache, feat
it 'makes a non-rate-limited API call' do
expect(client).to receive(:user).with(username, { headers: { 'If-None-Match' => etag } }).once
expect(finder).to receive(:in_lock).with(
lease_name, ttl: 3.minutes, sleep_sec: 1.second, retries: 30
lease_name, sleep_sec: 0.2.seconds, retries: 30
).and_call_original
email_for_github_username

View File

@ -30,39 +30,6 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
end
end
shared_context 'with mapped additional properties' do
let(:additional_properties) do
{
author: 'author_name',
age: 28
}
end
let(:aditional_event_config) { {} }
before do
config = {
action: event_name,
additional_properties: {
author: {
external_key: 'label'
},
age: {
external_key: 'value'
}
}.merge(aditional_event_config)
}
wrong_config = config.deep_merge(properties: { author: { external_key: 'property' } })
event_definition1 = Gitlab::Tracking::EventDefinition.new('path', config)
event_definition2 = Gitlab::Tracking::EventDefinition.new('path', wrong_config.merge(action: "action2"))
event_definition3 = Gitlab::Tracking::EventDefinition.new('path', wrong_config.merge(action: "action3"))
event_definitions = [event_definition3, event_definition1, event_definition2]
allow(Gitlab::Tracking::EventDefinition).to receive(:definitions).and_return(event_definitions)
described_class.instance_variable_set(:@_event_definitions, nil)
end
end
def expect_redis_hll_tracking(value_override = nil, property_name_override = nil)
expected_value = value_override || unique_value
expected_property_name = property_name_override || property_name
@ -205,42 +172,6 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
expect_snowplow_tracking(nil, additional_properties)
end
context "when additional properties have mapped names" do
include_context 'with mapped additional properties'
it 'is sent to Snowplow' do
described_class.track_event(
event_name,
additional_properties: additional_properties,
user: user,
project: project
)
expect_snowplow_tracking(nil, { label: 'author_name', value: 28 })
end
context "when there's an additional attribute config without external_key" do
let(:aditional_event_config) do
{
another_attribute: {
description: 'lorem ipsum'
}
}
end
it 'is sent to Snowplow' do
described_class.track_event(
event_name,
additional_properties: additional_properties,
user: user,
project: project
)
expect_snowplow_tracking(nil, { label: 'author_name', value: 28 })
end
end
end
end
context 'when arguments are invalid' do
@ -544,17 +475,6 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
track_event
end
context "when additional properties have mapped names" do
include_context 'with mapped additional properties'
it 'passes unmapped additional_properties to Product Analytics Ruby SDK', :aggregate_failures do
expect(sdk_client).to receive(:identify).with(user.id)
expect(sdk_client).to receive(:track).with(event_name, tracked_attributes)
track_event
end
end
end
context 'when GITLAB_ANALYTICS_ID is nil' do

View File

@ -125,19 +125,6 @@ RSpec.describe 'getting organization information', feature_category: :cell do
create(:group) { |g| g.add_developer(user) } # outside organization
end
context 'when resolve_organization_groups feature flag is disabled' do
before do
stub_feature_flags(resolve_organization_groups: false)
end
it 'returns no groups' do
request_organization
expect(graphql_data_at(:organization)).not_to be_nil
expect(graphql_data_at(:organization, :groups, :nodes)).to be_empty
end
end
it 'does not return ancestors of authorized groups' do
request_organization

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
require 'rubocop_spec_helper'
require 'rspec-parameterized'
require_relative '../../../../rubocop/cop/rails/migration_timestamp'
RSpec.describe RuboCop::Cop::Rails::MigrationTimestamp, feature_category: :shared do
@ -22,19 +24,48 @@ RSpec.describe RuboCop::Cop::Rails::MigrationTimestamp, feature_category: :share
end
end
context 'with timestamp in file name in the past' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY, '/db/migrate/19700101000000_some_migration.rb')
print 1
RUBY
context 'when file name format is bad' do
where(:filename) do
%w[
some_migration.rb
123456789_some_migration.rb
19700101000000_some_2fa_migration.rb.rb
19700101000000_some_2fa_migration..rb
19700101000000.rb
19700101000000_.rb
19700101000000_a.rb
19700101000000_a_.rb
19700101000000_1.rb
19700101000000_1_.rb
]
end
with_them do
it 'registers an offense' do
expect_offense(<<~RUBY, "/db/migrate/#{filename}")
print 1
^ The filename format of (`#{filename}`) must [...]
RUBY
end
end
end
context 'without timestamp in the file name' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY, '/db/migrate/some_migration.rb')
print 1
RUBY
context 'when file name is good' do
where(:filename) do
%w[
19700101000000_some_2fa_migration.rb
19700101000000_some_migration.rb
19700101000000_a_b.rb
19700101000000_1_2.rb
]
end
with_them do
it 'registers an offense' do
expect_no_offenses(<<~RUBY, "/db/migrate/#{filename}")
print 1
RUBY
end
end
end
end

View File

@ -46,7 +46,6 @@ RSpec.describe Issues::ConvertToTicketService, feature_category: :service_desk d
let(:email) { nil }
let(:service) { described_class.new(target: issue, current_user: user, email: email) }
let(:error_feature_flag) { "Feature flag convert_to_ticket_quick_action is not enabled for this project." }
let(:error_underprivileged) { _("You don't have permission to manage this issue.") }
let(:error_already_ticket) { s_("ServiceDesk|Cannot convert to ticket because it is already a ticket.") }
let(:error_invalid_email) do
@ -98,15 +97,5 @@ RSpec.describe Issues::ConvertToTicketService, feature_category: :service_desk d
end
end
end
context 'when feature flag convert_to_ticket_quick_action is disabled' do
let(:error_message) { error_feature_flag }
before do
stub_feature_flags(convert_to_ticket_quick_action: false)
end
it_behaves_like 'a failed service execution'
end
end
end

View File

@ -2665,16 +2665,6 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
expect(service.available_commands(issuable)).not_to include(a_hash_including(name: :convert_to_ticket))
end
end
context 'with feature flag convert_to_ticket_quick_action disabled' do
before do
stub_feature_flags(convert_to_ticket_quick_action: false)
end
it 'is not part of the available commands' do
expect(service.available_commands(issuable)).not_to include(a_hash_including(name: :convert_to_ticket))
end
end
end
context 'severity command' do

View File

@ -1411,7 +1411,6 @@
- './ee/spec/lib/gitlab/tracking/standard_context_spec.rb'
- './ee/spec/lib/gitlab/tree_summary_spec.rb'
- './ee/spec/lib/gitlab/usage_data_counters/epic_activity_unique_counter_spec.rb'
- './ee/spec/lib/gitlab/usage_data_counters/licenses_list_spec.rb'
- './ee/spec/lib/gitlab/usage_data_metrics_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/advanced_search/build_type_metric_spec.rb'
- './ee/spec/lib/gitlab/usage/metrics/instrumentations/advanced_search/distribution_metric_spec.rb'