Skip to content
Snippets Groups Projects
Commit fc7097e2 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖
Browse files

Add latest changes from gitlab-org/gitlab@master

parent aec24714
No related branches found
No related tags found
No related merge requests found
Pipeline #1666504744 passed
Showing
with 240 additions and 218 deletions
<script>
import { GlLink } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
export default {
components: {
GlLink,
GlButton,
},
props: {
......@@ -26,5 +26,5 @@ export default {
};
</script>
<template>
<gl-link :href="historyPathWithId">{{ s__('BulkImport|Migration details') }} &gt;</gl-link>
<gl-button :href="historyPathWithId">{{ s__('BulkImport|Migration details') }}</gl-button>
</template>
......@@ -29,10 +29,14 @@ export default {
<template>
<div>
<gl-link :href="group.webUrl" target="_blank" class="gl-inline-flex gl-h-7 gl-items-center">
<gl-link
:href="group.webUrl"
target="_blank"
class="gl-inline-flex gl-h-7 gl-items-center gl-gap-2"
>
{{ group.fullPath }} <gl-icon name="external-link" class="gl-fill-icon-link" />
</gl-link>
<div v-if="group.flags.isFinished && fullLastImportPath" class="gl-text-sm">
<div v-if="group.flags.isFinished && fullLastImportPath" class="gl-text-sm gl-text-subtle">
<gl-sprintf :message="s__('BulkImport|Last imported to %{link}')">
<template #link>
<gl-link :href="absoluteLastImportPath" class="gl-text-sm" target="_blank">{{
......
......@@ -145,14 +145,14 @@ export default {
key: 'selected',
label: '',
thClass: 'gl-w-3 !gl-pr-3',
tdClass: '!gl-pr-3',
tdClass: '!gl-flex lg:!gl-table-cell lg:!gl-pr-3',
},
{
key: 'webUrl',
label: s__('BulkImport|Source group'),
// eslint-disable-next-line @gitlab/require-i18n-strings
thClass: '!gl-pl-0 gl-w-1/2',
tdClass: '!gl-pl-0',
thClass: 'lg:!gl-pl-0 gl-w-1/2',
tdClass: 'lg:!gl-pl-0',
},
{
key: 'importTarget',
......@@ -162,11 +162,13 @@ export default {
{
key: 'progress',
label: __('Status'),
tdClass: '!gl-align-middle',
tdAttr: { 'data-testid': 'import-status-indicator' },
},
{
key: 'actions',
label: '',
tdClass: '!gl-flex lg:!gl-table-cell',
},
],
......@@ -342,12 +344,7 @@ export default {
methods: {
rowClasses(groupTableItem) {
const DEFAULT_CLASSES = [
'gl-border-strong',
'gl-border-0',
'gl-border-b-1',
'gl-border-solid',
];
const DEFAULT_CLASSES = ['gl-border-strong', 'gl-border-0', 'gl-border-b', 'gl-border-solid'];
const result = [...DEFAULT_CLASSES];
if (groupTableItem.flags.isUnselectable) {
result.push('!gl-cursor-default');
......@@ -675,24 +672,20 @@ export default {
{{ s__('BulkImport|View import history') }}
</gl-button>
</template>
<template #description
><span>{{ s__('BulkImport|Select the groups and projects you want to import.') }}</span>
<span>
<gl-sprintf
:message="
s__(
'BulkImport|Please note: importing projects is a %{docsLinkStart}beta%{docsLinkEnd} feature.',
)
"
<template #description>
{{ s__('BulkImport|Select the groups and projects you want to import.') }}
<gl-sprintf
:message="
s__('BulkImport|Importing projects is a %{docsLinkStart}beta%{docsLinkEnd} feature.')
"
>
<template #docsLink="{ content }"
><gl-link :href="$options.betaFeatureHelpPath" target="_blank">{{
content
}}</gl-link></template
>
<template #docsLink="{ content }"
><gl-link :href="$options.betaFeatureHelpPath" target="_blank">{{
content
}}</gl-link></template
>
</gl-sprintf>
</span></template
>
</gl-sprintf>
</template>
</page-heading>
<gl-alert
......@@ -728,7 +721,7 @@ export default {
</template>
</gl-sprintf>
</gl-alert>
<gl-alert variant="warning" :dismissible="false" class="mt-3">
<gl-alert variant="warning" :dismissible="false">
<gl-sprintf
:message="
s__(
......@@ -742,13 +735,6 @@ export default {
</gl-sprintf>
</gl-alert>
<div class="gl-border-0 gl-border-b-1 gl-border-solid gl-border-b-default gl-py-5">
<gl-search-box-by-click
class="gl-mb-5"
data-testid="filter-groups"
:placeholder="s__('BulkImport|Filter by source group')"
@submit="filter = $event"
@clear="filter = ''"
/>
<span v-if="!$apollo.loading && hasGroups">
<gl-sprintf :message="statusMessage">
<template #start>
......@@ -784,6 +770,15 @@ export default {
</help-popover>
</span>
</div>
<div class="gl-flex gl-flex-col gl-gap-3 gl-bg-subtle gl-p-5 gl-pb-4">
<gl-search-box-by-click
data-testid="filter-groups"
:placeholder="s__('BulkImport|Filter by source group')"
@submit="filter = $event"
@clear="filter = ''"
/>
</div>
<gl-loading-icon v-if="$apollo.loading" size="lg" class="gl-mt-5" />
<template v-else>
<gl-empty-state
......@@ -806,9 +801,9 @@ export default {
</gl-empty-state>
<template v-else>
<div
class="import-table-bar gl-sticky gl-z-3 gl-flex-col gl-bg-subtle gl-px-4 md:gl-flex md:gl-flex-row md:gl-items-center md:gl-justify-between"
class="import-table-bar gl-sticky gl-z-3 gl-flex-col gl-bg-subtle gl-px-5 md:gl-flex md:gl-flex-row md:gl-items-center md:gl-justify-between"
>
<div class="gl-items-center gl-gap-4 gl-py-3 md:gl-flex">
<div class="gl-flex gl-w-full gl-items-center gl-gap-4 gl-pb-4">
<span data-test-id="selection-count">
<gl-sprintf :message="__('%{count} selected')">
<template #count>
......@@ -834,7 +829,7 @@ export default {
v-gl-tooltip
:title="s__('BulkImport|Some groups will be imported without projects.')"
name="warning"
class="gl-text-orange-500"
variant="warning"
data-testid="import-projects-warning"
/>
</span>
......@@ -869,6 +864,7 @@ export default {
selectable
select-mode="multi"
selected-variant="primary"
stacked="lg"
@row-selected="preventSelectingAlreadyImportedGroups"
>
<template #head(selected)="{ selectAllRows, clearSelected }">
......@@ -911,13 +907,13 @@ export default {
/>
</template>
<template #cell(progress)="{ item: group }">
<import-status-cell :status="group.visibleStatus" :has-failures="hasFailures(group)" />
<import-history-link
v-if="showHistoryLink(group)"
:id="group.progress.id"
:history-path="historyShowPath"
class="gl-mt-2 gl-inline-block"
/>
<div class="gl-mt-3">
<import-status-cell
class="gl-items-end lg:gl-items-start"
:status="group.visibleStatus"
:has-failures="hasFailures(group)"
/>
</div>
</template>
<template #cell(actions)="{ item: group, index }">
<import-actions-cell
......@@ -928,6 +924,12 @@ export default {
:is-project-creation-allowed="group.flags.isProjectCreationAllowed"
@import-group="importGroup({ group, extraArgs: $event, index })"
/>
<import-history-link
v-if="showHistoryLink(group)"
:id="group.progress.id"
:history-path="historyShowPath"
class="gl-mt-3"
/>
</template>
</gl-table>
</template>
......
......@@ -92,6 +92,16 @@ const transformOptions = (options = {}) => {
const installed = new WeakMap();
export const getMatchedComponents = (instance, path) => {
if (instance.getMatchedComponents) {
return instance.getMatchedComponents(path);
}
const route = path ? instance.resolve(path) : instance.currentRoute.value;
return route.matched.flatMap((record) => Object.values(record.components));
};
export default class VueRouterCompat {
constructor(options) {
// eslint-disable-next-line no-constructor-return
......
<script>
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import { GRAPHQL_PAGE_SIZE, LIST_KEY_CREATED_AT } from '~/ml/model_registry/constants';
import { queryToObject, setUrlParams, updateHistory } from '~/lib/utils/url_utility';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
import LoadOrErrorOrShow from '~/ml/model_registry/components/load_or_error_or_show.vue';
export default {
name: 'SearchableList',
components: { RegistryList, RegistrySearch, LoadOrErrorOrShow },
props: {
items: {
type: Array,
required: true,
},
pageInfo: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
errorMessage: {
type: String,
required: false,
default: '',
},
showSearch: {
type: Boolean,
required: false,
default: false,
},
sortableFields: {
type: Array,
required: false,
default: () => [],
},
},
data() {
const query = queryToObject(window.location.search);
const filter = query.name ? [{ value: { data: query.name }, type: FILTERED_SEARCH_TERM }] : [];
const orderBy = query.orderBy || LIST_KEY_CREATED_AT;
return {
filters: filter,
sorting: {
orderBy,
sort: (query.sort || 'desc').toLowerCase(),
},
};
},
computed: {
isListEmpty() {
return this.items.length === 0;
},
parsedQuery() {
const name = this.filters
.map((f) => f.value.data)
.join(' ')
.trim();
const filterByQuery = name === '' ? {} : { name };
return { ...filterByQuery, ...this.sorting };
},
},
created() {
this.nextPage();
},
methods: {
prevPage() {
const variables = {
first: null,
last: GRAPHQL_PAGE_SIZE,
before: this.pageInfo.startCursor,
...this.parsedQuery,
};
this.fetchPage(variables);
},
nextPage() {
const variables = {
first: GRAPHQL_PAGE_SIZE,
last: null,
after: this.pageInfo.endCursor,
...this.parsedQuery,
};
this.fetchPage(variables);
},
fetchPage(variables) {
updateHistory({
url: setUrlParams(variables, window.location.href, true),
title: document.title,
replace: true,
});
this.$emit('fetch-page', variables);
},
submitFilters() {
this.fetchPage(this.parsedQuery);
},
updateFilters(newValue) {
this.filters = newValue;
},
updateSorting(newValue) {
this.sorting = { ...this.sorting, ...newValue };
},
updateSortingAndEmitUpdate(newValue) {
this.updateSorting(newValue);
this.submitFilters();
},
},
};
</script>
<template>
<div>
<registry-search
v-if="showSearch"
:filters="filters"
:sorting="sorting"
:sortable-fields="sortableFields"
@sorting:changed="updateSortingAndEmitUpdate"
@filter:changed="updateFilters"
@filter:submit="submitFilters"
@filter:clear="filters = []"
/>
<load-or-error-or-show :is-loading="isLoading" :error-message="errorMessage">
<slot v-if="isListEmpty" name="empty-state"></slot>
<registry-list
v-else
:hidden-delete="true"
:is-loading="isLoading"
:items="items"
:pagination="pageInfo"
@prev-page="prevPage"
@next-page="nextPage"
>
<template #default="{ item }">
<slot name="item" :item="item"></slot>
</template>
</registry-list>
</load-or-error-or-show>
</div>
</template>
......@@ -9,11 +9,21 @@ import { getRefType } from './utils/ref_type';
Vue.use(VueRouter);
const normalizePathParam = (pathParam) => {
// Vue Router 4 when there's more than one `:path` segment
if (Array.isArray(pathParam)) {
return joinPaths(...pathParam);
}
// Vue Router 3, or when there's zero or one `:path` segments.
return pathParam?.replace(/^\//, '') || '/';
};
export default function createRouter(base, baseRef) {
const treePathRoute = {
component: TreePage,
props: (route) => ({
path: route.params.path?.replace(/^\//, '') || '/',
path: normalizePathParam(route.params.path),
refType: getRefType(route.query.ref_type || null),
}),
};
......@@ -36,25 +46,25 @@ export default function createRouter(base, baseRef) {
{
name: 'treePathDecoded',
// Sometimes the ref needs decoding depending on how the backend sends it to us
path: `(/-)?/tree/${decodeURI(baseRef)}/:path*`,
path: `/:dash(-)?/tree/${decodeURI(baseRef)}/:path*`,
...treePathRoute,
},
{
name: 'treePath',
// Support without decoding as well just in case the ref doesn't need to be decoded
path: `(/-)?/tree/${escapeRegExp(baseRef)}/:path*`,
path: `/:dash(-)?/tree/${escapeRegExp(baseRef)}/:path*`,
...treePathRoute,
},
{
name: 'blobPathDecoded',
// Sometimes the ref needs decoding depending on how the backend sends it to us
path: `(/-)?/blob/${decodeURI(baseRef)}/:path*`,
path: `/:dash(-)?/blob/${decodeURI(baseRef)}/:path*`,
...blobPathRoute,
},
{
name: 'blobPath',
// Support without decoding as well just in case the ref doesn't need to be decoded
path: `(/-)?/blob/${escapeRegExp(baseRef)}/:path*`,
path: `/:dash(-)?/blob/${escapeRegExp(baseRef)}/:path*`,
...blobPathRoute,
},
{
......@@ -80,7 +90,7 @@ export default function createRouter(base, baseRef) {
'edit',
decodeURI(baseRef),
'-',
to.params.path || '',
normalizePathParam(to.params.path),
needsClosingSlash && '/',
),
);
......
......@@ -1623,7 +1623,9 @@ def github_enterprise_import?
# - Relation import
# - Direct Transfer
def any_import_in_progress?
relation_import_trackers.last&.started? ||
last_relation_import_tracker = relation_import_trackers.last
(last_relation_import_tracker&.started? && !last_relation_import_tracker.stale?) ||
import_started? ||
BulkImports::Entity.with_status(:started).where(project_id: id).any?
end
......
......@@ -10,6 +10,8 @@ class RelationImportTracker < ApplicationRecord
validates :relation, presence: true
validate :cannot_be_created_for_importing_project, on: :create
STALE_TIMEOUT = 24.hours
enum :relation, { issues: 0, merge_requests: 1, ci_pipelines: 2, milestones: 3 }
state_machine :status, initial: :created do
......@@ -20,6 +22,7 @@ class RelationImportTracker < ApplicationRecord
event :start do
transition created: :started
transition started: :started
end
event :finish do
......@@ -34,7 +37,7 @@ class RelationImportTracker < ApplicationRecord
def stale?
return false if finished? || failed?
created_at.before?(24.hours.ago)
created_at.before?(STALE_TIMEOUT.ago)
end
private
......
......@@ -7,10 +7,10 @@
= render ::Layouts::PageHeadingComponent.new(_('Group members')) do |c|
- c.with_description do
= group_member_header_subtext(@group)
- c.with_actions do
- if current_appearance&.member_guidelines?
.gl-w-full.order-md-1
= brand_member_guidelines
- c.with_actions do
.js-invite-group-trigger{ data: { classes: 'md:gl-w-auto gl-w-full', display_text: _('Invite a group') } }
.js-invite-members-trigger{ data: { variant: 'confirm',
classes: 'md:gl-w-auto gl-w-full',
......
......@@ -4,6 +4,7 @@ module Projects
module ImportExport
class RelationImportWorker
include ApplicationWorker
include Sidekiq::InterruptionsExhausted
sidekiq_options retry: 6
......@@ -16,12 +17,26 @@ class RelationImportWorker
attr_reader :tracker, :project, :current_user
sidekiq_retries_exhausted do |job, exception|
new.perform_failure(job['args'].first, exception)
end
sidekiq_interruptions_exhausted do |job|
new.perform_failure(job['args'].first,
::Import::Exceptions::SidekiqExhaustedInterruptionsError.new
)
end
def perform(tracker_id, user_id)
@current_user = User.find(user_id)
@tracker = ::Projects::ImportExport::RelationImportTracker.find(tracker_id)
@project = tracker.project
return unless tracker.can_start?
unless tracker.can_start?
::Import::Framework::Logger.info(message: 'Cannot start tracker', tracker_id: tracker.id,
tracker_status: tracker.status_name)
return
end
tracker.start!
......@@ -31,20 +46,21 @@ def perform(tracker_id, user_id)
tracker.finish!
rescue StandardError => error
failure_service = Gitlab::ImportExport::ImportFailureService.new(project)
failure_service.log_import_failure(
source: 'RelationImportWorker#perform',
exception: error,
relation_key: tracker.relation
)
tracker.fail_op!
log_failure(error)
raise
ensure
remove_extracted_import
end
def perform_failure(tracker_id, exception)
@tracker = ::Projects::ImportExport::RelationImportTracker.find(tracker_id)
@project = tracker.project
log_failure(exception)
tracker.fail_op!
end
private
def extract_import_file
......@@ -104,6 +120,15 @@ def members_mapper
def perform_post_import_tasks
project.reset_counters_and_iids
end
def log_failure(exception)
failure_service = Gitlab::ImportExport::ImportFailureService.new(project)
failure_service.log_import_failure(
source: 'RelationImportWorker#perform',
exception: exception,
relation_key: tracker.relation
)
end
end
end
end
- title: "`kpt`-based `agentk` is deprecated"
removal_milestone: "18.0"
announcement_milestone: "17.9"
breaking_change: true
window: 2
reporter: nagyv-gitlab
stage: deploy
issue_url: https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/issues/656
# Use the impact calculator https://gitlab-com.gitlab.io/gl-infra/breaking-change-impact-calculator/?
# https://gitlab-com.gitlab.io/gl-infra/breaking-change-impact-calculator/?usage=edge_case&migration_complexity=minor_manual&scope=project&identification_complexity=manual&additional_complexity=no&base_impact=major&pipeline_impact=none&compliance_impact=none&availability_impact=none&authorization_impact=none&API_impact=none
impact: low
scope: project
resolution_role: Maintainer
manual_task: true
body: | # (required) Don't change this line.
In GitLab 18.0, we'll remove support for the `kpt`-based installation of the agent for Kubernetes.
Instead, you should install the agent with one of the supported installation methods:
- Helm (recommended)
- GitLab CLI
- Flux
To migrate from `kpt` to Helm, follow [the agent installation documentation](https://docs.gitlab.com/ee/user/clusters/agent/install/) to overwrite your `kpt`-deployed `agentk` instance.
- title: "Support for project build as part of SpotBugs scans"
removal_milestone: "18.0"
announcement_milestone: "17.9"
breaking_change: false
window: 1 # Note: a change window is not applicable to a non-breaking change
reporter: thiagocsf
stage: application security testing
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/513409
impact: low
scope: project
resolution_role: Developer
manual_task: true
body: | # (required) Don't change this line.
The SpotBugs [SAST analyzer](https://docs.gitlab.com/ee/user/application_security/sast/index.html#supported-languages-and-frameworks)
can perform a build when the artifacts to be scanned aren't present. While this usually works well for simple projects, it can fail on more complex builds.
From GitLab 18.0, to resolve SpotBugs analyzer build failures, you should:
1. [Pre-compile](https://docs.gitlab.com/ee/user/application_security/sast/#pre-compilation) the project.
1. Pass the artifacts you want to scan to the analyzer.
end_of_support_milestone: 18.0
tiers: [Free, Silver, Gold, Core, Premium, Ultimate]
documentation_url: https://docs.gitlab.com/ee/user/application_security/sast/troubleshooting.html#project-couldnt-be-built
---
migration_job_name: BackfillBulkImportFailuresNamespaceId
description: Backfills sharding key `bulk_import_failures.namespace_id` from `bulk_import_entities`.
feature_category: importers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180435
milestone: '17.9'
queued_migration_version: 20250205194756
finalized_by: # version of the migration that finalized this BBM
---
migration_job_name: BackfillBulkImportFailuresOrganizationId
description: Backfills sharding key `bulk_import_failures.organization_id` from `bulk_import_entities`.
feature_category: importers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180435
milestone: '17.9'
queued_migration_version: 20250205194761
finalized_by: # version of the migration that finalized this BBM
---
migration_job_name: BackfillBulkImportFailuresProjectId
description: Backfills sharding key `bulk_import_failures.project_id` from `bulk_import_entities`.
feature_category: importers
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180435
milestone: '17.9'
queued_migration_version: 20250205194751
finalized_by: # version of the migration that finalized this BBM
......@@ -34,3 +34,7 @@ desired_sharding_key:
table: bulk_import_entities
sharding_key: organization_id
belongs_to: entity
desired_sharding_key_migration_job_name:
- BackfillBulkImportFailuresProjectId
- BackfillBulkImportFailuresNamespaceId
- BackfillBulkImportFailuresOrganizationId
# frozen_string_literal: true
class AddProjectIdToBulkImportFailures < Gitlab::Database::Migration[2.2]
milestone '17.9'
def change
add_column :bulk_import_failures, :project_id, :bigint
end
end
# frozen_string_literal: true
class AddNamespaceIdToBulkImportFailures < Gitlab::Database::Migration[2.2]
milestone '17.9'
def change
add_column :bulk_import_failures, :namespace_id, :bigint
end
end
# frozen_string_literal: true
class AddOrganizationIdToBulkImportFailures < Gitlab::Database::Migration[2.2]
milestone '17.9'
def change
add_column :bulk_import_failures, :organization_id, :bigint
end
end
# frozen_string_literal: true
class IndexBulkImportFailuresOnProjectId < Gitlab::Database::Migration[2.2]
milestone '17.9'
disable_ddl_transaction!
INDEX_NAME = 'index_bulk_import_failures_on_project_id'
def up
add_concurrent_index :bulk_import_failures, :project_id, name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :bulk_import_failures, INDEX_NAME
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment