Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
661b6b005b
commit
23a4cb9200
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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**.
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
},
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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/`;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue