Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0c1344a7c1
commit
0a353a9fa3
|
|
@ -1 +1 @@
|
|||
659bff3b53d7b9a6894ac9404edbc45d02b49497
|
||||
190b1b5820371124001739845957980599487c80
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { createAlert } from '~/flash';
|
||||
import { getParameterByName, updateHistory, mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import { convertToGraphQLId } from '~/graphql_shared/utils';
|
||||
|
|
@ -49,22 +48,6 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
description() {
|
||||
return this.runner?.description;
|
||||
},
|
||||
heading() {
|
||||
if (this.description) {
|
||||
return sprintf(s__('Runners|Register "%{runnerDescription}" runner'), {
|
||||
runnerDescription: this.description,
|
||||
});
|
||||
}
|
||||
return s__('Runners|Register runner');
|
||||
},
|
||||
ephemeralAuthenticationToken() {
|
||||
return this.runner?.ephemeralAuthenticationToken;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
platform(platform) {
|
||||
updateHistory({
|
||||
|
|
@ -84,12 +67,10 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="gl-font-size-h1">{{ heading }}</h1>
|
||||
|
||||
<registration-instructions
|
||||
:loading="$apollo.queries.runner.loading"
|
||||
:runner="runner"
|
||||
:platform="platform"
|
||||
:token="ephemeralAuthenticationToken"
|
||||
:loading="$apollo.queries.runner.loading"
|
||||
@toggleDrawer="onToggleDrawer"
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlIcon, GlLink, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
|
||||
import { EXECUTORS_HELP_URL, SERVICE_COMMANDS_HELP_URL } from '../../constants';
|
||||
import CliCommand from './cli_command.vue';
|
||||
|
|
@ -16,6 +17,11 @@ export default {
|
|||
CliCommand,
|
||||
},
|
||||
props: {
|
||||
runner: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
platform: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -25,26 +31,43 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
token: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
description() {
|
||||
return this.runner?.description;
|
||||
},
|
||||
heading() {
|
||||
if (this.description) {
|
||||
return sprintf(
|
||||
s__('Runners|Register "%{runnerDescription}" runner'),
|
||||
{
|
||||
runnerDescription: this.description,
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
return s__('Runners|Register runner');
|
||||
},
|
||||
token() {
|
||||
return this.runner?.ephemeralAuthenticationToken;
|
||||
},
|
||||
commandPrompt() {
|
||||
return commandPrompt({ platform: this.platform });
|
||||
},
|
||||
registerCommand() {
|
||||
return registerCommand({ platform: this.platform, registrationToken: this.token });
|
||||
return registerCommand({
|
||||
platform: this.platform,
|
||||
registrationToken: this.token,
|
||||
description: this.description,
|
||||
});
|
||||
},
|
||||
runCommand() {
|
||||
return runCommand({ platform: this.platform });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleDrawer(val) {
|
||||
this.$emit('toggleDrawer', val);
|
||||
toggleDrawer() {
|
||||
this.$emit('toggleDrawer');
|
||||
},
|
||||
},
|
||||
EXECUTORS_HELP_URL,
|
||||
|
|
@ -53,6 +76,8 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="gl-font-size-h1">{{ heading }}</h1>
|
||||
|
||||
<p>
|
||||
<gl-sprintf
|
||||
:message="
|
||||
|
|
@ -62,7 +87,7 @@ export default {
|
|||
"
|
||||
>
|
||||
<template #link="{ content }">
|
||||
<gl-link @click="toggleDrawer()">{{ content }}</gl-link>
|
||||
<gl-link @click="toggleDrawer">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -12,19 +12,36 @@ import windowsInstall from './scripts/windows/install.ps1?raw';
|
|||
|
||||
const OS = {
|
||||
[LINUX_PLATFORM]: {
|
||||
shell: 'bash',
|
||||
commandPrompt: '$',
|
||||
executable: 'gitlab-runner',
|
||||
},
|
||||
[MACOS_PLATFORM]: {
|
||||
shell: 'bash',
|
||||
commandPrompt: '$',
|
||||
executable: 'gitlab-runner',
|
||||
},
|
||||
[WINDOWS_PLATFORM]: {
|
||||
shell: 'powershell',
|
||||
commandPrompt: '>',
|
||||
executable: '.\\gitlab-runner.exe',
|
||||
},
|
||||
};
|
||||
|
||||
const escapedParam = (param, shell = 'bash') => {
|
||||
let escaped;
|
||||
if (shell === 'bash') {
|
||||
// replace single-quotes by the sequence '\''
|
||||
escaped = param.replaceAll("'", "'\\''");
|
||||
} else if (shell === 'powershell') {
|
||||
// replace single-quotes by the sequence ''
|
||||
// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.3
|
||||
escaped = param.replaceAll("'", "''");
|
||||
}
|
||||
// surround with single quotes.
|
||||
return `'${escaped}'`;
|
||||
};
|
||||
|
||||
export const commandPrompt = ({ platform }) => {
|
||||
return (OS[platform] || OS[DEFAULT_PLATFORM]).commandPrompt;
|
||||
};
|
||||
|
|
@ -33,12 +50,28 @@ export const executable = ({ platform }) => {
|
|||
return (OS[platform] || OS[DEFAULT_PLATFORM]).executable;
|
||||
};
|
||||
|
||||
export const registerCommand = ({ platform, url = gon.gitlab_url, registrationToken }) => {
|
||||
return [
|
||||
`${executable({ platform })} register`,
|
||||
` --url ${url}`,
|
||||
...(registrationToken ? [` --registration-token ${registrationToken}`] : []),
|
||||
];
|
||||
const shell = ({ platform }) => {
|
||||
return (OS[platform] || OS[DEFAULT_PLATFORM]).shell;
|
||||
};
|
||||
|
||||
export const registerCommand = ({
|
||||
platform,
|
||||
url = gon.gitlab_url,
|
||||
registrationToken,
|
||||
description,
|
||||
}) => {
|
||||
const lines = [`${executable({ platform })} register`];
|
||||
if (url) {
|
||||
lines.push(` --url ${url}`);
|
||||
}
|
||||
if (registrationToken) {
|
||||
lines.push(` --registration-token ${registrationToken}`);
|
||||
}
|
||||
if (description) {
|
||||
const escapedDescription = escapedParam(description, shell({ platform }));
|
||||
lines.push(` --description ${escapedDescription}`);
|
||||
}
|
||||
return lines;
|
||||
};
|
||||
|
||||
export const runCommand = ({ platform }) => {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ export default (input, parameters, escapeParameters = true) => {
|
|||
mappedParameters.forEach((key, parameterName) => {
|
||||
const parameterValue = mappedParameters.get(parameterName);
|
||||
const escapedParameterValue = escapeParameters ? escape(parameterValue) : parameterValue;
|
||||
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
|
||||
// Pass the param value as a function to ignore special replacement patterns like $` and $'.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#syntax
|
||||
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), () => escapedParameterValue);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const PERSISTENT_USER_CALLOUTS = [
|
|||
'.js-ultimate-feature-removal-banner',
|
||||
'.js-geo-enable-hashed-storage-callout',
|
||||
'.js-geo-migrate-hashed-storage-callout',
|
||||
'.js-unlimited-members-during-trial-alert',
|
||||
];
|
||||
|
||||
const initCallouts = () => {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default {
|
|||
:is="component"
|
||||
:aria-label="ariaLabel"
|
||||
:href="href"
|
||||
class="counter gl-display-block gl-flex-grow-1 gl-text-center gl-py-3 gl-bg-gray-10 gl-rounded-base gl-text-gray-900 gl-border gl-border-gray-a-08 gl-font-sm gl-hover-text-gray-900 gl-hover-text-decoration-none"
|
||||
class="counter gl-display-block gl-flex-grow-1 gl-text-center gl-py-3 gl-bg-gray-10 gl-rounded-base gl-text-gray-900 gl-border-none gl-inset-border-1-gray-a-08 gl-line-height-1 gl-font-sm gl-hover-text-gray-900 gl-hover-text-decoration-none"
|
||||
>
|
||||
<gl-icon aria-hidden="true" :name="icon" />
|
||||
<span v-if="count" aria-hidden="true" class="gl-ml-1">{{ count }}</span>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import { updateDraft, clearDraft, getDraft } from '~/lib/utils/autosave';
|
||||
import { EDITING_MODE_MARKDOWN_FIELD, EDITING_MODE_CONTENT_EDITOR } from '../../constants';
|
||||
import MarkdownField from './field.vue';
|
||||
|
||||
|
|
@ -52,10 +53,15 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
autosaveKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
markdown: this.value || '',
|
||||
markdown: this.value || (this.autosaveKey ? getDraft(this.autosaveKey) : '') || '',
|
||||
editingMode: EDITING_MODE_MARKDOWN_FIELD,
|
||||
autofocused: false,
|
||||
};
|
||||
|
|
@ -72,19 +78,27 @@ export default {
|
|||
watch: {
|
||||
value(val) {
|
||||
this.markdown = val;
|
||||
|
||||
this.saveDraft();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.autofocusTextarea();
|
||||
|
||||
this.saveDraft();
|
||||
},
|
||||
methods: {
|
||||
updateMarkdownFromContentEditor({ markdown }) {
|
||||
this.markdown = markdown;
|
||||
this.$emit('input', markdown);
|
||||
|
||||
this.saveDraft();
|
||||
},
|
||||
updateMarkdownFromMarkdownField({ target }) {
|
||||
this.markdown = target.value;
|
||||
this.$emit('input', target.value);
|
||||
|
||||
this.saveDraft();
|
||||
},
|
||||
renderMarkdown(markdown) {
|
||||
return axios.post(this.renderMarkdownPath, { text: markdown }).then(({ data }) => data.body);
|
||||
|
|
@ -110,6 +124,11 @@ export default {
|
|||
setEditorAsAutofocused() {
|
||||
this.autofocused = true;
|
||||
},
|
||||
saveDraft() {
|
||||
if (!this.autosaveKey) return;
|
||||
if (this.markdown) updateDraft(this.autosaveKey, this.markdown);
|
||||
else clearDraft(this.autosaveKey);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
.gl-new-dropdown-custom-toggle[aria-expanded='true'] .counter {
|
||||
background-color: $gray-50;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
mix-blend-mode: multiply;
|
||||
|
||||
.gl-dark & {
|
||||
|
|
|
|||
|
|
@ -135,10 +135,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Limits the width of the user bio for readability.
|
||||
.profile-user-bio {
|
||||
// Limits the width of the user bio for readability.
|
||||
max-width: 600px;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.user-calendar {
|
||||
|
|
|
|||
|
|
@ -252,19 +252,6 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.gl-gap-2 {
|
||||
gap: $gl-spacing-scale-2;
|
||||
}
|
||||
|
||||
.gl-bg-t-gray-a-08 {
|
||||
background-color: $t-gray-a-08;
|
||||
}
|
||||
|
||||
.gl-hover-bg-t-gray-a-08:hover {
|
||||
background-color: $t-gray-a-08;
|
||||
}
|
||||
|
||||
/* End gitlab-ui#1709 */
|
||||
|
||||
/*
|
||||
|
|
@ -284,3 +271,27 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709
|
|||
.gl-isolate {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/*
|
||||
* The below style will be moved to @gitlab/ui by
|
||||
* https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2177
|
||||
*/
|
||||
.gl-gap-2 {
|
||||
gap: $gl-spacing-scale-2;
|
||||
}
|
||||
|
||||
.gl-bg-t-gray-a-08 {
|
||||
background-color: $t-gray-a-08;
|
||||
}
|
||||
|
||||
.gl-hover-bg-t-gray-a-08:hover {
|
||||
background-color: $t-gray-a-08;
|
||||
}
|
||||
|
||||
.gl-inset-border-1-gray-a-08 {
|
||||
box-shadow: inset 0 0 0 $gl-border-size-1 $t-gray-a-08;
|
||||
}
|
||||
|
||||
.gl-line-height-1 {
|
||||
line-height: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Ci
|
||||
class RunnerMachineType < BaseObject
|
||||
graphql_name 'CiRunnerMachine'
|
||||
|
||||
connection_type_class(::Types::CountableConnectionType)
|
||||
|
||||
authorize :read_runner_machine
|
||||
|
||||
alias_method :runner_machine, :object
|
||||
|
||||
field :architecture_name, GraphQL::Types::String, null: true,
|
||||
description: 'Architecture provided by the runner machine.',
|
||||
method: :architecture
|
||||
field :contacted_at, Types::TimeType, null: true,
|
||||
description: 'Timestamp of last contact from the runner machine.',
|
||||
method: :contacted_at
|
||||
field :created_at, Types::TimeType, null: true,
|
||||
description: 'Timestamp of creation of the runner machine.'
|
||||
field :executor_name, GraphQL::Types::String, null: true,
|
||||
description: 'Executor last advertised by the runner.',
|
||||
method: :executor_name
|
||||
field :id, ::Types::GlobalIDType[::Ci::RunnerMachine], null: false,
|
||||
description: 'ID of the runner machine.'
|
||||
field :ip_address, GraphQL::Types::String, null: true,
|
||||
description: 'IP address of the runner machine.'
|
||||
field :platform_name, GraphQL::Types::String, null: true,
|
||||
description: 'Platform provided by the runner machine.',
|
||||
method: :platform
|
||||
field :revision, GraphQL::Types::String, null: true, description: 'Revision of the runner.'
|
||||
field :runner, RunnerType, null: true, description: 'Runner configuration for the runner machine.'
|
||||
field :status,
|
||||
Types::Ci::RunnerStatusEnum,
|
||||
null: false,
|
||||
description: 'Status of the runner machine.'
|
||||
field :version, GraphQL::Types::String, null: true, description: 'Version of the runner.'
|
||||
|
||||
def executor_name
|
||||
::Ci::Runner::EXECUTOR_TYPE_TO_NAMES[runner_machine.executor_type&.to_sym]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Types::Ci::RunnerType.prepend_mod_with('Types::Ci::RunnerType')
|
||||
|
|
@ -65,6 +65,9 @@ module Types
|
|||
resolver: ::Resolvers::Ci::RunnerJobsResolver
|
||||
field :locked, GraphQL::Types::Boolean, null: true,
|
||||
description: 'Indicates the runner is locked.'
|
||||
field :machines, ::Types::Ci::RunnerMachineType.connection_type, null: true,
|
||||
description: 'Machines associated with the runner configuration.',
|
||||
method: :runner_machines
|
||||
field :maintenance_note, GraphQL::Types::String, null: true,
|
||||
description: 'Runner\'s maintenance notes.'
|
||||
field :maximum_timeout, GraphQL::Types::Int, null: true,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ module Ci
|
|||
include Gitlab::OptimisticLocking
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include AtomicInternalId
|
||||
include EnumWithNil
|
||||
include Ci::HasRef
|
||||
include ShaAttribute
|
||||
include FromUnion
|
||||
|
|
@ -143,9 +142,9 @@ module Ci
|
|||
|
||||
# We use `Enums::Ci::Pipeline.sources` here so that EE can more easily extend
|
||||
# this `Hash` with new values.
|
||||
enum_with_nil source: Enums::Ci::Pipeline.sources
|
||||
enum source: Enums::Ci::Pipeline.sources
|
||||
|
||||
enum_with_nil config_source: Enums::Ci::Pipeline.config_sources
|
||||
enum config_source: Enums::Ci::Pipeline.config_sources
|
||||
|
||||
# We use `Enums::Ci::Pipeline.failure_reasons` here so that EE can more easily
|
||||
# extend this `Hash` with new values.
|
||||
|
|
|
|||
|
|
@ -84,8 +84,13 @@ module Ci
|
|||
scope :active, -> (value = true) { where(active: value) }
|
||||
scope :paused, -> { active(false) }
|
||||
scope :online, -> { where('contacted_at > ?', online_contact_time_deadline) }
|
||||
scope :recent, -> { where('ci_runners.created_at >= :date OR ci_runners.contacted_at >= :date', date: stale_deadline) }
|
||||
scope :stale, -> { where('ci_runners.created_at < :date AND (ci_runners.contacted_at IS NULL OR ci_runners.contacted_at < :date)', date: stale_deadline) }
|
||||
scope :recent, -> do
|
||||
where('ci_runners.created_at >= :datetime OR ci_runners.contacted_at >= :datetime', datetime: stale_deadline)
|
||||
end
|
||||
scope :stale, -> do
|
||||
where('ci_runners.created_at <= :datetime AND ' \
|
||||
'(ci_runners.contacted_at IS NULL OR ci_runners.contacted_at <= :datetime)', datetime: stale_deadline)
|
||||
end
|
||||
scope :offline, -> { where(arel_table[:contacted_at].lteq(online_contact_time_deadline)) }
|
||||
scope :never_contacted, -> { where(contacted_at: nil) }
|
||||
scope :ordered, -> { order(id: :desc) }
|
||||
|
|
@ -336,7 +341,7 @@ module Ci
|
|||
def stale?
|
||||
return false unless created_at
|
||||
|
||||
[created_at, contacted_at].compact.max < self.class.stale_deadline
|
||||
[created_at, contacted_at].compact.max <= self.class.stale_deadline
|
||||
end
|
||||
|
||||
def status(legacy_mode = nil)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,14 @@ module Ci
|
|||
remove_duplicates: false).where(created_some_time_ago)
|
||||
end
|
||||
|
||||
def self.online_contact_time_deadline
|
||||
Ci::Runner.online_contact_time_deadline
|
||||
end
|
||||
|
||||
def self.stale_deadline
|
||||
STALE_TIMEOUT.ago
|
||||
end
|
||||
|
||||
def heartbeat(values, update_contacted_at: true)
|
||||
##
|
||||
# We can safely ignore writes performed by a runner heartbeat. We do
|
||||
|
|
@ -64,8 +72,25 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def status
|
||||
return :stale if stale?
|
||||
return :never_contacted unless contacted_at
|
||||
|
||||
online? ? :online : :offline
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def online?
|
||||
contacted_at && contacted_at > self.class.online_contact_time_deadline
|
||||
end
|
||||
|
||||
def stale?
|
||||
return false unless created_at
|
||||
|
||||
[created_at, contacted_at].compact.max <= self.class.stale_deadline
|
||||
end
|
||||
|
||||
def persist_cached_data?
|
||||
# Use a random threshold to prevent beating DB updates.
|
||||
contacted_at_max_age = Random.rand(UPDATE_CONTACT_COLUMN_EVERY)
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
module Ci
|
||||
class RunnerVersion < Ci::ApplicationRecord
|
||||
include EachBatch
|
||||
include EnumWithNil
|
||||
|
||||
enum_with_nil status: {
|
||||
enum status: {
|
||||
not_processed: nil,
|
||||
invalid_version: -1,
|
||||
unavailable: 1,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ module Clusters
|
|||
module Platforms
|
||||
class Kubernetes < ApplicationRecord
|
||||
include Gitlab::Kubernetes
|
||||
include EnumWithNil
|
||||
include AfterCommitQueue
|
||||
include ReactiveCaching
|
||||
include NullifyIfBlank
|
||||
|
|
@ -63,7 +62,7 @@ module Clusters
|
|||
|
||||
alias_attribute :ca_pem, :ca_cert
|
||||
|
||||
enum_with_nil authorization_type: {
|
||||
enum authorization_type: {
|
||||
unknown_authorization: nil,
|
||||
rbac: 1,
|
||||
abac: 2
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ class CommitStatus < Ci::ApplicationRecord
|
|||
include Importable
|
||||
include AfterCommitQueue
|
||||
include Presentable
|
||||
include EnumWithNil
|
||||
include BulkInsertableAssociations
|
||||
include TaggableQueries
|
||||
|
||||
|
|
@ -26,7 +25,7 @@ class CommitStatus < Ci::ApplicationRecord
|
|||
enum scheduling_type: { stage: 0, dag: 1 }, _prefix: true
|
||||
# We use `Enums::Ci::CommitStatus.failure_reasons` here so that EE can more easily
|
||||
# extend this `Hash` with new values.
|
||||
enum_with_nil failure_reason: Enums::Ci::CommitStatus.failure_reasons
|
||||
enum failure_reason: Enums::Ci::CommitStatus.failure_reasons
|
||||
|
||||
delegate :commit, to: :pipeline
|
||||
delegate :sha, :short_sha, :before_sha, to: :pipeline
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module EnumWithNil
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def self.enum_with_nil(definitions)
|
||||
# use original `enum` to auto-define all methods
|
||||
enum(definitions)
|
||||
|
||||
# override auto-defined methods only for the
|
||||
# key which uses nil value
|
||||
definitions.each do |name, values|
|
||||
# E.g. for enum_with_nil failure_reason: { unknown_failure: nil }
|
||||
# this overrides auto-generated method `failure_reason`
|
||||
define_method(name) do
|
||||
orig = super()
|
||||
|
||||
return orig unless orig.nil?
|
||||
|
||||
self.class.public_send(name.to_s.pluralize).key(nil) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1722,11 +1722,7 @@ class User < ApplicationRecord
|
|||
end
|
||||
|
||||
def manageable_groups(include_groups_with_developer_maintainer_access: false)
|
||||
owned_and_maintainer_group_hierarchy = if Feature.enabled?(:linear_user_manageable_groups, self)
|
||||
owned_or_maintainers_groups.self_and_descendants
|
||||
else
|
||||
Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants
|
||||
end
|
||||
owned_and_maintainer_group_hierarchy = owned_or_maintainers_groups.self_and_descendants
|
||||
|
||||
if include_groups_with_developer_maintainer_access
|
||||
union_sql = ::Gitlab::SQL::Union.new(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ module Users
|
|||
usage_quota_trial_alert: 14, # EE-only
|
||||
preview_usage_quota_free_plan_alert: 15, # EE-only
|
||||
enforcement_at_limit_alert: 16, # EE-only
|
||||
web_hook_disabled: 17 # EE-only
|
||||
web_hook_disabled: 17, # EE-only
|
||||
unlimited_members_during_trial_alert: 18 # EE-only
|
||||
}
|
||||
|
||||
validates :group, presence: true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class RunnerMachinePolicy < BasePolicy
|
||||
with_options scope: :subject, score: 0
|
||||
|
||||
condition(:can_read_runner, scope: :subject) do
|
||||
can?(:read_runner, @subject.runner)
|
||||
end
|
||||
|
||||
rule { anonymous }.prevent_all
|
||||
|
||||
rule { can_read_runner }.policy do
|
||||
enable :read_builds
|
||||
enable :read_runner_machine
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
- add_page_specific_style 'page_bundles/members'
|
||||
- page_title _('Group members')
|
||||
|
||||
= content_for :page_level_alert do
|
||||
= render_if_exists 'shared/unlimited_members_during_trial_alert', group: @group.root_ancestor
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-12
|
||||
.gl-display-flex.gl-flex-wrap
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
|
||||
= content_for :page_level_alert do
|
||||
- if can_invite_members_for_project?(@project)
|
||||
= render_if_exists 'shared/unlimited_members_during_trial_alert', group: @project.root_ancestor
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-12
|
||||
.gl-display-flex.gl-flex-wrap
|
||||
|
|
|
|||
|
|
@ -115,10 +115,9 @@
|
|||
- if display_public_email?(@user)
|
||||
= render 'middle_dot_divider', stacking: true do
|
||||
= link_to @user.public_email, "mailto:#{@user.public_email}", itemprop: 'email'
|
||||
- if @user.bio.present? && @user.confirmed? && !@user.blocked?
|
||||
.gl-text-gray-900.gl-mx-5
|
||||
.profile-user-bio.gl-text-left
|
||||
= @user.bio
|
||||
- if @user.bio.present? && @user.confirmed? && !@user.blocked?
|
||||
%p.profile-user-bio.gl-mb-3
|
||||
= @user.bio
|
||||
|
||||
- if !profile_tabs.empty? && !Feature.enabled?(:profile_tabs_vue, current_user)
|
||||
.scrolling-tabs-container
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: linear_user_manageable_groups
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68845
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339434
|
||||
milestone: '14.3'
|
||||
type: development
|
||||
group: group::authentication and authorization
|
||||
default_enabled: false
|
||||
|
|
@ -244,6 +244,10 @@ p_ci_builds_metadata:
|
|||
- table: projects
|
||||
column: project_id
|
||||
on_delete: async_delete
|
||||
p_ci_runner_machine_builds:
|
||||
- table: ci_runner_machines
|
||||
column: runner_machine_id
|
||||
on_delete: async_delete
|
||||
packages_build_infos:
|
||||
- table: ci_pipelines
|
||||
column: pipeline_id
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DropRunnerMachinesConstraintOnCiBuildsMetadata < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
SOURCE_TABLE_NAME = 'p_ci_builds_metadata'
|
||||
TARGET_TABLE_NAME = 'ci_runner_machines'
|
||||
CONSTRAINT_NAME = 'fk_rails_fae01b2700'
|
||||
|
||||
def up
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
remove_foreign_key_if_exists(SOURCE_TABLE_NAME, TARGET_TABLE_NAME, name: CONSTRAINT_NAME)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries(raise_on_exhaustion: true) do
|
||||
next if check_constraint_exists?(SOURCE_TABLE_NAME, CONSTRAINT_NAME)
|
||||
|
||||
execute(<<~SQL)
|
||||
ALTER TABLE #{SOURCE_TABLE_NAME}
|
||||
ADD CONSTRAINT #{CONSTRAINT_NAME} FOREIGN KEY (runner_machine_id)
|
||||
REFERENCES #{TARGET_TABLE_NAME}(id) ON DELETE SET NULL
|
||||
SQL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class EnsureCiRunnerMachinesIsEmpty < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
return unless Gitlab::Database.gitlab_schemas_for_connection(connection).include?(:gitlab_ci)
|
||||
|
||||
# Ensure that the ci_runner_machines table is empty to ensure that new builds
|
||||
# don't try to create new join records until we add the missing FK.
|
||||
execute('TRUNCATE TABLE ci_runner_machines, p_ci_runner_machine_builds')
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexOnRunnerMachineIdOnRunnerMachineBuilds < Gitlab::Database::Migration[2.1]
|
||||
include Gitlab::Database::PartitioningMigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'index_p_ci_runner_machine_builds_on_runner_machine_id'
|
||||
|
||||
def up
|
||||
add_concurrent_partitioned_index :p_ci_runner_machine_builds, :runner_machine_id, unique: false, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_partitioned_index_by_name :p_ci_runner_machine_builds, INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TrackCiRunnerMachineRecordChanges < Gitlab::Database::Migration[2.1]
|
||||
include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
|
||||
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
track_record_deletions(:ci_runner_machines)
|
||||
end
|
||||
|
||||
def down
|
||||
untrack_record_deletions(:ci_runner_machines)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
ecb6f601d4f47e7c4974e097c0e87ff37f96fad93b2ab02439bfa44a7eb481cd
|
||||
|
|
@ -0,0 +1 @@
|
|||
671fe2bcc6b45d7f312144d6c1ceb7c5e085dbc1ab1069c5a340849a08437d72
|
||||
|
|
@ -0,0 +1 @@
|
|||
ca0e0d645cfd5f672d286e8977fc94d4c92579801cb4a781c495465cbc581a33
|
||||
|
|
@ -0,0 +1 @@
|
|||
2b918f516a004d3b3f1b310ad9421a29a9675a7670f6a653ba73209f8e7f0f41
|
||||
|
|
@ -31164,6 +31164,8 @@ CREATE UNIQUE INDEX index_ops_strategies_user_lists_on_strategy_id_and_user_list
|
|||
|
||||
CREATE UNIQUE INDEX index_organizations_on_unique_name_per_group ON customer_relations_organizations USING btree (group_id, lower(name), id);
|
||||
|
||||
CREATE INDEX index_p_ci_runner_machine_builds_on_runner_machine_id ON ONLY p_ci_runner_machine_builds USING btree (runner_machine_id);
|
||||
|
||||
CREATE INDEX index_packages_build_infos_on_pipeline_id ON packages_build_infos USING btree (pipeline_id);
|
||||
|
||||
CREATE INDEX index_packages_build_infos_package_id_pipeline_id_id ON packages_build_infos USING btree (package_id, pipeline_id, id);
|
||||
|
|
@ -33920,6 +33922,8 @@ CREATE TRIGGER ci_builds_loose_fk_trigger AFTER DELETE ON ci_builds REFERENCING
|
|||
|
||||
CREATE TRIGGER ci_pipelines_loose_fk_trigger AFTER DELETE ON ci_pipelines REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
||||
CREATE TRIGGER ci_runner_machines_loose_fk_trigger AFTER DELETE ON ci_runner_machines REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
||||
CREATE TRIGGER ci_runners_loose_fk_trigger AFTER DELETE ON ci_runners REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
||||
CREATE TRIGGER clusters_loose_fk_trigger AFTER DELETE ON clusters REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
|
||||
|
|
@ -36662,9 +36666,6 @@ ALTER TABLE ONLY merge_requests_closing_issues
|
|||
ALTER TABLE ONLY banned_users
|
||||
ADD CONSTRAINT fk_rails_fa5bb598e5 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE p_ci_builds_metadata
|
||||
ADD CONSTRAINT fk_rails_fae01b2700 FOREIGN KEY (runner_machine_id) REFERENCES ci_runner_machines(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY operations_feature_flags_issues
|
||||
ADD CONSTRAINT fk_rails_fb4d2a7cb1 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -7305,6 +7305,30 @@ The edge type for [`CiRunner`](#cirunner).
|
|||
| <a id="cirunneredgenode"></a>`node` | [`CiRunner`](#cirunner) | The item at the end of the edge. |
|
||||
| <a id="cirunneredgeweburl"></a>`webUrl` | [`String`](#string) | Web URL of the runner. The value depends on where you put this field in the query. You can use it for projects or groups. |
|
||||
|
||||
#### `CiRunnerMachineConnection`
|
||||
|
||||
The connection type for [`CiRunnerMachine`](#cirunnermachine).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnermachineconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
|
||||
| <a id="cirunnermachineconnectionedges"></a>`edges` | [`[CiRunnerMachineEdge]`](#cirunnermachineedge) | A list of edges. |
|
||||
| <a id="cirunnermachineconnectionnodes"></a>`nodes` | [`[CiRunnerMachine]`](#cirunnermachine) | A list of nodes. |
|
||||
| <a id="cirunnermachineconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `CiRunnerMachineEdge`
|
||||
|
||||
The edge type for [`CiRunnerMachine`](#cirunnermachine).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnermachineedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="cirunnermachineedgenode"></a>`node` | [`CiRunnerMachine`](#cirunnermachine) | The item at the end of the edge. |
|
||||
|
||||
#### `CiSecureFileRegistryConnection`
|
||||
|
||||
The connection type for [`CiSecureFileRegistry`](#cisecurefileregistry).
|
||||
|
|
@ -11809,6 +11833,7 @@ CI/CD variables for a project.
|
|||
| <a id="cirunnerjobcount"></a>`jobCount` | [`Int`](#int) | Number of jobs processed by the runner (limited to 1000, plus one to indicate that more items exist). |
|
||||
| <a id="cirunnerjobexecutionstatus"></a>`jobExecutionStatus` **{warning-solid}** | [`CiRunnerJobExecutionStatus`](#cirunnerjobexecutionstatus) | **Introduced** in 15.7. This feature is in Alpha. It can be changed or removed at any time. Job execution status of the runner. |
|
||||
| <a id="cirunnerlocked"></a>`locked` | [`Boolean`](#boolean) | Indicates the runner is locked. |
|
||||
| <a id="cirunnermachines"></a>`machines` | [`CiRunnerMachineConnection`](#cirunnermachineconnection) | Machines associated with the runner configuration. (see [Connections](#connections)) |
|
||||
| <a id="cirunnermaintenancenote"></a>`maintenanceNote` | [`String`](#string) | Runner's maintenance notes. |
|
||||
| <a id="cirunnermaintenancenotehtml"></a>`maintenanceNoteHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of `maintenance_note`. |
|
||||
| <a id="cirunnermaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
|
||||
|
|
@ -11879,6 +11904,24 @@ Returns [`CiRunnerStatus!`](#cirunnerstatus).
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnerstatuslegacymode"></a>`legacyMode` **{warning-solid}** | [`String`](#string) | **Deprecated** in 15.0. Will be removed in 17.0. In GitLab 16.0 and later, the field will act as if `legacyMode` is null. |
|
||||
|
||||
### `CiRunnerMachine`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="cirunnermachinearchitecturename"></a>`architectureName` | [`String`](#string) | Architecture provided by the runner machine. |
|
||||
| <a id="cirunnermachinecontactedat"></a>`contactedAt` | [`Time`](#time) | Timestamp of last contact from the runner machine. |
|
||||
| <a id="cirunnermachinecreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of creation of the runner machine. |
|
||||
| <a id="cirunnermachineexecutorname"></a>`executorName` | [`String`](#string) | Executor last advertised by the runner. |
|
||||
| <a id="cirunnermachineid"></a>`id` | [`CiRunnerMachineID!`](#cirunnermachineid) | ID of the runner machine. |
|
||||
| <a id="cirunnermachineipaddress"></a>`ipAddress` | [`String`](#string) | IP address of the runner machine. |
|
||||
| <a id="cirunnermachineplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner machine. |
|
||||
| <a id="cirunnermachinerevision"></a>`revision` | [`String`](#string) | Revision of the runner. |
|
||||
| <a id="cirunnermachinerunner"></a>`runner` | [`CiRunner`](#cirunner) | Runner configuration for the runner machine. |
|
||||
| <a id="cirunnermachinestatus"></a>`status` | [`CiRunnerStatus!`](#cirunnerstatus) | Status of the runner machine. |
|
||||
| <a id="cirunnermachineversion"></a>`version` | [`String`](#string) | Version of the runner. |
|
||||
|
||||
### `CiSecureFileRegistry`
|
||||
|
||||
Represents the Geo replication and verification state of a ci_secure_file.
|
||||
|
|
@ -24574,6 +24617,12 @@ A `CiRunnerID` is a global ID. It is encoded as a string.
|
|||
|
||||
An example `CiRunnerID` is: `"gid://gitlab/Ci::Runner/1"`.
|
||||
|
||||
### `CiRunnerMachineID`
|
||||
|
||||
A `CiRunnerMachineID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `CiRunnerMachineID` is: `"gid://gitlab/Ci::RunnerMachine/1"`.
|
||||
|
||||
### `ClustersAgentID`
|
||||
|
||||
A `ClustersAgentID` is a global ID. It is encoded as a string.
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
|
||||
| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||
| `default_syntax_highlighting_theme` | integer | no | Default syntax highlighting theme for users who are not signed in. See [IDs of available themes](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/themes.rb#L16).
|
||||
| `default_syntax_highlighting_theme` | integer | no | Default syntax highlighting theme for new users and users who are not signed in. See [IDs of available themes](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/themes.rb#L16).
|
||||
| `delayed_project_deletion` **(PREMIUM SELF)** | boolean | no | Enable delayed project deletion by default in new groups. Default is `false`. [From GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/352960), can only be enabled when `delayed_group_deletion` is true. |
|
||||
| `delayed_group_deletion` **(PREMIUM SELF)** | boolean | no | Enable delayed group deletion. Default is `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352959) in GitLab 15.0. [From GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/352960), disables and locks the group-level setting for delayed protect deletion when set to `false`. |
|
||||
| `deletion_adjourned_period` **(PREMIUM SELF)** | integer | no | The number of days to wait before deleting a project or group that is marked for deletion. Value must be between `1` and `90`. Defaults to `7`. [From GitLab 15.1](https://gitlab.com/gitlab-org/gitlab/-/issues/352960), a hook on `deletion_adjourned_period` sets the period to `1` on every update, and sets both `delayed_project_deletion` and `delayed_group_deletion` to `false` if the period is `0`. |
|
||||
|
|
|
|||
|
|
@ -395,9 +395,11 @@ scope.
|
|||
| GitLab Runner | `%15.10` | [Modify register command to allow new flow with glrt- prefixed authentication tokens](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29613). |
|
||||
| GitLab Runner | `%15.10` | Make the `gitlab-runner register` command happen in a single operation. |
|
||||
| GitLab Rails app | `%15.10` | Define feature flag and policies for "New Runner creation workflow" for groups and projects. |
|
||||
| GitLab Rails app | `%15.11` | Only update runner `contacted_at` and `status` when polled for jobs. |
|
||||
| GitLab Rails app | `%15.10` | Only update runner `contacted_at` and `status` when polled for jobs. |
|
||||
| GitLab Rails app | `%15.10` | Add GraphQL type to represent runner machines under `CiRunner`. |
|
||||
| GitLab Rails app | `%15.10` | Implement UI to create new instance runner. |
|
||||
| GitLab Rails app | `%15.11` | Update service and mutation to accept groups and projects. |
|
||||
| GitLab Rails app | `%15.11` | Implement UI to create new runner. |
|
||||
| GitLab Rails app | `%15.11` | Implement UI to create new group/project runners. |
|
||||
| GitLab Rails app | `%15.11` | GraphQL changes to `CiRunner` type. (?) |
|
||||
| GitLab Rails app | `%15.11` | UI changes to runner details view (listing of platform, architecture, IP address, etc.) (?) |
|
||||
| GitLab Rails app | `%15.11` | Adapt `POST /api/v4/runners` REST endpoint to accept a request from an authorized user with a scope instead of a registration token. |
|
||||
|
|
|
|||
|
|
@ -779,14 +779,18 @@ As much as possible, use text that follows one of these patterns:
|
|||
|
||||
For example:
|
||||
|
||||
- `For more information, see [merge requests](../../../user/project/merge_requests/index.md).`
|
||||
- `To create a review app, see [review apps](../../../ci/review_apps/index.md).`
|
||||
- `For more information, see [merge requests](LINK).`
|
||||
- `To create a review app, see [review apps](LINK).`
|
||||
|
||||
You can expand on this text by using phrases like
|
||||
`For more information about this feature, see...`
|
||||
|
||||
Do not to use alternate phrases, like `Learn more about...` or
|
||||
`To read more...`.
|
||||
Do not to use the following constructions:
|
||||
|
||||
- `Learn more about...`
|
||||
- `To read more...`.
|
||||
- `For more information, see the [Merge requests](LINK) page.`
|
||||
- `For more information, see the [Merge requests](LINK) documentation.`
|
||||
|
||||
#### Descriptive text rather than `here`
|
||||
|
||||
|
|
|
|||
|
|
@ -944,6 +944,17 @@ An Owner is the highest role a user can have.
|
|||
|
||||
Use title case for the GitLab Package Registry.
|
||||
|
||||
## page
|
||||
|
||||
If you write a phrase like, "On the **Issues** page," ensure steps for how to get to the page are nearby. Otherwise, people might not know what the **Issues** page is.
|
||||
|
||||
The page name should be visible in the UI at the top of the page.
|
||||
If it is not, you should be able to get the name from the breadcrumb.
|
||||
|
||||
The docs should match the case in the UI, and the page name should be bold. For example:
|
||||
|
||||
- On the **Test cases** page, ...
|
||||
|
||||
## permissions
|
||||
|
||||
Do not use [**roles**](#roles) and **permissions** interchangeably. Each user is assigned a role. Each role includes a set of permissions.
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ Dark theme only works with the **Dark** syntax highlighting theme.
|
|||
|
||||
## Syntax highlighting theme
|
||||
|
||||
> Changing the default syntax highlighting theme for new users and users who are not signed in [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25129) in GitLab 15.10.
|
||||
|
||||
GitLab uses the [rouge Ruby library](http://rouge.jneen.net/ "Rouge website")
|
||||
for syntax highlighting outside of any Editor context. The WebIDE (like Snippets)
|
||||
uses [Monaco Editor](https://microsoft.github.io/monaco-editor/) and it's provided
|
||||
|
|
@ -89,6 +91,10 @@ The default syntax theme is White, and you can choose among 5 different themes:
|
|||
|
||||
Introduced in GitLab 13.6, the themes [Solarized](https://gitlab.com/gitlab-org/gitlab/-/issues/221034) and [Monokai](https://gitlab.com/gitlab-org/gitlab/-/issues/221034) also apply to the [Web IDE](../project/web_ide/index.md) and [Snippets](../snippets.md).
|
||||
|
||||
You can use an API call to change the default syntax highlighting theme for new users and users
|
||||
who are not signed in. For more information, see the `default_syntax_highlighting_theme`
|
||||
in the [list of settings that can be accessed through API calls](../../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls).
|
||||
|
||||
## Diff colors
|
||||
|
||||
A diff compares the old/removed content with the new/added content (for example, when
|
||||
|
|
|
|||
|
|
@ -43,11 +43,14 @@ module Gitlab
|
|||
def optimize!
|
||||
return unless Feature.enabled?(:optimize_batched_migrations, type: :ops)
|
||||
|
||||
if multiplier = batch_size_multiplier
|
||||
max_batch = migration.max_batch_size || MAX_BATCH_SIZE
|
||||
migration.batch_size = (migration.batch_size * multiplier).to_i.clamp(MIN_BATCH_SIZE, max_batch)
|
||||
migration.save!
|
||||
end
|
||||
multiplier = batch_size_multiplier
|
||||
return if multiplier.nil?
|
||||
|
||||
max_batch = migration.max_batch_size || MAX_BATCH_SIZE
|
||||
min_batch = [max_batch, MIN_BATCH_SIZE].min
|
||||
|
||||
migration.batch_size = (migration.batch_size * multiplier).to_i.clamp(min_batch, max_batch)
|
||||
migration.save!
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -4735,12 +4735,105 @@ msgstr ""
|
|||
msgid "Analytics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Add to Dashboard"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|An error occurred while loading the %{visualizationTitle} visualization."
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Analytics dashboards"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Browser"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Browser Family"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Choose a chart type on the right"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Choose a measurement to start"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Code"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Configure Dashboard Project"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Custom dashboards"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Dashboards are created by editing the projects dashboard files."
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Data Table"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|For being able to create your own dashboards please configure a special project to store your dashboards."
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Host"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Language"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Line Chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|New Analytics Visualization Title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|OS"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|OS Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Page Language"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Page Path"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Page Title"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Pages"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Referer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Resulting Data"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Single Statistic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Viewport"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Visualization"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Visualization Designer"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analytics|Visualization Type"
|
||||
msgstr ""
|
||||
|
||||
msgid "Analyze your dependencies for known vulnerabilities."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32639,22 +32732,19 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Add the script to the page and assign the client SDK to window:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Add to Dashboard"
|
||||
msgid "ProductAnalytics|All Clicks Compared"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|All clicks compared"
|
||||
msgid "ProductAnalytics|All Events Compared"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|All events compared"
|
||||
msgid "ProductAnalytics|All Features"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|All features"
|
||||
msgid "ProductAnalytics|All Pages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|All pages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|All sessions compared"
|
||||
msgid "ProductAnalytics|All Sessions Compared"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|An error occured while loading the %{panelTitle} panel."
|
||||
|
|
@ -32672,33 +32762,21 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Audience"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Average Per User"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Average Session Duration"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Average duration in minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Average per User"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Back to dashboards"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Browser"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Browser Family"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Cancel Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Choose a chart type on the right"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Choose a measurement to start"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Click Events"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32717,24 +32795,12 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Compares feature usage of all features against each other"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Compares pageviews of all pages against each other"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Configure Dashboard Project"
|
||||
msgid "ProductAnalytics|Compares page views of all pages against each other"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Creating your product analytics instance..."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Custom dashboards"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Data"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Data Table"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Details on how to configure product analytics to collect data."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32756,13 +32822,7 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Events over time"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Feature Usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Feature usage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|For being able to create your own dashboards please configure a special project to store your dashboards."
|
||||
msgid "ProductAnalytics|Feature Usages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|For the product analytics dashboard to start showing you some data, you need to add the analytics tracking code to your project."
|
||||
|
|
@ -32771,13 +32831,10 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Go back"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Host"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|How many sessions a user has"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|How often sesions are repeated"
|
||||
msgid "ProductAnalytics|How often sessions are repeated"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Identifies the sender of tracking events"
|
||||
|
|
@ -32792,12 +32849,6 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Instrumentation details"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Language"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Line Chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Measure All tracked Events"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32813,43 +32864,13 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Measuring"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|New Analytics Panel Title"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|OS"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|OS Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|On what do you want to get insights?"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Page Language"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Page Path"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Page Title"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Page Views"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Pages"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Panel"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Referer"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Repeat Visit percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Resulting Data"
|
||||
msgid "ProductAnalytics|Repeat Visit Percentage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|SDK App ID"
|
||||
|
|
@ -32867,9 +32888,6 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Set up product analytics"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Single Statistic"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Steps to add product analytics as a CommonJS module"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32894,9 +32912,6 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Track specific features"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Unique Users"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32909,15 +32924,6 @@ msgstr ""
|
|||
msgid "ProductAnalytics|Users"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Viewport"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Visualization Designer"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Visualization Type"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|What do you want to measure?"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -46107,6 +46113,15 @@ msgstr ""
|
|||
msgid "Unlimited"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnlimitedMembersDuringTrialAlert|During your trial, invite as many members as you like to %{group} to collaborate with you."
|
||||
msgstr ""
|
||||
|
||||
msgid "UnlimitedMembersDuringTrialAlert|Explore paid plans"
|
||||
msgstr ""
|
||||
|
||||
msgid "UnlimitedMembersDuringTrialAlert|Get the most out of your trial with space for more members"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unlink"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -48700,6 +48715,11 @@ msgstr ""
|
|||
msgid "When you transfer your project to a group, you can easily manage multiple projects, view usage quotas for storage, pipeline minutes, and users, and start a trial or upgrade to a paid tier."
|
||||
msgstr ""
|
||||
|
||||
msgid "When your trial ends, you'll have a maximum of %d member on the Free tier, or you can get more by upgrading to a paid tier."
|
||||
msgid_plural "When your trial ends, you'll have a maximum of %d members on the Free tier, or you can get more by upgrading to a paid tier."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "When your trial ends, you'll move to the Free tier, which has a limit of %{free_user_limit} seat. %{free_user_limit} seat will remain active, and members not occupying a seat will have the %{link_start}Over limit status%{link_end} and lose access to this group."
|
||||
msgid_plural "When your trial ends, you'll move to the Free tier, which has a limit of %{free_user_limit} seats. %{free_user_limit} seats will remain active, and members not occupying a seat will have the %{link_start}Over limit status%{link_end} and lose access to this group."
|
||||
msgstr[0] ""
|
||||
|
|
@ -51011,6 +51031,9 @@ msgstr ""
|
|||
msgid "ciReport|Solution"
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|Something went wrong while fetching the finding. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
msgid "ciReport|Static Application Security Testing (SAST)"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -110,15 +110,12 @@ def add_definition_to_yaml(definition)
|
|||
content = YAML.load_file(Rails.root.join('config/gitlab_loose_foreign_keys.yml'))
|
||||
table_definitions = content[definition.from_table]
|
||||
|
||||
# insert new entry at random place to avoid conflicts
|
||||
# insert new entry in alphabetic order
|
||||
unless table_definitions
|
||||
table_definitions = []
|
||||
insert_idx = rand(content.count+1)
|
||||
|
||||
# insert at a given index in ordered hash
|
||||
content = content.to_a
|
||||
content.insert(insert_idx, [definition.from_table, table_definitions])
|
||||
content = content.to_h
|
||||
content[definition.from_table] = table_definitions
|
||||
content = content.sort.to_h
|
||||
end
|
||||
|
||||
on_delete =
|
||||
|
|
@ -217,7 +214,7 @@ def add_test_to_specs(definition)
|
|||
puts "Adding test to #{spec_path}..."
|
||||
|
||||
spec_test = <<-EOF.strip_heredoc.indent(2)
|
||||
context 'loose foreign key on #{definition.from_table}.#{definition.column}' do
|
||||
context 'with loose foreign key on #{definition.from_table}.#{definition.column}' do
|
||||
it_behaves_like 'cleanup by a loose foreign key' do
|
||||
let!(:parent) { create(:#{definition.to_table.singularize}) }
|
||||
let!(:model) { create(:#{definition.from_table.singularize}, #{definition.column.delete_suffix("_id").singularize}: parent) }
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
ci_build_trace_metadata: %w[partition_id build_id],
|
||||
ci_builds: %w[erased_by_id trigger_request_id partition_id],
|
||||
ci_builds_runner_session: %w[partition_id build_id],
|
||||
p_ci_builds_metadata: %w[partition_id build_id],
|
||||
p_ci_builds_metadata: %w[partition_id build_id runner_machine_id],
|
||||
ci_job_artifacts: %w[partition_id job_id],
|
||||
ci_job_variables: %w[partition_id job_id],
|
||||
ci_namespace_monthly_usages: %w[namespace_id],
|
||||
|
|
@ -89,7 +89,7 @@ RSpec.describe 'Database schema', feature_category: :database do
|
|||
oauth_access_grants: %w[resource_owner_id application_id],
|
||||
oauth_access_tokens: %w[resource_owner_id application_id],
|
||||
oauth_applications: %w[owner_id],
|
||||
p_ci_runner_machine_builds: %w[partition_id build_id runner_machine_id],
|
||||
p_ci_runner_machine_builds: %w[partition_id build_id],
|
||||
product_analytics_events_experimental: %w[event_id txn_id user_id],
|
||||
project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
|
||||
project_data_transfers: %w[project_id namespace_id],
|
||||
|
|
|
|||
|
|
@ -66,6 +66,12 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
trait :with_runner_machine do
|
||||
after(:build) do |runner, evaluator|
|
||||
runner.runner_machines << build(:ci_runner_machine, runner: runner)
|
||||
end
|
||||
end
|
||||
|
||||
trait :inactive do
|
||||
active { false }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import setWindowLocation from 'helpers/set_window_location_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
|
||||
import { s__ } from '~/locale';
|
||||
import { updateHistory } from '~/lib/utils/url_utility';
|
||||
import runnerForRegistrationQuery from '~/ci/runner/graphql/register/runner_for_registration.query.graphql';
|
||||
import { PARAM_KEY_PLATFORM, DEFAULT_PLATFORM, WINDOWS_PLATFORM } from '~/ci/runner/constants';
|
||||
|
|
@ -21,7 +20,6 @@ import { runnerForRegistration } from '../mock_data';
|
|||
const mockRunner = runnerForRegistration.data.runner;
|
||||
const mockRunnerId = `${getIdFromGraphQLId(mockRunner.id)}`;
|
||||
const mockRunnersPath = '/admin/runners';
|
||||
const MOCK_TOKEN = 'MOCK_TOKEN';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
@ -51,7 +49,7 @@ describe('AdminRegisterRunnerApp', () => {
|
|||
beforeEach(() => {
|
||||
mockRunnerQuery = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
runner: { ...mockRunner, ephemeralAuthenticationToken: MOCK_TOKEN },
|
||||
runner: mockRunner,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -66,15 +64,11 @@ describe('AdminRegisterRunnerApp', () => {
|
|||
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: mockRunner.id });
|
||||
});
|
||||
|
||||
it('shows heading', () => {
|
||||
expect(wrapper.find('h1').text()).toContain(mockRunner.description);
|
||||
});
|
||||
|
||||
it('shows registration instructions', () => {
|
||||
expect(findRegistrationInstructions().props()).toEqual({
|
||||
loading: false,
|
||||
platform: DEFAULT_PLATFORM,
|
||||
token: MOCK_TOKEN,
|
||||
runner: mockRunner,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -114,21 +108,21 @@ describe('AdminRegisterRunnerApp', () => {
|
|||
});
|
||||
|
||||
it('opens platform drawer', () => {
|
||||
expect(findPlatformsDrawer().props('open')).toEqual(true);
|
||||
expect(findPlatformsDrawer().props('open')).toBe(true);
|
||||
});
|
||||
|
||||
it('closes platform drawer', async () => {
|
||||
findRegistrationInstructions().vm.$emit('toggleDrawer');
|
||||
await nextTick();
|
||||
|
||||
expect(findPlatformsDrawer().props('open')).toEqual(false);
|
||||
expect(findPlatformsDrawer().props('open')).toBe(false);
|
||||
});
|
||||
|
||||
it('closes platform drawer from drawer', async () => {
|
||||
findPlatformsDrawer().vm.$emit('close');
|
||||
await nextTick();
|
||||
|
||||
expect(findPlatformsDrawer().props('open')).toEqual(false);
|
||||
expect(findPlatformsDrawer().props('open')).toBe(false);
|
||||
});
|
||||
|
||||
describe('when selecting a platform', () => {
|
||||
|
|
@ -151,18 +145,14 @@ describe('AdminRegisterRunnerApp', () => {
|
|||
});
|
||||
|
||||
describe('When runner is loading', () => {
|
||||
beforeEach(async () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('shows heading', () => {
|
||||
expect(wrapper.find('h1').text()).toBe(s__('Runners|Register runner'));
|
||||
});
|
||||
|
||||
it('shows registration instructions', () => {
|
||||
expect(findRegistrationInstructions().props()).toEqual({
|
||||
loading: true,
|
||||
token: null,
|
||||
runner: null,
|
||||
platform: DEFAULT_PLATFORM,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ Array [
|
|||
"gitlab-runner register",
|
||||
" --url http://test.host",
|
||||
" --registration-token REGISTRATION_TOKEN",
|
||||
" --description 'RUNNER'",
|
||||
]
|
||||
`;
|
||||
|
||||
|
|
@ -130,6 +131,7 @@ Array [
|
|||
"gitlab-runner register",
|
||||
" --url http://test.host",
|
||||
" --registration-token REGISTRATION_TOKEN",
|
||||
" --description 'RUNNER'",
|
||||
]
|
||||
`;
|
||||
|
||||
|
|
@ -188,6 +190,7 @@ Array [
|
|||
".\\\\gitlab-runner.exe register",
|
||||
" --url http://test.host",
|
||||
" --registration-token REGISTRATION_TOKEN",
|
||||
" --description 'RUNNER'",
|
||||
]
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
|
|||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { extendedWrapper, shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
import RegistrationInstructions from '~/ci/runner/components/registration/registration_instructions.vue';
|
||||
import CliCommand from '~/ci/runner/components/registration/cli_command.vue';
|
||||
|
|
@ -10,24 +11,32 @@ import {
|
|||
EXECUTORS_HELP_URL,
|
||||
SERVICE_COMMANDS_HELP_URL,
|
||||
} from '~/ci/runner/constants';
|
||||
import { runnerForRegistration } from '../../mock_data';
|
||||
|
||||
const REGISTRATION_TOKEN = 'REGISTRATION_TOKEN';
|
||||
const DUMMY_GON = {
|
||||
gitlab_url: TEST_HOST,
|
||||
};
|
||||
|
||||
const AUTH_TOKEN = 'AUTH_TOKEN';
|
||||
|
||||
const mockRunner = {
|
||||
...runnerForRegistration.data.runner,
|
||||
ephemeralAuthenticationToken: AUTH_TOKEN,
|
||||
};
|
||||
|
||||
describe('RegistrationInstructions', () => {
|
||||
let wrapper;
|
||||
let originalGon;
|
||||
|
||||
const findHeading = () => wrapper.find('h1');
|
||||
const findStepAt = (i) => extendedWrapper(wrapper.findAll('section').at(i));
|
||||
const findByText = (text, container = wrapper) => container.findByText(text);
|
||||
|
||||
const createComponent = (props) => {
|
||||
wrapper = shallowMountExtended(RegistrationInstructions, {
|
||||
propsData: {
|
||||
runner: mockRunner,
|
||||
platform: DEFAULT_PLATFORM,
|
||||
token: REGISTRATION_TOKEN,
|
||||
...props,
|
||||
},
|
||||
stubs: {
|
||||
|
|
@ -45,29 +54,60 @@ describe('RegistrationInstructions', () => {
|
|||
window.gon = originalGon;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
describe('renders heading', () => {
|
||||
it('when runner is loaded, shows heading', () => {
|
||||
createComponent();
|
||||
expect(findHeading().text()).toContain(mockRunner.description);
|
||||
});
|
||||
|
||||
it('when runner is loaded, shows heading safely', () => {
|
||||
const description = '<script>hacked();</script>';
|
||||
|
||||
createComponent({
|
||||
runner: {
|
||||
...mockRunner,
|
||||
description,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findHeading().text()).toBe('Register "<script>hacked();</script>" runner');
|
||||
expect(findHeading().element.innerHTML).toBe(
|
||||
'Register "<script>hacked();</script>" runner',
|
||||
);
|
||||
});
|
||||
|
||||
it('when runner is loading, shows default heading', () => {
|
||||
createComponent({
|
||||
loading: true,
|
||||
runner: null,
|
||||
});
|
||||
|
||||
expect(findHeading().text()).toBe(s__('Runners|Register runner'));
|
||||
});
|
||||
});
|
||||
|
||||
it('renders legacy instructions', () => {
|
||||
createComponent();
|
||||
findByText('How do I install GitLab Runner?').vm.$emit('click');
|
||||
|
||||
expect(wrapper.emitted('toggleDrawer')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders step 1', () => {
|
||||
createComponent();
|
||||
const step1 = findStepAt(0);
|
||||
|
||||
expect(step1.findComponent(CliCommand).props()).toEqual({
|
||||
command: [
|
||||
'gitlab-runner register',
|
||||
` --url ${TEST_HOST}`,
|
||||
` --registration-token ${REGISTRATION_TOKEN}`,
|
||||
` --registration-token ${AUTH_TOKEN}`,
|
||||
` --description '${mockRunner.description}'`,
|
||||
],
|
||||
prompt: '$',
|
||||
});
|
||||
expect(step1.find('code').text()).toBe(REGISTRATION_TOKEN);
|
||||
expect(step1.findComponent(ClipboardButton).props('text')).toBe(REGISTRATION_TOKEN);
|
||||
expect(step1.find('code').text()).toBe(AUTH_TOKEN);
|
||||
expect(step1.findComponent(ClipboardButton).props('text')).toBe(AUTH_TOKEN);
|
||||
});
|
||||
|
||||
it('renders step 1 in loading state', () => {
|
||||
|
|
@ -83,6 +123,7 @@ describe('RegistrationInstructions', () => {
|
|||
});
|
||||
|
||||
it('renders step 2', () => {
|
||||
createComponent();
|
||||
const step2 = findStepAt(1);
|
||||
|
||||
expect(findByText('Not sure which one to select?', step2).attributes('href')).toBe(
|
||||
|
|
@ -91,6 +132,7 @@ describe('RegistrationInstructions', () => {
|
|||
});
|
||||
|
||||
it('renders step 3', () => {
|
||||
createComponent();
|
||||
const step3 = findStepAt(2);
|
||||
|
||||
expect(step3.findComponent(CliCommand).props()).toEqual({
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '~/ci/runner/components/registration/utils';
|
||||
|
||||
const REGISTRATION_TOKEN = 'REGISTRATION_TOKEN';
|
||||
const DESCRIPTION = 'RUNNER';
|
||||
const DUMMY_GON = {
|
||||
gitlab_url: TEST_HOST,
|
||||
};
|
||||
|
|
@ -37,19 +38,45 @@ describe('registration utils', () => {
|
|||
it('commandPrompt is correct', () => {
|
||||
expect(commandPrompt({ platform })).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('registerCommand is correct', () => {
|
||||
expect(
|
||||
registerCommand({ platform, registrationToken: REGISTRATION_TOKEN }),
|
||||
registerCommand({
|
||||
platform,
|
||||
registrationToken: REGISTRATION_TOKEN,
|
||||
description: DESCRIPTION,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
|
||||
expect(registerCommand({ platform })).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('runCommand is correct', () => {
|
||||
expect(runCommand({ platform })).toMatchSnapshot();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe.each([LINUX_PLATFORM, MACOS_PLATFORM])('for "%s" platform', (platform) => {
|
||||
it.each`
|
||||
description | parameter
|
||||
${'my runner'} | ${"'my runner'"}
|
||||
${"bob's runner"} | ${"'bob'\\''s runner'"}
|
||||
`('registerCommand escapes description `$description`', ({ description, parameter }) => {
|
||||
expect(registerCommand({ platform, description })[2]).toBe(` --description ${parameter}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([WINDOWS_PLATFORM])('for "%s" platform', (platform) => {
|
||||
it.each`
|
||||
description | parameter
|
||||
${'my runner'} | ${"'my runner'"}
|
||||
${"bob's runner"} | ${"'bob''s runner'"}
|
||||
`('registerCommand escapes description `$description`', ({ description, parameter }) => {
|
||||
expect(registerCommand({ platform, description })[2]).toBe(` --description ${parameter}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('for missing platform', () => {
|
||||
it('commandPrompt uses the default', () => {
|
||||
const expected = commandPrompt({ platform: DEFAULT_PLATFORM });
|
||||
|
|
|
|||
|
|
@ -84,5 +84,16 @@ describe('locale', () => {
|
|||
expect(output).toBe('contains duplicated 15%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ignores special replacements in the input', () => {
|
||||
it.each(['$$', '$&', '$`', `$'`])('replacement "%s" is ignored', (replacement) => {
|
||||
const input = 'My odd %{replacement} is preserved';
|
||||
|
||||
const parameters = { replacement };
|
||||
|
||||
const output = sprintf(input, parameters, false);
|
||||
expect(output).toBe(`My odd ${replacement} is preserved`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue
|
|||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
|
||||
import { stubComponent } from 'helpers/stub_component';
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
|
||||
jest.mock('~/emoji');
|
||||
|
||||
describe('vue_shared/component/markdown/markdown_editor', () => {
|
||||
useLocalStorageSpy();
|
||||
|
||||
let wrapper;
|
||||
const value = 'test markdown';
|
||||
const renderMarkdownPath = '/api/markdown';
|
||||
|
|
@ -64,6 +67,8 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
|
|||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('displays markdown field by default', () => {
|
||||
|
|
@ -102,6 +107,42 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('autosave', () => {
|
||||
it('automatically saves the textarea value to local storage if autosaveKey is defined', () => {
|
||||
buildWrapper({ propsData: { autosaveKey: 'issue/1234', value: 'This is **markdown**' } });
|
||||
|
||||
expect(localStorage.getItem('autosave/issue/1234')).toBe('This is **markdown**');
|
||||
});
|
||||
|
||||
it("loads value from local storage if autosaveKey is defined, and value isn't", () => {
|
||||
localStorage.setItem('autosave/issue/1234', 'This is **markdown**');
|
||||
|
||||
buildWrapper({ propsData: { autosaveKey: 'issue/1234', value: '' } });
|
||||
|
||||
expect(findTextarea().element.value).toBe('This is **markdown**');
|
||||
});
|
||||
|
||||
it("doesn't load value from local storage if autosaveKey is defined, and value is", () => {
|
||||
localStorage.setItem('autosave/issue/1234', 'This is **markdown**');
|
||||
|
||||
buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
|
||||
|
||||
expect(findTextarea().element.value).toBe('test markdown');
|
||||
});
|
||||
|
||||
it('does not save the textarea value to local storage if autosaveKey is not defined', () => {
|
||||
buildWrapper({ propsData: { value: 'This is **markdown**' } });
|
||||
|
||||
expect(localStorage.setItem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not save the textarea value to local storage if value is empty', () => {
|
||||
buildWrapper({ propsData: { autosaveKey: 'issue/1234', value: '' } });
|
||||
|
||||
expect(localStorage.setItem).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders markdown field textarea', () => {
|
||||
buildWrapper({ propsData: { supportsQuickActions: true } });
|
||||
|
||||
|
|
@ -158,6 +199,16 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
|
|||
expect(wrapper.emitted('input')).toEqual([[newValue]]);
|
||||
});
|
||||
|
||||
it('autosaves the markdown value to local storage', async () => {
|
||||
buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
|
||||
|
||||
const newValue = 'new value';
|
||||
|
||||
await findTextarea().setValue(newValue);
|
||||
|
||||
expect(localStorage.getItem('autosave/issue/1234')).toBe(newValue);
|
||||
});
|
||||
|
||||
describe('when autofocus is true', () => {
|
||||
beforeEach(async () => {
|
||||
buildWrapper({ attachTo: document.body, propsData: { autofocus: true } });
|
||||
|
|
@ -219,7 +270,7 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
|
|||
|
||||
describe(`when editingMode is ${EDITING_MODE_CONTENT_EDITOR}`, () => {
|
||||
beforeEach(() => {
|
||||
buildWrapper();
|
||||
buildWrapper({ propsData: { autosaveKey: 'issue/1234' } });
|
||||
findMarkdownField().vm.$emit('enableContentEditor');
|
||||
});
|
||||
|
||||
|
|
@ -244,6 +295,14 @@ describe('vue_shared/component/markdown/markdown_editor', () => {
|
|||
expect(wrapper.emitted('input')).toEqual([[newValue]]);
|
||||
});
|
||||
|
||||
it('autosaves the content editor value to local storage', async () => {
|
||||
const newValue = 'new value';
|
||||
|
||||
await findContentEditor().vm.$emit('change', { markdown: newValue });
|
||||
|
||||
expect(localStorage.getItem('autosave/issue/1234')).toBe(newValue);
|
||||
});
|
||||
|
||||
it('bubbles up keydown event', () => {
|
||||
const event = new Event('keydown');
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { mount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue';
|
||||
|
||||
describe('Toggle Button', () => {
|
||||
|
|
@ -16,7 +15,7 @@ describe('Toggle Button', () => {
|
|||
remain,
|
||||
};
|
||||
|
||||
const Component = Vue.extend({
|
||||
const Component = {
|
||||
components: {
|
||||
SmartVirtualScrollList,
|
||||
},
|
||||
|
|
@ -26,7 +25,7 @@ describe('Toggle Button', () => {
|
|||
<smart-virtual-scroll-list v-bind="$options.smartListProperties">
|
||||
<li v-for="(val, key) in $options.items" :key="key">{{ key + 1 }}</li>
|
||||
</smart-virtual-scroll-list>`,
|
||||
});
|
||||
};
|
||||
|
||||
return mount(Component).vm;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GitlabSchema.types['CiRunnerMachine'], feature_category: :runner_fleet do
|
||||
specify { expect(described_class.graphql_name).to eq('CiRunnerMachine') }
|
||||
|
||||
specify { expect(described_class).to require_graphql_authorizations(:read_runner_machine) }
|
||||
|
||||
it 'contains attributes related to a runner machine' do
|
||||
expected_fields = %w[
|
||||
architecture_name contacted_at created_at executor_name id ip_address platform_name revision
|
||||
runner status version
|
||||
]
|
||||
|
||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||
end
|
||||
end
|
||||
|
|
@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['CiRunner'], feature_category: :runner do
|
|||
|
||||
it 'contains attributes related to a runner' do
|
||||
expected_fields = %w[
|
||||
id description created_by created_at contacted_at maximum_timeout access_level active paused status
|
||||
id description created_by created_at contacted_at machines maximum_timeout access_level active paused status
|
||||
version short_sha revision locked run_untagged ip_address runner_type tag_list
|
||||
project_count job_count admin_url edit_admin_url register_admin_url user_permissions executor_name
|
||||
architecture_name platform_name maintenance_note maintenance_note_html groups projects jobs token_expires_at
|
||||
|
|
|
|||
|
|
@ -113,5 +113,14 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchOptimizer do
|
|||
expect { subject }.to change { migration.reload.batch_size }.to(1_000)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when migration max_batch_size is less than MIN_BATCH_SIZE' do
|
||||
let(:migration_params) { { max_batch_size: 900 } }
|
||||
|
||||
it 'does not raise an error' do
|
||||
mock_efficiency(0.7)
|
||||
expect { subject }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -872,6 +872,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
|
|||
expect(pipeline).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source is unknown' do
|
||||
subject(:pipeline) { create(:ci_empty_pipeline, :created) }
|
||||
|
||||
let(:attr) { :source }
|
||||
let(:attr_value) { :unknown }
|
||||
|
||||
it_behaves_like 'having enum with nil value'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#config_source' do
|
||||
context 'when source is unknown' do
|
||||
subject(:pipeline) { create(:ci_empty_pipeline, :created) }
|
||||
|
||||
let(:attr) { :config_source }
|
||||
let(:attr_value) { :unknown_source }
|
||||
|
||||
it_behaves_like 'having enum with nil value'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#block' do
|
||||
|
|
|
|||
|
|
@ -47,4 +47,11 @@ RSpec.describe Ci::RunnerMachineBuild, model: true, feature_category: :runner_fl
|
|||
it { expect(partitioning_strategy.current_partitions).to include partitioning_strategy.initial_partition }
|
||||
it { expect(partitioning_strategy.active_partition).to be_present }
|
||||
end
|
||||
|
||||
context 'loose foreign key on p_ci_runner_machine_builds.runner_machine_id' do # rubocop:disable RSpec/ContextWording
|
||||
it_behaves_like 'cleanup by a loose foreign key' do
|
||||
let!(:parent) { create(:ci_runner_machine) }
|
||||
let!(:model) { create(:ci_runner_machine_build, runner_machine: parent) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model do
|
||||
it_behaves_like 'having unique enum values'
|
||||
|
||||
it_behaves_like 'it has loose foreign keys' do
|
||||
let(:factory_name) { :ci_runner_machine }
|
||||
end
|
||||
|
||||
it { is_expected.to belong_to(:runner) }
|
||||
it { is_expected.to belong_to(:runner_version).with_foreign_key(:version) }
|
||||
it { is_expected.to have_many(:runner_machine_builds) }
|
||||
|
|
@ -53,6 +57,64 @@ RSpec.describe Ci::RunnerMachine, feature_category: :runner_fleet, type: :model
|
|||
end
|
||||
end
|
||||
|
||||
describe '.online_contact_time_deadline', :freeze_time do
|
||||
subject { described_class.online_contact_time_deadline }
|
||||
|
||||
it { is_expected.to eq(2.hours.ago) }
|
||||
end
|
||||
|
||||
describe '.stale_deadline', :freeze_time do
|
||||
subject { described_class.stale_deadline }
|
||||
|
||||
it { is_expected.to eq(7.days.ago) }
|
||||
end
|
||||
|
||||
describe '#status', :freeze_time do
|
||||
let(:runner_machine) { build(:ci_runner_machine, created_at: 8.days.ago) }
|
||||
|
||||
subject { runner_machine.status }
|
||||
|
||||
context 'if never connected' do
|
||||
before do
|
||||
runner_machine.contacted_at = nil
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:stale) }
|
||||
|
||||
context 'if created recently' do
|
||||
before do
|
||||
runner_machine.created_at = 1.day.ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:never_contacted) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'if contacted 1s ago' do
|
||||
before do
|
||||
runner_machine.contacted_at = 1.second.ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:online) }
|
||||
end
|
||||
|
||||
context 'if contacted recently' do
|
||||
before do
|
||||
runner_machine.contacted_at = 2.hours.ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:offline) }
|
||||
end
|
||||
|
||||
context 'if contacted long time ago' do
|
||||
before do
|
||||
runner_machine.contacted_at = 7.days.ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:stale) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#heartbeat', :freeze_time do
|
||||
let(:runner_machine) { create(:ci_runner_machine, version: '15.0.0') }
|
||||
let(:executor) { 'shell' }
|
||||
|
|
|
|||
|
|
@ -541,9 +541,9 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
describe '.stale', :freeze_time do
|
||||
subject { described_class.stale }
|
||||
|
||||
let!(:runner1) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago + 10.seconds) }
|
||||
let!(:runner2) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago - 1.second) }
|
||||
let!(:runner3) { create(:ci_runner, :instance, created_at: 3.months.ago - 1.second, contacted_at: nil) }
|
||||
let!(:runner1) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago + 1.second) }
|
||||
let!(:runner2) { create(:ci_runner, :instance, created_at: 4.months.ago, contacted_at: 3.months.ago) }
|
||||
let!(:runner3) { create(:ci_runner, :instance, created_at: 3.months.ago, contacted_at: nil) }
|
||||
let!(:runner4) { create(:ci_runner, :instance, created_at: 2.months.ago, contacted_at: nil) }
|
||||
|
||||
it 'returns stale runners' do
|
||||
|
|
@ -551,7 +551,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#stale?', :clean_gitlab_redis_cache do
|
||||
describe '#stale?', :clean_gitlab_redis_cache, :freeze_time do
|
||||
let(:runner) { build(:ci_runner, :instance) }
|
||||
|
||||
subject { runner.stale? }
|
||||
|
|
@ -570,11 +570,11 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:created_at, :contacted_at, :expected_stale?) do
|
||||
nil | nil | false
|
||||
3.months.ago - 1.second | 3.months.ago - 0.001.seconds | true
|
||||
3.months.ago - 1.second | 3.months.ago + 1.hour | false
|
||||
3.months.ago - 1.second | nil | true
|
||||
3.months.ago + 1.hour | nil | false
|
||||
nil | nil | false
|
||||
3.months.ago | 3.months.ago | true
|
||||
3.months.ago | (3.months - 1.hour).ago | false
|
||||
3.months.ago | nil | true
|
||||
(3.months - 1.hour).ago | nil | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -588,9 +588,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
runner.contacted_at = contacted_at
|
||||
end
|
||||
|
||||
specify do
|
||||
is_expected.to eq(expected_stale?)
|
||||
end
|
||||
it { is_expected.to eq(expected_stale?) }
|
||||
end
|
||||
|
||||
context 'with cache value' do
|
||||
|
|
@ -599,9 +597,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
stub_redis_runner_contacted_at(contacted_at.to_s)
|
||||
end
|
||||
|
||||
specify do
|
||||
is_expected.to eq(expected_stale?)
|
||||
end
|
||||
it { is_expected.to eq(expected_stale?) }
|
||||
end
|
||||
|
||||
def stub_redis_runner_contacted_at(value)
|
||||
|
|
@ -617,7 +613,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.online' do
|
||||
describe '.online', :freeze_time do
|
||||
subject { described_class.online }
|
||||
|
||||
let!(:runner1) { create(:ci_runner, :instance, contacted_at: 2.hours.ago) }
|
||||
|
|
@ -626,7 +622,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
it { is_expected.to match_array([runner2]) }
|
||||
end
|
||||
|
||||
describe '#online?', :clean_gitlab_redis_cache do
|
||||
describe '#online?', :clean_gitlab_redis_cache, :freeze_time do
|
||||
let(:runner) { build(:ci_runner, :instance) }
|
||||
|
||||
subject { runner.online? }
|
||||
|
|
@ -891,8 +887,8 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#status' do
|
||||
let(:runner) { build(:ci_runner, :instance, created_at: 4.months.ago) }
|
||||
describe '#status', :freeze_time do
|
||||
let(:runner) { build(:ci_runner, :instance, created_at: 3.months.ago) }
|
||||
let(:legacy_mode) {}
|
||||
|
||||
subject { runner.status(legacy_mode) }
|
||||
|
|
@ -948,7 +944,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
|
||||
context 'contacted recently' do
|
||||
before do
|
||||
runner.contacted_at = (3.months - 1.hour).ago
|
||||
runner.contacted_at = (3.months - 1.second).ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:offline) }
|
||||
|
|
@ -956,7 +952,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
|
||||
context 'contacted long time ago' do
|
||||
before do
|
||||
runner.contacted_at = (3.months + 1.second).ago
|
||||
runner.contacted_at = 3.months.ago
|
||||
end
|
||||
|
||||
context 'with legacy_mode enabled' do
|
||||
|
|
@ -971,7 +967,7 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#deprecated_rest_status' do
|
||||
describe '#deprecated_rest_status', :freeze_time do
|
||||
let(:runner) { create(:ci_runner, :instance, contacted_at: 1.second.ago) }
|
||||
|
||||
subject { runner.deprecated_rest_status }
|
||||
|
|
@ -994,8 +990,8 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do
|
|||
|
||||
context 'contacted long time ago' do
|
||||
before do
|
||||
runner.created_at = 1.year.ago
|
||||
runner.contacted_at = 1.year.ago
|
||||
runner.created_at = 3.months.ago
|
||||
runner.contacted_at = 3.months.ago
|
||||
end
|
||||
|
||||
it { is_expected.to eq(:stale) }
|
||||
|
|
|
|||
|
|
@ -39,4 +39,15 @@ RSpec.describe Ci::RunnerVersion, feature_category: :runner_fleet do
|
|||
describe 'validation' do
|
||||
it { is_expected.to validate_length_of(:version).is_at_most(2048) }
|
||||
end
|
||||
|
||||
describe '#status' do
|
||||
context 'when is not processed' do
|
||||
subject(:ci_runner_version) { create(:ci_runner_version, version: 'abc124', status: :not_processed) }
|
||||
|
||||
let(:attr) { :status }
|
||||
let(:attr_value) { :not_processed }
|
||||
|
||||
it_behaves_like 'having enum with nil value'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -930,4 +930,13 @@ RSpec.describe Clusters::Platforms::Kubernetes do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#authorization_type' do
|
||||
subject(:kubernetes) { create(:cluster_platform_kubernetes) }
|
||||
|
||||
let(:attr) { :authorization_type }
|
||||
let(:attr_value) { :unknown_authorization }
|
||||
|
||||
it_behaves_like 'having enum with nil value'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1039,4 +1039,13 @@ RSpec.describe CommitStatus do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#failure_reason' do
|
||||
subject(:status) { commit_status }
|
||||
|
||||
let(:attr) { :failure_reason }
|
||||
let(:attr_value) { :unknown_failure }
|
||||
|
||||
it_behaves_like 'having enum with nil value'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2402,14 +2402,6 @@ RSpec.describe User, feature_category: :user_profile do
|
|||
end
|
||||
|
||||
it_behaves_like 'manageable groups examples'
|
||||
|
||||
context 'when feature flag :linear_user_manageable_groups is disabled' do
|
||||
before do
|
||||
stub_feature_flags(linear_user_manageable_groups: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'manageable groups examples'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::RunnerMachinePolicy, feature_category: :runner_fleet do
|
||||
let_it_be(:owner) { create(:user) }
|
||||
|
||||
describe 'ability :read_runner_machine' do
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
|
||||
let_it_be_with_reload(:group) { create(:group, name: 'top-level', path: 'top-level') }
|
||||
let_it_be_with_reload(:subgroup) { create(:group, name: 'subgroup', path: 'subgroup', parent: group) }
|
||||
let_it_be_with_reload(:project) { create(:project, group: subgroup) }
|
||||
|
||||
let_it_be(:instance_runner) { create(:ci_runner, :instance, :with_runner_machine) }
|
||||
let_it_be(:group_runner) { create(:ci_runner, :group, :with_runner_machine, groups: [group]) }
|
||||
let_it_be(:project_runner) { create(:ci_runner, :project, :with_runner_machine, projects: [project]) }
|
||||
|
||||
let(:runner_machine) { runner.runner_machines.first }
|
||||
|
||||
subject(:policy) { described_class.new(user, runner_machine) }
|
||||
|
||||
before_all do
|
||||
group.add_guest(guest)
|
||||
group.add_developer(developer)
|
||||
group.add_maintainer(maintainer)
|
||||
group.add_owner(owner)
|
||||
end
|
||||
|
||||
shared_examples 'a policy allowing reading instance runner machine depending on runner sharing' do
|
||||
context 'with instance runner' do
|
||||
let(:runner) { instance_runner }
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
|
||||
context 'with shared runners disabled on projects' do
|
||||
before do
|
||||
project.update!(shared_runners_enabled: false)
|
||||
end
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
end
|
||||
|
||||
context 'with shared runners disabled for groups and projects' do
|
||||
before do
|
||||
group.update!(shared_runners_enabled: false)
|
||||
project.update!(shared_runners_enabled: false)
|
||||
end
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a policy allowing reading group runner machine depending on runner sharing' do
|
||||
context 'with group runner' do
|
||||
let(:runner) { group_runner }
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
|
||||
context 'with sharing of group runners disabled' do
|
||||
before do
|
||||
project.update!(group_runners_enabled: false)
|
||||
end
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'does not allow reading runners machines on any scope' do
|
||||
context 'with instance runner' do
|
||||
let(:runner) { instance_runner }
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
|
||||
context 'with shared runners disabled for groups and projects' do
|
||||
before do
|
||||
group.update!(shared_runners_enabled: false)
|
||||
project.update!(shared_runners_enabled: false)
|
||||
end
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with group runner' do
|
||||
let(:runner) { group_runner }
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
|
||||
context 'with sharing of group runners disabled' do
|
||||
before do
|
||||
project.update!(group_runners_enabled: false)
|
||||
end
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project runner' do
|
||||
let(:runner) { project_runner }
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
|
||||
context 'without access' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
it_behaves_like 'does not allow reading runners machines on any scope'
|
||||
end
|
||||
|
||||
context 'with guest access' do
|
||||
let(:user) { guest }
|
||||
|
||||
it_behaves_like 'does not allow reading runners machines on any scope'
|
||||
end
|
||||
|
||||
context 'with developer access' do
|
||||
let(:user) { developer }
|
||||
|
||||
it_behaves_like 'a policy allowing reading instance runner machine depending on runner sharing'
|
||||
|
||||
it_behaves_like 'a policy allowing reading group runner machine depending on runner sharing'
|
||||
|
||||
context 'with project runner' do
|
||||
let(:runner) { project_runner }
|
||||
|
||||
it { expect_disallowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with maintainer access' do
|
||||
let(:user) { maintainer }
|
||||
|
||||
it_behaves_like 'a policy allowing reading instance runner machine depending on runner sharing'
|
||||
|
||||
it_behaves_like 'a policy allowing reading group runner machine depending on runner sharing'
|
||||
|
||||
context 'with project runner' do
|
||||
let(:runner) { project_runner }
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with owner access' do
|
||||
let(:user) { owner }
|
||||
|
||||
it_behaves_like 'a policy allowing reading instance runner machine depending on runner sharing'
|
||||
|
||||
context 'with group runner' do
|
||||
let(:runner) { group_runner }
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
|
||||
context 'with sharing of group runners disabled' do
|
||||
before do
|
||||
project.update!(group_runners_enabled: false)
|
||||
end
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with project runner' do
|
||||
let(:runner) { project_runner }
|
||||
|
||||
it { expect_allowed :read_runner_machine }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
|
|||
let_it_be(:group) { create(:group) }
|
||||
|
||||
let_it_be(:active_instance_runner) do
|
||||
create(:ci_runner, :instance,
|
||||
create(:ci_runner, :instance, :with_runner_machine,
|
||||
description: 'Runner 1',
|
||||
creator: user,
|
||||
contacted_at: 2.hours.ago,
|
||||
|
|
@ -58,7 +58,9 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
|
|||
end
|
||||
|
||||
let_it_be(:project1) { create(:project) }
|
||||
let_it_be(:active_project_runner) { create(:ci_runner, :project, projects: [project1]) }
|
||||
let_it_be(:active_project_runner) do
|
||||
create(:ci_runner, :project, :with_runner_machine, projects: [project1])
|
||||
end
|
||||
|
||||
shared_examples 'runner details fetch' do
|
||||
let(:query) do
|
||||
|
|
@ -118,7 +120,12 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
|
|||
'updateRunner' => true,
|
||||
'deleteRunner' => true,
|
||||
'assignRunner' => true
|
||||
}
|
||||
},
|
||||
machines: a_hash_including(
|
||||
"count" => runner.runner_machines.count,
|
||||
"nodes" => an_instance_of(Array),
|
||||
"pageInfo" => anything
|
||||
)
|
||||
)
|
||||
expect(runner_data['tagList']).to match_array runner.tag_list
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,3 +10,13 @@ RSpec.shared_examples 'having unique enum values' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'having enum with nil value' do
|
||||
it 'has enum with nil value' do
|
||||
subject.public_send("#{attr_value}!")
|
||||
|
||||
expect(subject.public_send("#{attr}_for_database")).to be_nil
|
||||
expect(subject.public_send("#{attr}?")).to eq(true)
|
||||
expect(subject.class.public_send(attr_value)).to eq([subject])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue