Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-10-09 00:12:38 +00:00
parent 61829a6fc9
commit 63fde89de0
51 changed files with 512 additions and 492 deletions

View File

@ -1,6 +1,6 @@
import $ from 'jquery';
import Pikaday from 'pikaday';
import { parsePikadayDate, toISODateFormat } from '~/lib/utils/datetime_utility';
import { newDate, toISODateFormat } from '~/lib/utils/datetime_utility';
export default function initDatePickers() {
$('.datepicker').each(function initPikaday() {
@ -12,7 +12,7 @@ export default function initDatePickers() {
theme: 'gl-datepicker-theme animate-picker',
format: 'yyyy-mm-dd',
container: $datePicker.parent().get(0),
parse: (dateString) => parsePikadayDate(dateString),
parse: (dateString) => newDate(dateString),
toString: (date) => toISODateFormat(date),
onSelect(dateText) {
$datePicker.val(calendar.toString(dateText));
@ -20,7 +20,7 @@ export default function initDatePickers() {
firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate(datePickerVal));
calendar.setDate(newDate(datePickerVal));
$datePicker.data('pikaday', calendar);
});

View File

@ -1,12 +1,7 @@
<script>
import { GlTooltip, GlIcon } from '@gitlab/ui';
import dateFormat from '~/lib/dateformat';
import {
getDayDifference,
getTimeago,
dateInWords,
parsePikadayDate,
} from '~/lib/utils/datetime_utility';
import { getDayDifference, getTimeago, dateInWords, newDate } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
export default {
@ -66,7 +61,7 @@ export default {
return standardDateFormat;
},
issueDueDate() {
return parsePikadayDate(this.date);
return newDate(this.date);
},
timeDifference() {
const today = new Date();

View File

@ -1,6 +1,6 @@
import { identity, memoize, isEmpty } from 'lodash';
import { initEmojiMap, getAllEmoji, searchEmoji } from '~/emoji';
import { parsePikadayDate } from '~/lib/utils/datetime_utility';
import { newDate } from '~/lib/utils/datetime_utility';
import axios from '~/lib/utils/axios_utils';
import { COMMANDS } from '../constants';
@ -38,7 +38,7 @@ function parseMilestone(milestone) {
return milestone;
}
const dueDate = milestone.due_date ? parsePikadayDate(milestone.due_date) : null;
const dueDate = milestone.due_date ? newDate(milestone.due_date) : null;
const expired = dueDate ? Date.now() > dueDate.getTime() : false;
return {

View File

@ -17,7 +17,7 @@ import {
} from '~/work_items/constants';
import AjaxCache from './lib/utils/ajax_cache';
import { spriteIcon } from './lib/utils/common_utils';
import { parsePikadayDate } from './lib/utils/datetime_utility';
import { newDate } from './lib/utils/datetime_utility';
import { unicodeLetters } from './lib/utils/regexp';
import { renderVueComponentForLegacyJS } from './render_vue_component_for_legacy_js';
@ -556,7 +556,7 @@ class GfmAutoComplete {
return m;
}
const dueDate = m.due_date ? parsePikadayDate(m.due_date) : null;
const dueDate = m.due_date ? newDate(m.due_date) : null;
const expired = dueDate ? Date.now() > dueDate.getTime() : false;
return {

View File

@ -14,6 +14,7 @@ export const TYPENAME_DESIGN_VERSION = 'DesignManagement::Version';
export const TYPENAME_DISCUSSION = 'Discussion';
export const TYPENAME_EPIC = 'Epic';
export const TYPENAME_EPIC_BOARD = 'Boards::EpicBoard';
export const TYPENAME_FEATURE_FLAG = 'FeatureFlag';
export const TYPENAME_GROUP = 'Group';
export const TYPENAME_ISSUE = 'Issue';
export const TYPENAME_ITERATION = 'Iteration';

View File

@ -2,7 +2,7 @@ import $ from 'jquery';
import Pikaday from 'pikaday';
import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete';
import Autosave from '~/autosave';
import { parsePikadayDate, toISODateFormat } from '~/lib/utils/datetime_utility';
import { newDate, toISODateFormat } from '~/lib/utils/datetime_utility';
import { queryToObject, objectToQuery } from '~/lib/utils/url_utility';
import UsersSelect from '~/users_select';
import ZenMode from '~/zen_mode';
@ -113,7 +113,7 @@ export default class IssuableForm {
theme: 'gl-datepicker-theme animate-picker',
format: 'yyyy-mm-dd',
container: $issuableDueDate.parent().get(0),
parse: (dateString) => parsePikadayDate(dateString),
parse: (dateString) => newDate(dateString),
toString: (date) => toISODateFormat(date),
onSelect: (dateText) => {
$issuableDueDate.val(calendar.toString(dateText));
@ -121,7 +121,7 @@ export default class IssuableForm {
},
firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
calendar.setDate(newDate($issuableDueDate.val()));
}
}

View File

@ -1,13 +0,0 @@
/**
* Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
export const parsePikadayDate = (dateString) => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
const day = parseInt(parts[2], 10);
return new Date(year, month, day);
};

View File

@ -1,5 +1,4 @@
export * from './datetime/timeago_utility';
export * from './datetime/date_format_utility';
export * from './datetime/date_calculation_utility';
export * from './datetime/pikaday_utility';
export * from './datetime/date_format_utility';
export * from './datetime/locale_dateformat';
export * from './datetime/timeago_utility';

View File

@ -1,4 +1,4 @@
import { parsePikadayDate } from '~/lib/utils/datetime_utility';
import { newDate } from '~/lib/utils/datetime_utility';
/**
* This method is to be used with `Array.prototype.sort` function
@ -14,8 +14,8 @@ import { parsePikadayDate } from '~/lib/utils/datetime_utility';
export function sortMilestonesByDueDate(milestoneA, milestoneB) {
const rawDueDateA = milestoneA.due_date || milestoneA.dueDate;
const rawDueDateB = milestoneB.due_date || milestoneB.dueDate;
const dueDateA = rawDueDateA ? parsePikadayDate(rawDueDateA) : null;
const dueDateB = rawDueDateB ? parsePikadayDate(rawDueDateB) : null;
const dueDateA = rawDueDateA ? newDate(rawDueDateA) : null;
const dueDateB = rawDueDateB ? newDate(rawDueDateB) : null;
const expiredA = milestoneA.expired || Date.now() > dueDateA?.getTime();
const expiredB = milestoneB.expired || Date.now() > dueDateB?.getTime();

View File

@ -6,7 +6,6 @@ import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import { MODEL_ENTITIES } from '~/ml/model_registry/constants';
import ModelVersionList from '~/ml/model_registry/components/model_version_list.vue';
import CandidateList from '~/ml/model_registry/components/candidate_list.vue';
import ModelDetail from '~/ml/model_registry/components/model_detail.vue';
import ModelVersionCreate from '~/ml/model_registry/components/model_version_create.vue';
import ActionsDropdown from '~/ml/model_registry/components/actions_dropdown.vue';
@ -21,7 +20,6 @@ import ModelEdit from '../components/model_edit.vue';
const ROUTE_DETAILS = 'details';
const ROUTE_VERSIONS = 'versions';
const ROUTE_CANDIDATES = 'candidates';
const deletionSuccessfulAlert = {
id: 'ml-model-deleted-successfully',
@ -40,11 +38,6 @@ const routes = [
name: ROUTE_VERSIONS,
component: ModelVersionList,
},
{
path: '/candidates',
name: ROUTE_CANDIDATES,
component: CandidateList,
},
{ path: '*', redirect: { name: ROUTE_DETAILS } },
];
@ -142,9 +135,6 @@ export default {
versionCount() {
return this.model?.versionCount || 0;
},
candidateCount() {
return this.model?.candidateCount || 0;
},
tabIndex() {
return routes.findIndex(({ name }) => name === this.$route.name);
},
@ -185,7 +175,6 @@ export default {
modelVersionEntity: MODEL_ENTITIES.modelVersion,
ROUTE_DETAILS,
ROUTE_VERSIONS,
ROUTE_CANDIDATES,
};
</script>
@ -229,12 +218,6 @@ export default {
<gl-badge class="gl-tab-counter-badge">{{ versionCount }}</gl-badge>
</template>
</gl-tab>
<gl-tab @click="goTo($options.ROUTE_CANDIDATES)">
<template #title>
{{ s__('MlModelRegistry|Version candidates') }}
<gl-badge class="gl-tab-counter-badge">{{ candidateCount }}</gl-badge>
</template>
</gl-tab>
<router-view :model-id="model.id" :model="model" />
</gl-tabs>

View File

@ -1,5 +1,6 @@
<script>
import { GlEmptyState, GlSprintf, GlLink, GlSkeletonLoader } from '@gitlab/ui';
import EmptyResult from '~/vue_shared/components/empty_result.vue';
import HarborListHeader from '~/packages_and_registries/harbor_registry/components/list/harbor_list_header.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import HarborList from '~/packages_and_registries/harbor_registry/components/list/harbor_list.vue';
@ -37,6 +38,7 @@ export default {
GlEmptyState,
GlSprintf,
GlLink,
EmptyResult,
PersistedSearch,
CliCommands: () =>
import(
@ -164,7 +166,6 @@ export default {
v-if="showConnectionError"
:title="$options.i18n.connectionErrorTitle"
:svg-path="containersErrorImage"
:svg-height="null"
>
<template #description>
<p>
@ -218,10 +219,10 @@ export default {
@prev-page="fetchPrevPage"
@next-page="fetchNextPage"
/>
<empty-result v-else-if="name" data-testid="emptySearch" />
<gl-empty-state
v-else
:svg-path="noContainersImage"
:svg-height="null"
data-testid="emptySearch"
:title="emptyStateTexts.title"
>

View File

@ -1,6 +1,6 @@
<script>
import { GlFormRadio } from '@gitlab/ui';
import { dateInWords, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { dateInWords, newDate } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import { dateFields } from '../../constants';
import SidebarFormattedDate from './sidebar_formatted_date.vue';
@ -45,7 +45,7 @@ export default {
return this.$options.i18n.noDate;
}
return dateInWords(parsePikadayDate(dateFixed), true);
return dateInWords(newDate(dateFixed), true);
},
formattedInheritedDate() {
const dateFromMilestones = this.issuable[dateFields[this.dateType].dateFromMilestones];
@ -53,7 +53,7 @@ export default {
return this.$options.i18n.noDate;
}
return dateInWords(parsePikadayDate(dateFromMilestones), true);
return dateInWords(newDate(dateFromMilestones), true);
},
},
i18n: {

View File

@ -85,6 +85,9 @@ export default {
linkedMergeRequests() {
return this.workItemDevelopment?.closingMergeRequests?.nodes || [];
},
featureFlags() {
return this.workItemDevelopment?.featureFlags?.nodes || [];
},
shouldShowEmptyState() {
return this.isRelatedDevelopmentListEmpty ? this.workItemsAlphaEnabled : true;
},
@ -92,7 +95,7 @@ export default {
return this.workItemDevelopment && this.shouldShowEmptyState;
},
isRelatedDevelopmentListEmpty() {
return !this.error && this.linkedMergeRequests.length === 0;
return !this.error && this.linkedMergeRequests.length === 0 && this.featureFlags.length === 0;
},
showAutoCloseInformation() {
return (
@ -171,7 +174,11 @@ export default {
:aria-label="__('Add branch or merge request')"
/>
</div>
<template v-if="isRelatedDevelopmentListEmpty">
<work-item-development-relationship-list
v-if="!isRelatedDevelopmentListEmpty"
:work-item-dev-widget="workItemDevelopment"
/>
<template v-else>
<span v-if="!canUpdate" class="gl-text-secondary">{{ __('None') }}</span>
<template v-else>
<gl-button category="secondary" size="small" data-testid="create-mr-button">{{
@ -182,6 +189,5 @@ export default {
}}</gl-button>
</template>
</template>
<work-item-development-relationship-list v-else :work-item-dev-widget="workItemDevelopment" />
</div>
</template>

View File

@ -0,0 +1,49 @@
<script>
import { GlIcon, GlTooltip, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlIcon,
GlTooltip,
GlLink,
},
props: {
itemContent: {
type: Object,
required: true,
},
},
methods: {
icon({ active }) {
return active ? 'feature-flag' : 'feature-flag-disabled';
},
iconColor({ active }) {
return active ? 'gl-text-blue-500' : 'gl-text-gray-500';
},
flagStatus(flag) {
return flag.active ? __('Enabled') : __('Disabled');
},
},
};
</script>
<template>
<div
ref="flagInfo"
class="gl-grid-cols-[auto, 1fr] gl-grid gl-w-fit gl-gap-2 gl-gap-5 gl-p-2 gl-pl-0 gl-pr-3"
>
<gl-link
:href="itemContent.path"
class="gl-truncate gl-text-primary hover:gl-text-primary hover:gl-underline"
>
<gl-icon :name="icon(itemContent)" :class="iconColor(itemContent)" />
{{ itemContent.name }}
</gl-link>
<gl-tooltip :target="() => $refs.flagInfo" placement="top">
<span class="gl-inline-block gl-font-bold"> {{ __('Feature flag') }} </span>
<span class="gl-inline-block">{{ itemContent.name }} {{ itemContent.reference }}</span>
<span class="gl-inline-block gl-text-secondary">{{ flagStatus(itemContent) }}</span>
</gl-tooltip>
</div>
</template>

View File

@ -22,20 +22,20 @@ export default {
GlTooltip: GlTooltipDirective,
},
props: {
mergeRequest: {
itemContent: {
type: Object,
required: true,
},
},
computed: {
assignees() {
return this.mergeRequest?.assignees?.nodes || [];
return this.itemContent?.assignees?.nodes || [];
},
stateIconClass() {
return {
'gl-text-green-500': this.mergeRequest.state === STATUS_OPEN,
'gl-text-red-500': this.mergeRequest.state === STATUS_CLOSED,
'gl-text-blue-500': this.mergeRequest.state === STATUS_MERGED,
'gl-text-green-500': this.itemContent.state === STATUS_OPEN,
'gl-text-red-500': this.itemContent.state === STATUS_CLOSED,
'gl-text-blue-500': this.itemContent.state === STATUS_MERGED,
};
},
stateIcon() {
@ -44,7 +44,7 @@ export default {
[STATUS_MERGED]: 'merge',
[STATUS_CLOSED]: 'merge-request-close',
};
return stateIcons[this.mergeRequest.state];
return stateIcons[this.itemContent.state];
},
assigneesCollapsedTooltip() {
if (this.assignees.length > 2) {
@ -55,7 +55,7 @@ export default {
return '';
},
projectPath() {
return `${this.mergeRequest.project.namespace.path}/${this.mergeRequest.project.name}`;
return `${this.itemContent.project.namespace.path}/${this.itemContent.project.name}`;
},
},
};
@ -63,15 +63,15 @@ export default {
<template>
<div class="gl-mb-2 gl-flex gl-items-center gl-justify-between gl-gap-2">
<gl-link
:href="mergeRequest.webUrl"
:href="itemContent.webUrl"
class="gfm-merge_request gl-truncate gl-text-gray-900 hover:gl-text-gray-900 hover:gl-underline"
data-reference-type="merge_request"
:data-project-path="projectPath"
:data-iid="mergeRequest.iid"
:data-mr-title="mergeRequest.title"
:data-iid="itemContent.iid"
:data-mr-title="itemContent.title"
data-placement="left"
>
<gl-icon :name="stateIcon" :class="stateIconClass" /> {{ mergeRequest.title }}
<gl-icon :name="stateIcon" :class="stateIconClass" /> {{ itemContent.title }}
</gl-link>
<gl-avatars-inline
v-if="assignees.length"

View File

@ -2,8 +2,10 @@
import { GlButton } from '@gitlab/ui';
import { sprintf, __ } from '~/locale';
import { renderGFM } from '~/behaviors/markdown/render_gfm';
import { TYPENAME_FEATURE_FLAG } from '~/graphql_shared/constants';
import WorkItemDevelopmentMrItem from './work_item_development_mr_item.vue';
import WorkItemDevelopmentFfItem from './work_item_development_ff_item.vue';
const DEFAULT_RENDER_COUNT = 3;
@ -25,12 +27,22 @@ export default {
},
computed: {
list() {
// keeping as a separate prop , will be appending with FF and branches
return [...this.mergeRequests];
// keeping as a separate prop, will be appending with branches
return [...this.sortedFeatureFlags, ...this.mergeRequests];
},
mergeRequests() {
return this.workItemDevWidget.closingMergeRequests?.nodes || [];
},
featureFlags() {
return this.workItemDevWidget.featureFlags?.nodes || [];
},
sortedFeatureFlags() {
const flagsSortedByRelationshipDate = [...this.featureFlags].reverse();
const enabledFlags = flagsSortedByRelationshipDate.filter((flag) => flag.active);
const disabledFlags = flagsSortedByRelationshipDate.filter((flag) => !flag.active);
return [...enabledFlags, ...disabledFlags];
},
hiddenItemsLabel() {
const { moreCount } = this;
return sprintf(__('+ %{moreCount} more'), { moreCount });
@ -53,11 +65,24 @@ export default {
},
methods: {
itemComponent(item) {
return this.isMergeRequest(item) ? WorkItemDevelopmentMrItem : 'li';
let component;
if (this.isMergeRequest(item)) {
component = WorkItemDevelopmentMrItem;
} else if (this.isFeatureFlag(item)) {
component = WorkItemDevelopmentFfItem;
} else {
component = 'li';
}
return component;
},
isMergeRequest(item) {
return item.fromMrDescription !== undefined;
},
isFeatureFlag(item) {
// eslint-disable-next-line no-underscore-dangle
return item.__typename === TYPENAME_FEATURE_FLAG;
},
async toggleShowLess() {
this.showLess = !this.showLess;
await this.$nextTick();
@ -76,7 +101,7 @@ export default {
<div>
<ul ref="list-body" class="gl-m-0 gl-list-none gl-p-0" data-testid="work-item-dev-items-list">
<li v-for="item in uncollapsedItems" :key="itemId(item)" class="gl-mr-3">
<component :is="itemComponent(item)" :merge-request="itemObject(item)" />
<component :is="itemComponent(item)" :item-content="itemObject(item)" />
</li>
</ul>
<gl-button

View File

@ -55,12 +55,6 @@
}
:root.gl-dark {
.terms {
.logo-text {
fill: var(--black);
}
}
.md :not(pre.code) > code {
background-color: $gray-200;
}

View File

@ -95,15 +95,12 @@ module AppearancesHelper
end
def brand_header_logo(options = {})
add_gitlab_white_text = options[:add_gitlab_white_text] || false
add_gitlab_black_text = options[:add_gitlab_black_text] || false
add_gitlab_logo_text = options[:add_gitlab_logo_text] || false
if current_appearance&.header_logo?
image_tag current_appearance.header_logo_path, class: 'brand-header-logo', alt: ''
elsif add_gitlab_white_text
render partial: 'shared/logo_with_white_text', formats: :svg
elsif add_gitlab_black_text
render partial: 'shared/logo_with_black_text', formats: :svg
elsif add_gitlab_logo_text
render partial: 'shared/logo_with_text', formats: :svg
else
render partial: 'shared/logo', formats: :svg
end

View File

@ -13,8 +13,8 @@ module ContainerRegistry
help_page_path: help_page_path('user/packages/container_registry/index.md'),
two_factor_auth_help_link: help_page_path('user/profile/account/two_factor_authentication.md'),
personal_access_tokens_help_link: help_page_path('user/profile/personal_access_tokens.md'),
no_containers_image: image_path('illustrations/docker-empty-state.svg'),
containers_error_image: image_path('illustrations/docker-error-state.svg'),
no_containers_image: image_path('illustrations/status/status-nothing-md.svg'),
containers_error_image: image_path('illustrations/status/status-fail-md.svg'),
repository_url: escape_once(project.container_registry_url),
registry_host_url_with_port: escape_once(registry_config.host_port),
expiration_policy_help_page_path:

View File

@ -516,14 +516,7 @@ module Ci
end
def ensure_manager(system_xid, &blk)
# rubocop: disable Performance/ActiveRecordSubtransactionMethods -- This is used only in API endpoints outside of transactions
RunnerManager.safe_find_or_create_by!(
runner_id: id,
runner_type: runner_type,
sharding_key_id: sharding_key_id,
system_xid: system_xid.to_s,
&blk)
# rubocop: enable Performance/ActiveRecordSubtransactionMethods
RunnerManager.safe_find_or_create_by!(runner_id: id, system_xid: system_xid.to_s, &blk) # rubocop: disable Performance/ActiveRecordSubtransactionMethods
end
def registration_available?

View File

@ -43,8 +43,6 @@ module Ci
finished: 100
}, _suffix: true
enum runner_type: Runner.runner_types
has_many :runner_manager_builds, inverse_of: :runner_manager, foreign_key: :runner_machine_id,
class_name: 'Ci::RunnerManagerBuild'
has_many :builds, through: :runner_manager_builds, class_name: 'Ci::Build'
@ -52,9 +50,7 @@ module Ci
class_name: 'Ci::RunnerVersion'
validates :runner, presence: true
validates :runner_type, presence: true, on: :create
validates :system_xid, presence: true, length: { maximum: 64 }
validates :sharding_key_id, presence: true, on: :create, unless: :instance_type?
validates :version, length: { maximum: 2048 }
validates :revision, length: { maximum: 255 }
validates :platform, length: { maximum: 255 }
@ -62,10 +58,6 @@ module Ci
validates :ip_address, length: { maximum: 1024 }
validates :config, json_schema: { filename: 'ci_runner_config' }
validate :no_sharding_key_id, if: :instance_type?
before_validation :copy_runner_fields
cached_attr_reader :version, :revision, :platform, :architecture, :ip_address, :contacted_at, :executor_type
# The `STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner manager
@ -181,19 +173,6 @@ module Ci
Ci::Runners::ProcessRunnerVersionUpdateWorker.perform_async(new_version)
end
def copy_runner_fields
return unless runner
self.runner_type = runner.runner_type
self.sharding_key_id = runner.sharding_key_id
end
def no_sharding_key_id
return if sharding_key_id.nil?
errors.add(:runner_manager, 'cannot have sharding_key_id assigned')
end
def self.version_regex_expression_for_version(version)
case version
when /\d+\.\d+\.\d+/

View File

@ -1,8 +1,8 @@
- page_title _("Harbor Registry")
#js-harbor-registry-list-group{ data: { endpoint: group_harbor_repositories_path(@group),
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"no_containers_image" => image_path('illustrations/status/status-nothing-md.svg'),
"containers_error_image" => image_path('illustrations/status/status-fail-md.svg'),
"repository_url" => @group.harbor_integration.hostname,
"harbor_integration_project_name" => @group.harbor_integration.project_name,
full_path: @group.full_path,

View File

@ -6,8 +6,8 @@
"help_page_path" => help_page_path('user/packages/container_registry/index.md'),
"two_factor_auth_help_link" => help_page_path('user/profile/account/two_factor_authentication.md'),
"personal_access_tokens_help_link" => help_page_path('user/profile/personal_access_tokens.md'),
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"no_containers_image" => image_path('illustrations/status/status-nothing-md.svg'),
"containers_error_image" => image_path('illustrations/status/status-fail-md.svg'),
"registry_host_url_with_port" => escape_once(registry_config.host_port),
"garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry.md', anchor: 'container-registry-garbage-collection'),
"run_cleanup_policies_help_page_path" => help_page_path('administration/packages/container_registry.md', anchor: 'run-the-cleanup-policy-now'),

View File

@ -1,5 +1,5 @@
%header.navbar.fixed-top.navbar-gitlab.justify-content-center
.gl-hidden.lg:gl-block
= render partial: 'shared/logo_with_white_text', formats: :svg
= render partial: 'shared/logo_with_text', formats: :svg
.lg:gl-hidden
= render partial: 'shared/logo', formats: :svg

View File

@ -12,7 +12,7 @@
.limit-container-width.gl-my-5{ class: container_class }
= render Pajamas::CardComponent.new( body_options: { class: 'gl-p-0' }, header_options: { class: 'gl-flex gl-items-center gl-justify-between' }) do |c|
- c.with_header do
= brand_header_logo({add_gitlab_black_text: true})
= brand_header_logo({add_gitlab_logo_text: true})
- if current_user
.gl-flex.gl-gap-2.gl-items-center
.gl-text-right.gl-leading-normal

View File

@ -1,8 +1,8 @@
- page_title _("Harbor Registry")
#js-harbor-registry-list-project{ data: { endpoint: project_harbor_repositories_path(@project),
"no_containers_image" => image_path('illustrations/docker-empty-state.svg'),
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"no_containers_image" => image_path('illustrations/status/status-nothing-md.svg'),
"containers_error_image" => image_path('illustrations/status/status-fail-md.svg'),
"repository_url" => @project.harbor_integration.hostname,
"harbor_integration_project_name" => @project.harbor_integration.project_name,
"project_name" => @project.name,

View File

@ -1,12 +0,0 @@
<svg aria-hidden="true" class="tanuki-logo" width="111" height="24" viewBox="0 0 111 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="logo-text" d="M44.814 9.042h3.645c-.608-3.875-3.963-6.574-8.33-6.574-5.166 0-9.043 3.798-9.043 10.16 0 6.248 3.703 10.123 9.15 10.123 4.887 0 8.386-3.144 8.386-8.234v-2.37h-8.01v2.794h4.55c-.058 2.816-1.938 4.599-4.908 4.599-3.305 0-5.57-2.477-5.57-6.95 0-4.445 2.303-6.913 5.494-6.913 2.38 0 4.01 1.272 4.636 3.365Zm6.218 13.438h3.49V7.68h-3.49v14.8Zm1.76-17.151c1.109 0 2.014-.85 2.014-1.89s-.905-1.9-2.014-1.9c-1.109 0-2.024.849-2.024 1.9s.9 1.89 2.017 1.89h.007ZM64.971 7.68H62.05V4.126h-3.49v3.556h-2.1v2.699h2.1v8.233c-.018 2.786 2.007 4.16 4.628 4.079a7.089 7.089 0 0 0 2.055-.348l-.59-2.73a4.247 4.247 0 0 1-1.02.137c-.878 0-1.582-.309-1.582-1.717v-7.662h2.921V7.68Zm2.701 14.8h12.272v-2.998H71.25V2.737h-3.578V22.48Zm18.957.3c2.323 0 3.71-1.09 4.347-2.333h.115v2.033h3.36v-9.91c0-3.913-3.19-5.09-6.016-5.09-3.113 0-5.504 1.388-6.275 4.087l3.26.464c.345-1.013 1.329-1.88 3.04-1.88 1.62 0 2.506.829 2.506 2.285v.057c0 1.002-1.05 1.051-3.664 1.33-2.872.309-5.619 1.166-5.619 4.502-.01 2.912 2.12 4.455 4.946 4.455Zm1.147-2.56c-1.456 0-2.498-.666-2.498-1.948 0-1.34 1.167-1.899 2.72-2.121.917-.125 2.75-.357 3.2-.722v1.744c.01 1.643-1.321 3.042-3.422 3.042v.005Zm9.244 2.26h3.433v-2.332h.201c.551 1.08 1.698 2.593 4.244 2.593 3.489 0 6.102-2.768 6.102-7.644 0-4.936-2.69-7.616-6.112-7.616-2.613 0-3.702 1.57-4.234 2.641h-.147V2.737h-3.486V22.48Zm3.423-7.403c0-2.88 1.234-4.734 3.48-4.734 2.323 0 3.52 1.976 3.52 4.734 0 2.759-1.214 4.8-3.52 4.8-2.227 0-3.48-1.928-3.48-4.8Z"
fill="#171321"/>
<path class="tanuki-shape tanuki" d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
fill="#E24329"/>
<path class="tanuki-shape right-cheek" d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
fill="#FC6D26"/>
<path class="tanuki-shape chin" d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584Z"
fill="#FCA326"/>
<path class="tanuki-shape left-cheek" d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632Z"
fill="#FC6D26"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,17 @@
<svg aria-hidden="true" class="tanuki-logo" width="111" height="24" viewBox="0 0 111 24" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path class="gl-fill-current"
d="M44.814 9.042h3.645c-.608-3.875-3.963-6.574-8.33-6.574-5.166 0-9.043 3.798-9.043 10.16 0 6.248 3.703 10.123 9.15 10.123 4.887 0 8.386-3.144 8.386-8.234v-2.37h-8.01v2.794h4.55c-.058 2.816-1.938 4.599-4.908 4.599-3.305 0-5.57-2.477-5.57-6.95 0-4.445 2.303-6.913 5.494-6.913 2.38 0 4.01 1.272 4.636 3.365Zm6.218 13.438h3.49V7.68h-3.49v14.8Zm1.76-17.151c1.109 0 2.014-.85 2.014-1.89s-.905-1.9-2.014-1.9c-1.109 0-2.024.849-2.024 1.9s.9 1.89 2.017 1.89h.007ZM64.971 7.68H62.05V4.126h-3.49v3.556h-2.1v2.699h2.1v8.233c-.018 2.786 2.007 4.16 4.628 4.079a7.089 7.089 0 0 0 2.055-.348l-.59-2.73a4.247 4.247 0 0 1-1.02.137c-.878 0-1.582-.309-1.582-1.717v-7.662h2.921V7.68Zm2.701 14.8h12.272v-2.998H71.25V2.737h-3.578V22.48Zm18.957.3c2.323 0 3.71-1.09 4.347-2.333h.115v2.033h3.36v-9.91c0-3.913-3.19-5.09-6.016-5.09-3.113 0-5.504 1.388-6.275 4.087l3.26.464c.345-1.013 1.329-1.88 3.04-1.88 1.62 0 2.506.829 2.506 2.285v.057c0 1.002-1.05 1.051-3.664 1.33-2.872.309-5.619 1.166-5.619 4.502-.01 2.912 2.12 4.455 4.946 4.455Zm1.147-2.56c-1.456 0-2.498-.666-2.498-1.948 0-1.34 1.167-1.899 2.72-2.121.917-.125 2.75-.357 3.2-.722v1.744c.01 1.643-1.321 3.042-3.422 3.042v.005Zm9.244 2.26h3.433v-2.332h.201c.551 1.08 1.698 2.593 4.244 2.593 3.489 0 6.102-2.768 6.102-7.644 0-4.936-2.69-7.616-6.112-7.616-2.613 0-3.702 1.57-4.234 2.641h-.147V2.737h-3.486V22.48Zm3.423-7.403c0-2.88 1.234-4.734 3.48-4.734 2.323 0 3.52 1.976 3.52 4.734 0 2.759-1.214 4.8-3.52 4.8-2.227 0-3.48-1.928-3.48-4.8Z" />
<path class="tanuki-shape tanuki"
d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
fill="#E24329" />
<path class="tanuki-shape right-cheek"
d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
fill="#FC6D26" />
<path class="tanuki-shape chin"
d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584Z"
fill="#FCA326" />
<path class="tanuki-shape left-cheek"
d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632Z"
fill="#FC6D26" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,12 +0,0 @@
<svg aria-hidden="true" class="tanuki-logo" width="111" height="24" viewBox="0 0 111 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="logo-text" d="M44.814 9.042h3.645c-.608-3.875-3.963-6.574-8.33-6.574-5.166 0-9.043 3.798-9.043 10.16 0 6.248 3.703 10.123 9.15 10.123 4.887 0 8.386-3.144 8.386-8.234v-2.37h-8.01v2.794h4.55c-.058 2.816-1.938 4.599-4.908 4.599-3.305 0-5.57-2.477-5.57-6.95 0-4.445 2.303-6.913 5.494-6.913 2.38 0 4.01 1.272 4.636 3.365Zm6.218 13.438h3.49V7.68h-3.49v14.8Zm1.76-17.151c1.109 0 2.014-.85 2.014-1.89s-.905-1.9-2.014-1.9c-1.109 0-2.024.849-2.024 1.9s.9 1.89 2.017 1.89h.007ZM64.971 7.68H62.05V4.126h-3.49v3.556h-2.1v2.699h2.1v8.233c-.018 2.786 2.007 4.16 4.628 4.079a7.089 7.089 0 0 0 2.055-.348l-.59-2.73a4.247 4.247 0 0 1-1.02.137c-.878 0-1.582-.309-1.582-1.717v-7.662h2.921V7.68Zm2.701 14.8h12.272v-2.998H71.25V2.737h-3.578V22.48Zm18.957.3c2.323 0 3.71-1.09 4.347-2.333h.115v2.033h3.36v-9.91c0-3.913-3.19-5.09-6.016-5.09-3.113 0-5.504 1.388-6.275 4.087l3.26.464c.345-1.013 1.329-1.88 3.04-1.88 1.62 0 2.506.829 2.506 2.285v.057c0 1.002-1.05 1.051-3.664 1.33-2.872.309-5.619 1.166-5.619 4.502-.01 2.912 2.12 4.455 4.946 4.455Zm1.147-2.56c-1.456 0-2.498-.666-2.498-1.948 0-1.34 1.167-1.899 2.72-2.121.917-.125 2.75-.357 3.2-.722v1.744c.01 1.643-1.321 3.042-3.422 3.042v.005Zm9.244 2.26h3.433v-2.332h.201c.551 1.08 1.698 2.593 4.244 2.593 3.489 0 6.102-2.768 6.102-7.644 0-4.936-2.69-7.616-6.112-7.616-2.613 0-3.702 1.57-4.234 2.641h-.147V2.737h-3.486V22.48Zm3.423-7.403c0-2.88 1.234-4.734 3.48-4.734 2.323 0 3.52 1.976 3.52 4.734 0 2.759-1.214 4.8-3.52 4.8-2.227 0-3.48-1.928-3.48-4.8Z"
fill="#fff"/>
<path class="tanuki-shape tanuki" d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
fill="#E24329"/>
<path class="tanuki-shape right-cheek" d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
fill="#FC6D26"/>
<path class="tanuki-shape chin" d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584Z"
fill="#FCA326"/>
<path class="tanuki-shape left-cheek" d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632Z"
fill="#FC6D26"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,10 +0,0 @@
---
migration_job_name: BackfillRunnerTypeAndShardingKeyIdOnCiRunnerManagers
description: >
Backfills the `runner_type` and `sharding_key_id` columns from `ci_runners`.
The `sharding_key_id` column will serve as the sharding key in the future partitioned (by `runner_type`) table.
feature_category: runners
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/166916
milestone: '17.5'
queued_migration_version: 20241003110148
finalized_by: # version of the migration that finalized this BBM

View File

@ -5,23 +5,11 @@ class QueueBackfillRunnerTypeAndShardingKeyIdOnCiRunnerManagers < Gitlab::Databa
restrict_gitlab_migration gitlab_schema: :gitlab_ci
MIGRATION = 'BackfillRunnerTypeAndShardingKeyIdOnCiRunnerManagers'
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:ci_runner_machines,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
# no-op
end
def down
delete_batched_background_migration(MIGRATION, :ci_runner_machines, :id, [])
# no-op
end
end

View File

@ -0,0 +1,120 @@
---
stage: Create
group: Editor Extensions
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Custom queries in the VS Code extension
This extension adds a **GitLab Workflow**
[sidebar](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main?ref_type=heads#sidebar-details)
to VS Code. This sidebar shows default search queries for each of your projects:
- Issues assigned to me
- Issues created by me
- Merge requests assigned to me
- Merge requests created by me
- Merge requests I'm reviewing
## View search query results in VS Code
Prerequisites:
- You're a member of a GitLab project.
- You've [installed the extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow).
- You've signed in to your GitLab instance, as described in [Setup](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main/#setup).
To see search results from your project:
1. On the left vertical menu bar, select **GitLab Workflow** (**{tanuki}**) to display the extension sidebar.
1. On the sidebar, expand **Issues and merge requests**.
1. Select a project to view its queries, then select the query you want to run.
1. Below the query title, select the search result you want to see.
1. If your search result is a merge request, select what you want to view in VS Code:
- **Overview**: the description, status, and any comments on this merge request.
- The **filenames** of all files changed in this merge request. Select a file to view a diff
of its changes.
1. If your search result is an issue, select it to view its description, history, and comments in VS Code.
## Create a custom query
Any custom queries you define override the default queries shown in the
[VS Code sidebar](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/tree/main?ref_type=heads#sidebar-details),
under **Issues and Merge requests**.
To override the extension's default queries and replace them with your own:
1. In VS Code, on the top bar, go to **Code > Preferences > Settings**.
1. On the top right corner, select **Open Settings (JSON)** to edit your `settings.json` file.
1. In the file, define `gitlab.customQueries`, like in this example. Each query should be an entry
in the `gitlab.customQueries` JSON array:
```json
{
"gitlab.customQueries": [
{
"name": "Issues assigned to me",
"type": "issues",
"scope": "assigned_to_me",
"noItemText": "No issues assigned to you.",
"state": "opened"
}
]
}
```
1. Optional. When you customize `gitlab.customQueries`, your definition overrides all default queries.
To restore any of the default queries, copy them from the `default` array in the extension's
[`desktop.package.json` file](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/8e4350232154fe5bf0ef8a6c0765b2eac0496dc7/desktop.package.json#L955-998).
1. Save your changes.
### Supported parameters for all queries
Not all item types support all parameters. These parameters apply to all query types:
| Parameter | Required | Default | Definition |
|--------------|----------|-------------------|------------|
| `name` | **{check-circle}** Yes | not applicable | The label to show in the GitLab panel. |
| `noItemText` | **{dotted-circle}** No | `No items found.` | The text to show if the query returns no items. |
| `type` | **{dotted-circle}** No | `merge_requests` | Which item types to return. Possible values: `issues`, `merge_requests`, `epics`, `snippets`, `vulnerabilities`. Snippets [don't support](../../api/project_snippets.md) any other filters. Epics are available only on GitLab Premium and Ultimate.|
### Supported parameters for issue, epic, and merge request queries
| Parameter | Required | Default | Definition |
|--------------------|------------------------|--------------|------------|
| `assignee` | **{dotted-circle}** No | not applicable | Return items assigned to the given username. `None` returns unassigned GitLab items. `Any` returns GitLab items with an assignee. Not available for epics and vulnerabilities. |
| `author` | **{dotted-circle}** No | not applicable | Return items created by the given username. |
| `confidential` | **{dotted-circle}** No | not applicable | Filter confidential or public issues. Available only for issues. |
| `createdAfter` | **{dotted-circle}** No | not applicable | Return items created after the given date. |
| `createdBefore` | **{dotted-circle}** No | not applicable | Return items created before the given date. |
| `draft` | **{dotted-circle}** No | `no` | Filter merge requests against their draft status: `yes` returns only merge requests in [draft status](../../user/project/merge_requests/drafts.md), `no` returns only merge requests not in draft status. Available only for merge requests. |
| `excludeAssignee` | **{dotted-circle}** No | not applicable | Return items not assigned to the given username. Available only for issues. For the current user, set to `<current_user>`. |
| `excludeAuthor` | **{dotted-circle}** No | not applicable | Return items not created by the given username. Available only for issues. For the current user, set to `<current_user>`. |
| `excludeLabels` | **{dotted-circle}** No | `[]` | Array of label names. Available only for issues. To be returned, items must have none of the labels in the array. Predefined names are case-insensitive. |
| `excludeMilestone` | **{dotted-circle}** No | not applicable | The milestone title to exclude. Available only for issues. |
| `excludeSearch` | **{dotted-circle}** No | not applicable | Search GitLab items that doesn't have the search key in their title or description. Works only with issues. |
| `labels` | **{dotted-circle}** No | `[]` | Array of label names. To be returned, items must have all labels in the array. `None` returns items with no labels. `Any` returns items with at least one label. Predefined names are case-insensitive. |
| `maxResults` | **{dotted-circle}** No | 20 | The number of results to show. |
| `milestone` | **{dotted-circle}** No | not applicable | The milestone title. `None` lists all items with no milestone. `Any` lists all items with an assigned milestone. Not available for epics and vulnerabilities. |
| `orderBy` | **{dotted-circle}** No | `created_at` | Return entities ordered by the selected value. Possible values: `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight`. Some values are specific to issues, and some to merge requests. For more information, see [List merge requests](../../api/merge_requests.md#list-merge-requests). |
| `reviewer` | **{dotted-circle}** No | not applicable | Return merge requests assigned for review to this username. For the current user, set to `<current_user>`. `None` returns items without a reviewer. `Any` returns items with a reviewer. |
| `scope` | **{dotted-circle}** No | `all` | Return GitLab items for the given scope. Not applicable for epics. Possible values: `assigned_to_me`, `created_by_me`, `all`. |
| `search` | **{dotted-circle}** No | not applicable | Search GitLab items against their title and description. |
| `searchIn` | **{dotted-circle}** No | `all` | Change the scope of the `excludeSearch` search attribute. Possible values: `all`, `title`, `description`. Works only with issues. |
| `sort` | **{dotted-circle}** No | `desc` | Return issues sorted in ascending or descending order. Possible values: `asc`, `desc`. |
| `state` | **{dotted-circle}** No | `opened` | Return all issues, or only those matching a particular state. Possible values: `all`, `opened`, `closed`. |
| `updatedAfter` | **{dotted-circle}** No | not applicable | Return items updated after the given date. |
| `updatedBefore` | **{dotted-circle}** No | not applicable | Return items updated before the given date. |
### Supported parameters for vulnerability report queries
Vulnerability reports don't share
[any common query parameters](../../api/vulnerability_findings.md)
with other entry types. Each parameter listed in this table works with vulnerability reports only:
| Parameter | Required | Default | Definition |
|--------------------|------------------------|----------------|------------|
| `confidenceLevels` | **{dotted-circle}** No | `all` | Returns vulnerabilities belonging to specified confidence levels. Possible values: `undefined`, `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, `confirmed`. |
| `reportTypes` | **{dotted-circle}** No | Not applicable | Returns vulnerabilities belonging to specified report types. Possible values: `sast`, `dast`, `dependency_scanning`, `container_scanning`. |
| `scope` | **{dotted-circle}** No | `dismissed` | Returns vulnerability findings for the given scope. Possible values: `all`, `dismissed`. For more information, see the [Vulnerability findings API](../../api/vulnerability_findings.md). |
| `severityLevels` | **{dotted-circle}** No | `all` | Returns vulnerabilities belonging to specified severity levels. Possible values: `undefined`, `info`, `unknown`, `low`, `medium`, `high`, `critical`. |

View File

@ -0,0 +1,62 @@
---
stage: Create
group: Editor Extensions
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# GitLab remote URL format
In VS Code, you can browse GitLab repositories
[in read-only mode](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/main/README.md#browse-a-repository-without-cloning)
with a custom remote URL.
GitLab remote URLs require these parameters:
- `instanceUrl`: The GitLab instance URL, not including `https://` or `http://`.
- If the GitLab instance [uses a relative URL](../../install/relative_url.md), include the relative URL in the URL.
- For example, the URL for the `main` branch of the project `templates/ui` on the instance `example.com/gitlab` is
`gitlab-remote://example.com/gitlab/<label>?project=templates/ui&ref=main`.
- `label`: The text Visual Studio Code uses as the name of this workspace folder:
- It must appear immediately after the instance URL.
- It can't contain unescaped URL components, such as `/` or `?`.
- For an instance installed at the domain root, such as `https://gitlab.com`, the label must be the first path element.
- For URLs that refer to the root of a repository, the label must be the last path element.
- VS Code treats any path elements that appear after the label as a path inside the repository. For example,
`gitlab-remote://gitlab.com/GitLab/app?project=gitlab-org/gitlab&ref=master` refers to the `app` directory of
the `gitlab-org/gitlab` repository on GitLab.com.
- `projectId`: Can be either the numeric ID (like `5261717`) or the namespace (`gitlab-org/gitlab-vscode-extension`) of the
project. If your instance uses a reverse proxy, specify `projectId` with the numeric ID. For more information, see
[issue 18775](https://gitlab.com/gitlab-org/gitlab/-/issues/18775).
- `gitReference`: The repository branch or commit SHA.
The parameters are then placed together in this order:
```plaintext
gitlab-remote://<INSTANCE_URL>/<LABEL>?project=<PROJECT_ID>&ref=<GIT_REFERENCE>
```
For example, the `projectID` for the main GitLab project is `278964`, so the remote URL for the main GitLab project is:
```plaintext
gitlab-remote://gitlab.com/<LABEL>?project=278964&ref=master
```
## Browse a repository in read-only mode
With this extension, you can browse a GitLab repository in read-only mode without cloning it.
Prerequisites:
- You have [registered an access token](https://gitlab.com/gitlab-org/gitlab-vscode-extension/#setup) for that GitLab instance.
To browse a GitLab repository in read-only mode:
1. Open the command palette by pressing <kbd>Control</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd>.
1. Run the **GitLab: Open Remote Repository** command.
1. Select **Open in current window**, **Open in new window**, or **Add to workspace**.
1. To add a repository, select `Enter gitlab-remote URL`, then enter the `gitlab-remote://` URL for your desired project.
1. To view a repository you've already added, select **Choose a project**, then select your desired project from the dropdown list.
1. In the dropdown list, select the Git branch you want to view, then press <kbd>Enter</kbd> to confirm.
To add a `gitlab-remote` URL to your workspace file, see
[Workspace file](https://code.visualstudio.com/docs/editor/multi-root-workspaces#_workspace-file) in the VS Code documentation.

View File

@ -13,16 +13,21 @@ DETAILS:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/368434) in GitLab 15.11.
> - Detection of personal access tokens with a custom prefix was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/411146) in GitLab 16.1. GitLab self-managed only.
When you create an issue or epic, propose a merge request, or write a comment, you might accidentally post a sensitive value.
For example, you might paste in the details of an API request or an environment variable that contains an authentication token.
When you create an issue, propose a merge request, or write a comment, you might accidentally post a
secret. For example, you might paste in the details of an API request or an environment variable
that contains an authentication token. If a secret is leaked it could be used to do harm.
When you edit the description or comment in an issue, epic, or merge request, GitLab checks if it contains a sensitive token.
If a token is found, a warning message is displayed. You can then edit your description or comment before posting it.
This check happens in your browser before the message is sent to the server.
The check is always on; you don't have to set it up.
Client-side secret detection helps to minimize the risk of that happening. When you edit the
description or comment in an issue or merge request, GitLab checks if it contains a secret. If a
secret is found, a warning message is displayed. You can then edit the description or comment to
remove the secret before posting your message, or add the description or comment as it is. This
check occurs in your browser, so the secret is not revealed to anyone else unless you add it to
GitLab. The check is always on; you don't have to set it up.
Your text is checked for the following secret types:
Client-side secret detection checks only the following for secrets:
- GitLab [personal access tokens](../../../../security/tokens/index.md#personal-access-tokens)
- If a [personal access token prefix](../../../../administration/settings/account_and_limit_settings.md#personal-access-token-prefix) has been configured, a token using this prefix is checked.
- GitLab [feed tokens](../../../../security/tokens/index.md#feed-token)
- Comments in issues or merge requests.
- Descriptions of issues or merge requests.
For details of which types of secrets are covered by client-side secret detection, see
[Detected secrets](../detected_secrets.md).

View File

@ -1,32 +0,0 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillRunnerTypeAndShardingKeyIdOnCiRunnerManagers < BatchedMigrationJob
operation_name :backfill_runner_type_and_sharding_key_on_ci_runner_machines
feature_category :runner
UPDATE_RUNNER_MANAGERS_SQL = <<~SQL
UPDATE ci_runner_machines
SET
sharding_key_id = ci_runners.sharding_key_id,
runner_type = ci_runners.runner_type
FROM ci_runners
WHERE ci_runner_machines.runner_id = ci_runners.id
AND ci_runner_machines.id IN (?);
SQL
class CiRunnerManager < ::Ci::ApplicationRecord
self.table_name = 'ci_runner_machines'
end
def perform
each_sub_batch do |sub_batch|
sub_batch = sub_batch.where(sharding_key_id: nil).limit(sub_batch_size).select(:id)
connection.exec_update(CiRunnerManager.sanitize_sql([UPDATE_RUNNER_MANAGERS_SQL, sub_batch]))
end
end
end
end
end

View File

@ -23125,6 +23125,9 @@ msgstr ""
msgid "Feature disabled"
msgstr ""
msgid "Feature flag"
msgstr ""
msgid "Feature flag status"
msgstr ""
@ -35082,9 +35085,6 @@ msgstr ""
msgid "MlModelRegistry|Using the MLflow client"
msgstr ""
msgid "MlModelRegistry|Version candidates"
msgstr ""
msgid "MlModelRegistry|Version description"
msgstr ""

View File

@ -7,11 +7,6 @@ FactoryBot.define do
creation_state { :finished }
after(:build) do |runner_manager, evaluator|
runner_manager.runner_type ||= evaluator.runner.runner_type
runner_manager.sharding_key_id ||= evaluator.runner.sharding_key_id
end
trait :unregistered do
contacted_at { nil }
creation_state { :started }

View File

@ -12,7 +12,7 @@ describe('date_picker behavior', () => {
beforeEach(() => {
pikadayMock = jest.spyOn(Pikaday, 'default');
parseMock = jest.spyOn(utils, 'parsePikadayDate');
parseMock = jest.spyOn(utils, 'newDate');
setHTMLFixture(`
<div>
<input class="datepicker" value="2020-10-01" />

View File

@ -316,12 +316,6 @@ describe('formatTime', () => {
});
describe('datefix', () => {
describe('parsePikadayDate', () => {
it('should return a UTC date', () => {
expect(datetimeUtility.parsePikadayDate('2020-01-29')).toEqual(new Date(2020, 0, 29));
});
});
describe('toISODateFormat', () => {
it('should format a Date object into yyyy-mm-dd format', () => {
expect(datetimeUtility.toISODateFormat(new Date('2020-01-29:00:00'))).toEqual('2020-01-29');

View File

@ -106,9 +106,7 @@ describe('ml/model_registry/apps/show_ml_model', () => {
const findVersionsCountBadge = () => findVersionsTab().findComponent(GlBadge);
const findModelVersionList = () => wrapper.findComponent(ModelVersionList);
const findModelDetail = () => wrapper.findComponent(ModelDetail);
const findCandidateTab = () => wrapper.findAllComponents(GlTab).at(2);
const findCandidateList = () => wrapper.findComponent(CandidateList);
const findCandidatesCountBadge = () => findCandidateTab().findComponent(GlBadge);
const findTitleArea = () => wrapper.findComponent(TitleArea);
const findVersionCountMetadataItem = () => findTitleArea().findComponent(MetadataItem);
const findActionsDropdown = () => wrapper.findComponent(ActionsDropdown);
@ -197,10 +195,6 @@ describe('ml/model_registry/apps/show_ml_model', () => {
it('shows the number of versions in the tab', () => {
expect(findVersionsCountBadge().text()).toBe(model.versionCount.toString());
});
it('shows the number of candidates in the tab', () => {
expect(findCandidatesCountBadge().text()).toBe(model.candidateCount.toString());
});
});
describe('Model loading', () => {
@ -245,17 +239,6 @@ describe('ml/model_registry/apps/show_ml_model', () => {
expect(findCandidateList().exists()).toBe(false);
});
it('shows candidate list when location hash is `#/candidates`', async () => {
await createWrapper({ mountFn: mountExtended });
await findCandidateTab().vm.$emit('click');
expect(findTabs().props('value')).toBe(2);
expect(findModelDetail().exists()).toBe(false);
expect(findModelVersionList().exists()).toBe(false);
expect(findCandidateList().props('modelId')).toBe(model.id);
});
describe.each`
location | tab | navigatedTo
${'#/'} | ${findDetailTab} | ${0}

View File

@ -0,0 +1,72 @@
import { GlLink, GlIcon, GlTooltip } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { workItemDevelopmentFeatureFlagNodes } from 'jest/work_items/mock_data';
import WorkItemDevelopmentFfItem from '~/work_items/components/work_item_development/work_item_development_ff_item.vue';
jest.mock('~/alert');
describe('WorkItemDevelopmentFfItem', () => {
let wrapper;
const enabledFeatureFlag = workItemDevelopmentFeatureFlagNodes[0];
const disabledFeatureFlag = workItemDevelopmentFeatureFlagNodes[1];
const createComponent = ({ featureFlag = enabledFeatureFlag }) => {
wrapper = shallowMount(WorkItemDevelopmentFfItem, {
propsData: {
itemContent: featureFlag,
},
});
};
const findFlagIcon = () => wrapper.findComponent(GlIcon);
const findFlagLink = () => wrapper.findComponent(GlLink);
const findFlagTooltip = () => wrapper.findComponent(GlTooltip);
describe('feature flag status icon', () => {
it.each`
state | icon | featureFlag | iconClass
${'Enabled'} | ${'feature-flag'} | ${enabledFeatureFlag} | ${'gl-text-blue-500'}
${'Disabled'} | ${'feature-flag-disabled'} | ${disabledFeatureFlag} | ${'gl-text-gray-500'}
`(
'renders icon "$icon" when the state of the feature flag is "$state"',
({ icon, iconClass, featureFlag }) => {
createComponent({ featureFlag });
expect(findFlagIcon().props('name')).toBe(icon);
expect(findFlagIcon().attributes('class')).toBe(iconClass);
},
);
});
describe('feature flag link and name', () => {
it('should render the flag path and name', () => {
createComponent({ featureFlag: enabledFeatureFlag });
expect(findFlagLink().attributes('href')).toBe(enabledFeatureFlag.path);
expect(findFlagLink().attributes('href')).toContain(`/edit`);
expect(findFlagLink().text()).toBe(enabledFeatureFlag.name);
});
});
describe('eature flag tooltip', () => {
it('should render the tooltip with flag name, reference and "Enabled" copy if active', () => {
createComponent({ featureFlag: enabledFeatureFlag });
expect(findFlagTooltip().exists()).toBe(true);
expect(findFlagTooltip().text()).toBe(
`Feature flag ${enabledFeatureFlag.name} ${enabledFeatureFlag.reference} Enabled`,
);
});
it('should render the tooltip with flag name, reference and "Disabled" copy if not active', () => {
createComponent({ featureFlag: disabledFeatureFlag });
expect(findFlagTooltip().exists()).toBe(true);
expect(findFlagTooltip().text()).toBe(
`Feature flag ${disabledFeatureFlag.name} ${disabledFeatureFlag.reference} Disabled`,
);
});
});
});

View File

@ -1,6 +1,6 @@
import { GlLink, GlIcon, GlAvatarsInline, GlAvatarLink, GlAvatar } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { workItemDevelopmentNodes } from 'jest/work_items/mock_data';
import { workItemDevelopmentMRNodes } from 'jest/work_items/mock_data';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { STATUS_OPEN, STATUS_CLOSED, STATUS_MERGED } from '~/issues/constants';
import WorkItemDevelopmentMRItem from '~/work_items/components/work_item_development/work_item_development_mr_item.vue';
@ -8,7 +8,7 @@ import WorkItemDevelopmentMRItem from '~/work_items/components/work_item_develop
describe('WorkItemDevelopmentMRItem', () => {
let wrapper;
const openMergeRequest = workItemDevelopmentNodes[0].mergeRequest;
const openMergeRequest = workItemDevelopmentMRNodes[0].mergeRequest;
const closedMergeRequest = {
...openMergeRequest,
state: STATUS_CLOSED,
@ -18,12 +18,12 @@ describe('WorkItemDevelopmentMRItem', () => {
state: STATUS_MERGED,
};
const mergeRequestWithNoAssignees = workItemDevelopmentNodes[1].mergeRequest;
const mergeRequestWithNoAssignees = workItemDevelopmentMRNodes[1].mergeRequest;
const createComponent = ({ mergeRequest = openMergeRequest, mountFn = shallowMount } = {}) => {
wrapper = mountFn(WorkItemDevelopmentMRItem, {
propsData: {
mergeRequest,
itemContent: mergeRequest,
},
});
};

View File

@ -6,12 +6,31 @@ import WorkItemDevelopmentRelationshipList from '~/work_items/components/work_it
describe('WorkItemDevelopmentRelationshipList', () => {
let wrapper;
const devWidgetWithLessNumberOfItems = {
const devWidgetWithTwoItems = {
...workItemDevelopmentFragmentResponse(),
closingMergeRequests: {
nodes: [workItemDevelopmentFragmentResponse().closingMergeRequests.nodes[0]],
__typename: 'WorkItemClosingMergeRequestConnection',
},
featureFlags: {
nodes: [workItemDevelopmentFragmentResponse().featureFlags.nodes[0]],
__typename: 'FeatureFlagConnection',
},
};
const devWidgetWithThreeItems = {
...workItemDevelopmentFragmentResponse(),
closingMergeRequests: {
nodes: [workItemDevelopmentFragmentResponse().closingMergeRequests.nodes[0]],
__typename: 'WorkItemClosingMergeRequestConnection',
},
featureFlags: {
nodes: [
workItemDevelopmentFragmentResponse().featureFlags.nodes[0],
workItemDevelopmentFragmentResponse().featureFlags.nodes[1],
],
__typename: 'FeatureFlagConnection',
},
};
const createComponent = ({ workItemDevWidget = workItemDevelopmentFragmentResponse() } = {}) => {
@ -36,8 +55,13 @@ describe('WorkItemDevelopmentRelationshipList', () => {
expect(findShowMoreButton().exists()).toBe(true);
});
it('should not render the more button when the number of items are more than 3', () => {
createComponent({ workItemDevWidget: devWidgetWithLessNumberOfItems });
it('should not render the more button when the number of items are exactly 3', () => {
createComponent({ workItemDevWidget: devWidgetWithThreeItems });
expect(findShowMoreButton().exists()).toBe(false);
});
it('should not render the more button when the number of items are less than 3', () => {
createComponent({ workItemDevWidget: devWidgetWithTwoItems });
expect(findShowMoreButton().exists()).toBe(false);
});
});

View File

@ -11,7 +11,7 @@ import { STATE_CLOSED, STATE_OPEN } from '~/work_items/constants';
import {
workItemResponseFactory,
workItemDevelopmentFragmentResponse,
workItemDevelopmentNodes,
workItemDevelopmentMRNodes,
} from 'jest/work_items/mock_data';
import WorkItemDevelopment from '~/work_items/components/work_item_development/work_item_development.vue';
@ -30,11 +30,15 @@ describe('WorkItemDevelopment CE', () => {
});
const workItemWithOneMR = workItemResponseFactory({
developmentWidgetPresent: true,
developmentItems: workItemDevelopmentFragmentResponse([workItemDevelopmentNodes[0]], true),
developmentItems: workItemDevelopmentFragmentResponse(
[workItemDevelopmentMRNodes[0]],
true,
null,
),
});
const workItemWithMRList = workItemResponseFactory({
developmentWidgetPresent: true,
developmentItems: workItemDevelopmentFragmentResponse(workItemDevelopmentNodes, true),
developmentItems: workItemDevelopmentFragmentResponse(workItemDevelopmentMRNodes, true, null),
});
const projectWorkItemResponseWithMRList = {
@ -94,11 +98,11 @@ describe('WorkItemDevelopment CE', () => {
const workItemWithEmptyMRList = workItemResponseFactory({
canUpdate: true,
developmentWidgetPresent: true,
developmentItems: workItemDevelopmentFragmentResponse([]),
developmentItems: workItemDevelopmentFragmentResponse([], false, null),
});
const workItemWithAutoCloseFlagEnabled = workItemResponseFactory({
developmentWidgetPresent: true,
developmentItems: workItemDevelopmentFragmentResponse(workItemDevelopmentNodes, true),
developmentItems: workItemDevelopmentFragmentResponse(workItemDevelopmentMRNodes, true, null),
});
const successQueryHandlerWithEmptyMRList = jest.fn().mockResolvedValue({
@ -264,8 +268,8 @@ describe('WorkItemDevelopment CE', () => {
it.each`
queryHandler | message | workItemState | linkedMRsNumber
${successQueryHandlerWithOneMR} | ${'This task will be closed when the following is merged.'} | ${STATE_OPEN} | ${1}
${successQueryHandlerWithMRList} | ${'This task will be closed when any of the following is merged.'} | ${STATE_OPEN} | ${workItemDevelopmentNodes.length}
${successQueryHandlerWithClosedWorkItem} | ${'The task was closed automatically when a branch was merged.'} | ${STATE_CLOSED} | ${workItemDevelopmentNodes.length}
${successQueryHandlerWithMRList} | ${'This task will be closed when any of the following is merged.'} | ${STATE_OPEN} | ${workItemDevelopmentMRNodes.length}
${successQueryHandlerWithClosedWorkItem} | ${'The task was closed automatically when a branch was merged.'} | ${STATE_CLOSED} | ${workItemDevelopmentMRNodes.length}
`(
'when the workItemState is `$workItemState` and number of linked MRs is `$linkedMRsNumber` shows message `$message`',
async ({ queryHandler, message }) => {

View File

@ -899,7 +899,7 @@ export const workItemBlockedByLinkedItemsResponse = {
},
};
export const workItemDevelopmentNodes = [
export const workItemDevelopmentMRNodes = [
{
fromMrDescription: true,
mergeRequest: {
@ -1084,15 +1084,47 @@ export const workItemDevelopmentNodes = [
},
];
export const workItemDevelopmentFeatureFlagNodes = [
{
active: true,
id: 'gid://gitlab/Operations::FeatureFlag/1',
name: 'flag1',
path: 'http://127.0.0.1:3000/flightjs/Flight/-/feature_flags/1/edit',
reference: '[feature_flag:1]',
__typename: 'FeatureFlag',
},
{
active: false,
id: 'gid://gitlab/Operations::FeatureFlag/2',
name: 'flag2',
path: 'http://127.0.0.1:3000/flightjs/Flight/-/feature_flags/2/edit',
reference: '[feature_flag:2]',
__typename: 'FeatureFlag',
},
{
active: false,
id: 'gid://gitlab/Operations::FeatureFlag/3',
name: 'flag3',
path: 'http://127.0.0.1:3000/flightjs/Flight/-/feature_flags/3/edit',
reference: '[feature_flag:3]',
__typename: 'FeatureFlag',
},
];
export const workItemDevelopmentFragmentResponse = (
nodes = workItemDevelopmentNodes,
mrNodes = workItemDevelopmentMRNodes,
willAutoCloseByMergeRequest = false,
featureFlagNodes = workItemDevelopmentFeatureFlagNodes,
) => {
return {
type: 'DEVELOPMENT',
willAutoCloseByMergeRequest,
featureFlags: {
nodes: featureFlagNodes,
__typename: 'FeatureFlagConnection',
},
closingMergeRequests: {
nodes,
nodes: mrNodes,
__typename: 'WorkItemClosingMergeRequestConnection',
},
__typename: 'WorkItemWidgetDevelopment',

View File

@ -262,21 +262,11 @@ RSpec.describe AppearancesHelper do
end
end
context 'with add_gitlab_white_text option' do
let(:options) { { add_gitlab_white_text: true } }
context 'with add_gitlab_logo_text option' do
let(:options) { { add_gitlab_logo_text: true } }
it 'renders shared/logo_with_white_text partial' do
expect(helper).to receive(:render).with(partial: 'shared/logo_with_white_text', formats: :svg)
subject
end
end
context 'with add_gitlab_black_text option' do
let(:options) { { add_gitlab_black_text: true } }
it 'renders shared/logo_with_black_text partial' do
expect(helper).to receive(:render).with(partial: 'shared/logo_with_black_text', formats: :svg)
it 'renders shared/logo_with_text partial' do
expect(helper).to receive(:render).with(partial: 'shared/logo_with_text', formats: :svg)
subject
end

View File

@ -32,8 +32,8 @@ RSpec.describe ContainerRegistry::ContainerRegistryHelper, feature_category: :co
help_page_path: help_page_path('user/packages/container_registry/index.md'),
two_factor_auth_help_link: help_page_path('user/profile/account/two_factor_authentication.md'),
personal_access_tokens_help_link: help_page_path('user/profile/personal_access_tokens.md'),
no_containers_image: match_asset_path('illustrations/docker-empty-state.svg'),
containers_error_image: match_asset_path('illustrations/docker-error-state.svg'),
no_containers_image: match_asset_path('illustrations/status/status-nothing-md.svg'),
containers_error_image: match_asset_path('illustrations/status/status-fail-md.svg'),
repository_url: escape_once(project.container_registry_url),
registry_host_url_with_port: escape_once(Gitlab.config.registry.host_port),
expiration_policy_help_page_path:

View File

@ -86,9 +86,7 @@ RSpec.describe API::Ci::Helpers::Runner, feature_category: :runner do
end
describe '#current_runner_manager', :freeze_time, feature_category: :fleet_visibility do
let_it_be(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, token: 'foo', groups: [group]) }
let(:runner) { create(:ci_runner, token: 'foo') }
let(:runner_manager) { create(:ci_runner_machine, runner: runner, system_xid: 'bar', contacted_at: 1.hour.ago) }
subject(:current_runner_manager) { helper.current_runner_manager }
@ -115,8 +113,6 @@ RSpec.describe API::Ci::Helpers::Runner, feature_category: :runner do
expect(current_runner_manager.system_xid).to eq('new_system_id')
expect(current_runner_manager.contacted_at).to be_nil
expect(current_runner_manager.runner).to eq(runner)
expect(current_runner_manager.runner_type).to eq(runner.runner_type)
expect(current_runner_manager.sharding_key_id).to eq(runner.sharding_key_id)
end
it 'creates a new <legacy> runner manager if system_id is not specified', :aggregate_failures do
@ -127,8 +123,6 @@ RSpec.describe API::Ci::Helpers::Runner, feature_category: :runner do
expect(current_runner_manager).not_to be_nil
expect(current_runner_manager.system_xid).to eq(::API::Ci::Helpers::Runner::LEGACY_SYSTEM_XID)
expect(current_runner_manager.runner).to eq(runner)
expect(current_runner_manager.runner_type).to eq(runner.runner_type)
expect(current_runner_manager.sharding_key_id).to eq(runner.sharding_key_id)
end
end
end

View File

@ -1,59 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillRunnerTypeAndShardingKeyIdOnCiRunnerManagers,
schema: 20241003110148, migration: :gitlab_ci, feature_category: :runner do
let(:connection) { Ci::ApplicationRecord.connection }
describe '#perform' do
let(:runner_managers) { table(:ci_runner_machines) }
let(:runners) { table(:ci_runners) }
let(:args) do
min, max = runner_managers.pick('MIN(id)', 'MAX(id)')
{
start_id: min,
end_id: max,
batch_table: 'ci_runner_machines',
batch_column: 'id',
sub_batch_size: 100,
pause_ms: 0,
connection: connection
}
end
let!(:instance_runner) { runners.create!(runner_type: 1) }
let!(:group_runner) { runners.create!(runner_type: 2, sharding_key_id: 89) }
let!(:project_runner1) { runners.create!(runner_type: 3, sharding_key_id: 10) }
let!(:project_runner2) { runners.create!(runner_type: 3, sharding_key_id: 100) }
let!(:runner_manager1) { create_runner_manager(instance_runner, system_xid: 'a') }
let!(:runner_manager2_1) { create_runner_manager(group_runner, system_xid: 'a') }
let!(:runner_manager2_2) { create_runner_manager(group_runner, system_xid: 'b') }
let!(:runner_manager3) { create_runner_manager(project_runner1, system_xid: 'a') }
let!(:runner_manager4) { create_runner_manager(project_runner2, system_xid: 'b') }
subject(:perform_migration) { described_class.new(**args).perform }
it 'backfills runner_type and sharding_key_id', :aggregate_failures do
expect { perform_migration }
.to change { runner_manager2_1.reload.sharding_key_id }.from(nil).to(group_runner.sharding_key_id)
.and change { runner_manager2_2.reload.sharding_key_id }.from(nil).to(group_runner.sharding_key_id)
.and change { runner_manager3.reload.sharding_key_id }.from(nil).to(project_runner1.sharding_key_id)
.and change { runner_manager4.reload.sharding_key_id }.from(nil).to(project_runner2.sharding_key_id)
.and not_change { runner_manager2_1.reload.runner_type }.from(group_runner.runner_type)
.and not_change { runner_manager2_2.reload.runner_type }.from(group_runner.runner_type)
.and not_change { runner_manager3.reload.runner_type }.from(project_runner1.runner_type)
.and not_change { runner_manager4.reload.runner_type }.from(project_runner2.runner_type)
expect(runner_manager1.sharding_key_id).to be_nil
end
private
def create_runner_manager(runner, **attrs)
runner_managers.create!(runner_id: runner.id, runner_type: runner.runner_type, **attrs)
end
end
end

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillRunnerTypeAndShardingKeyIdOnCiRunnerManagers, migration: :gitlab_ci, feature_category: :runner do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_ci,
table_name: :ci_runner_machines,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -3,9 +3,6 @@
require 'spec_helper'
RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :model do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
it_behaves_like 'having unique enum values'
it_behaves_like 'it has loose foreign keys' do
@ -21,32 +18,12 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
it { is_expected.to validate_presence_of(:runner) }
it { is_expected.to validate_presence_of(:system_xid) }
it { is_expected.to validate_length_of(:system_xid).is_at_most(64) }
it { is_expected.to validate_presence_of(:runner_type).on(:create) }
it { is_expected.to validate_presence_of(:sharding_key_id).on(:create) }
it { is_expected.to validate_length_of(:version).is_at_most(2048) }
it { is_expected.to validate_length_of(:revision).is_at_most(255) }
it { is_expected.to validate_length_of(:platform).is_at_most(255) }
it { is_expected.to validate_length_of(:architecture).is_at_most(255) }
it { is_expected.to validate_length_of(:ip_address).is_at_most(1024) }
context 'when runner manager is instance type', :aggregate_failures do
let(:runner_manager) { build(:ci_runner_machine, runner_type: :instance_type) }
it { expect(runner_manager).to be_valid }
context 'when sharding_key_id is present' do
let(:runner_manager) do
build(:ci_runner_machine, runner: build(:ci_runner, sharding_key_id: non_existing_record_id))
end
it 'is invalid' do
expect(runner_manager).to be_invalid
expect(runner_manager.errors.full_messages).to contain_exactly(
'Runner manager cannot have sharding_key_id assigned')
end
end
end
context 'when runner has config' do
it 'is valid' do
runner_manager = build(:ci_runner_machine, config: { gpus: "all" })
@ -62,60 +39,6 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
expect(runner_manager).not_to be_valid
end
end
describe 'shading_key_id validations' do
let(:runner_manager) { build(:ci_runner_machine, runner: runner) }
context 'with instance runner' do
let(:runner) { build(:ci_runner, :instance) }
it { expect(runner).to be_valid }
context 'when sharding_key_id is not present' do
before do
runner.sharding_key_id = nil
runner_manager.sharding_key_id = nil
end
it { expect(runner_manager).to be_valid }
end
end
context 'with group runner' do
let(:runner) { build(:ci_runner, :group, groups: [group]) }
it { expect(runner_manager).to be_valid }
context 'when sharding_key_id is not present' do
before do
runner.sharding_key_id = nil
runner_manager.sharding_key_id = nil
end
it 'adds error to model', :aggregate_failures do
expect(runner_manager).not_to be_valid
expect(runner_manager.errors[:sharding_key_id]).to contain_exactly("can't be blank")
end
end
end
context 'with project runner' do
let(:runner) { build(:ci_runner, :project, projects: [project]) }
it { expect(runner).to be_valid }
context 'when sharding_key_id is not present' do
before do
runner.sharding_key_id = nil
end
it 'adds error to model', :aggregate_failures do
expect(runner_manager).not_to be_valid
expect(runner_manager.errors[:sharding_key_id]).to contain_exactly("can't be blank")
end
end
end
end
end
describe 'status scopes', :freeze_time do
@ -665,44 +588,4 @@ RSpec.describe Ci::RunnerManager, feature_category: :fleet_visibility, type: :mo
it { is_expected.to contain_exactly existing_build }
end
end
describe '#set_runner_type_and_sharding_key_id' do
let(:runner_manager) { build(:ci_runner_machine, runner: runner) }
before do
runner_manager.runner_type = nil
runner_manager.sharding_key_id = nil
end
shared_examples 'when sharding_key_id is not present' do
before do
runner_manager.save!
end
it 'sets runner_type and sharding_key_id from runner', :aggregate_failures do
expect(runner_manager.runner_type).to eq(runner.runner_type)
expect(runner_manager.sharding_key_id).to eq(runner.sharding_key_id)
end
end
context 'with instance runner' do
let(:runner) { build(:ci_runner, :instance) }
it { expect(runner).to be_valid }
it_behaves_like 'when sharding_key_id is not present'
end
context 'with group runner' do
let(:runner) { build(:ci_runner, :group, groups: [group]) }
it_behaves_like 'when sharding_key_id is not present'
end
context 'with project runner' do
let(:runner) { build(:ci_runner, :project, projects: [project]) }
it_behaves_like 'when sharding_key_id is not present'
end
end
end