From aafd8d0b3611b7a8549231a0eda20a301a2951fe Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Fri, 21 Feb 2025 15:07:16 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .eslint_todo/vue-no-unused-properties.mjs | 3 - .rubocop_todo/rspec/be_nil.yml | 21 - GITALY_SERVER_VERSION | 2 +- GITLAB_KAS_VERSION | 2 +- ...pp.vue => credentials_filter_sort_app.vue} | 67 ++- .../javascripts/credentials/constants.js | 35 ++ app/assets/javascripts/credentials/index.js | 8 +- app/assets/javascripts/credentials/utils.js | 24 +- .../javascripts/diffs/components/app.vue | 15 +- .../pages/admin/credentials/index.js | 4 +- .../groups/security/credentials/index.js | 4 +- .../rapid_diffs/app/file_browser.vue | 53 ++ .../javascripts/rapid_diffs/app/index.js | 2 +- .../{file_browser.js => init_file_browser.js} | 22 +- .../javascripts/rapid_diffs/diff_file.js | 11 +- .../javascripts/rapid_diffs/dom_events.js | 1 + .../rapid_diffs/diff_file_component.scss | 7 + .../rapid_diffs/diff_file_component.html.haml | 2 +- .../rapid_diffs/diff_file_component.rb | 2 +- app/workers/process_commit_worker.rb | 2 +- ...oncurrency_limit_process_commit_worker.yml | 9 - ...rules_protected_environment_project_id.yml | 5 +- db/docs/draft_notes.yml | 12 +- ...d_organization_id_to_ai_duo_chat_events.rb | 9 + ...ft_notes_project_id_not_null_constraint.rb | 13 + ...i_duo_chat_events_organization_id_index.rb | 18 + ...teable_type_and_id_index_in_notes_table.rb | 19 + ...fill_ai_duo_chat_events_organization_id.rb | 21 + ...ent_approval_rules_protected_environmen.rb | 22 + db/schema_migrations/20250210065034 | 1 + db/schema_migrations/20250212110138 | 1 + db/schema_migrations/20250212132647 | 1 + db/schema_migrations/20250213125548 | 1 + db/schema_migrations/20250214085204 | 1 + db/schema_migrations/20250220231747 | 1 + db/structure.sql | 9 +- .../gitlab_duo_self_hosted/troubleshooting.md | 10 +- doc/user/gitlab_duo/setup.md | 2 + doc/user/packages/yarn_repository/_index.md | 475 +++++++++--------- locale/gitlab.pot | 17 +- qa/qa/service/docker_run/gitlab_runner.rb | 2 +- qa/spec/page/element_spec.rb | 2 +- .../mixins/third_party_docker_spec.rb | 2 +- qa/spec/service/shellout_spec.rb | 2 +- scripts/frontend/quarantined_vue3_specs.txt | 1 - spec/config/object_store_settings_spec.rb | 6 +- .../application_controller_spec.rb | 2 +- spec/db/schema_spec.rb | 2 +- .../shared_context_and_examples.rb | 6 +- .../users/admin_impersonates_user_spec.rb | 2 +- .../container_repositories_finder_spec.rb | 6 +- spec/finders/uploader_finder_spec.rb | 2 +- ...js => credentials_filter_sort_app_spec.js} | 113 ++++- spec/frontend/credentials/utils_spec.js | 11 + spec/frontend/rapid_diffs/app/app_spec.js | 4 +- .../rapid_diffs/app/file_browser_spec.js | 53 ++ .../rapid_diffs/app/init_file_browser_spec.js | 75 +++ spec/frontend/rapid_diffs/diff_file_spec.js | 24 +- .../mutations/issues/set_due_date_spec.rb | 4 +- .../container_repositories_resolver_spec.rb | 2 +- .../resolvers/paginated_tree_resolver_spec.rb | 4 +- spec/graphql/resolvers/tree_resolver_spec.rb | 2 +- .../users/group_count_resolver_spec.rb | 4 +- spec/lib/gitlab/database/sharding_key_spec.rb | 4 +- .../merge_requests/diffs_stream_spec.rb | 4 +- .../shared_examples/with_diffs_blobs_param.rb | 8 +- spec/workers/process_commit_worker_spec.rb | 10 - 67 files changed, 886 insertions(+), 405 deletions(-) rename app/assets/javascripts/credentials/components/{credentials_filter_app.vue => credentials_filter_sort_app.vue} (53%) create mode 100644 app/assets/javascripts/rapid_diffs/app/file_browser.vue rename app/assets/javascripts/rapid_diffs/app/{file_browser.js => init_file_browser.js} (52%) create mode 100644 app/assets/javascripts/rapid_diffs/dom_events.js delete mode 100644 config/feature_flags/worker/concurrency_limit_process_commit_worker.yml create mode 100644 db/migrate/20250212110138_add_organization_id_to_ai_duo_chat_events.rb create mode 100644 db/post_migrate/20250210065034_validate_draft_notes_project_id_not_null_constraint.rb create mode 100644 db/post_migrate/20250212132647_add_ai_duo_chat_events_organization_id_index.rb create mode 100644 db/post_migrate/20250213125548_add_noteable_id_noteable_type_and_id_index_in_notes_table.rb create mode 100644 db/post_migrate/20250214085204_fill_ai_duo_chat_events_organization_id.rb create mode 100644 db/post_migrate/20250220231747_finalize_hk_backfill_protected_environment_approval_rules_protected_environmen.rb create mode 100644 db/schema_migrations/20250210065034 create mode 100644 db/schema_migrations/20250212110138 create mode 100644 db/schema_migrations/20250212132647 create mode 100644 db/schema_migrations/20250213125548 create mode 100644 db/schema_migrations/20250214085204 create mode 100644 db/schema_migrations/20250220231747 rename spec/frontend/credentials/components/{credentials_filter_app_spec.js => credentials_filter_sort_app_spec.js} (50%) create mode 100644 spec/frontend/credentials/utils_spec.js create mode 100644 spec/frontend/rapid_diffs/app/file_browser_spec.js create mode 100644 spec/frontend/rapid_diffs/app/init_file_browser_spec.js diff --git a/.eslint_todo/vue-no-unused-properties.mjs b/.eslint_todo/vue-no-unused-properties.mjs index 3bb95b243ec..5922a318033 100644 --- a/.eslint_todo/vue-no-unused-properties.mjs +++ b/.eslint_todo/vue-no-unused-properties.mjs @@ -593,9 +593,6 @@ export default { 'ee/app/assets/javascripts/tracing/details/tracing_details.vue', 'ee/app/assets/javascripts/usage_quotas/code_suggestions/components/code_suggestions_info_card.vue', 'ee/app/assets/javascripts/usage_quotas/code_suggestions/components/search_and_sort_bar.vue', - 'ee/app/assets/javascripts/usage_quotas/pipelines/components/app.vue', - 'ee/app/assets/javascripts/usage_quotas/pipelines/components/minutes_usage_per_project_chart.vue', - 'ee/app/assets/javascripts/usage_quotas/pipelines/components/shared_runner_usage_month_chart.vue', 'ee/app/assets/javascripts/usage_quotas/seats/components/statistics_seats_card.vue', 'ee/app/assets/javascripts/usage_quotas/transfer/components/usage_by_month.vue', 'ee/app/assets/javascripts/users/identity_verification/components/credit_card_verification.vue', diff --git a/.rubocop_todo/rspec/be_nil.yml b/.rubocop_todo/rspec/be_nil.yml index 2ccf04bd4e2..c0ce8404f75 100644 --- a/.rubocop_todo/rspec/be_nil.yml +++ b/.rubocop_todo/rspec/be_nil.yml @@ -2,27 +2,6 @@ # Cop supports --autocorrect. RSpec/BeNil: Exclude: - - 'ee/spec/services/app_sec/dast/profiles/update_service_spec.rb' - - 'ee/spec/services/gitlab_subscriptions/reconciliations/calculate_seat_count_data_service_spec.rb' - - 'ee/spec/workers/concerns/geo/skip_secondary_spec.rb' - - 'ee/spec/workers/repository_update_mirror_worker_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_license_finding_spec.rb' - - 'qa/qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb' - - 'qa/spec/page/element_spec.rb' - - 'qa/spec/service/docker_run/mixins/third_party_docker_spec.rb' - - 'qa/spec/service/shellout_spec.rb' - - 'spec/config/object_store_settings_spec.rb' - - 'spec/controllers/application_controller_spec.rb' - - 'spec/dot_gitlab_ci/ci_configuration_validation/shared_context_and_examples.rb' - - 'spec/features/admin/users/admin_impersonates_user_spec.rb' - - 'spec/finders/container_repositories_finder_spec.rb' - - 'spec/finders/uploader_finder_spec.rb' - - 'spec/graphql/mutations/issues/set_due_date_spec.rb' - - 'spec/graphql/resolvers/container_repositories_resolver_spec.rb' - - 'spec/graphql/resolvers/paginated_tree_resolver_spec.rb' - - 'spec/graphql/resolvers/tree_resolver_spec.rb' - - 'spec/graphql/resolvers/users/group_count_resolver_spec.rb' - 'spec/helpers/namespaces_helper_spec.rb' - 'spec/helpers/tree_helper_spec.rb' - 'spec/helpers/version_check_helper_spec.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 5d07e610b98..7d20b85656a 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -6af2d5f99e37feee2b7221af5f276040b8109195 +7dbf8d6fbb832f81e2bfb0a4c143a0932cbecf53 diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION index 4b83a03173d..52622c6e8c9 100644 --- a/GITLAB_KAS_VERSION +++ b/GITLAB_KAS_VERSION @@ -1 +1 @@ -93b9e36e23c2a4e51dc2012932830b72c8f838aa +fecc9b9bcb1ab8b69fb72be11705fd47925302d2 diff --git a/app/assets/javascripts/credentials/components/credentials_filter_app.vue b/app/assets/javascripts/credentials/components/credentials_filter_sort_app.vue similarity index 53% rename from app/assets/javascripts/credentials/components/credentials_filter_app.vue rename to app/assets/javascripts/credentials/components/credentials_filter_sort_app.vue index b3c9dae7ff9..27b6fb123fa 100644 --- a/app/assets/javascripts/credentials/components/credentials_filter_app.vue +++ b/app/assets/javascripts/credentials/components/credentials_filter_sort_app.vue @@ -1,38 +1,43 @@ diff --git a/app/assets/javascripts/credentials/constants.js b/app/assets/javascripts/credentials/constants.js index 883fab05753..854b1f7a21f 100644 --- a/app/assets/javascripts/credentials/constants.js +++ b/app/assets/javascripts/credentials/constants.js @@ -6,6 +6,10 @@ import { } from '~/vue_shared/components/filtered_search_bar/constants'; import DateToken from '~/vue_shared/components/filtered_search_bar/tokens/date_token.vue'; +export const SORT_KEY_NAME = 'name'; +export const SORT_KEY_CREATED = 'created'; +export const SORT_KEY_EXPIRES = 'expires'; + export const TOKENS = [ { icon: 'key', @@ -73,3 +77,34 @@ export const TOKENS = [ unique: true, }, ]; + +export const SORT_OPTIONS = [ + { + text: __('Name'), + value: SORT_KEY_NAME, + sort: { + asc: 'name_asc', + desc: 'name_desc', + }, + }, + { + text: __('Created date'), + value: SORT_KEY_CREATED, + sort: { + asc: 'created_asc', + desc: 'created_desc', + }, + }, + { + text: __('Expiration date'), + value: SORT_KEY_EXPIRES, + sort: { + asc: 'expires_at_asc_id_desc', + }, + }, +]; + +export const DEFAULT_SORT = { + value: SORT_KEY_EXPIRES, + isAsc: true, +}; diff --git a/app/assets/javascripts/credentials/index.js b/app/assets/javascripts/credentials/index.js index 614a02b7a0b..abf054077a9 100644 --- a/app/assets/javascripts/credentials/index.js +++ b/app/assets/javascripts/credentials/index.js @@ -1,9 +1,9 @@ import Vue from 'vue'; -import CredentialsFilterApp from './components/credentials_filter_app.vue'; +import CredentialsFilterSortApp from './components/credentials_filter_sort_app.vue'; -export const initCredentialsFilterApp = () => { +export const initCredentialsFilterSortApp = () => { return new Vue({ - el: document.querySelector('#js-credentials-filter-app'), - render: (createElement) => createElement(CredentialsFilterApp), + el: document.querySelector('#js-credentials-filter-sort-app'), + render: (createElement) => createElement(CredentialsFilterSortApp), }); }; diff --git a/app/assets/javascripts/credentials/utils.js b/app/assets/javascripts/credentials/utils.js index c54db68f5c7..af646ca6dde 100644 --- a/app/assets/javascripts/credentials/utils.js +++ b/app/assets/javascripts/credentials/utils.js @@ -1,9 +1,9 @@ -import { queryToObject } from '~/lib/utils/url_utility'; +import { queryToObject, setUrlParams } from '~/lib/utils/url_utility'; import { OPERATORS_BEFORE, OPERATORS_AFTER, } from '~/vue_shared/components/filtered_search_bar/constants'; -import { TOKENS } from './constants'; +import { TOKENS, SORT_OPTIONS, DEFAULT_SORT } from './constants'; /** * @typedef {{type: string, value: {data: string, operator: string}}} Token @@ -16,9 +16,10 @@ import { TOKENS } from './constants'; * @returns {Array} */ export function initializeValuesFromQuery(query = document.location.search) { - const tokens = []; + const tokens = /** @type {Array} */ ([]); + const sorting = DEFAULT_SORT; - const { search, ...terms } = queryToObject(query); + const { search, sort, ...terms } = queryToObject(query); for (const [key, value] of Object.entries(terms)) { const isBefore = key.endsWith('_before'); @@ -54,5 +55,18 @@ export function initializeValuesFromQuery(query = document.location.search) { tokens.push(search); } - return tokens; + const sortOption = SORT_OPTIONS.find((item) => [item.sort.desc, item.sort.asc].includes(sort)); + if (sort && sortOption) { + sorting.value = sortOption.value; + sorting.isAsc = sortOption.sort.asc === sort; + } + + return { tokens, sorting }; +} + +export function buildSortedUrl(value, isAsc) { + const sortedOption = SORT_OPTIONS.find((sortOption) => sortOption.value === value); + const sort = isAsc ? sortedOption.sort.asc : sortedOption.sort.desc; + const newUrl = setUrlParams({ sort }); + return newUrl; } diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index c6aadaee3ef..85756c4e486 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -639,15 +639,12 @@ export default { Mousetrap.bind(keysFor(MR_PREVIOUS_FILE_IN_DIFF), () => this.jumpToFile(-1)); Mousetrap.bind(keysFor(MR_NEXT_FILE_IN_DIFF), () => this.jumpToFile(+1)); - - if (this.commit) { - Mousetrap.bind(keysFor(MR_COMMITS_NEXT_COMMIT), () => - this.moveToNeighboringCommit({ direction: 'next' }), - ); - Mousetrap.bind(keysFor(MR_COMMITS_PREVIOUS_COMMIT), () => - this.moveToNeighboringCommit({ direction: 'previous' }), - ); - } + Mousetrap.bind(keysFor(MR_COMMITS_NEXT_COMMIT), () => + this.moveToNeighboringCommit({ direction: 'next' }), + ); + Mousetrap.bind(keysFor(MR_COMMITS_PREVIOUS_COMMIT), () => + this.moveToNeighboringCommit({ direction: 'previous' }), + ); Mousetrap.bind(['mod+f', 'mod+g'], () => { this.keydownTime = new Date().getTime(); diff --git a/app/assets/javascripts/pages/admin/credentials/index.js b/app/assets/javascripts/pages/admin/credentials/index.js index 46437aaed5b..10e2411aaff 100644 --- a/app/assets/javascripts/pages/admin/credentials/index.js +++ b/app/assets/javascripts/pages/admin/credentials/index.js @@ -1,5 +1,5 @@ import initConfirmModal from '~/confirm_modal'; -import { initCredentialsFilterApp } from '~/credentials'; +import { initCredentialsFilterSortApp } from '~/credentials'; initConfirmModal(); -initCredentialsFilterApp(); +initCredentialsFilterSortApp(); diff --git a/app/assets/javascripts/pages/groups/security/credentials/index.js b/app/assets/javascripts/pages/groups/security/credentials/index.js index 46437aaed5b..10e2411aaff 100644 --- a/app/assets/javascripts/pages/groups/security/credentials/index.js +++ b/app/assets/javascripts/pages/groups/security/credentials/index.js @@ -1,5 +1,5 @@ import initConfirmModal from '~/confirm_modal'; -import { initCredentialsFilterApp } from '~/credentials'; +import { initCredentialsFilterSortApp } from '~/credentials'; initConfirmModal(); -initCredentialsFilterApp(); +initCredentialsFilterSortApp(); diff --git a/app/assets/javascripts/rapid_diffs/app/file_browser.vue b/app/assets/javascripts/rapid_diffs/app/file_browser.vue new file mode 100644 index 00000000000..0ebcb016fb4 --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/app/file_browser.vue @@ -0,0 +1,53 @@ + + + diff --git a/app/assets/javascripts/rapid_diffs/app/index.js b/app/assets/javascripts/rapid_diffs/app/index.js index fa6605cd8ce..81a2616b4c0 100644 --- a/app/assets/javascripts/rapid_diffs/app/index.js +++ b/app/assets/javascripts/rapid_diffs/app/index.js @@ -3,7 +3,7 @@ import { initViewSettings } from '~/rapid_diffs/app/view_settings'; import { DiffFile } from '~/rapid_diffs/diff_file'; import { DiffFileMounted } from '~/rapid_diffs/diff_file_mounted'; import { useDiffsList } from '~/rapid_diffs/stores/diffs_list'; -import { initFileBrowser } from '~/rapid_diffs/app/file_browser'; +import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser'; // This facade interface joins together all the bits and pieces of Rapid Diffs: DiffFile, Settings, File browser, etc. // It's a unified entrypoint for Rapid Diffs and all external communications should happen through this interface. diff --git a/app/assets/javascripts/rapid_diffs/app/file_browser.js b/app/assets/javascripts/rapid_diffs/app/init_file_browser.js similarity index 52% rename from app/assets/javascripts/rapid_diffs/app/file_browser.js rename to app/assets/javascripts/rapid_diffs/app/init_file_browser.js index ff60a731df1..347d5c1bcc5 100644 --- a/app/assets/javascripts/rapid_diffs/app/file_browser.js +++ b/app/assets/javascripts/rapid_diffs/app/init_file_browser.js @@ -1,6 +1,8 @@ import Vue from 'vue'; import store from '~/mr_notes/stores'; -import DiffFileTree from '~/diffs/components/diffs_file_tree.vue'; +import { pinia } from '~/pinia/instance'; +import { DiffFile } from '~/rapid_diffs/diff_file'; +import FileBrowser from './file_browser.vue'; export async function initFileBrowser() { const el = document.querySelector('[data-file-browser]'); @@ -9,21 +11,21 @@ export async function initFileBrowser() { store.state.diffs.endpointMetadata = metadataEndpoint; await store.dispatch('diffs/fetchDiffFilesMeta'); + const loadedFiles = Object.fromEntries(DiffFile.getAll().map((file) => [file.id, true])); + // eslint-disable-next-line no-new new Vue({ el, - data() { - return { - visible: true, - }; - }, store, + pinia, render(h) { - return h(DiffFileTree, { - props: { visible: this.visible }, + return h(FileBrowser, { + props: { + loadedFiles, + }, on: { - toggled: () => { - this.visible = !this.visible; + clickFile(file) { + DiffFile.findByFileHash(file.fileHash).selectFile(); }, }, }); diff --git a/app/assets/javascripts/rapid_diffs/diff_file.js b/app/assets/javascripts/rapid_diffs/diff_file.js index 0b4f773d3d4..448b623095d 100644 --- a/app/assets/javascripts/rapid_diffs/diff_file.js +++ b/app/assets/javascripts/rapid_diffs/diff_file.js @@ -1,3 +1,4 @@ +import { DIFF_FILE_MOUNTED } from './dom_events'; import { VIEWER_ADAPTERS } from './adapters'; // required for easier mocking in tests import IntersectionObserver from './intersection_observer'; @@ -24,11 +25,11 @@ export class DiffFile extends HTMLElement { adapterConfig = VIEWER_ADAPTERS; static findByFileHash(hash) { - return document.querySelector(`diff-file#${hash}`); + return document.querySelector(`diff-file[id="${hash}"]`); } static getAll() { - return document.querySelectorAll('diff-file'); + return Array.from(document.querySelectorAll('diff-file')); } mount() { @@ -38,6 +39,7 @@ export class DiffFile extends HTMLElement { this.observeVisibility(); this.diffElement.addEventListener('click', this.onClick.bind(this)); this.trigger(events.MOUNTED); + this.dispatchEvent(new CustomEvent(DIFF_FILE_MOUNTED, { bubbles: true })); } trigger(event, ...args) { @@ -73,6 +75,11 @@ export class DiffFile extends HTMLElement { this.trigger(events.CLICK, event); } + selectFile() { + this.scrollIntoView(); + // TODO: add outline for active file + } + get data() { const data = { ...this.dataset }; // viewer is dynamic, should be accessed via this.viewer diff --git a/app/assets/javascripts/rapid_diffs/dom_events.js b/app/assets/javascripts/rapid_diffs/dom_events.js new file mode 100644 index 00000000000..59d879b6f9d --- /dev/null +++ b/app/assets/javascripts/rapid_diffs/dom_events.js @@ -0,0 +1 @@ +export const DIFF_FILE_MOUNTED = 'DiffFileMounted'; diff --git a/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss b/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss index 457554bdd74..0991aeb2cd3 100644 --- a/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss +++ b/app/assets/stylesheets/components/rapid_diffs/diff_file_component.scss @@ -1,3 +1,10 @@ +@import 'framework/variables'; + +.rd-diff-file-component { + // TODO: this must be defined using CSS Custom Properties to work across all pages + scroll-margin-top: calc(#{$calc-application-header-height} + #{$mr-sticky-header-height} + #{12px}); +} + .rd-diff-file { padding-bottom: $gl-padding; diff --git a/app/components/rapid_diffs/diff_file_component.html.haml b/app/components/rapid_diffs/diff_file_component.html.haml index 948278b97c2..63ca9715a91 100644 --- a/app/components/rapid_diffs/diff_file_component.html.haml +++ b/app/components/rapid_diffs/diff_file_component.html.haml @@ -1,6 +1,6 @@ -# TODO: add fork suggestion (commits only) -%diff-file{ id: id, data: server_data } +%diff-file.rd-diff-file-component{ id: id, data: server_data } .rd-diff-file = render RapidDiffs::DiffFileHeaderComponent.new(diff_file: @diff_file) -# extra wrapper needed so content-visibility: hidden doesn't require removing border or other styles diff --git a/app/components/rapid_diffs/diff_file_component.rb b/app/components/rapid_diffs/diff_file_component.rb index 1bc5246fde4..3fa068af61f 100644 --- a/app/components/rapid_diffs/diff_file_component.rb +++ b/app/components/rapid_diffs/diff_file_component.rb @@ -10,7 +10,7 @@ module RapidDiffs end def id - @diff_file.file_identifier_hash + @diff_file.file_hash end def server_data diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index 0616cd4d476..cdcdd403b24 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -21,7 +21,7 @@ class ProcessCommitWorker loggable_arguments 2, 3 deduplicate :until_executed, feature_flag: :deduplicate_process_commit_worker - concurrency_limit -> { 1000 if Feature.enabled?(:concurrency_limit_process_commit_worker, Feature.current_request) } + concurrency_limit -> { 1000 } # project_id - The ID of the project this commit belongs to. # user_id - The ID of the user that pushed the commit. diff --git a/config/feature_flags/worker/concurrency_limit_process_commit_worker.yml b/config/feature_flags/worker/concurrency_limit_process_commit_worker.yml deleted file mode 100644 index d4bc513708e..00000000000 --- a/config/feature_flags/worker/concurrency_limit_process_commit_worker.yml +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: concurrency_limit_process_commit_worker -feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/472602 -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/171786 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/502784 -milestone: '17.6' -group: group::source code -type: worker -default_enabled: false diff --git a/db/docs/batched_background_migrations/backfill_protected_environment_approval_rules_protected_environment_project_id.yml b/db/docs/batched_background_migrations/backfill_protected_environment_approval_rules_protected_environment_project_id.yml index 705279670ea..484a4011f86 100644 --- a/db/docs/batched_background_migrations/backfill_protected_environment_approval_rules_protected_environment_project_id.yml +++ b/db/docs/batched_background_migrations/backfill_protected_environment_approval_rules_protected_environment_project_id.yml @@ -1,8 +1,9 @@ --- migration_job_name: BackfillProtectedEnvironmentApprovalRulesProtectedEnvironmentProjectId -description: Backfills sharding key `protected_environment_approval_rules.protected_environment_project_id` from `protected_environments`. +description: Backfills sharding key `protected_environment_approval_rules.protected_environment_project_id` + from `protected_environments`. feature_category: continuous_delivery introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162704 milestone: '17.3' queued_migration_version: 20240814104154 -finalized_by: # version of the migration that finalized this BBM +finalized_by: '20250220231747' diff --git a/db/docs/draft_notes.yml b/db/docs/draft_notes.yml index d37a8192b88..1fe47721d2d 100644 --- a/db/docs/draft_notes.yml +++ b/db/docs/draft_notes.yml @@ -8,14 +8,6 @@ description: Notes created during the review of an MR that are not yet published introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/4213 milestone: '11.4' gitlab_schema: gitlab_main_cell -desired_sharding_key: - project_id: - references: projects - backfill_via: - parent: - foreign_key: merge_request_id - table: merge_requests - sharding_key: target_project_id - belongs_to: merge_request -desired_sharding_key_migration_job_name: BackfillDraftNotesProjectId table_size: small +sharding_key: + project_id: projects diff --git a/db/migrate/20250212110138_add_organization_id_to_ai_duo_chat_events.rb b/db/migrate/20250212110138_add_organization_id_to_ai_duo_chat_events.rb new file mode 100644 index 00000000000..d35d7da929c --- /dev/null +++ b/db/migrate/20250212110138_add_organization_id_to_ai_duo_chat_events.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddOrganizationIdToAiDuoChatEvents < Gitlab::Database::Migration[2.2] + milestone '17.10' + + def change + add_column :ai_duo_chat_events, :organization_id, :bigint + end +end diff --git a/db/post_migrate/20250210065034_validate_draft_notes_project_id_not_null_constraint.rb b/db/post_migrate/20250210065034_validate_draft_notes_project_id_not_null_constraint.rb new file mode 100644 index 00000000000..5799aeecd3d --- /dev/null +++ b/db/post_migrate/20250210065034_validate_draft_notes_project_id_not_null_constraint.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class ValidateDraftNotesProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2] + milestone '17.10' + + def up + validate_not_null_constraint :draft_notes, :project_id + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20250212132647_add_ai_duo_chat_events_organization_id_index.rb b/db/post_migrate/20250212132647_add_ai_duo_chat_events_organization_id_index.rb new file mode 100644 index 00000000000..59471397232 --- /dev/null +++ b/db/post_migrate/20250212132647_add_ai_duo_chat_events_organization_id_index.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class AddAiDuoChatEventsOrganizationIdIndex < Gitlab::Database::Migration[2.2] + include Gitlab::Database::PartitioningMigrationHelpers + + disable_ddl_transaction! + milestone '17.10' + + INDEX_NAME = 'index_ai_duo_chat_events_on_organization_id' + + def up + add_concurrent_partitioned_index :ai_duo_chat_events, :organization_id, name: INDEX_NAME + end + + def down + remove_concurrent_partitioned_index_by_name :ai_duo_chat_events, INDEX_NAME + end +end diff --git a/db/post_migrate/20250213125548_add_noteable_id_noteable_type_and_id_index_in_notes_table.rb b/db/post_migrate/20250213125548_add_noteable_id_noteable_type_and_id_index_in_notes_table.rb new file mode 100644 index 00000000000..a049fe42371 --- /dev/null +++ b/db/post_migrate/20250213125548_add_noteable_id_noteable_type_and_id_index_in_notes_table.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# This index was prepared in 17.9 PrepareNoteableIdNoteableTypeAndIdIndexInNotesTable migration +class AddNoteableIdNoteableTypeAndIdIndexInNotesTable < Gitlab::Database::Migration[2.2] + milestone '17.10' + disable_ddl_transaction! + + INDEX_NAME = 'index_notes_on_noteable_id_noteable_type_and_id' + + def up + # rubocop:disable Migration/PreventIndexCreation -- index prepared in advance + add_concurrent_index :notes, [:noteable_id, :noteable_type, :id], name: INDEX_NAME + # rubocop:enable Migration/PreventIndexCreation + end + + def down + remove_concurrent_index_by_name :notes, INDEX_NAME + end +end diff --git a/db/post_migrate/20250214085204_fill_ai_duo_chat_events_organization_id.rb b/db/post_migrate/20250214085204_fill_ai_duo_chat_events_organization_id.rb new file mode 100644 index 00000000000..4b16c247d51 --- /dev/null +++ b/db/post_migrate/20250214085204_fill_ai_duo_chat_events_organization_id.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class FillAiDuoChatEventsOrganizationId < Gitlab::Database::Migration[2.2] + disable_ddl_transaction! + restrict_gitlab_migration gitlab_schema: :gitlab_main + milestone '17.10' + + def up + return unless Gitlab.ee? # Only EE has proper table partitions and data. + + chat_events = define_batchable_model(:ai_duo_chat_events) + + chat_events.each_batch(of: 1000, column: :id) do |batch| + batch.where(organization_id: nil).update_all(organization_id: 1) # DEFAULT_ORGANIZATION_ID + end + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20250220231747_finalize_hk_backfill_protected_environment_approval_rules_protected_environmen.rb b/db/post_migrate/20250220231747_finalize_hk_backfill_protected_environment_approval_rules_protected_environmen.rb new file mode 100644 index 00000000000..55107f77d71 --- /dev/null +++ b/db/post_migrate/20250220231747_finalize_hk_backfill_protected_environment_approval_rules_protected_environmen.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class FinalizeHkBackfillProtectedEnvironmentApprovalRulesProtectedEnvironmen < Gitlab::Database::Migration[2.2] + milestone '17.10' + + disable_ddl_transaction! + + restrict_gitlab_migration gitlab_schema: :gitlab_main_cell + + def up + ensure_batched_background_migration_is_finished( + job_class_name: 'BackfillProtectedEnvironmentApprovalRulesProtectedEnvironmentProjectId', + table_name: :protected_environment_approval_rules, + column_name: :id, + job_arguments: [:protected_environment_project_id, :protected_environments, :project_id, + :protected_environment_id], + finalize: true + ) + end + + def down; end +end diff --git a/db/schema_migrations/20250210065034 b/db/schema_migrations/20250210065034 new file mode 100644 index 00000000000..301d194c152 --- /dev/null +++ b/db/schema_migrations/20250210065034 @@ -0,0 +1 @@ +17898021bbd8b5bdef675c73e0aef30a77b0cbbeb6348b89e75f2b4837ec58b4 \ No newline at end of file diff --git a/db/schema_migrations/20250212110138 b/db/schema_migrations/20250212110138 new file mode 100644 index 00000000000..fac1f650dc3 --- /dev/null +++ b/db/schema_migrations/20250212110138 @@ -0,0 +1 @@ +1d540f78bcd88abb82caedee131a8aa7cc73c1900784af93e797162e6f0edd51 \ No newline at end of file diff --git a/db/schema_migrations/20250212132647 b/db/schema_migrations/20250212132647 new file mode 100644 index 00000000000..0d149aeb0af --- /dev/null +++ b/db/schema_migrations/20250212132647 @@ -0,0 +1 @@ +5fc9dfad9645f9d0a955e37a7fe1702e572701226ec1bdbfba754e1455e52d1a \ No newline at end of file diff --git a/db/schema_migrations/20250213125548 b/db/schema_migrations/20250213125548 new file mode 100644 index 00000000000..becc7259e99 --- /dev/null +++ b/db/schema_migrations/20250213125548 @@ -0,0 +1 @@ +f6e2391f8d78b18c53b5c6e4e44fb93c59355badb4bc2aedbfdcb5f6f44b1548 \ No newline at end of file diff --git a/db/schema_migrations/20250214085204 b/db/schema_migrations/20250214085204 new file mode 100644 index 00000000000..3ab76e69c4c --- /dev/null +++ b/db/schema_migrations/20250214085204 @@ -0,0 +1 @@ +7984c710d624f787cf69ba62ec525875ffcf7bada3fa763966125c21c5259043 \ No newline at end of file diff --git a/db/schema_migrations/20250220231747 b/db/schema_migrations/20250220231747 new file mode 100644 index 00000000000..b245af541d5 --- /dev/null +++ b/db/schema_migrations/20250220231747 @@ -0,0 +1 @@ +f471852ecae0f6af89d72a75897d3c13cebe57e25fdb509c1816d219d6c11cd5 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 663e6af3d2b..5b6ce385a00 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -3973,6 +3973,7 @@ CREATE TABLE ai_duo_chat_events ( event smallint NOT NULL, namespace_path text, payload jsonb, + organization_id bigint, CONSTRAINT check_628cdfbf3f CHECK ((char_length(namespace_path) <= 255)) ) PARTITION BY RANGE ("timestamp"); @@ -13114,6 +13115,7 @@ CREATE TABLE draft_notes ( internal boolean DEFAULT false NOT NULL, note_type smallint, project_id bigint, + CONSTRAINT check_2a752d05fe CHECK ((project_id IS NOT NULL)), CONSTRAINT check_c497a94a0e CHECK ((char_length(line_code) <= 255)) ); @@ -27268,9 +27270,6 @@ ALTER TABLE ONLY chat_names ALTER TABLE ONLY chat_teams ADD CONSTRAINT chat_teams_pkey PRIMARY KEY (id); -ALTER TABLE draft_notes - ADD CONSTRAINT check_2a752d05fe CHECK ((project_id IS NOT NULL)) NOT VALID; - ALTER TABLE workspaces ADD CONSTRAINT check_2a89035b04 CHECK ((personal_access_token_id IS NOT NULL)) NOT VALID; @@ -31511,6 +31510,8 @@ CREATE INDEX index_ai_conversation_threads_on_organization_id ON ai_conversation CREATE INDEX index_ai_conversation_threads_on_user_id_and_last_updated_at ON ai_conversation_threads USING btree (user_id, last_updated_at); +CREATE INDEX index_ai_duo_chat_events_on_organization_id ON ONLY ai_duo_chat_events USING btree (organization_id); + CREATE INDEX index_ai_duo_chat_events_on_personal_namespace_id ON ONLY ai_duo_chat_events USING btree (personal_namespace_id); CREATE INDEX index_ai_duo_chat_events_on_user_id ON ONLY ai_duo_chat_events USING btree (user_id); @@ -33959,6 +33960,8 @@ CREATE INDEX index_notes_on_namespace_id ON notes USING btree (namespace_id); CREATE INDEX index_notes_on_noteable_id_and_noteable_type_and_system ON notes USING btree (noteable_id, noteable_type, system); +CREATE INDEX index_notes_on_noteable_id_noteable_type_and_id ON notes USING btree (noteable_id, noteable_type, id); + CREATE INDEX index_notes_on_project_id_and_id_and_system_false ON notes USING btree (project_id, id) WHERE (NOT system); CREATE INDEX index_notes_on_project_id_and_noteable_type ON notes USING btree (project_id, noteable_type); diff --git a/doc/administration/gitlab_duo_self_hosted/troubleshooting.md b/doc/administration/gitlab_duo_self_hosted/troubleshooting.md index 614ed4c3a16..bbcf0720682 100644 --- a/doc/administration/gitlab_duo_self_hosted/troubleshooting.md +++ b/doc/administration/gitlab_duo_self_hosted/troubleshooting.md @@ -92,7 +92,7 @@ We provide two debugging scripts to help administrators verify their self-hosted ``` For a `mixtral` model running on vLLM: - + ```shell poetry run troubleshoot \ --model-family=mixtral \ @@ -114,6 +114,14 @@ Verify the output of the commands, and fix accordingly. If both commands are successful, but GitLab Duo Code Suggestions is still not working, raise an issue on the issue tracker. +## GitLab Duo health check is not working + +When you [run a health check for GitLab Duo](../../user/gitlab_duo/setup.md#run-a-health-check-for-gitlab-duo), you might get an error like a `401 response from the AI gateway`. + +To resolve, first check if GitLab Duo features are functioning correctly. For example, send a message to Duo Chat. + +If this does not work, the error might be because of a known issue with GitLab Duo health check. For more information, see [issue 517097](https://gitlab.com/gitlab-org/gitlab/-/issues/517097). + ## Check if GitLab can make a request to the model From the GitLab Rails console, verify that GitLab can make a request to the model diff --git a/doc/user/gitlab_duo/setup.md b/doc/user/gitlab_duo/setup.md index 69c9a2d6f69..07fafc2ec6b 100644 --- a/doc/user/gitlab_duo/setup.md +++ b/doc/user/gitlab_duo/setup.md @@ -112,3 +112,5 @@ These tests are performed: | Network | Tests whether your instance can connect to `customers.gitlab.com` and `cloud.gitlab.com`.

If your instance cannot connect to either destination, ensure that your firewall or proxy server settings [allow connection](setup.md). | | Synchronization | Tests whether your subscription:
- Has been activated with an activation code and can be synchronized with `customers.gitlab.com`.
- Has correct access credentials.
- Has been synchronized recently. If it hasn't or the access credentials are missing or expired, you can [manually synchronize](../../subscriptions/self_managed/_index.md#manually-synchronize-subscription-data) your subscription data. | | System exchange | Tests whether Code Suggestions can be used in your instance. If the system exchange assessment fails, users might not be able to use GitLab Duo features. | + +If you are experiencing any issues with the health check, see [GitLab Duo Self-Hosted troubleshooting](../../administration/gitlab_duo_self_hosted/troubleshooting.md#gitlab-duo-health-check-is-not-working). diff --git a/doc/user/packages/yarn_repository/_index.md b/doc/user/packages/yarn_repository/_index.md index 55055a128ae..aada5a45c88 100644 --- a/doc/user/packages/yarn_repository/_index.md +++ b/doc/user/packages/yarn_repository/_index.md @@ -5,347 +5,348 @@ info: To determine the technical writer assigned to the Stage/Group associated w title: Publish packages with Yarn --- -You can publish packages with [Yarn 1 (Classic)](https://classic.yarnpkg.com) and [Yarn 2+](https://yarnpkg.com). +You can publish and install packages with [Yarn 1 (Classic)](https://classic.yarnpkg.com) and [Yarn 2+](https://yarnpkg.com). -To find the Yarn version used in the deployment container, run `yarn --version` in the `script` block of the CI +To find the Yarn version used in the deployment container, run `yarn --version` in the `script` block of the CI/CD script job block that is responsible for calling `yarn publish`. The Yarn version is shown in the pipeline output. -Learn how to build a [Yarn](../workflows/build_packages.md#yarn) package. +## Authenticating to the package registry -You can use the Yarn documentation to get started with -[Yarn Classic](https://classic.yarnpkg.com/en/docs/getting-started) and -[Yarn 2+](https://yarnpkg.com/getting-started). - -## Publish to GitLab package registry - -You can use Yarn to publish to the GitLab package registry. - -### Authentication to the package registry - -You need a token to publish a package. Different tokens are available depending on what you're trying to +You need a token to interact with the package registry. Different tokens are available depending on what you're trying to achieve. For more information, review the [guidance on tokens](../package_registry/_index.md#authenticate-with-the-registry). - If your organization uses two-factor authentication (2FA), you must use a - personal access token with the scope set to `api`. -- If you publish a package via CI/CD pipelines, you can use a CI job token in - private runners or you can register a variable for instance runners. + [personal access token](../../profile/personal_access_tokens.md) with the scope set to `api`. +- If you publish a package with CI/CD pipelines, you can use a [CI/CD job token](../../../ci/jobs/ci_job_token.md) with + private runners. You can also [register a variable](https://docs.gitlab.com/runner/register/#register-with-a-runner-authentication-token) for instance runners. -### Publish configuration +### Configure Yarn for publication -To publish, set the following configuration in `.yarnrc.yml`. This file should be -located in the root directory of your package project source where `package.json` is found. +To configure Yarn to publish to the package registry, edit your `.yarnrc.yml` file. +You can find this file in root directory of your project, in the same place as the `package.json` file. -```yaml -npmScopes: - : - npmPublishRegistry: 'https:///api/v4/projects//packages/npm/' - npmAlwaysAuth: true - npmAuthToken: '' -``` +- Edit `.yarnrc.yml` and add the following configuration: -In this configuration: + ```yaml + npmScopes: + : + npmPublishRegistry: 'https:///api/v4/projects//packages/npm/' + npmAlwaysAuth: true + npmAuthToken: '' + ``` -- Replace `` with your organization scope, excluding the `@` symbol. -- Replace `` with your domain name. -- Replace `` with your project's ID, which you can find on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id). -- Replace `` with a deployment token, group access token, project access token, or personal access token. + In this configuration: -Scoped registry does not work in Yarn Classic in `package.json` file, based on -this [issue](https://github.com/yarnpkg/yarn/pull/7829). -Therefore, under `publishConfig` there should be `registry` and not `@scope:registry` for Yarn Classic. -You can publish using your command line or a CI/CD pipeline to the GitLab package registry. + - Replace `` with your organization scope. Do not include the `@` symbol. + - Replace `` with your domain name. + - Replace `` with your project's ID, which you can find on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id). + - Replace `` with a deployment token, group access token, project access token, or personal access token. -### Publishing via the command line - Manual Publish +In Yarn Classic, scoped registries with `publishConfig["@scope:registry"]` are not supported. See [Yarn pull request 7829](https://github.com/yarnpkg/yarn/pull/7829) for more information. +Instead, set `publishConfig` to `registry` in your `package.json` file. -```shell -# Yarn 1 (Classic) -yarn publish +## Publish a package -# Yarn 2+ -yarn npm publish -``` +You can publish a package from the command line, or with GitLab CI/CD. -Your package should now publish to the package registry. +### With the command line -### Publishing via a CI/CD pipeline - Automated Publish +To publish a package manually: -You can use pipeline variables when you use this method. +- Run the following command: -You can use **instance runners** *(Default)* or **Private Runners** (Advanced). + ```shell + # Yarn 1 (Classic) + yarn publish -#### Instance runners + # Yarn 2+ + yarn npm publish + ``` -To create an authentication token for your project or group: +### With CI/CD -1. On the left sidebar, select **Search or go to** and find your project or group. -1. On the left sidebar, select **Settings > Repository > Deploy Tokens**. -1. Create a deployment token with `read_package_registry` and `write_package_registry` scopes and copy the generated token. -1. On the left sidebar, select **Settings > CI/CD > Variables**. -1. Select `Add variable` and use the following settings: +You can publish a package automatically with instance runners (default) or private runners (advanced). +You can use pipeline variables when you publish with CI/CD. -| Field | Value | -|--------------------|------------------------------| -| key | `NPM_AUTH_TOKEN` | -| value | `` | -| type | Variable | -| Protected variable | `CHECKED` | -| Mask variable | `CHECKED` | -| Expand variable | `CHECKED` | +{{< tabs >}} -To use any **Protected variable**: +{{< tab title="Instance runners" >}} + +1. Create an authentication token for your project or group: + + 1. On the left sidebar, select **Search or go to** and find your project or group. + 1. On the left sidebar, select **Settings > Repository > Deploy Tokens**. + 1. Create a deployment token with `read_package_registry` and `write_package_registry` scopes and copy the generated token. + 1. On the left sidebar, select **Settings > CI/CD > Variables**. + 1. Select `Add variable` and use the following settings: + + | Field | Value | + |--------------------|------------------------------| + | key | `NPM_AUTH_TOKEN` | + | value | `` | + | type | Variable | + | Protected variable | `CHECKED` | + | Mask variable | `CHECKED` | + | Expand variable | `CHECKED` | + +1. Optional. To use protected variables: 1. Go to the repository that contains the Yarn package source code. 1. On the left sidebar, select **Settings > Repository**. - If you are building from branches with tags, select **Protected Tags** and add `v*` (wildcard) for semantic versioning. - If you are building from branches without tags, select **Protected Branches**. -Then add the `NPM_AUTH_TOKEN` created above, to the `.yarnrc.yml` configuration +1. Add the `NPM_AUTH_TOKEN` you created to the `.yarnrc.yml` configuration in your package project root directory where `package.json` is found: -```yaml -npmScopes: - : - npmPublishRegistry: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/" - npmAlwaysAuth: true - npmAuthToken: "${NPM_AUTH_TOKEN}" -``` + ```yaml + npmScopes: + : + npmPublishRegistry: '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/' + npmAlwaysAuth: true + npmAuthToken: '${NPM_AUTH_TOKEN}' + ``` -In this configuration, replace `` with your organization scope, excluding the `@` symbol. + In this configuration, replace `` with your organization scope, excluding the `@` symbol. -#### Private runners +{{< /tab >}} -Add the `CI_JOB_TOKEN` to the `.yarnrc.yml` configuration in your package project -root directory where `package.json` is found: +{{< tab title="Private runners" >}} -```yaml -npmScopes: - : - npmPublishRegistry: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/" - npmAlwaysAuth: true - npmAuthToken: "${CI_JOB_TOKEN}" -``` +1. Add your `CI_JOB_TOKEN` to the `.yarnrc.yml` configuration in the root directory of your package project, where `package.json` is located: -In this configuration, replace `` with your organization scope, excluding the `@` symbol. + ```yaml + npmScopes: + : + npmPublishRegistry: '${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/' + npmAlwaysAuth: true + npmAuthToken: '${CI_JOB_TOKEN}' + ``` -To publish the package using CI/CD pipeline, In the GitLab project that houses -your `.yarnrc.yml`, edit or create a `.gitlab-ci.yml` file. For example to trigger -only on any tag push: + In this configuration, replace `` with your organization scope, excluding the `@` symbol. -```yaml -# Yarn 1 -image: node:lts +1. In the GitLab project with your `.yarnrc.yml`, edit or create a `.gitlab-ci.yml` file. +For example, to trigger only on any tag push: -stages: - - deploy + In Yarn 1: + + ```yaml + image: node:lts -rules: -- if: $CI_COMMIT_TAG + stages: + - deploy -deploy: - stage: deploy - script: - - yarn publish -``` + rules: + - if: $CI_COMMIT_TAG -```yaml -# Yarn 2+ -image: node:lts + deploy: + stage: deploy + script: + - yarn publish + ``` -stages: - - deploy + In Yarn 2 and higher: -rules: - - if: $CI_COMMIT_TAG + ```yaml + image: node:lts -deploy: - stage: deploy - before_script: - - corepack enable - - yarn set version stable - script: - - yarn npm publish -``` + stages: + - deploy -Your package should now publish to the package registry when the pipeline runs. + rules: + - if: $CI_COMMIT_TAG + + deploy: + stage: deploy + before_script: + - corepack enable + - yarn set version stable + script: + - yarn npm publish + ``` + +When the pipeline runs, your package is added to the package registry. + +{{< /tab >}} + +{{< /tabs >}} ## Install a package -{{< alert type="note" >}} +You can install from an instance or project. If multiple packages have the same name and version, +only the most recently published package is retrieved when you install a package. -If multiple packages have the same name and version, the most recently-published -package is retrieved when you install a package. +### Scoped package names -{{< /alert >}} +To install from an instance, a package must be named with a [scope](https://docs.npmjs.com/misc/scope/). +You can set up the scope for your package in the `.yarnrc.yml` file and with the `publishConfig` option in the `package.json`. +You don't need to follow package naming conventions if you install from a project or group. -You can use one of two API endpoints to install packages: +A package scope begins with a `@` and follows the format `@owner/package-name`: -- **Instance-level**: Best used when working with many packages in an organization scope. +- The `@owner` is the top-level project that hosts the packages, not the root of the project with the package source code. +- The package name can be anything. -- If you plan to install a package through the [instance level](#install-from-the-instance-level), - then you must name your package with a [scope](https://docs.npmjs.com/misc/scope/). - Scoped packages begin with a `@` and have the `@owner/package-name` format. You can set up - the scope for your package in the `.yarnrc.yml` file and by using the `publishConfig` - option in the `package.json`. +For example: -- The value used for the `@scope` is the organization root (top-level project) `...com/my-org` - *(@my-org)* that hosts the packages, not the root of the project with the package's source code. -- The scope is always lowercase. -- The package name can be anything you want `@my-org/any-name`. - -- **Project-level**: For when you have a one-off package. - -If you plan to install a package through the [project level](#install-from-the-project-level), -you do not have to adhere to the naming convention. - -| Project URL | Package registry | Organization Scope | Full package name | +| Project URL | Package registry | Organization scope | Full package name | |-------------------------------------------------------------------|----------------------|--------------------|-----------------------------| | `https://gitlab.com///` | Package Name Example | `@my-org` | `@my-org/package-name` | | `https://gitlab.com///` | Project Name | `@example-org` | `@example-org/project-name` | -You can install from the instance level or from the project level. +### Install from the instance -The configurations for `.yarnrc.yml` can be added per package consuming project -root where `package.json` is located, or you can use a global -configuration located in your system user home directory. +If you're working with many packages in the same organization scope, consider installing from the instance. -### Install from the instance level +1. Configure your organization scope. In your `.yarnrc.yml` file, add the following: -Use these steps for global configuration in the `.yarnrc.yml` file: + ```yaml + npmScopes: + : + npmRegistryServer: 'https:///api/v4/packages/npm' + ``` -1. [Configure organization scope](#configure-organization-scope). -1. [Set the registry](#set-the-registry). + - Replace `` with the root level group of the project you're installing to the package from excluding the `@` symbol. + - Replace `` with your domain name, for example, `gitlab.com`. -#### Configure organization scope +1. Optional. If your package is private, you must configure access to the package registry: -```yaml -npmScopes: - : - npmRegistryServer: "https:///api/v4/packages/npm" -``` + ```yaml + npmRegistries: + ///api/v4/packages/npm: + npmAlwaysAuth: true + npmAuthToken: '' + ``` -- Replace `` with the root level group of the project you're installing to the package from excluding the `@` symbol. -- Replace `` with your domain name, for example, `gitlab.com`. + - Replace `` with your domain name, for example, `gitlab.com`. + - Replace `` with a deployment token (recommended), group access token, project access token, or personal access token. -#### Set the registry +1. [Install the package with Yarn](#install-with-yarn). -Skip this step if your package is public not private. +### Install from a group or project -```yaml -npmRegistries: - ///api/v4/packages/npm: - npmAlwaysAuth: true - npmAuthToken: "" -``` +If you have a one-off package, you can install it from a group or project. -- Replace `` with your domain name, for example, `gitlab.com`. -- Replace `` with a deployment token (recommended), group access token, project access token, or personal access token. +{{< tabs >}} -### Install from the group level +{{< tab title="From a group" >}} -Use these steps for global configuration in the `.yarnrc.yml` file: +1. Configure the group scope. In your `.yarnrc.yml` file, add the following: -1. [Configure group scope](#configure-group-scope) -1. [Set the registry](#set-the-registry-group-level) + ```yaml + npmScopes: + : + npmRegistryServer: 'https:///api/v4/groups//-/packages/npm' + ``` -#### Configure group scope + - Replace `` with the top-level group that contains the group you want to install from. Exclude the `@` symbol. + - Replace `` with your domain name, for example, `gitlab.com`. + - Replace `` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id). -```yaml -npmScopes: - : - npmRegistryServer: "https:///api/v4/groups//-/packages/npm" -``` +1. Optional. If your package is private, you must set the registry: -- Replace `` with the root level group of the project you're installing to the package from excluding the `@` symbol. -- Replace `` with your domain name, for example, `gitlab.com`. -- Replace `` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id). + ```yaml + npmRegistries: + ///api/v4/groups//-/packages/npm: + npmAlwaysAuth: true + npmAuthToken: "" + ``` -#### Set the registry (group level) + - Replace `` with your domain name, for example, `gitlab.com`. + - Replace `` with a deployment token (recommended), group access token, project access token, or personal access token. + - Replace `` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id). -```yaml -npmRegistries: - ///api/v4/groups//-/packages/npm: - npmAlwaysAuth: true - npmAuthToken: "" -``` +1. [Install the package with Yarn](#install-with-yarn). -- Replace `` with the root level group of the project you're installing to the package from excluding the `@` symbol. -- Replace `` with your domain name, for example, `gitlab.com`. -- Replace `` with your group ID, found on the [group overview page](../../group#access-a-group-by-using-the-group-id). +{{< /tab >}} -### Install from the project level +{{< tab title="From a project" >}} -Use these steps for each project in the `.yarnrc.yml` file: +1. Configure the project scope. In your `.yarnrc.yml` file, add the following: -1. [Configure project scope](#configure-project-scope). -1. [Set the registry](#set-the-registry-project-level). + ```yaml + npmScopes: + : + npmRegistryServer: "https:///api/v4/projects//packages/npm" + ``` -#### Configure project scope + - Replace `` with the top-level group that contains the project you want to install from. Exclude the `@` symbol. + - Replace `` with your domain name, for example, `gitlab.com`. + - Replace `` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id). -```yaml -npmScopes: - : - npmRegistryServer: "https:///api/v4/projects//packages/npm" -``` +1. Optional. If your package is private, you must set the registry: -- Replace `` with the root level group of the project you're installing to the package from excluding the `@` symbol. -- Replace `` with your domain name, for example, `gitlab.com`. -- Replace `` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id). + ```yaml + npmRegistries: + ///api/v4/projects//packages/npm: + npmAlwaysAuth: true + npmAuthToken: "" + ``` -#### Set the registry (project level) + - Replace `` with your domain name, for example, `gitlab.com`. + - Replace `` with a deployment token (recommended), group access token, project access token, or personal access token. + - Replace `` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id). -Skip this step if your package is public not private. +1. [Install the package with Yarn](#install-with-yarn). -```yaml -npmRegistries: - ///api/v4/projects//packages/npm: - npmAlwaysAuth: true - npmAuthToken: "" -``` +{{< /tab >}} -- Replace `` with your domain name, for example, `gitlab.com`. -- Replace `` with a deployment token (recommended), group access token, project access token, or personal access token. -- Replace `` with your project ID, found on the [project overview page](../../project/working_with_projects.md#access-a-project-by-using-the-project-id). +{{< /tabs >}} -### Install the package +### Install with Yarn -For Yarn 2+, use `yarn add` either in the command line or in the CI/CD pipelines to install your packages: +{{< tabs >}} + +{{< tab title="Yarn 2 or later" >}} + +- Run `yarn add` either from the command line, or from a CI/CD pipeline: ```shell yarn add @scope/my-package ``` -#### For Yarn Classic +{{< /tab >}} -The Yarn Classic setup, requires both `.npmrc` and `.yarnrc` files as -[mentioned in issue](https://github.com/yarnpkg/yarn/issues/4451#issuecomment-753670295): +{{< tab title="Yarn Classic" >}} -- Place credentials in the `.npmrc` file. -- Place the scoped registry in the `.yarnrc` file. +Yarn Classic requires both a `.npmrc` and a `.yarnrc` file. +See [Yarn issue 4451](https://github.com/yarnpkg/yarn/issues/4451#issuecomment-753670295) for more information. -```shell -# .npmrc -## Instance level -///api/v4/packages/npm/:_authToken="" -## Group level -///api/v4/groups//-/packages/npm/:_authToken="" -## Project level -///api/v4/projects//packages/npm/:_authToken="" +1. Place your credentials in the `.npmrc` file, and the scoped registry in the `.yarnrc` file: -# .yarnrc -## Instance level -"@scope:registry" "https:///api/v4/packages/npm/" -## Group level -"@scope:registry" "https:///api/v4/groups//-/packages/npm/" -## Project level -"@scope:registry" "https:///api/v4/projects//packages/npm/" -``` + ```shell + # .npmrc + ## For the instance + ///api/v4/packages/npm/:_authToken='' + ## For the group + ///api/v4/groups//-/packages/npm/:_authToken='' + ## For the project + ///api/v4/projects//packages/npm/:_authToken='' -Then you can use `yarn add` to install your packages. + # .yarnrc + ## For the instance + '@scope:registry' 'https:///api/v4/packages/npm/' + ## For the group + '@scope:registry' 'https:///api/v4/groups//-/packages/npm/' + ## For the project + '@scope:registry' 'https:///api/v4/projects//packages/npm/' + ``` + +1. Run `yarn add` either from the command line, or from a CI/CD pipeline: + + ```shell + yarn add @scope/my-package + ``` + +{{< /tab >}} + +{{< /tabs >}} ## Related topics -- [npm documentation](../npm_registry/_index.md#helpful-hints) +- [npm package registry documentation](../npm_registry/_index.md#helpful-hints) - [Yarn Migration Guide](https://yarnpkg.com/migration/guide) +- [Build a Yarn package](../workflows/build_packages.md#yarn) ## Troubleshooting @@ -365,11 +366,11 @@ info If you think this is a bug, please open a bug report with the information p info Visit https://classic.yarnpkg.com/en/docs/cli/install for documentation about this command ``` -In this case, the following commands creates a file called `.yarnrc` in the current directory. Make sure to be in either your user home directory for global configuration or your project root for per-project configuration: +In this case, the following commands create a file called `.yarnrc` in the current directory. Make sure to be in either your user home directory for global configuration or your project root for per-project configuration: ```shell -yarn config set '//gitlab.example.com/api/v4/projects//packages/npm/:_authToken' "" -yarn config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' "" +yarn config set '//gitlab.example.com/api/v4/projects//packages/npm/:_authToken' '' +yarn config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' '' ``` ### `yarn install` fails to clone repository as a dependency diff --git a/locale/gitlab.pot b/locale/gitlab.pot index adc44cf890c..6c6e66f3727 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9693,9 +9693,6 @@ msgstr "" msgid "Billing|User successfully scheduled for removal. This process might take some time. Refresh the page to see the changes." msgstr "" -msgid "Billing|User was successfully removed" -msgstr "" - msgid "Billing|You are about to remove user %{username} from your subscription. If you continue, the user will be removed from the %{namespace} group and all its subgroups and projects. This action can't be undone." msgstr "" @@ -19742,6 +19739,9 @@ msgstr "" msgid "Dependency list" msgstr "" +msgid "DependencyListExport|License Identifiers" +msgstr "" + msgid "DependencyListExport|Location" msgstr "" @@ -19751,9 +19751,18 @@ msgstr "" msgid "DependencyListExport|Packager" msgstr "" +msgid "DependencyListExport|Project" +msgstr "" + msgid "DependencyListExport|Version" msgstr "" +msgid "DependencyListExport|Vulnerabilities Detected" +msgstr "" + +msgid "DependencyListExport|Vulnerability IDs" +msgstr "" + msgid "DependencyProxy|%{docLinkStart}See the documentation%{docLinkEnd} for other ways to store Docker images in Dependency Proxy cache." msgstr "" @@ -64399,7 +64408,7 @@ msgstr "" msgid "Vulnerability|The CVSS (Common Vulnerability Scoring System) is a standardized framework for assessing and communicating the severity of security vulnerabilities in software. It provides a numerical score (ranging from 0.0 to 10.0) to indicate the severity risk of the vulnerability." msgstr "" -msgid "Vulnerability|The Exploit Prediction Scoring System model produces a probability score between 0 and 1 indicating the likelihood that a vulnerability will be exploited in the next 30 days." +msgid "Vulnerability|The Exploit Prediction Scoring System model produces a percentage value between 0 and 100 that represents the likelihood that a vulnerability will be exploited in the next 30 days." msgstr "" msgid "Vulnerability|The scanner determined this vulnerability to be a false positive. Verify the evaluation before changing its status. %{linkStart}Learn more about false positive detection.%{linkEnd}" diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index 15f31f44b5c..10108bd366c 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -115,7 +115,7 @@ module QA def runner_auth_token runner_list = shell("docker exec #{@name} sh -c 'gitlab-runner list'") - runner_list.match(/Token\e\[0;m=([a-zA-Z0-9_-]+)/i)&.[](1) + runner_list.match(/Token\e\[0;m=([^ ]+)/)&.[](1) end def unregister_command diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index 4b72524d66d..71d1ee058ea 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -53,7 +53,7 @@ RSpec.describe QA::Page::Element do subject { described_class.new(:something) } it 'has no attribute[pattern]' do - expect(subject.attributes[:pattern]).to be(nil) + expect(subject.attributes[:pattern]).to be_nil end it 'is not required by default' do diff --git a/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb b/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb index 5488dca4c40..f75c31f2fc2 100644 --- a/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb +++ b/qa/spec/service/docker_run/mixins/third_party_docker_spec.rb @@ -52,7 +52,7 @@ module QA end it 'resolving the registry returns nil' do - expect(service.third_party_registry).to be(nil) + expect(service.third_party_registry).to be_nil end it 'throws if environment is missing' do diff --git a/qa/spec/service/shellout_spec.rb b/qa/spec/service/shellout_spec.rb index 52f095f165a..393615cc462 100644 --- a/qa/spec/service/shellout_spec.rb +++ b/qa/spec/service/shellout_spec.rb @@ -40,7 +40,7 @@ module QA expect(wait_thread).to receive(:value).twice.and_return(non_errored_wait) subject.shell('docker login -u user -p secret', mask_secrets: %w[secret user]) do |output| - expect(output).not_to be(nil) + expect(output).not_to be_nil expect(output).to eql('logged in as **** with password ****') end end diff --git a/scripts/frontend/quarantined_vue3_specs.txt b/scripts/frontend/quarantined_vue3_specs.txt index a1b3b829a37..8e07546608b 100644 --- a/scripts/frontend/quarantined_vue3_specs.txt +++ b/scripts/frontend/quarantined_vue3_specs.txt @@ -30,7 +30,6 @@ ee/spec/frontend/boards/components/epic_board_content_sidebar_spec.js ee/spec/frontend/boards/components/epics_swimlanes_spec.js ee/spec/frontend/ci/pipeline_details/header/pipeline_header_spec.js ee/spec/frontend/ci/runner/components/runner_usage_spec.js -ee/spec/frontend/ci/secrets/components/secrets_app_spec.js ee/spec/frontend/ci/secrets/components/secrets_breadcrumbs_spec.js ee/spec/frontend/ci/secrets/router_spec.js ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/policies_section_spec.js diff --git a/spec/config/object_store_settings_spec.rb b/spec/config/object_store_settings_spec.rb index bd4d38bddd2..221b3997512 100644 --- a/spec/config/object_store_settings_spec.rb +++ b/spec/config/object_store_settings_spec.rb @@ -331,8 +331,8 @@ RSpec.describe ObjectStoreSettings, feature_category: :shared do expect(settings['enabled']).to be false expect(settings['direct_upload']).to be true - expect(settings['remote_directory']).to be nil - expect(settings['bucket_prefix']).to be nil + expect(settings['remote_directory']).to be_nil + expect(settings['bucket_prefix']).to be_nil end it 'respects original values' do @@ -346,7 +346,7 @@ RSpec.describe ObjectStoreSettings, feature_category: :shared do expect(settings['enabled']).to be true expect(settings['direct_upload']).to be true expect(settings['remote_directory']).to eq 'artifacts' - expect(settings['bucket_prefix']).to be nil + expect(settings['bucket_prefix']).to be_nil end it 'supports bucket prefixes' do diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 2edd4d88b9c..18369ee2b39 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -775,7 +775,7 @@ RSpec.describe ApplicationController, feature_category: :shared do it 'sets stream headers', :aggregate_failures do subject - expect(response.headers['Content-Length']).to be nil + expect(response.headers['Content-Length']).to be_nil expect(response.headers['X-Accel-Buffering']).to eq 'no' expect(response.headers['Last-Modified']).to eq '0' end diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 2f3a076a30b..f75eb30c558 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -63,7 +63,7 @@ RSpec.describe 'Database schema', abuse_reports: %w[reporter_id user_id], abuse_report_notes: %w[discussion_id], ai_code_suggestion_events: %w[user_id], - ai_duo_chat_events: %w[user_id], + ai_duo_chat_events: %w[user_id organization_id], application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_app_id eks_account_id eks_access_key_id], approvals: %w[user_id project_id], diff --git a/spec/dot_gitlab_ci/ci_configuration_validation/shared_context_and_examples.rb b/spec/dot_gitlab_ci/ci_configuration_validation/shared_context_and_examples.rb index 0ea76a57f14..a3c6e84ddca 100644 --- a/spec/dot_gitlab_ci/ci_configuration_validation/shared_context_and_examples.rb +++ b/spec/dot_gitlab_ci/ci_configuration_validation/shared_context_and_examples.rb @@ -104,7 +104,7 @@ end RSpec.shared_examples 'default branch pipeline' do it 'is valid' do - expect(pipeline.yaml_errors).to be nil + expect(pipeline.yaml_errors).to be_nil expect(pipeline.errors).to be_empty expect(pipeline.status).to eq('created') expect(jobs).to include(expected_job_name) @@ -113,7 +113,7 @@ end RSpec.shared_examples 'merge request pipeline' do it "succeeds with expected job" do - expect(pipeline.yaml_errors).to be nil + expect(pipeline.yaml_errors).to be_nil expect(pipeline.errors).to be_empty expect(pipeline.status).to eq('created') expect(jobs).to include(expected_job_name) @@ -124,7 +124,7 @@ RSpec.shared_examples 'merge train pipeline' do let(:ci_merge_request_event_type) { 'merge_train' } it "succeeds with expected job" do - expect(pipeline.yaml_errors).to be nil + expect(pipeline.yaml_errors).to be_nil expect(pipeline.errors).to be_empty expect(pipeline.status).to eq('created') expect(jobs).to include('pre-merge-checks') diff --git a/spec/features/admin/users/admin_impersonates_user_spec.rb b/spec/features/admin/users/admin_impersonates_user_spec.rb index e37b4bf1562..524f257bf78 100644 --- a/spec/features/admin/users/admin_impersonates_user_spec.rb +++ b/spec/features/admin/users/admin_impersonates_user_spec.rb @@ -135,7 +135,7 @@ RSpec.describe 'Admin impersonates user', feature_category: :user_management do subject icon = first('[data-testid="incognito-icon"]') - expect(icon).not_to be nil + expect(icon).not_to be_nil end context 'when viewing the confirm email warning', :js do diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb index 472c39d1f23..b1924d05dad 100644 --- a/spec/finders/container_repositories_finder_spec.rb +++ b/spec/finders/container_repositories_finder_spec.rb @@ -102,7 +102,7 @@ RSpec.describe ContainerRepositoriesFinder do project.project_feature.update!(container_registry_access_level: ProjectFeature::DISABLED) end - it { is_expected.to be nil } + it { is_expected.to be_nil } end end @@ -119,13 +119,13 @@ RSpec.describe ContainerRepositoriesFinder do context 'when subject_type is group' do let(:subject_type) { group } - it { is_expected.to be nil } + it { is_expected.to be_nil } end context 'when subject_type is project' do let(:subject_type) { project } - it { is_expected.to be nil } + it { is_expected.to be_nil } end end end diff --git a/spec/finders/uploader_finder_spec.rb b/spec/finders/uploader_finder_spec.rb index 875c6f0dd30..248c7366cbe 100644 --- a/spec/finders/uploader_finder_spec.rb +++ b/spec/finders/uploader_finder_spec.rb @@ -38,7 +38,7 @@ RSpec.describe UploaderFinder, feature_category: :shared do end it 'returns nil' do - expect(subject).to be(nil) + expect(subject).to be_nil end end diff --git a/spec/frontend/credentials/components/credentials_filter_app_spec.js b/spec/frontend/credentials/components/credentials_filter_sort_app_spec.js similarity index 50% rename from spec/frontend/credentials/components/credentials_filter_app_spec.js rename to spec/frontend/credentials/components/credentials_filter_sort_app_spec.js index e684815ba8b..886eeadc6b4 100644 --- a/spec/frontend/credentials/components/credentials_filter_app_spec.js +++ b/spec/frontend/credentials/components/credentials_filter_sort_app_spec.js @@ -1,8 +1,10 @@ -import { shallowMount } from '@vue/test-utils'; +import { mount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import { GlFilteredSearch } from '@gitlab/ui'; -import CredentialsFilterApp from '~/credentials/components/credentials_filter_app.vue'; +import { GlFilteredSearch, GlSorting } from '@gitlab/ui'; +import CredentialsFilterSortApp from '~/credentials/components/credentials_filter_sort_app.vue'; import { visitUrl, getBaseURL } from '~/lib/utils/url_utility'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { SORT_KEY_NAME } from '~/credentials/constants'; const mockFilters = [ 'dummy', @@ -35,15 +37,28 @@ jest.mock('~/lib/utils/url_utility', () => { }; }); -describe('CredentialsFilterApp', () => { +describe('CredentialsFilterSortApp', () => { let wrapper; + const URL_HOST = 'https://localhost/'; const createComponent = () => { - wrapper = shallowMount(CredentialsFilterApp); + wrapper = mount(CredentialsFilterSortApp, { + stubs: { + GlFilteredSearch: true, + }, + }); }; + beforeEach(() => { + setWindowLocation(URL_HOST); + }); + const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch); const findAvailableTokens = () => findFilteredSearch().props('availableTokens'); + const findSortingComponent = () => wrapper.findComponent(GlSorting); + const findSortDirectionToggle = () => + findSortingComponent().find('button[title^="Sort direction"]'); + const findDropdownToggle = () => findSortingComponent().find('button[aria-haspopup="listbox"]'); describe('Mounts GlFilteredSearch with corresponding filters', () => { it.each` @@ -126,4 +141,92 @@ describe('CredentialsFilterApp', () => { ); }); }); + describe('renders CredentialsSortApp component', () => { + it('when url has filter param with value personal_access_tokens', async () => { + setWindowLocation('?filter=personal_access_tokens'); + createComponent(); + await nextTick(); + + expect(findSortingComponent().exists()).toBe(true); + }); + it('when url has no filter param', async () => { + createComponent(); + await nextTick(); + + expect(findSortingComponent().exists()).toBe(true); + }); + }); + + describe('sort dropdown', () => { + it('defaults to sorting by "Created date" in ascending order', async () => { + createComponent(); + await nextTick(); + expect(findSortingComponent().props('isAscending')).toBe(true); + expect(findDropdownToggle().text()).toBe('Expiration date'); + }); + + it('sets the sort label correctly', () => { + setWindowLocation('?sort=name_asc'); + + createComponent(); + + expect(findDropdownToggle().text()).toBe('Name'); + }); + + describe('new sort option is selected', () => { + beforeEach(async () => { + visitUrl.mockImplementation(() => {}); + createComponent(); + + findSortingComponent().vm.$emit('sortByChange', SORT_KEY_NAME); + await nextTick(); + }); + + it('sorts by new option', () => { + expect(visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=name_asc`); + }); + }); + }); + + describe('sort direction toggle', () => { + beforeEach(() => { + visitUrl.mockImplementation(() => {}); + }); + + describe('when current sort direction is ascending', () => { + beforeEach(() => { + setWindowLocation('?sort=name_asc'); + + createComponent(); + }); + + describe('when sort direction toggle is clicked', () => { + beforeEach(() => { + findSortDirectionToggle().trigger('click'); + }); + + it('sorts in descending order', () => { + expect(visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=name_desc`); + }); + }); + }); + + describe('when current sort direction is descending', () => { + beforeEach(() => { + setWindowLocation('?sort=name_desc'); + + createComponent(); + }); + + describe('when sort direction toggle is clicked', () => { + beforeEach(() => { + findSortDirectionToggle().trigger('click'); + }); + + it('sorts in ascending order', () => { + expect(visitUrl).toHaveBeenCalledWith(`${URL_HOST}?sort=name_asc`); + }); + }); + }); + }); }); diff --git a/spec/frontend/credentials/utils_spec.js b/spec/frontend/credentials/utils_spec.js new file mode 100644 index 00000000000..19bde7f8b68 --- /dev/null +++ b/spec/frontend/credentials/utils_spec.js @@ -0,0 +1,11 @@ +import { buildSortedUrl } from '~/credentials/utils'; + +describe('buildSortedUrl', () => { + it('builds correct URL for ascending sort', () => { + expect(buildSortedUrl('name', false)).toBe('http://test.host/?sort=name_desc'); + }); + + it('builds correct URL for descending sort', () => { + expect(buildSortedUrl('created', true)).toBe('http://test.host/?sort=created_asc'); + }); +}); diff --git a/spec/frontend/rapid_diffs/app/app_spec.js b/spec/frontend/rapid_diffs/app/app_spec.js index 2c513c1422e..b5293880a9b 100644 --- a/spec/frontend/rapid_diffs/app/app_spec.js +++ b/spec/frontend/rapid_diffs/app/app_spec.js @@ -6,10 +6,10 @@ import { DiffFile } from '~/rapid_diffs/diff_file'; import { DiffFileMounted } from '~/rapid_diffs/diff_file_mounted'; import { useDiffsList } from '~/rapid_diffs/stores/diffs_list'; import { pinia } from '~/pinia/instance'; -import { initFileBrowser } from '~/rapid_diffs/app/file_browser'; +import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser'; jest.mock('~/rapid_diffs/app/view_settings'); -jest.mock('~/rapid_diffs/app/file_browser'); +jest.mock('~/rapid_diffs/app/init_file_browser'); describe('Rapid Diffs App', () => { let app; diff --git a/spec/frontend/rapid_diffs/app/file_browser_spec.js b/spec/frontend/rapid_diffs/app/file_browser_spec.js new file mode 100644 index 00000000000..56717b09b30 --- /dev/null +++ b/spec/frontend/rapid_diffs/app/file_browser_spec.js @@ -0,0 +1,53 @@ +import { shallowMount } from '@vue/test-utils'; +import FileBrowser from '~/rapid_diffs/app/file_browser.vue'; +import DiffsFileTree from '~/diffs/components/diffs_file_tree.vue'; +import store from '~/mr_notes/stores'; +import * as types from '~/diffs/store/mutation_types'; + +describe('FileBrowser', () => { + let wrapper; + let commit; + + const createComponent = ({ loadedFiles = {}, ...rest } = {}) => { + wrapper = shallowMount(FileBrowser, { + store, + propsData: { + loadedFiles, + ...rest, + }, + }); + }; + + beforeEach(() => { + commit = jest.spyOn(store, 'commit'); + }); + + it('passes down loaded files', () => { + const loadedFiles = { foo: 1 }; + createComponent({ loadedFiles }); + expect(wrapper.findComponent(DiffsFileTree).props('loadedFiles')).toStrictEqual(loadedFiles); + }); + + it('is visible by default', () => { + createComponent(); + expect(wrapper.findComponent(DiffsFileTree).props('visible')).toBe(true); + }); + + it('toggles visibility', async () => { + createComponent(); + await wrapper.findComponent(DiffsFileTree).vm.$emit('toggled'); + expect(wrapper.findComponent(DiffsFileTree).props('visible')).toBe(false); + }); + + it('handles click', async () => { + const file = { fileHash: 'foo' }; + createComponent(); + await wrapper.findComponent(DiffsFileTree).vm.$emit('clickFile', file); + expect(wrapper.emitted('clickFile')).toStrictEqual([[file]]); + expect(commit).toHaveBeenCalledWith( + `diffs/${types.SET_CURRENT_DIFF_FILE}`, + file.fileHash, + undefined, + ); + }); +}); diff --git a/spec/frontend/rapid_diffs/app/init_file_browser_spec.js b/spec/frontend/rapid_diffs/app/init_file_browser_spec.js new file mode 100644 index 00000000000..ba92ba86e0c --- /dev/null +++ b/spec/frontend/rapid_diffs/app/init_file_browser_spec.js @@ -0,0 +1,75 @@ +import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures'; +import store from '~/mr_notes/stores'; +import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser'; +import createEventHub from '~/helpers/event_hub_factory'; +import waitForPromises from 'helpers/wait_for_promises'; +import { DiffFile } from '~/rapid_diffs/diff_file'; + +jest.mock('~/rapid_diffs/app/file_browser.vue', () => ({ + props: jest.requireActual('~/rapid_diffs/app/file_browser.vue').default.props, + render(h) { + return h('div', { + attrs: { + 'data-file-browser-component': true, + 'data-loaded-files': JSON.stringify(this.loadedFiles), + }, + on: { + click: () => { + this.$emit('clickFile', { fileHash: 'first' }); + }, + }, + }); + }, +})); + +describe('Init file browser', () => { + let dispatch; + + const getMountElement = () => document.querySelector('[data-file-browser]'); + const getFileBrowser = () => document.querySelector('[data-file-browser-component]'); + + beforeEach(() => { + dispatch = jest.spyOn(store, 'dispatch').mockResolvedValue(); + window.mrTabs = { eventHub: createEventHub() }; + setHTMLFixture( + ` +
+ + `, + ); + }); + + beforeAll(() => { + customElements.define('diff-file', DiffFile); + }); + + afterEach(() => { + resetHTMLFixture(); + }); + + it('sets metadata endpoint', () => { + initFileBrowser(); + expect(store.state.diffs.endpointMetadata).toBe(getMountElement().dataset.metadataEndpoint); + }); + + it('fetches metadata', () => { + initFileBrowser(); + expect(dispatch).toHaveBeenCalledWith('diffs/fetchDiffFilesMeta'); + }); + + it('provides already loaded files', async () => { + initFileBrowser(); + await waitForPromises(); + expect(JSON.parse(getFileBrowser().dataset.loadedFiles)).toStrictEqual({ first: true }); + }); + + it('handles file clicks', async () => { + const selectFile = jest.fn(); + const spy = jest.spyOn(DiffFile, 'findByFileHash').mockReturnValue({ selectFile }); + initFileBrowser(); + await waitForPromises(); + getFileBrowser().click(); + expect(spy).toHaveBeenCalledWith('first'); + expect(selectFile).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/rapid_diffs/diff_file_spec.js b/spec/frontend/rapid_diffs/diff_file_spec.js index d09362add80..7e947f0cc3d 100644 --- a/spec/frontend/rapid_diffs/diff_file_spec.js +++ b/spec/frontend/rapid_diffs/diff_file_spec.js @@ -1,5 +1,6 @@ import { DiffFile } from '~/rapid_diffs/diff_file'; import IS from '~/rapid_diffs/intersection_observer'; +import { DIFF_FILE_MOUNTED } from '~/rapid_diffs/dom_events'; // We have to use var here because jest hoists mock calls, so let would be uninitialized at this point // eslint-disable-next-line no-var @@ -61,19 +62,36 @@ describe('DiffFile Web Component', () => { invisible: jest.fn(), mounted: jest.fn(), }); - getWebComponentElement().mount(); }); it('observes diff element', () => { + getWebComponentElement().mount(); expect(IS.prototype.observe).toHaveBeenCalledWith(getWebComponentElement()); }); it('triggers mounted event', () => { + let emitted = false; + document.addEventListener(DIFF_FILE_MOUNTED, () => { + emitted = true; + }); + getWebComponentElement().mount(); expect(adapter.mounted).toHaveBeenCalled(); expect(adapter.mounted.mock.instances[0]).toStrictEqual(getContext()); + expect(emitted).toBe(true); + }); + + it('#selectFile', () => { + getWebComponentElement().mount(); + const spy = jest.spyOn(getWebComponentElement(), 'scrollIntoView'); + getWebComponentElement().selectFile(); + expect(spy).toHaveBeenCalled(); }); describe('when visible', () => { + beforeEach(() => { + getWebComponentElement().mount(); + }); + it('handles all clicks', () => { triggerVisibility(true); getDiffElement().click(); @@ -102,11 +120,11 @@ describe('DiffFile Web Component', () => { }); describe('static methods', () => { - it('findByFileHash', () => { + it('#findByFileHash', () => { expect(DiffFile.findByFileHash('fileHash')).toBeInstanceOf(DiffFile); }); - it('getAll', () => { + it('#getAll', () => { document.body.innerHTML = ``; const instances = DiffFile.getAll(); expect(instances.length).toBe(2); diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index edf9981359e..44dd6c9d5f0 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Mutations::Issues::SetDueDate, feature_category: :api do let(:due_date) { nil } it 'updates due date to be nil' do - expect(mutated_issue.due_date).to be nil + expect(mutated_issue.due_date).to be_nil end end @@ -44,7 +44,7 @@ RSpec.describe Mutations::Issues::SetDueDate, feature_category: :api do let(:due_date) { 'test' } it 'updates due date to be nil' do - expect(mutated_issue.due_date).to be nil + expect(mutated_issue.due_date).to be_nil end end end diff --git a/spec/graphql/resolvers/container_repositories_resolver_spec.rb b/spec/graphql/resolvers/container_repositories_resolver_spec.rb index d2d1d622cf4..7abdcdb2bbd 100644 --- a/spec/graphql/resolvers/container_repositories_resolver_spec.rb +++ b/spec/graphql/resolvers/container_repositories_resolver_spec.rb @@ -88,7 +88,7 @@ RSpec.describe Resolvers::ContainerRepositoriesResolver do end context 'with unauthorized user' do - it { is_expected.to be nil } + it { is_expected.to be_nil } end end end diff --git a/spec/graphql/resolvers/paginated_tree_resolver_spec.rb b/spec/graphql/resolvers/paginated_tree_resolver_spec.rb index 7e0e55e8d2a..05d4ff5c388 100644 --- a/spec/graphql/resolvers/paginated_tree_resolver_spec.rb +++ b/spec/graphql/resolvers/paginated_tree_resolver_spec.rb @@ -57,7 +57,7 @@ RSpec.describe Resolvers::PaginatedTreeResolver, feature_category: :source_code_ end it 'returns nil' do - is_expected.to be(nil) + is_expected.to be_nil end end @@ -67,7 +67,7 @@ RSpec.describe Resolvers::PaginatedTreeResolver, feature_category: :source_code_ end it 'returns nil' do - is_expected.to be(nil) + is_expected.to be_nil end end diff --git a/spec/graphql/resolvers/tree_resolver_spec.rb b/spec/graphql/resolvers/tree_resolver_spec.rb index 9eafd272771..c78f664a6ff 100644 --- a/spec/graphql/resolvers/tree_resolver_spec.rb +++ b/spec/graphql/resolvers/tree_resolver_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Resolvers::TreeResolver do result = resolve_repository({ ref: "master" }) - expect(result).to be(nil) + expect(result).to be_nil end end end diff --git a/spec/graphql/resolvers/users/group_count_resolver_spec.rb b/spec/graphql/resolvers/users/group_count_resolver_spec.rb index 47160a33646..b5afd5c1e97 100644 --- a/spec/graphql/resolvers/users/group_count_resolver_spec.rb +++ b/spec/graphql/resolvers/users/group_count_resolver_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Resolvers::Users::GroupCountResolver do it do result = batch_sync { resolve_group_count(user1, user2) } - expect(result).to be nil + expect(result).to be_nil end end @@ -50,7 +50,7 @@ RSpec.describe Resolvers::Users::GroupCountResolver do it do result = batch_sync { resolve_group_count(user1, nil) } - expect(result).to be nil + expect(result).to be_nil end end end diff --git a/spec/lib/gitlab/database/sharding_key_spec.rb b/spec/lib/gitlab/database/sharding_key_spec.rb index d115a280d0d..d656d92a7aa 100644 --- a/spec/lib/gitlab/database/sharding_key_spec.rb +++ b/spec/lib/gitlab/database/sharding_key_spec.rb @@ -186,6 +186,7 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do WHERE c.column_name = 'organization_id' AND (fk.referenced_table_name = 'organizations' OR fk.referenced_table_name IS NULL) AND (c.column_default IS NOT NULL OR c.is_nullable::boolean OR fk.name IS NULL OR NOT fk.is_valid) + AND (c.table_schema = 'public') ORDER BY c.table_name; SQL @@ -202,7 +203,8 @@ RSpec.describe 'new tables missing sharding_key', feature_category: :cell do "oauth_openid_requests" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717", "oauth_device_grants" => "https://gitlab.com/gitlab-org/gitlab/-/issues/496717", "uploads" => "https://gitlab.com/gitlab-org/gitlab/-/issues/398199", - "bulk_import_trackers" => "https://gitlab.com/gitlab-org/gitlab/-/issues/517823" + "bulk_import_trackers" => "https://gitlab.com/gitlab-org/gitlab/-/issues/517823", + "ai_duo_chat_events" => "https://gitlab.com/gitlab-org/gitlab/-/issues/516140" } has_lfk = ->(lfks) { lfks.any? { |k| k.options[:column] == 'organization_id' && k.to_table == 'organizations' } } diff --git a/spec/requests/projects/merge_requests/diffs_stream_spec.rb b/spec/requests/projects/merge_requests/diffs_stream_spec.rb index d6ee6488f75..17769f95f62 100644 --- a/spec/requests/projects/merge_requests/diffs_stream_spec.rb +++ b/spec/requests/projects/merge_requests/diffs_stream_spec.rb @@ -81,8 +81,8 @@ RSpec.describe 'Merge Requests Diffs stream', feature_category: :code_review_wor it 'streams diffs except the offset' do go(offset: offset) - offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_identifier_hash) - remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_identifier_hash) + offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_hash) + remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_hash) expect(response).to have_gitlab_http_status(:success) expect(response.body).not_to include(*offset_file_identifier_hashes) diff --git a/spec/support/shared_examples/with_diffs_blobs_param.rb b/spec/support/shared_examples/with_diffs_blobs_param.rb index d8e808fcd7f..3f6408bc638 100644 --- a/spec/support/shared_examples/with_diffs_blobs_param.rb +++ b/spec/support/shared_examples/with_diffs_blobs_param.rb @@ -7,7 +7,7 @@ RSpec.shared_examples 'with diffs_blobs param' do go(diff_blobs: true) expect(response).to have_gitlab_http_status(:success) - expect(response.body).to include(*diff_files.to_a.map(&:file_identifier_hash)) + expect(response.body).to include(*diff_files.to_a.map(&:file_hash)) end end @@ -17,8 +17,8 @@ RSpec.shared_examples 'with diffs_blobs param' do it 'streams diffs except the offset' do go(diff_blobs: true, offset: offset) - offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_identifier_hash) - remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_identifier_hash) + offset_file_identifier_hashes = diff_files.to_a.take(offset).map(&:file_hash) + remaining_file_identifier_hashes = diff_files.to_a.slice(offset..).map(&:file_hash) expect(response).to have_gitlab_http_status(:success) expect(response.body).not_to include(*offset_file_identifier_hashes) @@ -28,6 +28,6 @@ RSpec.shared_examples 'with diffs_blobs param' do end def file_identifier_hashes(diff) - diff.diffs.diff_files.to_a.map(&:file_identifier_hash) + diff.diffs.diff_files.to_a.map(&:file_hash) end end diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index 92e7d0d0cee..2a8ba890053 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -22,16 +22,6 @@ RSpec.describe ProcessCommitWorker, feature_category: :source_code_management do expect(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: described_class)).to eq(1000) end - context 'when concurrency_limit_process_commit_worker is disabled' do - before do - stub_feature_flags(concurrency_limit_process_commit_worker: false) - end - - it 'does not have a concurrency limit' do - expect(::Gitlab::SidekiqMiddleware::ConcurrencyLimit::WorkersMap.limit_for(worker: described_class)).to eq(0) - end - end - describe '#perform' do subject(:perform) { worker.perform(project_id, user_id, commit.to_hash, default) }