From 46df8e869e08b387d539c1de65434f6ffaeb3c26 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 20 Jun 2025 18:12:55 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../javascripts/issues/list/constants.js | 2 + app/assets/javascripts/issues/list/index.js | 17 +-- .../javascripts/issues/list/issue_client.js | 20 +++ app/assets/javascripts/issues/list/utils.js | 27 +++- .../command_palette/search_item.vue | 16 +- .../components/frequent_item.vue | 14 +- .../super_sidebar/super_sidebar_bundle.js | 6 +- .../components/work_item_detail.vue | 4 +- .../work_items/graphql/cache_utils.js | 12 +- app/assets/javascripts/work_items/index.js | 2 + .../work_items/pages/list/constants.js | 69 --------- .../work_items/pages/work_items_list_app.vue | 26 +++- .../page_bundles/merge_request.scss | 14 +- app/controllers/glql/base_controller.rb | 19 +-- .../projects/merge_requests/_page.html.haml | 2 +- .../import_export/relation_export_worker.rb | 43 ++++-- ...amespaces_redirect_routes_namespace_id.yml | 2 +- .../backfill_operations_scopes_project_id.yml | 2 +- ...ackages_conan_file_metadata_project_id.yml | 2 +- db/fixtures/development/01_admin.rb | 10 +- ...packages_conan_file_metadata_project_id.rb | 20 +++ ...namespaces_redirect_routes_namespace_id.rb | 20 +++ ...e_backfill_operations_scopes_project_id.rb | 20 +++ db/schema_migrations/20250616173500 | 1 + db/schema_migrations/20250618220559 | 1 + db/schema_migrations/20250619145012 | 1 + .../auth/ldap/ldap_synchronization.md | 4 +- doc/administration/guest_users.md | 2 +- doc/api/_index.md | 1 + doc/api/member_roles.md | 2 +- doc/api/members.md | 4 +- doc/development/_index.md | 2 +- doc/development/policies.md | 2 +- doc/security/rate_limits.md | 4 +- doc/security/user_file_uploads.md | 4 +- doc/solutions/_index.md | 1 + doc/subscriptions/_index.md | 1 + doc/tutorials/_index.md | 3 +- doc/user/_index.md | 1 + .../dependency_list/_index.md | 1 + doc/user/gitlab_duo/_index.md | 2 +- doc/user/profile/achievements.md | 2 +- doc/user/profile/contributions_calendar.md | 2 +- .../lib/gitlab/backup/cli/commands.rb | 1 - .../backup/cli/commands/backup_subcommand.rb | 2 +- .../cli/commands/object_storage_command.rb | 31 ---- .../backup/cli/commands/restore_subcommand.rb | 2 +- .../backup/cli/context/source_context.rb | 8 + .../lib/gitlab/backup/cli/targets.rb | 1 - .../backup/cli/targets/object_storage.rb | 26 ---- .../cli/targets/object_storage/google.rb | 138 ------------------ .../lib/gitlab/backup/cli/tasks/registry.rb | 14 +- .../lib/gitlab/backup/cli/tasks/task.rb | 19 +-- .../cli/targets/object_storage/google_spec.rb | 133 ----------------- .../thor/gitlab_backup_cli_backup_spec.rb | 8 - .../thor/gitlab_backup_cli_restore_spec.rb | 8 - package.json | 2 +- scripts/frontend/jest_ci.js | 13 +- scripts/frontend/quarantined_vue3_specs.txt | 1 - spec/controllers/glql/base_controller_spec.rb | 13 -- .../__snapshots__/search_item_spec.js.snap | 21 +-- .../command_palette/search_item_spec.js | 83 ++++++++++- .../components/work_item_detail_spec.js | 4 +- .../components/work_items_list_app_spec.js | 128 +++++++++++++++- .../relation_export_worker_spec.rb | 72 ++++++--- yarn.lock | 12 +- 66 files changed, 526 insertions(+), 624 deletions(-) create mode 100644 app/assets/javascripts/issues/list/issue_client.js delete mode 100644 app/assets/javascripts/work_items/pages/list/constants.js create mode 100644 db/post_migrate/20250616173500_finalize_backfill_packages_conan_file_metadata_project_id.rb create mode 100644 db/post_migrate/20250618220559_finalize_backfill_namespaces_redirect_routes_namespace_id.rb create mode 100644 db/post_migrate/20250619145012_finalize_backfill_operations_scopes_project_id.rb create mode 100644 db/schema_migrations/20250616173500 create mode 100644 db/schema_migrations/20250618220559 create mode 100644 db/schema_migrations/20250619145012 delete mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/object_storage_command.rb delete mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage.rb delete mode 100644 gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb delete mode 100644 gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index 420c3bd5f29..8e41d2ec199 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -137,6 +137,8 @@ export const urlSortParams = { [RELATIVE_POSITION_ASC]: RELATIVE_POSITION, [TITLE_ASC]: 'title_asc', [TITLE_DESC]: 'title_desc', + [START_DATE_ASC]: 'start_date_asc', + [START_DATE_DESC]: 'start_date_desc', [HEALTH_STATUS_ASC]: 'health_status_asc', [HEALTH_STATUS_DESC]: 'health_status_desc', [WEIGHT_ASC]: 'weight', diff --git a/app/assets/javascripts/issues/list/index.js b/app/assets/javascripts/issues/list/index.js index 106694aaf01..9e2419d0597 100644 --- a/app/assets/javascripts/issues/list/index.js +++ b/app/assets/javascripts/issues/list/index.js @@ -2,24 +2,13 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import VueRouter from 'vue-router'; import IssuesListApp from 'ee_else_ce/issues/list/components/issues_list_app.vue'; -import { resolvers, config } from '~/graphql_shared/issuable_client'; -import createDefaultClient, { createApolloClientWithCaching } from '~/lib/graphql'; +import { getApolloProvider } from '~/issues/list/issue_client'; import { parseBoolean } from '~/lib/utils/common_utils'; import DesignDetail from '~/work_items/components/design_management/design_preview/design_details.vue'; import { ROUTES } from '~/work_items/constants'; import JiraIssuesImportStatusApp from './components/jira_issues_import_status_app.vue'; import { gqlClient } from './graphql'; -let issuesClient; - -export async function issuesListClient() { - if (issuesClient) return issuesClient; - issuesClient = gon.features?.frontendCaching - ? await createApolloClientWithCaching(resolvers, { localCacheKey: 'issues_list', ...config }) - : createDefaultClient(resolvers, config); - return issuesClient; -} - export async function mountJiraIssuesListApp() { const el = document.querySelector('.js-jira-issues-import-status-root'); @@ -124,9 +113,7 @@ export async function mountIssuesListApp() { return new Vue({ el, name: 'IssuesListRoot', - apolloProvider: new VueApollo({ - defaultClient: await issuesListClient(), - }), + apolloProvider: await getApolloProvider(), router: new VueRouter({ base: window.location.pathname, mode: 'history', diff --git a/app/assets/javascripts/issues/list/issue_client.js b/app/assets/javascripts/issues/list/issue_client.js new file mode 100644 index 00000000000..73cbbd316b8 --- /dev/null +++ b/app/assets/javascripts/issues/list/issue_client.js @@ -0,0 +1,20 @@ +import VueApollo from 'vue-apollo'; +import { config, defaultClient, resolvers } from '~/graphql_shared/issuable_client'; +import { createApolloClientWithCaching } from '~/lib/graphql'; + +let issuesClientPromise; + +async function getIssuesClient() { + if (issuesClientPromise) return issuesClientPromise; + issuesClientPromise = gon.features?.frontendCaching + ? createApolloClientWithCaching(resolvers, { localCacheKey: 'issues_list', ...config }) + : Promise.resolve(defaultClient); + return issuesClientPromise; +} + +export async function getApolloProvider() { + const client = ['projects:issues:index', 'groups:issues'].includes(document.body.dataset.page) + ? await getIssuesClient() + : defaultClient; + return new VueApollo({ defaultClient: client }); +} diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index 3655a9f6972..fc0431e87e2 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -76,6 +76,8 @@ import { WEIGHT_DESC, MERGED_AT_ASC, MERGED_AT_DESC, + START_DATE_ASC, + START_DATE_DESC, } from './constants'; /** @@ -127,12 +129,17 @@ export const getSortOptions = ({ hasBlockedIssuesFeature, hasIssuableHealthStatusFeature, hasIssueWeightsFeature, - hasManualSort = true, - hasMergedDate = false, + hasPriority = true, + hasMilestoneDueDate = true, hasDueDate = true, + hasLabelPriority = true, + hasManualSort = true, + hasStartDate = false, + hasMergedDate = false, + hasWeight = true, } = {}) => { const sortOptions = [ - { + hasPriority && { id: 1, title: __('Priority'), sortDirection: { @@ -164,7 +171,7 @@ export const getSortOptions = ({ descending: CLOSED_AT_DESC, }, }, - { + hasMilestoneDueDate && { id: 5, title: __('Milestone due date'), sortDirection: { @@ -188,7 +195,7 @@ export const getSortOptions = ({ descending: POPULARITY_DESC, }, }, - { + hasLabelPriority && { id: 8, title: __('Label priority'), sortDirection: { @@ -212,6 +219,14 @@ export const getSortOptions = ({ descending: TITLE_DESC, }, }, + hasStartDate && { + id: 11, + title: __('Start date'), + sortDirection: { + ascending: START_DATE_ASC, + descending: START_DATE_DESC, + }, + }, ]; if (hasMergedDate) { @@ -236,7 +251,7 @@ export const getSortOptions = ({ }); } - if (hasIssueWeightsFeature) { + if (hasIssueWeightsFeature && hasWeight) { sortOptions.push({ id: sortOptions.length + 1, title: __('Weight'), diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue index d2a1fa8b684..9faf2b285f2 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue +++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/search_item.vue @@ -51,15 +51,17 @@ export default { :shape="avatarShape" aria-hidden="true" /> - + - - + diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_item.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_item.vue index da3b00c746d..bda75d8972c 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_item.vue +++ b/app/assets/javascripts/super_sidebar/components/global_search/components/frequent_item.vue @@ -33,14 +33,12 @@ export default {
{{ item.title }} - -
- {{ item.subtitle }} -
+
diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js index 90723b6ddfb..fcced25ab11 100644 --- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js +++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js @@ -1,8 +1,8 @@ import Vue from 'vue'; import { GlToast } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; +import { getApolloProvider } from '~/issues/list/issue_client'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; -import { apolloProvider } from '~/graphql_shared/issuable_client'; import { JS_TOGGLE_EXPAND_CLASS, CONTEXT_NAMESPACE_GROUPS } from './constants'; import createStore from './components/global_search/store'; import { @@ -94,7 +94,7 @@ export const getSuperSidebarData = () => { }; }; -export const initSuperSidebar = ({ +export const initSuperSidebar = async ({ el, rootPath, currentPath, @@ -125,7 +125,7 @@ export const initSuperSidebar = ({ return new Vue({ el, name: 'SuperSidebarRoot', - apolloProvider, + apolloProvider: await getApolloProvider(), provide: { rootPath, currentPath, diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index c75bbe7712c..b4e6f0ce00e 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -452,7 +452,7 @@ export default { }, titleClassHeader() { return { - 'sm:!gl-hidden gl-mt-3': this.shouldShowAncestors, + 'sm:!gl-hidden !gl-mt-3': this.shouldShowAncestors, 'sm:!gl-block': !this.shouldShowAncestors, 'gl-w-full': !this.shouldShowAncestors && !this.editMode, 'editable-wi-title': this.editMode && !this.shouldShowAncestors, @@ -461,7 +461,7 @@ export default { titleClassComponent() { return { 'sm:!gl-block': !this.shouldShowAncestors, - 'gl-hidden sm:!gl-block gl-mt-3': this.shouldShowAncestors, + 'gl-hidden sm:!gl-block !gl-mt-3': this.shouldShowAncestors, 'editable-wi-title': this.workItemsAlphaEnabled, }; }, diff --git a/app/assets/javascripts/work_items/graphql/cache_utils.js b/app/assets/javascripts/work_items/graphql/cache_utils.js index 04f01defb9b..a1ffe5c33d1 100644 --- a/app/assets/javascripts/work_items/graphql/cache_utils.js +++ b/app/assets/javascripts/work_items/graphql/cache_utils.js @@ -1,8 +1,6 @@ import { produce } from 'immer'; -import VueApollo from 'vue-apollo'; import { map, isEqual } from 'lodash'; -import { apolloProvider } from '~/graphql_shared/issuable_client'; -import { issuesListClient } from '~/issues/list'; +import { getApolloProvider } from '~/issues/list/issue_client'; import { TYPENAME_USER } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { getBaseURL } from '~/lib/utils/url_utility'; @@ -854,13 +852,7 @@ export const setNewWorkItemCache = async ({ }); } - const issuesListApolloProvider = new VueApollo({ - defaultClient: await issuesListClient(), - }); - - const cacheProvider = document.querySelector('.js-issues-list-app') - ? issuesListApolloProvider - : apolloProvider; + const cacheProvider = await getApolloProvider(); const newWorkItemPath = newWorkItemFullPath(fullPath, workItemType); diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js index 8e5a1879ed0..99fe3ff6433 100644 --- a/app/assets/javascripts/work_items/index.js +++ b/app/assets/javascripts/work_items/index.js @@ -37,6 +37,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {} labelsManagePath, registerPath, signInPath, + hasBlockedIssuesFeature, hasGroupBulkEditFeature, hasIterationsFeature, hasOkrsFeature, @@ -104,6 +105,7 @@ export const initWorkItemsRoot = ({ workItemType, workspaceType, withTabs } = {} fullPath, isGroup, isProject: !isGroup, + hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature), hasGroupBulkEditFeature: parseBoolean(hasGroupBulkEditFeature), hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), hasOkrsFeature: parseBoolean(hasOkrsFeature), diff --git a/app/assets/javascripts/work_items/pages/list/constants.js b/app/assets/javascripts/work_items/pages/list/constants.js deleted file mode 100644 index d621755c81b..00000000000 --- a/app/assets/javascripts/work_items/pages/list/constants.js +++ /dev/null @@ -1,69 +0,0 @@ -import { __ } from '~/locale'; -import { - CREATED_ASC, - CREATED_DESC, - DUE_DATE_ASC, - DUE_DATE_DESC, - START_DATE_ASC, - START_DATE_DESC, - TITLE_ASC, - TITLE_DESC, - UPDATED_ASC, - UPDATED_DESC, -} from '~/issues/list/constants'; - -export const sortOptions = [ - { - id: 1, - title: __('Created date'), - sortDirection: { - ascending: CREATED_ASC, - descending: CREATED_DESC, - }, - }, - { - id: 2, - title: __('Updated date'), - sortDirection: { - ascending: UPDATED_ASC, - descending: UPDATED_DESC, - }, - }, - { - id: 3, - title: __('Start date'), - sortDirection: { - ascending: START_DATE_ASC, - descending: START_DATE_DESC, - }, - }, - { - id: 4, - title: __('Due date'), - sortDirection: { - ascending: DUE_DATE_ASC, - descending: DUE_DATE_DESC, - }, - }, - { - id: 5, - title: __('Title'), - sortDirection: { - ascending: TITLE_ASC, - descending: TITLE_DESC, - }, - }, -]; - -export const urlSortParams = { - [CREATED_ASC]: 'created_asc', - [CREATED_DESC]: 'created_date', - [DUE_DATE_ASC]: 'due_date_asc', - [DUE_DATE_DESC]: 'due_date_desc', - [START_DATE_ASC]: 'start_date_asc', - [START_DATE_DESC]: 'start_date_desc', - [TITLE_ASC]: 'title_asc', - [TITLE_DESC]: 'title_desc', - [UPDATED_ASC]: 'updated_asc', - [UPDATED_DESC]: 'updated_desc', -}; diff --git a/app/assets/javascripts/work_items/pages/work_items_list_app.vue b/app/assets/javascripts/work_items/pages/work_items_list_app.vue index 64caf4105b0..4e8e69c622f 100644 --- a/app/assets/javascripts/work_items/pages/work_items_list_app.vue +++ b/app/assets/javascripts/work_items/pages/work_items_list_app.vue @@ -12,6 +12,7 @@ import { getDefaultWorkItemTypes, getFilterTokens, getInitialPageParams, + getSortOptions, getTypeTokenOptions, } from 'ee_else_ce/issues/list/utils'; import { TYPENAME_NAMESPACE, TYPENAME_USER } from '~/graphql_shared/constants'; @@ -33,6 +34,7 @@ import { PARAM_PAGE_BEFORE, PARAM_SORT, PARAM_STATE, + urlSortParams, } from '~/issues/list/constants'; import searchLabelsQuery from '~/issues/list/queries/search_labels.query.graphql'; import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql'; @@ -100,7 +102,6 @@ import { WORK_ITEM_TYPE_NAME_KEY_RESULT, WORK_ITEM_TYPE_NAME_OBJECTIVE, } from '../constants'; -import { sortOptions, urlSortParams } from './list/constants'; const EmojiToken = () => import('~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'); @@ -120,7 +121,6 @@ const statusMap = { export default { issuableListTabs, - sortOptions, components: { GlLoadingIcon, GlButton, @@ -141,9 +141,12 @@ export default { 'autocompleteAwardEmojisPath', 'canBulkUpdate', 'canBulkEditEpics', + 'hasBlockedIssuesFeature', 'hasEpicsFeature', 'hasGroupBulkEditFeature', + 'hasIssuableHealthStatusFeature', 'hasIssueDateFilterFeature', + 'hasIssueWeightsFeature', 'hasOkrsFeature', 'hasQualityManagementFeature', 'hasCustomFieldsFeature', @@ -581,6 +584,21 @@ export default { showPageSizeSelector() { return this.workItems.length > 0; }, + sortOptions() { + return getSortOptions({ + hasBlockedIssuesFeature: this.hasBlockedIssuesFeature, + hasIssuableHealthStatusFeature: this.hasIssuableHealthStatusFeature, + hasIssueWeightsFeature: this.hasIssueWeightsFeature, + hasManualSort: false, + hasStartDate: true, + hasPriority: !this.isEpicsList, + hasMilestoneDueDate: Boolean( + !this.isEpicsList || (this.isEpicsList && this.glFeatures.workItemEpicMilestones), + ), + hasLabelPriority: !this.isEpicsList, + hasWeight: !this.isEpicsList, + }); + }, tabCounts() { const { all, closed, opened } = this.workItemStateCounts; return { @@ -880,7 +898,7 @@ export default { // Trigger pageSize UI component update based on URL changes this.pageSize = this.pageParams.firstPageSize; - this.sortKey = deriveSortKey({ sort, sortMap: urlSortParams }); + this.sortKey = deriveSortKey({ sort }); this.state = state || STATUS_OPEN; }, checkDrawerParams() { @@ -976,7 +994,7 @@ export default { :show-page-size-selector="showPageSizeSelector" :show-pagination-controls="showPaginationControls" show-work-item-type-icon - :sort-options="$options.sortOptions" + :sort-options="sortOptions" sync-filter-and-sort :tab-counts="tabCounts" :tabs="tabs" diff --git a/app/assets/stylesheets/page_bundles/merge_request.scss b/app/assets/stylesheets/page_bundles/merge_request.scss index 25f903240d0..74e447d106a 100644 --- a/app/assets/stylesheets/page_bundles/merge_request.scss +++ b/app/assets/stylesheets/page_bundles/merge_request.scss @@ -159,7 +159,7 @@ $comparison-empty-state-height: 62px; @supports (container-type: scroll-state) { .merge-request-sticky-header { - @include media-breakpoint-up(lg) { + @include media-breakpoint-up(md) { @apply gl-sticky; container-type: scroll-state; container-name: sticky-header; @@ -179,7 +179,7 @@ $comparison-empty-state-height: 62px; } @container sticky-header scroll-state(stuck: top) { - &::after { + &::after, .merge-request-sticky-title { @apply gl-block; } @@ -199,13 +199,11 @@ $comparison-empty-state-height: 62px; .merge-request-author-container { @apply gl-hidden; } - } - } - @include media-breakpoint-up(lg) { - @container sticky-header scroll-state(stuck: top) { - .merge-request-sticky-title { - @apply gl-block; + @include media-breakpoint-down(md) { + .merge-request-tabs-actions { + @apply gl-hidden; + } } } } diff --git a/app/controllers/glql/base_controller.rb b/app/controllers/glql/base_controller.rb index 6806ac07c54..89d8062f269 100644 --- a/app/controllers/glql/base_controller.rb +++ b/app/controllers/glql/base_controller.rb @@ -23,7 +23,6 @@ module Glql # We catch all errors here so they are tracked by SLIs. # But we only increment the rate limiter failure count for ActiveRecord::QueryAborted. increment_rate_limit_counter if error.is_a?(ActiveRecord::QueryAborted) - ensure_logs_populated raise error ensure @@ -56,19 +55,13 @@ module Glql def logs graphql_logs = super.presence || [{}] - graphql_logs.map { |log| log.merge(log_data) } - end - def ensure_logs_populated - RequestStore.store[:graphql_logs] ||= [] - RequestStore.store[:graphql_logs] << log_data - end - - def log_data - { - glql_referer: request.headers["Referer"], - glql_query_sha: query_sha - } + graphql_logs.map do |log| + log.merge( + glql_referer: request.headers["Referer"], + glql_query_sha: query_sha + ) + end end def check_rate_limit diff --git a/app/views/projects/merge_requests/_page.html.haml b/app/views/projects/merge_requests/_page.html.haml index e6b77c6450d..0bf6f92933b 100644 --- a/app/views/projects/merge_requests/_page.html.haml +++ b/app/views/projects/merge_requests/_page.html.haml @@ -47,7 +47,7 @@ = tab_link_for @merge_request, :diffs do = _("Changes") = gl_badge_tag tab_count_display(@merge_request, @diffs_count), { class: 'js-changes-tab-count', data: { gid: @merge_request.to_gid.to_s } } - .gl-flex.gl-flex-wrap.gl-items-center.gl-gap-3 + .merge-request-tabs-actions.gl-flex.gl-flex-wrap.gl-items-center.gl-gap-3 #js-vue-discussion-counter{ data: { blocks_merge: @project.only_allow_merge_if_all_discussions_are_resolved?.to_s } } - if !!@issuable_sidebar.dig(:current_user, :id) .js-sidebar-todo-widget-root{ data: { project_path: @issuable_sidebar[:project_full_path], iid: @issuable_sidebar[:iid], id: @issuable_sidebar[:id] } } diff --git a/app/workers/projects/import_export/relation_export_worker.rb b/app/workers/projects/import_export/relation_export_worker.rb index d24d41cb97e..4c87806d3dd 100644 --- a/app/workers/projects/import_export/relation_export_worker.rb +++ b/app/workers/projects/import_export/relation_export_worker.rb @@ -16,26 +16,17 @@ module Projects tags :import_shared_storage sidekiq_retries_exhausted do |job, exception| - relation_export = Projects::ImportExport::RelationExport.find(job['args'].first) - project_export_job = relation_export.project_export_job - project = project_export_job.project - - relation_export.mark_as_failed(job['error_message']) - - log_payload = { - message: 'Project relation export failed', - export_error: job['error_message'], - relation: relation_export.relation, - project_export_job_id: project_export_job.id, - project_name: project.name, - project_id: project.id - } - Gitlab::ExceptionLogFormatter.format!(exception, log_payload) - Gitlab::Export::Logger.error(log_payload) + new.mark_relation_export_failed!(job['args'].first, job['error_message'], exception: exception) end def perform(project_relation_export_id, user_id, params = {}) user = User.find(user_id) + + if user.banned? + mark_relation_export_failed!(project_relation_export_id, "User #{user_id} is banned") + return + end + params.symbolize_keys! relation_export = Projects::ImportExport::RelationExport.find(project_relation_export_id) @@ -47,6 +38,26 @@ module Projects Projects::ImportExport::RelationExportService.new(relation_export, user, jid, params).execute end end + + def mark_relation_export_failed!(project_relation_export_id, message, exception: nil) + relation_export = Projects::ImportExport::RelationExport.find(project_relation_export_id) + project_export_job = relation_export.project_export_job + project = project_export_job.project + + relation_export.mark_as_failed(message) + + log_payload = { + message: 'Project relation export failed', + export_error: message, + relation: relation_export.relation, + project_export_job_id: project_export_job.id, + project_name: project.name, + project_id: project.id + } + + Gitlab::ExceptionLogFormatter.format!(exception, log_payload) if exception.present? + Gitlab::Export::Logger.error(log_payload) + end end end end diff --git a/db/docs/batched_background_migrations/backfill_namespaces_redirect_routes_namespace_id.yml b/db/docs/batched_background_migrations/backfill_namespaces_redirect_routes_namespace_id.yml index 844953a55e9..66d92873b7f 100644 --- a/db/docs/batched_background_migrations/backfill_namespaces_redirect_routes_namespace_id.yml +++ b/db/docs/batched_background_migrations/backfill_namespaces_redirect_routes_namespace_id.yml @@ -5,4 +5,4 @@ feature_category: groups_and_projects introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183967 milestone: '17.11' queued_migration_version: 20250403051401 -finalized_by: # version of the migration that finalized this BBM +finalized_by: '20250618220559' diff --git a/db/docs/batched_background_migrations/backfill_operations_scopes_project_id.yml b/db/docs/batched_background_migrations/backfill_operations_scopes_project_id.yml index a97c0f3a402..a83d1c76ff7 100644 --- a/db/docs/batched_background_migrations/backfill_operations_scopes_project_id.yml +++ b/db/docs/batched_background_migrations/backfill_operations_scopes_project_id.yml @@ -5,4 +5,4 @@ feature_category: feature_flags introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180223 milestone: '17.9' queued_migration_version: 20250204151548 -finalized_by: # version of the migration that finalized this BBM +finalized_by: '20250619145012' diff --git a/db/docs/batched_background_migrations/backfill_packages_conan_file_metadata_project_id.yml b/db/docs/batched_background_migrations/backfill_packages_conan_file_metadata_project_id.yml index c56630d8c0f..ec26e6616d4 100644 --- a/db/docs/batched_background_migrations/backfill_packages_conan_file_metadata_project_id.yml +++ b/db/docs/batched_background_migrations/backfill_packages_conan_file_metadata_project_id.yml @@ -5,4 +5,4 @@ feature_category: package_registry introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/185222 milestone: '17.11' queued_migration_version: 20250320085452 -finalized_by: # version of the migration that finalized this BBM +finalized_by: '20250616173500' diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index fa9f118b711..379a1329ddd 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -6,14 +6,20 @@ Gitlab::Seeder.quiet do return end + password_via_env = ENV['GITLAB_ROOT_PASSWORD'].presence + password = password_via_env || '5iveL!fe' + # When password is set via environment variable, don't force reset on first login. + # Otherwise, expire password immediately to require reset. + password_expires_at = DateTime.now unless password_via_env + admin = User.create!( name: 'Administrator', email: "gitlab_admin_#{SecureRandom.hex(3)}@example.com", username: 'root', - password: '5iveL!fe', + password: password, admin: true, confirmed_at: DateTime.now, - password_expires_at: DateTime.now + password_expires_at: password_expires_at ) do |user| user.assign_personal_namespace(Organizations::Organization.default_organization) end diff --git a/db/post_migrate/20250616173500_finalize_backfill_packages_conan_file_metadata_project_id.rb b/db/post_migrate/20250616173500_finalize_backfill_packages_conan_file_metadata_project_id.rb new file mode 100644 index 00000000000..ed9fc0b77d0 --- /dev/null +++ b/db/post_migrate/20250616173500_finalize_backfill_packages_conan_file_metadata_project_id.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class FinalizeBackfillPackagesConanFileMetadataProjectId < Gitlab::Database::Migration[2.3] + milestone '18.2' + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'BackfillPackagesConanFileMetadataProjectId', + table_name: :packages_conan_file_metadata, + column_name: :id, + job_arguments: [:project_id, :packages_package_files, :project_id, :package_file_id], + finalize: true + ) + end + + def down; end +end diff --git a/db/post_migrate/20250618220559_finalize_backfill_namespaces_redirect_routes_namespace_id.rb b/db/post_migrate/20250618220559_finalize_backfill_namespaces_redirect_routes_namespace_id.rb new file mode 100644 index 00000000000..65522ebd717 --- /dev/null +++ b/db/post_migrate/20250618220559_finalize_backfill_namespaces_redirect_routes_namespace_id.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class FinalizeBackfillNamespacesRedirectRoutesNamespaceId < Gitlab::Database::Migration[2.3] + milestone '18.2' + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'BackfillNamespacesRedirectRoutesNamespaceId', + table_name: :redirect_routes, + column_name: :id, + job_arguments: [], + finalize: true + ) + end + + def down; end +end diff --git a/db/post_migrate/20250619145012_finalize_backfill_operations_scopes_project_id.rb b/db/post_migrate/20250619145012_finalize_backfill_operations_scopes_project_id.rb new file mode 100644 index 00000000000..307074764c7 --- /dev/null +++ b/db/post_migrate/20250619145012_finalize_backfill_operations_scopes_project_id.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class FinalizeBackfillOperationsScopesProjectId < Gitlab::Database::Migration[2.3] + milestone '18.2' + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'BackfillOperationsScopesProjectId', + table_name: :operations_scopes, + column_name: :id, + job_arguments: [:project_id, :operations_strategies, :project_id, :strategy_id], + finalize: true + ) + end + + def down; end +end diff --git a/db/schema_migrations/20250616173500 b/db/schema_migrations/20250616173500 new file mode 100644 index 00000000000..74003667843 --- /dev/null +++ b/db/schema_migrations/20250616173500 @@ -0,0 +1 @@ +0233515b55c32b5ebcb73dfec6aa362f107a10538484cac3ee0e36833175c2ef \ No newline at end of file diff --git a/db/schema_migrations/20250618220559 b/db/schema_migrations/20250618220559 new file mode 100644 index 00000000000..510cfc11978 --- /dev/null +++ b/db/schema_migrations/20250618220559 @@ -0,0 +1 @@ +a5521a381e16c988f130e632991eb22eadb7a5f500a4cc429fedf72ddfce9f53 \ No newline at end of file diff --git a/db/schema_migrations/20250619145012 b/db/schema_migrations/20250619145012 new file mode 100644 index 00000000000..9b27bdfc8a3 --- /dev/null +++ b/db/schema_migrations/20250619145012 @@ -0,0 +1 @@ +81c5ba0591c9e439013df34658b86dfbbc635c667b1928652bca0cbaffb82ba4 \ No newline at end of file diff --git a/doc/administration/auth/ldap/ldap_synchronization.md b/doc/administration/auth/ldap/ldap_synchronization.md index 6d5008d2138..dcb43faa27a 100644 --- a/doc/administration/auth/ldap/ldap_synchronization.md +++ b/doc/administration/auth/ldap/ldap_synchronization.md @@ -1,6 +1,6 @@ --- -stage: Software Supply Chain Security -group: Authentication +stage: Fulfillment +group: Provision info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments gitlab_dedicated: no title: LDAP synchronization diff --git a/doc/administration/guest_users.md b/doc/administration/guest_users.md index ece321e0cde..5bf14782e5b 100644 --- a/doc/administration/guest_users.md +++ b/doc/administration/guest_users.md @@ -1,6 +1,6 @@ --- stage: Software Supply Chain Security -group: Authentication +group: Authorization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Guest users --- diff --git a/doc/api/_index.md b/doc/api/_index.md index 701d4ea66f3..29ad7813a3c 100644 --- a/doc/api/_index.md +++ b/doc/api/_index.md @@ -3,6 +3,7 @@ stage: Foundations group: Import and Integrate info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Extend with GitLab +description: Connect GitLab to your tools and workflows to build a customized development environment. --- Connect GitLab to your tools and workflows to build a customized development environment. diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index f57f15b9afb..c5627762af9 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -1,6 +1,6 @@ --- stage: Software Supply Chain Security -group: Authentication +group: Authorization info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Member roles API --- diff --git a/doc/api/members.md b/doc/api/members.md index 118357c1dfb..965c7327ab4 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -1,6 +1,6 @@ --- -stage: Software Supply Chain Security -group: Authentication +stage: Tenant Scale +group: Organizations info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Group and project members API --- diff --git a/doc/development/_index.md b/doc/development/_index.md index 50f6817a570..0bf3a25ce61 100644 --- a/doc/development/_index.md +++ b/doc/development/_index.md @@ -2,8 +2,8 @@ stage: none group: unassigned info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review. -description: 'Development Guidelines: learn how to contribute to GitLab.' title: Contribute to development +description: Learn how to contribute to the development of the GitLab product. --- Learn how to contribute to the development of the GitLab product. diff --git a/doc/development/policies.md b/doc/development/policies.md index d3799f598ef..2c38788dd30 100644 --- a/doc/development/policies.md +++ b/doc/development/policies.md @@ -1,6 +1,6 @@ --- stage: Software Supply Chain Security -group: Authentication +group: Authorization info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/development/development_processes/#development-guidelines-review. title: The `DeclarativePolicy` framework --- diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md index 6f950584f88..9f2bcbcb96f 100644 --- a/doc/security/rate_limits.md +++ b/doc/security/rate_limits.md @@ -1,6 +1,6 @@ --- -stage: Software Supply Chain Security -group: Authentication +stage: none +group: unassigned info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Rate limits --- diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md index 8d195c291d3..57c65541b4b 100644 --- a/doc/security/user_file_uploads.md +++ b/doc/security/user_file_uploads.md @@ -1,6 +1,6 @@ --- -stage: Software Supply Chain Security -group: Authentication +stage: Plan +group: Project Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: User file uploads --- diff --git a/doc/solutions/_index.md b/doc/solutions/_index.md index 8686fb6dc5e..0885e4f3291 100644 --- a/doc/solutions/_index.md +++ b/doc/solutions/_index.md @@ -3,6 +3,7 @@ stage: Solutions Architecture group: Solutions Architecture info: This page is owned by the Solutions Architecture team. title: Solutions architecture +description: Use these reference solutions to integrate GitLab with your people, process, and technology. --- As with all extensible platforms, GitLab has many features that can be creatively combined together with third party functionality to create solutions that address the specific people, process, and technology challenges of the organizations that use it. Reference solutions and implementations can also be crafted at a more general level so that they can be adopted and customized by customers with similar needs to the reference solution. diff --git a/doc/subscriptions/_index.md b/doc/subscriptions/_index.md index 82c5b55e8af..aa1e2c22136 100644 --- a/doc/subscriptions/_index.md +++ b/doc/subscriptions/_index.md @@ -3,6 +3,7 @@ stage: Fulfillment group: Subscription Management info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Subscribe to GitLab +description: Choose and manage the subscription that's right for you and your organization. --- Choose and manage the subscription that's right for you and your organization. diff --git a/doc/tutorials/_index.md b/doc/tutorials/_index.md index c08c0b05c46..bd6cad2fc8a 100644 --- a/doc/tutorials/_index.md +++ b/doc/tutorials/_index.md @@ -3,9 +3,10 @@ stage: none group: Tutorials info: For assistance with this tutorials page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects. title: Learn GitLab with tutorials +description: Help learn key GitLab workflows by following guided instructions. --- -These tutorials can help you learn how to use GitLab. +Learn about GitLab fundamentals by following guided instructions. {{< cards >}} diff --git a/doc/user/_index.md b/doc/user/_index.md index 11ba2a4ba86..019a7c7654d 100644 --- a/doc/user/_index.md +++ b/doc/user/_index.md @@ -3,6 +3,7 @@ stage: none group: unassigned info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Use GitLab +description: Get to know the GitLab end-to-end workflow. --- Get to know the GitLab end-to-end workflow. Configure permissions, diff --git a/doc/user/application_security/dependency_list/_index.md b/doc/user/application_security/dependency_list/_index.md index 507e4aefca2..1c7da8380c7 100644 --- a/doc/user/application_security/dependency_list/_index.md +++ b/doc/user/application_security/dependency_list/_index.md @@ -92,6 +92,7 @@ Details of each dependency are listed, sorted by decreasing severity of vulnerab - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/513321) in GitLab 17.10. Feature flag `project_component_filter` removed. - Dependency version filtering introduced for [projects](https://gitlab.com/gitlab-org/gitlab/-/issues/520771) and [groups](https://gitlab.com/gitlab-org/gitlab/-/issues/523061) in GitLab 18.0 with [flags](../../../administration/feature_flags/_index.md) named `version_filtering_on_project_level_dependency_list` and `version_filtering_on_group_level_dependency_list`. Disabled by default. - Dependency version filtering [enabled](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/192291) on GitLab.com, GitLab Self-Managed, and GitLab Dedicated in GitLab 18.1. +- Feature flags `version_filtering_on_project_level_dependency_list` and `version_filtering_on_group_level_dependency_list` removed. {{< /history >}} diff --git a/doc/user/gitlab_duo/_index.md b/doc/user/gitlab_duo/_index.md index aa3e2f84ee3..1c19085b7b9 100644 --- a/doc/user/gitlab_duo/_index.md +++ b/doc/user/gitlab_duo/_index.md @@ -2,7 +2,7 @@ stage: AI-powered group: AI Framework info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments -description: AI-native features and functionality. +description: Get help from a suite of AI-native features while you work in GitLab. title: GitLab Duo --- diff --git a/doc/user/profile/achievements.md b/doc/user/profile/achievements.md index abe40f785e1..dc54c9d0437 100644 --- a/doc/user/profile/achievements.md +++ b/doc/user/profile/achievements.md @@ -1,6 +1,6 @@ --- stage: Tenant Scale -group: Organizations +group: Authentication info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Achievements --- diff --git a/doc/user/profile/contributions_calendar.md b/doc/user/profile/contributions_calendar.md index daf266b4b45..9718e8eb7fe 100644 --- a/doc/user/profile/contributions_calendar.md +++ b/doc/user/profile/contributions_calendar.md @@ -1,6 +1,6 @@ --- stage: Tenant Scale -group: Organizations +group: Authentication info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments title: Contributions calendar --- diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands.rb index 9302d83c491..e0ad6132040 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands.rb @@ -6,7 +6,6 @@ module Gitlab module Commands autoload :BackupSubcommand, 'gitlab/backup/cli/commands/backup_subcommand' autoload :Command, 'gitlab/backup/cli/commands/command' - autoload :ObjectStorageCommand, 'gitlab/backup/cli/commands/object_storage_command' autoload :RestoreSubcommand, 'gitlab/backup/cli/commands/restore_subcommand' end end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/backup_subcommand.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/backup_subcommand.rb index 96ed6380d0d..37afd4d8ebd 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/backup_subcommand.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/backup_subcommand.rb @@ -4,7 +4,7 @@ module Gitlab module Backup module Cli module Commands - class BackupSubcommand < ObjectStorageCommand + class BackupSubcommand < Command package_name 'Backup' desc 'all', 'Creates a backup including repositories, database and local files' diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/object_storage_command.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/object_storage_command.rb deleted file mode 100644 index ff5727141a8..00000000000 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/object_storage_command.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Backup - module Cli - module Commands - class ObjectStorageCommand < Command - class_option :backup_bucket, - desc: "When backing up object storage, this is the bucket to backup to", - required: false, - type: :string - - class_option :wait_for_completion, - desc: "Wait for object storage backups to complete", - type: :boolean, - default: true - - class_option :registry_bucket, - desc: "When backing up registry from object storage, this is the source bucket", - required: false, - type: :string - - class_option :service_account_file, - desc: "JSON file containing the Google service account credentials", - default: "/etc/gitlab/backup-account-credentials.json", - type: :string - end - end - end - end -end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/restore_subcommand.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/restore_subcommand.rb index 935f6fa208a..26dfaba3b27 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/restore_subcommand.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/commands/restore_subcommand.rb @@ -4,7 +4,7 @@ module Gitlab module Backup module Cli module Commands - class RestoreSubcommand < ObjectStorageCommand + class RestoreSubcommand < Command package_name 'Restore' desc 'all BACKUP_ID', 'Restores a backup including repositories, database and local files' diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb index 5723e526913..cb98c615c11 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/context/source_context.rb @@ -136,6 +136,14 @@ module Gitlab raise ::Gitlab::Backup::Cli::Error, 'GITLAB_PATH is missing' end + def registry_enabled? + gitlab_config.dig(env, 'registry', 'enabled') + end + + def object_storage_connection + gitlab_config.dig(env, 'object_storage', 'connection') + end + private # Return the shared path used as a fallback base location to each blob type diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb index 553d4349cbf..c0d7afdbe51 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets.rb @@ -7,7 +7,6 @@ module Gitlab autoload :Target, 'gitlab/backup/cli/targets/target' autoload :Database, 'gitlab/backup/cli/targets/database' autoload :Files, 'gitlab/backup/cli/targets/files' - autoload :ObjectStorage, 'gitlab/backup/cli/targets/object_storage' autoload :Repositories, 'gitlab/backup/cli/targets/repositories' end end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage.rb deleted file mode 100644 index 678841bfd45..00000000000 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require "google/cloud/storage_transfer" - -require_relative "object_storage/google" - -module Gitlab - module Backup - module Cli - module Targets - class ObjectStorage - SUPPORTED_PROVIDERS = [ - "Google" - ].freeze - - def self.find_task(object_type, options, config) - # For objects that don't use the consolidated config (like the registry), try the global - # object_store for connection information. This will go away with a config file - # https://gitlab.com/gitlab-org/gitlab/-/issues/475114 - const_get(config.object_store.connection.provider, false).new(object_type, options, config) - end - end - end - end - end -end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb deleted file mode 100644 index 3a823b72df8..00000000000 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/targets/object_storage/google.rb +++ /dev/null @@ -1,138 +0,0 @@ -# frozen_string_literal: true - -require "google/cloud/storage_transfer" - -module Gitlab - module Backup - module Cli - module Targets - class ObjectStorage - class Google < Target - OperationNotFoundError = Class.new(StandardError) - - attr_accessor :object_type, :backup_bucket, :client, :config, :results - - def initialize(object_type, remote_directory, config) - @object_type = object_type - @backup_bucket = remote_directory - @config = config - @client = ::Google::Cloud::StorageTransfer.storage_transfer_service - end - - # @param [String] backup_id unique identifier for the backup - def dump(backup_id) - response = find_or_create_job(backup_id, "backup") - run_request = { - project_id: backup_job_spec(backup_id)[:project_id], - job_name: response.name - } - @results = client.run_transfer_job run_request - end - - # @param [String] backup_id unique identifier for the backup - def restore(backup_id) - response = find_or_create_job(backup_id, "restore") - run_request = { - project_id: restore_job_spec(backup_id)[:project_id], - job_name: response.name - } - @results = client.run_transfer_job run_request - end - - def job_name(operation) - "transferJobs/#{object_type}-#{operation}" - end - - def backup_job_spec(backup_id) - job_spec( - config.object_store.remote_directory, - backup_bucket, - operation: "backup", - destination_path: backup_path(backup_id) - ) - end - - def restore_job_spec(backup_id) - job_spec( - backup_bucket, - config.object_store.remote_directory, - operation: "restore", - source_path: backup_path(backup_id) - ) - end - - def backup_path(backup_id) - "backups/#{backup_id}/#{object_type}/" - end - - def find_job_spec(backup_id, operation) - case operation - when "backup" - backup_job_spec(backup_id) - when "restore" - restore_job_spec(backup_id) - else - raise StandardError "Operation #{operation} not found" - end - end - - def job_spec(source, destination, operation:, source_path: nil, destination_path: nil) - { - project_id: config.object_store.connection.google_project, - name: job_name(operation), - transfer_spec: { - gcs_data_source: { - bucket_name: source, - path: source_path - }, - gcs_data_sink: { - bucket_name: destination, - # NOTE: The trailing '/' is required - path: destination_path - } - }, - status: :ENABLED - } - end - - def asynchronous? - true - end - - def wait_until_done! - @results.wait_until_done! - end - - private - - def find_or_create_job(backup_id, operation) - begin - name = job_name(operation) - response = client.get_transfer_job( - job_name: name, project_id: config.object_store.connection.google_project - ) - log.info("Existing job for #{object_type} found, using") - job_update = find_job_spec(backup_id, operation) - job_update.delete(:project_id) - - client.update_transfer_job( - job_name: name, - project_id: config.object_store.connection.google_project, - transfer_job: job_update - ) - rescue ::Google::Cloud::NotFoundError - log.info("Existing job for #{object_type} not found, creating one") - response = client.create_transfer_job transfer_job: find_job_spec(backup_id, operation) - end - response - end - - def log - Gitlab::Backup::Cli::Output - end - end - end - end - end - end -end diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb index dfc7a9e82df..f2af07337c4 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/registry.rb @@ -7,7 +7,7 @@ module Gitlab class Registry < Task def self.id = 'registry' - def enabled = Gitlab.config.registry.enabled + def enabled = context.registry_enabled? def human_name = 'Container Registry Images' @@ -25,13 +25,17 @@ module Gitlab # Registry does not use consolidated object storage config. def config - settings = { - object_store: { - connection: context.gitlab_config('object_store').connection.to_hash, + unless context + Output.warning("No context passed to derive configuration from.") + return nil + end + + { + object_storage: { + connection: context.object_storage_connection, remote_directory: registry_bucket } } - GitlabSettings::Options.build(settings) end private diff --git a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb index 81295abea0b..bdb3fb5396d 100644 --- a/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb +++ b/gems/gitlab-backup-cli/lib/gitlab/backup/cli/tasks/task.rb @@ -74,17 +74,6 @@ module Gitlab nil end - def object_storage? - return false unless config - return false unless config.respond_to?(:object_store) && config.object_store.enabled - - return false unless Gitlab::Backup::Cli::Targets::ObjectStorage::SUPPORTED_PROVIDERS.include?( - config.object_store.connection.provider - ) - - true - end - def asynchronous? target.asynchronous? || false end @@ -96,13 +85,7 @@ module Gitlab def target return @target unless @target.nil? - @target = if object_storage? - ::Gitlab::Backup::Cli::Targets::ObjectStorage.find_task(id, options, config) - else - local - end - - @target + @target ||= local end private diff --git a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb b/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb deleted file mode 100644 index f034ff184fa..00000000000 --- a/gems/gitlab-backup-cli/spec/gitlab/backup/cli/targets/object_storage/google_spec.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe Gitlab::Backup::Cli::Targets::ObjectStorage::Google do - let(:gitlab_config) { class_double("GitlabSettings::Settings") } - - let(:supported_config) { instance_double("GitlabSettings::Options", object_store: supported_object_store) } - let(:supported_provider) do - instance_double( - "GitlabSettings::Options", provider: "Google", google_application_default: true, google_project: "fake_project" - ) - end - - let(:supported_object_store) do - instance_double( - "GitlabSettings::Options", enabled: true, connection: supported_provider, remote_directory: "fake_source_bucket" - ) - end - - let(:client) { instance_double("::Google::Cloud::StorageTransfer::V1::StorageTransferService::Client") } - let(:backup_transfer_job) { build(:google_cloud_storage_transfer_job) } - let(:restore_transfer_job) { build(:google_cloud_storage_transfer_job) } - let(:new_backup_transfer_job_spec) do - { - name: "transferJobs/fake_object-backup", - project_id: "fake_project", - transfer_spec: { - gcs_data_source: { - bucket_name: "fake_source_bucket", - path: nil - }, - gcs_data_sink: { - bucket_name: "fake_backup_bucket", - path: "backups/12345/fake_object/" - } - }, - status: :ENABLED - } - end - - let(:new_restore_transfer_job_spec) do - { - name: "transferJobs/fake_object-restore", - project_id: "fake_project", - transfer_spec: { - gcs_data_source: { - bucket_name: "fake_backup_bucket", - path: "backups/12345/fake_object/" - }, - gcs_data_sink: { - bucket_name: "fake_source_bucket", - path: nil - } - }, - status: :ENABLED - } - end - - before do - allow(Gitlab).to receive(:config).and_return(gitlab_config) - allow(::Google::Cloud::StorageTransfer).to receive(:storage_transfer_service).and_return(client) - allow(gitlab_config).to receive(:[]).with('fake_object').and_return(supported_config) - end - - subject(:object_storage) { described_class.new("fake_object", 'fake_backup_bucket', supported_config) } - - describe "#dump", :silence_output do - context "when job exists" do - before do - allow(client).to receive(:get_transfer_job).and_return(backup_transfer_job) - end - - it "reuses existing job" do - updated_spec = new_backup_transfer_job_spec - expect(client).to receive(:update_transfer_job).with( - job_name: updated_spec[:name], - project_id: updated_spec.delete(:project_id), - transfer_job: updated_spec - ) - expect(client).to receive(:run_transfer_job).with({ job_name: "fake_transfer_job", project_id: "fake_project" }) - object_storage.dump(12345) - end - end - - context "when job does not exist" do - before do - allow(client).to receive(:get_transfer_job).with( - job_name: "transferJobs/fake_object-backup", project_id: "fake_project" - ).and_raise(::Google::Cloud::NotFoundError) - allow(client).to receive(:run_transfer_job) - end - - it "creates a new job" do - expect(client).to receive(:create_transfer_job) - .with(transfer_job: new_backup_transfer_job_spec).and_return(backup_transfer_job) - object_storage.dump(12345) - end - end - end - - describe "#restore", :silence_output do - context "when job exists" do - before do - allow(client).to receive(:get_transfer_job).and_return(restore_transfer_job) - end - - it "reuses existing job" do - updated_spec = new_restore_transfer_job_spec - expect(client).to receive(:update_transfer_job).with( - job_name: updated_spec[:name], - project_id: updated_spec.delete(:project_id), - transfer_job: updated_spec - ) - expect(client).to receive(:run_transfer_job).with({ job_name: "fake_transfer_job", project_id: "fake_project" }) - object_storage.restore(12345) - end - end - - context "when job does not exist" do - before do - allow(client).to receive(:get_transfer_job).with( - job_name: "transferJobs/fake_object-restore", project_id: "fake_project" - ).and_raise(::Google::Cloud::NotFoundError) - allow(client).to receive(:run_transfer_job) - end - - it "creates a new job" do - expect(client).to receive(:create_transfer_job) - .with(transfer_job: new_restore_transfer_job_spec).and_return(restore_transfer_job) - object_storage.restore(12345) - end - end - end -end diff --git a/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_backup_spec.rb b/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_backup_spec.rb index e59d8243088..e7d483dbb2b 100644 --- a/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_backup_spec.rb +++ b/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_backup_spec.rb @@ -13,14 +13,6 @@ RSpec.describe 'gitlab-backup-cli backup subcommand', type: :thor do gitlab-backup-cli backup all # Creates a backup including repositories, database and local files gitlab-backup-cli backup help [COMMAND] # Describe subcommands or one specific subcommand - Options: - [--backup-bucket=BACKUP_BUCKET] # When backing up object storage, this is the bucket to backup to - [--wait-for-completion], [--no-wait-for-completion], [--skip-wait-for-completion] # Wait for object storage backups to complete - # Default: true - [--registry-bucket=REGISTRY_BUCKET] # When backing up registry from object storage, this is the source bucket - [--service-account-file=SERVICE_ACCOUNT_FILE] # JSON file containing the Google service account credentials - # Default: /etc/gitlab/backup-account-credentials.json - COMMAND end diff --git a/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_restore_spec.rb b/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_restore_spec.rb index 52c38fc9134..e010f604ba5 100644 --- a/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_restore_spec.rb +++ b/gems/gitlab-backup-cli/spec/thor/gitlab_backup_cli_restore_spec.rb @@ -13,14 +13,6 @@ RSpec.describe 'gitlab-backup-cli restore subcommand', type: :thor do gitlab-backup-cli restore all BACKUP_ID # Restores a backup including repositories, database and local files gitlab-backup-cli restore help [COMMAND] # Describe subcommands or one specific subcommand - Options: - [--backup-bucket=BACKUP_BUCKET] # When backing up object storage, this is the bucket to backup to - [--wait-for-completion], [--no-wait-for-completion], [--skip-wait-for-completion] # Wait for object storage backups to complete - # Default: true - [--registry-bucket=REGISTRY_BUCKET] # When backing up registry from object storage, this is the source bucket - [--service-account-file=SERVICE_ACCOUNT_FILE] # JSON file containing the Google service account credentials - # Default: /etc/gitlab/backup-account-credentials.json - COMMAND end diff --git a/package.json b/package.json index 7dc5f9005ae..e3e5fe99ab3 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@gitlab/application-sdk-browser": "^0.3.4", "@gitlab/at.js": "1.5.7", "@gitlab/cluster-client": "^3.0.0", - "@gitlab/duo-ui": "^8.18.0", + "@gitlab/duo-ui": "^8.22.0", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/fonts": "^1.3.0", "@gitlab/query-language-rust": "0.9.0", diff --git a/scripts/frontend/jest_ci.js b/scripts/frontend/jest_ci.js index 43782e226e0..441a1a4ec37 100755 --- a/scripts/frontend/jest_ci.js +++ b/scripts/frontend/jest_ci.js @@ -96,10 +96,21 @@ function parseArgumentsAndEnvironment() { } } + const coverageBranchExclusions = [ + /^as-if-foss\//, + /^\d+-\d+-stable(-ee)?$/, // exclude stable branches like 17-10-stable-ee, 18-0-stable-ee, etc. + ]; + + // Enable coverage if: + // - explicitly requested via --coverage + // - not running in a merge request (CI_MERGE_REQUEST_IID is unset) + // - the current branch is not excluded by coverageBranchExclusions + // - not running under Vue 3 (Vue 3 tests are not currently covered) + // - not running in predictive mode (predictive mode does not support coverage) const coverage = options.coverage || (!process.env.CI_MERGE_REQUEST_IID && - !/^as-if-foss\//.test(process.env.CI_COMMIT_BRANCH) && + !coverageBranchExclusions.some((rule) => rule.test(process.env.CI_COMMIT_BRANCH)) && !options.vue3 && !options.predictive); diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt index f331794c43f..fc889282860 100644 --- a/scripts/frontend/quarantined_vue3_specs.txt +++ b/scripts/frontend/quarantined_vue3_specs.txt @@ -37,7 +37,6 @@ ee/spec/frontend/metrics/details/metrics_details_spec.js ee/spec/frontend/metrics/details/related_traces_spec.js ee/spec/frontend/ml/ai_agents/views/edit_agent_spec.js ee/spec/frontend/oncall_schedule/schedule/components/preset_days/days_header_sub_item_spec.js -ee/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js ee/spec/frontend/related_items_tree/components/related_items_tree_body_spec.js ee/spec/frontend/related_items_tree/components/tree_root_spec.js ee/spec/frontend/roadmap/components/roadmap_shell_spec.js diff --git a/spec/controllers/glql/base_controller_spec.rb b/spec/controllers/glql/base_controller_spec.rb index ce9367d667d..d9f30532c56 100644 --- a/spec/controllers/glql/base_controller_spec.rb +++ b/spec/controllers/glql/base_controller_spec.rb @@ -90,19 +90,6 @@ RSpec.describe Glql::BaseController, feature_category: :integrations do execute_request end - - it 'tracks glql related logs' do - RequestStore.clear! - - execute_request - - expect(RequestStore.store[:graphql_logs]).to match([ - hash_including( - glql_referer: anything, - glql_query_sha: anything - ) - ]) - end end context 'when 2 consecutive ActiveRecord::QueryAborted errors occur' do diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap b/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap index 57ae2b7d0e8..16f43451cce 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/__snapshots__/search_item_spec.js.snap @@ -22,12 +22,6 @@ exports[`SearchItem should render the item 1`] = ` > Cole Dickinson - `; @@ -38,6 +32,7 @@ exports[`SearchItem should render the item 2`] = ` > Manage > Activity - `; @@ -85,11 +74,13 @@ exports[`SearchItem should render the item 3`] = ` Gitlab Org / MockProject1 @@ -119,12 +110,6 @@ exports[`SearchItem should render the item 4`] = ` > Dismiss Cipher with no integrity - `; diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js index c7e49310588..82d5f4bb121 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/search_item_spec.js @@ -1,4 +1,5 @@ -import { shallowMount } from '@vue/test-utils'; +import { GlAvatar } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import SearchItem from '~/super_sidebar/components/global_search/command_palette/search_item.vue'; import { getFormattedItem } from '~/super_sidebar/components/global_search/utils'; import { linksReducer } from '~/super_sidebar/components/global_search/command_palette/utils'; @@ -17,7 +18,7 @@ describe('SearchItem', () => { let wrapper; const createComponent = (item) => { - wrapper = shallowMount(SearchItem, { + wrapper = shallowMountExtended(SearchItem, { propsData: { item, searchQuery: 'root', @@ -25,9 +26,87 @@ describe('SearchItem', () => { }); }; + const findAvatar = () => wrapper.findComponent(GlAvatar); + const findIcon = () => wrapper.findByTestId('icon'); + const findNamespace = () => wrapper.findByTestId('namespace'); + const findNamespaceBullet = () => wrapper.findByTestId('namespace-bullet'); + it.each([mockUser, mockCommand, mockProject, mockIssue])('should render the item', (item) => { createComponent(item); expect(wrapper.element).toMatchSnapshot(); }); + + describe('item rendering', () => { + it('should render avatar when avatar_url is provided', () => { + const item = { + text: 'Test Item', + avatar_url: 'https://example.com/avatar.png', + entity_id: '123', + entity_name: 'Test Entity', + }; + + createComponent(item); + + expect(findAvatar().props('src')).toBe(item.avatar_url); + expect(findAvatar().props('entityId')).toBe(item.entity_id); + expect(findAvatar().props('entityName')).toBe(item.entity_name); + expect(findAvatar().props('size')).toBe(16); + expect(findAvatar().attributes('aria-hidden')).toBe('true'); + }); + + it('should not render avatar when avatar_url is undefined', () => { + const item = { + text: 'Test Item', + }; + + createComponent(item); + + expect(findAvatar().exists()).toBe(false); + }); + + it('should render icon when present', () => { + const item = { + icon: 'search-results', + text: 'Test Item', + }; + + createComponent(item); + + expect(findIcon().props('name')).toBe(item.icon); + }); + + it('should not render icon when not present', () => { + const item = { + text: 'Test Item', + }; + + createComponent(item); + + expect(findIcon().exists()).toBe(false); + }); + + it('should render namespace when present', () => { + const item = { + text: 'Test Item', + namespace: 'test-namespace', + }; + + createComponent(item); + + expect(findNamespaceBullet().exists()).toBe(true); + expect(findNamespace().text()).toBe('test-namespace'); + }); + + it('should not render namespace when not present', () => { + const item = { + text: 'Test Item', + }; + + createComponent(item); + + expect(findNamespaceBullet().exists()).toBe(false); + expect(findNamespace().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index 5eb41abe0ee..93480469759 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -501,7 +501,7 @@ describe('WorkItemDetail component', () => { }); it('does not show title in the header when parent exists', () => { - expect(findWorkItemType().classes()).toEqual(['sm:!gl-hidden', 'gl-mt-3']); + expect(findWorkItemType().classes()).toEqual(['sm:!gl-hidden', '!gl-mt-3']); }); }); @@ -518,7 +518,7 @@ describe('WorkItemDetail component', () => { }); it('does not show title in the header when parent exists', () => { - expect(findWorkItemType().classes()).toEqual(['sm:!gl-hidden', 'gl-mt-3']); + expect(findWorkItemType().classes()).toEqual(['sm:!gl-hidden', '!gl-mt-3']); }); }); }); diff --git a/spec/frontend/work_items/list/components/work_items_list_app_spec.js b/spec/frontend/work_items/list/components/work_items_list_app_spec.js index 817227db674..ff6e833fdac 100644 --- a/spec/frontend/work_items/list/components/work_items_list_app_spec.js +++ b/spec/frontend/work_items/list/components/work_items_list_app_spec.js @@ -21,7 +21,7 @@ import { import setWindowLocation from 'helpers/set_window_location_helper'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants'; -import { CREATED_DESC, UPDATED_DESC } from '~/issues/list/constants'; +import { CREATED_DESC, UPDATED_DESC, urlSortParams } from '~/issues/list/constants'; import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql'; import { scrollUp } from '~/lib/utils/scroll_utils'; import { getParameterByName, removeParams, updateHistory } from '~/lib/utils/url_utility'; @@ -47,7 +47,6 @@ import { import IssuableList from '~/vue_shared/issuable/list/components/issuable_list_root.vue'; import CreateWorkItemModal from '~/work_items/components/create_work_item_modal.vue'; import WorkItemsListApp from '~/work_items/pages/work_items_list_app.vue'; -import { sortOptions, urlSortParams } from '~/work_items/pages/list/constants'; import getWorkItemStateCountsQuery from 'ee_else_ce/work_items/graphql/list/get_work_item_state_counts.query.graphql'; import getWorkItemsFullQuery from 'ee_else_ce/work_items/graphql/list/get_work_items_full.query.graphql'; import getWorkItemsSlimQuery from 'ee_else_ce/work_items/graphql/list/get_work_items_slim.query.graphql'; @@ -142,8 +141,12 @@ describeSkipVue3(skipReason, () => { autocompleteAwardEmojisPath: 'autocomplete/award/emojis/path', canBulkUpdate: true, canBulkEditEpics: true, + hasBlockedIssuesFeature: false, hasEpicsFeature: false, hasGroupBulkEditFeature: true, + hasIssuableHealthStatusFeature: false, + hasIssueDateFilterFeature: false, + hasIssueWeightsFeature: false, hasOkrsFeature: false, hasQualityManagementFeature: false, hasCustomFieldsFeature: false, @@ -152,8 +155,6 @@ describeSkipVue3(skipReason, () => { isSignedIn: true, showNewWorkItem: true, workItemType: null, - hasIssueDateFilterFeature: false, - timeTrackingLimitToHours: false, ...provide, }, propsData: { @@ -205,7 +206,6 @@ describeSkipVue3(skipReason, () => { namespace: 'work-items', recentSearchesStorageKey: 'issues', showWorkItemTypeIcon: true, - sortOptions, tabs: WorkItemsListApp.issuableListTabs, }); }); @@ -260,6 +260,124 @@ describeSkipVue3(skipReason, () => { }); }); + describe('sort options', () => { + describe('when all features are enabled', () => { + it('renders all sort options', async () => { + mountComponent({ + provide: { + hasBlockedIssuesFeature: true, + hasIssuableHealthStatusFeature: true, + hasIssueWeightsFeature: true, + }, + }); + await waitForPromises(); + + expect(findIssuableList().props('sortOptions')).toEqual([ + expect.objectContaining({ title: 'Priority' }), + expect.objectContaining({ title: 'Created date' }), + expect.objectContaining({ title: 'Updated date' }), + expect.objectContaining({ title: 'Closed date' }), + expect.objectContaining({ title: 'Milestone due date' }), + expect.objectContaining({ title: 'Due date' }), + expect.objectContaining({ title: 'Popularity' }), + expect.objectContaining({ title: 'Label priority' }), + expect.objectContaining({ title: 'Title' }), + expect.objectContaining({ title: 'Start date' }), + expect.objectContaining({ title: 'Health' }), + expect.objectContaining({ title: 'Weight' }), + expect.objectContaining({ title: 'Blocking' }), + ]); + }); + }); + + describe('when all features are not enabled', () => { + it('renders base sort options', async () => { + mountComponent({ + provide: { + hasBlockedIssuesFeature: false, + hasIssuableHealthStatusFeature: false, + hasIssueWeightsFeature: false, + }, + }); + await waitForPromises(); + + expect(findIssuableList().props('sortOptions')).toEqual([ + expect.objectContaining({ title: 'Priority' }), + expect.objectContaining({ title: 'Created date' }), + expect.objectContaining({ title: 'Updated date' }), + expect.objectContaining({ title: 'Closed date' }), + expect.objectContaining({ title: 'Milestone due date' }), + expect.objectContaining({ title: 'Due date' }), + expect.objectContaining({ title: 'Popularity' }), + expect.objectContaining({ title: 'Label priority' }), + expect.objectContaining({ title: 'Title' }), + expect.objectContaining({ title: 'Start date' }), + ]); + }); + }); + + describe('when epics list', () => { + describe('when workItemEpicMilestones is disabled', () => { + it('does not render "Priority", "Milestone due date", "Label priority", and "Weight" sort options', async () => { + mountComponent({ + provide: { + glFeatures: { + workItemEpicMilestones: false, + }, + hasBlockedIssuesFeature: true, + hasIssuableHealthStatusFeature: true, + hasIssueWeightsFeature: true, + workItemType: WORK_ITEM_TYPE_NAME_EPIC, + }, + }); + await waitForPromises(); + + expect(findIssuableList().props('sortOptions')).toEqual([ + expect.objectContaining({ title: 'Created date' }), + expect.objectContaining({ title: 'Updated date' }), + expect.objectContaining({ title: 'Closed date' }), + expect.objectContaining({ title: 'Due date' }), + expect.objectContaining({ title: 'Popularity' }), + expect.objectContaining({ title: 'Title' }), + expect.objectContaining({ title: 'Start date' }), + expect.objectContaining({ title: 'Health' }), + expect.objectContaining({ title: 'Blocking' }), + ]); + }); + }); + + describe('when workItemEpicMilestones is enabled', () => { + it('does not render "Priority", "Label priority", and "Weight" sort options', async () => { + mountComponent({ + provide: { + glFeatures: { + workItemEpicMilestones: true, + }, + hasBlockedIssuesFeature: true, + hasIssuableHealthStatusFeature: true, + hasIssueWeightsFeature: true, + workItemType: WORK_ITEM_TYPE_NAME_EPIC, + }, + }); + await waitForPromises(); + + expect(findIssuableList().props('sortOptions')).toEqual([ + expect.objectContaining({ title: 'Created date' }), + expect.objectContaining({ title: 'Updated date' }), + expect.objectContaining({ title: 'Closed date' }), + expect.objectContaining({ title: 'Milestone due date' }), + expect.objectContaining({ title: 'Due date' }), + expect.objectContaining({ title: 'Popularity' }), + expect.objectContaining({ title: 'Title' }), + expect.objectContaining({ title: 'Start date' }), + expect.objectContaining({ title: 'Health' }), + expect.objectContaining({ title: 'Blocking' }), + ]); + }); + }); + }); + }); + describe('pagination controls', () => { describe.each` description | pageInfo | exists diff --git a/spec/workers/projects/import_export/relation_export_worker_spec.rb b/spec/workers/projects/import_export/relation_export_worker_spec.rb index a5ddb85f4e3..7979beb639e 100644 --- a/spec/workers/projects/import_export/relation_export_worker_spec.rb +++ b/spec/workers/projects/import_export/relation_export_worker_spec.rb @@ -9,8 +9,42 @@ RSpec.describe Projects::ImportExport::RelationExportWorker, type: :worker, feat it_behaves_like 'an idempotent worker' + shared_examples 'marks relation export failed' do + let(:error_message) { 'Error message' } + let(:exception) { nil } + + it 'does not call service, sets relation export status to `failed`, and logs error (exception too if present)' do + expect(Projects::ImportExport::RelationExportService).not_to receive(:new) + + expect_next_instance_of(Gitlab::Export::Logger) do |logger| + expect(logger).to receive(:error).with( + hash_including( + message: 'Project relation export failed', + export_error: error_message + ) + ) + end + + if exception.present? + expect_next_instance_of(Gitlab::ExceptionLogFormatter) do |formatter| + expect(formatter).to receive(:format!).with( + exception, + hash_including( + message: 'Project relation export failed', + export_error: error_message + ) + ) + end + end + + worker + + expect(project_relation_export.reload.failed?).to eq(true) + end + end + describe '#perform' do - subject(:worker) { described_class.new } + subject(:worker) { described_class.new.perform(*job_args) } context 'when relation export has initial status `queued`' do it 'exports the relation' do @@ -18,7 +52,7 @@ RSpec.describe Projects::ImportExport::RelationExportWorker, type: :worker, feat expect(service).to receive(:execute) end - worker.perform(*job_args) + worker end end @@ -30,7 +64,7 @@ RSpec.describe Projects::ImportExport::RelationExportWorker, type: :worker, feat expect(service).to receive(:execute) end - worker.perform(*job_args) + worker expect(project_relation_export.reload.queued?).to eq(true) end @@ -42,31 +76,27 @@ RSpec.describe Projects::ImportExport::RelationExportWorker, type: :worker, feat it 'does not export the relation' do expect(Projects::ImportExport::RelationExportService).not_to receive(:new) - worker.perform(*job_args) + worker + end + end + + context 'when importing user is banned' do + let(:user) { create(:user, :banned) } + + it_behaves_like 'marks relation export failed' do + let(:error_message) { "User #{user.id} is banned" } end end end describe '.sidekiq_retries_exhausted' do - let(:job) { { 'args' => job_args, 'error_message' => 'Error message' } } + let(:job) { { 'args' => job_args, 'error_message' => 'Sidekiq error message' } } + let(:exception) { StandardError.new('Sidekiq error occurred') } - it 'sets relation export status to `failed`' do - described_class.sidekiq_retries_exhausted_block.call(job) + subject(:worker) { described_class.sidekiq_retries_exhausted_block.call(job) } - expect(project_relation_export.reload.failed?).to eq(true) - end - - it 'logs the error message' do - expect_next_instance_of(Gitlab::Export::Logger) do |logger| - expect(logger).to receive(:error).with( - hash_including( - message: 'Project relation export failed', - export_error: 'Error message' - ) - ) - end - - described_class.sidekiq_retries_exhausted_block.call(job) + it_behaves_like 'marks relation export failed' do + let(:error_message) { 'Sidekiq error message' } end end end diff --git a/yarn.lock b/yarn.lock index 5ae04d95e52..fdc1068af92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1346,7 +1346,7 @@ "@floating-ui/core" "^1.7.0" "@floating-ui/utils" "^0.2.9" -"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.7.1": +"@floating-ui/dom@1.7.1", "@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.1.tgz#76a4e3cbf7a08edf40c34711cf64e0cc8053d912" integrity sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ== @@ -1390,12 +1390,12 @@ core-js "^3.29.1" mitt "^3.0.1" -"@gitlab/duo-ui@^8.18.0": - version "8.18.0" - resolved "https://registry.yarnpkg.com/@gitlab/duo-ui/-/duo-ui-8.18.0.tgz#ced9368e5f069cb162bb14a9602b45bf9bed6de9" - integrity sha512-FFmQJ8O3I9xoQgALjCfsf4Xy04GQLqEycpc0jkQgUzBuml49Van8Rh3Ig/ZnB6QR40Y+dMDteBE0g3+CpcqlDw== +"@gitlab/duo-ui@^8.22.0": + version "8.22.0" + resolved "https://registry.yarnpkg.com/@gitlab/duo-ui/-/duo-ui-8.22.0.tgz#d7e079df4fdbdd0b7997926392cf3207d6ba20fa" + integrity sha512-q7tsA1PvVh0cszI44TjxTmpS657YqWGuig2s3XUu5q7VQOPr0mnZhMIAFl8VvGK8mLUWk4JUm6mdy0Os+K1QUQ== dependencies: - "@floating-ui/dom" "1.7.0" + "@floating-ui/dom" "1.7.1" echarts "^5.3.2" iframe-resizer "^4.3.2" lodash "^4.17.20"