diff options
47 files changed, 494 insertions, 109 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 7a45d32b666..85f601f19c7 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -30,6 +30,8 @@ GITALY_SERVER_VERSION @project_278964_bot6 @gitlab-org/maintainers/rails-backend /doc/.markdownlint/ @marcel.amirault @eread @aqualls @gitlab-org/tw-leadership /doc/.vale/ @marcel.amirault @eread @aqualls @gitlab-org/tw-leadership /lib/tasks/gitlab/tw/codeowners.rake @aqualls @gitlab-org/tw-leadership +/scripts/lint-doc.sh @marcel.amirault @eread @axil @sarahgerman @gitlab-org/tw-leadership +/scripts/lint-docs-metadata.sh @marcel.amirault @eread @axil @sarahgerman @gitlab-org/tw-leadership ^[Source code editing] .solargraph.yml.example @igor.drozdov @@ -1434,8 +1436,8 @@ ee/lib/ee/api/entities/project.rb /ee/lib/sidebars/ [Global Search] @gitlab-org/search-team/migration-maintainers -/ee/elastic/migrate -/ee/spec/elastic/migrate +/ee/elastic/migrate/ +/ee/spec/elastic/migrate/ /ee/spec/support/elastic.rb # JiHu GitLab rules. See https://gitlab.com/gitlab-jh/gitlab-jh-enablement/-/issues/213#note_1024367528 @@ -182,7 +182,7 @@ gem 'typhoeus', '~> 1.4.0' # Used with Elasticsearch to support http keep-alive gem 'html-pipeline', '~> 2.14.3' gem 'deckar01-task_list', '2.3.2' gem 'gitlab-markup', '~> 1.9.0', require: 'github/markup' -gem 'commonmarker', '~> 0.23.6' +gem 'commonmarker', '~> 0.23.9' gem 'kramdown', '~> 2.3.1' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~> 6.3.2' @@ -367,7 +367,7 @@ gem 'prometheus-client-mmap', '~> 0.24', require: 'prometheus/client' gem 'warning', '~> 1.3.0' group :development do - gem 'lefthook', '~> 1.4.0', require: false + gem 'lefthook', '~> 1.4.1', require: false gem 'rubocop' gem 'solargraph', '~> 0.47.2', require: false diff --git a/Gemfile.checksum b/Gemfile.checksum index a7492e77dc2..b23ee78f108 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -84,7 +84,7 @@ {"name":"coderay","version":"1.1.3","platform":"ruby","checksum":"dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b"}, {"name":"coercible","version":"1.0.0","platform":"ruby","checksum":"5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc"}, {"name":"colored2","version":"3.1.2","platform":"ruby","checksum":"b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a"}, -{"name":"commonmarker","version":"0.23.6","platform":"ruby","checksum":"c8aeaaaff4ba497bf180f762db63a0069794fafb6eff221224c9c8199d337b38"}, +{"name":"commonmarker","version":"0.23.9","platform":"ruby","checksum":"2e739c85a6961531cb6f5ba5169f2c7f64471b7e700c64b048ec22a5b230811c"}, {"name":"concurrent-ruby","version":"1.2.2","platform":"ruby","checksum":"3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f"}, {"name":"connection_pool","version":"2.3.0","platform":"ruby","checksum":"677985be912f33c90f98f229aaa0c0ddb2ef8776f21929a36eeeb25251c944da"}, {"name":"cork","version":"0.3.0","platform":"ruby","checksum":"a0a0ac50e262f8514d1abe0a14e95e71c98b24e3378690e5d044daf0013ad4bc"}, @@ -331,7 +331,7 @@ {"name":"kramdown-parser-gfm","version":"1.1.0","platform":"ruby","checksum":"fb39745516427d2988543bf01fc4cf0ab1149476382393e0e9c48592f6581729"}, {"name":"kubeclient","version":"4.11.0","platform":"ruby","checksum":"4985fcd749fb8c364a668a8350a49821647f03aa52d9ee6cbc582beb8e883fcc"}, {"name":"launchy","version":"2.5.0","platform":"ruby","checksum":"954243c4255920982ce682f89a42e76372dba94770bf09c23a523e204bdebef5"}, -{"name":"lefthook","version":"1.4.0","platform":"ruby","checksum":"363e055d4bf985a5409c548f7b0d584ab87902221f6c5b3a5cc3fcfc84a2f356"}, +{"name":"lefthook","version":"1.4.1","platform":"ruby","checksum":"b0cd741c3557ec3223d466cabd80a45e852bc9933645cd0b12b80f82acb18181"}, {"name":"letter_opener","version":"1.7.0","platform":"ruby","checksum":"095bc0d58e006e5b43ea7d219e64ecf2de8d1f7d9dafc432040a845cf59b4725"}, {"name":"letter_opener_web","version":"2.0.0","platform":"ruby","checksum":"33860ad41e1785d75456500e8ca8bba8ed71ee6eaf08a98d06bbab67c5577b6f"}, {"name":"libyajl2","version":"1.2.0","platform":"ruby","checksum":"1117cd1e48db013b626e36269bbf1cef210538ca6d2e62d3fa3db9ded005b258"}, diff --git a/Gemfile.lock b/Gemfile.lock index 0d6f2d36e9a..655953117e8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -307,7 +307,7 @@ GEM coercible (1.0.0) descendants_tracker (~> 0.0.1) colored2 (3.1.2) - commonmarker (0.23.6) + commonmarker (0.23.9) concurrent-ruby (1.2.2) connection_pool (2.3.0) cork (0.3.0) @@ -878,7 +878,7 @@ GEM rest-client (~> 2.0) launchy (2.5.0) addressable (~> 2.7) - lefthook (1.4.0) + lefthook (1.4.1) letter_opener (1.7.0) launchy (~> 2.2) letter_opener_web (2.0.0) @@ -1690,7 +1690,7 @@ DEPENDENCIES charlock_holmes (~> 0.7.7) circuitbox (= 2.0.0) cloud_profiler_agent (~> 0.0.0)! - commonmarker (~> 0.23.6) + commonmarker (~> 0.23.9) concurrent-ruby (~> 1.1) connection_pool (~> 2.0) countries (~> 4.0.0) @@ -1803,7 +1803,7 @@ DEPENDENCIES knapsack (~> 1.21.1) kramdown (~> 2.3.1) kubeclient (~> 4.11.0) - lefthook (~> 1.4.0) + lefthook (~> 1.4.1) letter_opener_web (~> 2.0.0) license_finder (~> 7.0) licensee (~> 9.15) diff --git a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue index a58b6e62254..eb5d1d39142 100644 --- a/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue +++ b/app/assets/javascripts/add_context_commits_modal/components/add_context_commits_modal_trigger.vue @@ -38,7 +38,7 @@ export default { :class="[ { 'gl-ml-5': !contextCommitsEmpty, - 'gl-mt-5': !commitsEmpty && contextCommitsEmpty, + 'gl-mt-1': !commitsEmpty && contextCommitsEmpty, }, ]" :variant="commitsEmpty ? 'confirm' : 'default'" diff --git a/app/assets/javascripts/authentication/password/components/password_input.vue b/app/assets/javascripts/authentication/password/components/password_input.vue index fa9a7782b74..6e3af96cf33 100644 --- a/app/assets/javascripts/authentication/password/components/password_input.vue +++ b/app/assets/javascripts/authentication/password/components/password_input.vue @@ -15,27 +15,27 @@ export default { title: { type: String, required: false, - default: '', + default: null, }, id: { type: String, required: false, - default: '', + default: null, }, minimumPasswordLength: { type: String, required: false, - default: '', + default: null, }, qaSelector: { type: String, required: false, - default: '', + default: null, }, testid: { type: String, required: false, - default: '', + default: null, }, autocomplete: { type: String, diff --git a/app/assets/javascripts/projects/settings/components/access_dropdown.vue b/app/assets/javascripts/projects/settings/components/access_dropdown.vue index 08a1c586f69..a2e4827cbfa 100644 --- a/app/assets/javascripts/projects/settings/components/access_dropdown.vue +++ b/app/assets/javascripts/projects/settings/components/access_dropdown.vue @@ -63,6 +63,11 @@ export default { required: false, default: () => [], }, + items: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { @@ -143,11 +148,37 @@ export default { query: debounce(function debouncedSearch() { return this.getData(); }, 500), + items(items) { + this.setDataForSave(items); + }, }, created() { this.getData({ initial: true }); }, methods: { + setDataForSave(items) { + this.selected = items.reduce( + (selected, item) => { + if (item.group_id) { + selected[LEVEL_TYPES.GROUP].push({ id: item.group_id, ...item }); + } else if (item.user_id) { + selected[LEVEL_TYPES.USER].push({ id: item.user_id, ...item }); + } else if (item.access_level) { + const level = this.accessLevelsData.find(({ id }) => item.access_level === id); + selected[LEVEL_TYPES.ROLE].push(level); + } else if (item.deploy_key_id) { + selected[LEVEL_TYPES.DEPLOY_KEY].push({ id: item.deploy_key_id, ...item }); + } + return selected; + }, + { + [LEVEL_TYPES.GROUP]: [], + [LEVEL_TYPES.USER]: [], + [LEVEL_TYPES.ROLE]: [], + [LEVEL_TYPES.DEPLOY_KEY]: [], + }, + ); + }, focusInput() { this.$refs.search.focusInput(); }, diff --git a/app/assets/javascripts/search/index.js b/app/assets/javascripts/search/index.js index 1e4b1e36514..f5684cebbf9 100644 --- a/app/assets/javascripts/search/index.js +++ b/app/assets/javascripts/search/index.js @@ -15,7 +15,7 @@ export const initSearchApp = () => { const store = createStore({ query, navigation, - useNewNavigation: gon.use_new_navigation, + useSidebarNavigation: gon.use_new_navigation, }); initTopbar(store); diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index 317145d4cd1..7046a57058d 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -1,7 +1,7 @@ <script> import { mapState, mapGetters } from 'vuex'; -import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; -import ScopeNewNavigation from '~/search/sidebar/components/scope_new_navigation.vue'; +import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue'; +import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue'; import SidebarPortal from '~/super_sidebar/components/sidebar_portal.vue'; import { SCOPE_ISSUES, SCOPE_MERGE_REQUESTS, SCOPE_BLOB } from '../constants'; import ResultsFilters from './results_filters.vue'; @@ -11,13 +11,14 @@ export default { name: 'GlobalSearchSidebar', components: { ResultsFilters, - ScopeNavigation, - ScopeNewNavigation, + ScopeLegacyNavigation, + ScopeSidebarNavigation, LanguageFilter, SidebarPortal, }, computed: { - ...mapState(['urlQuery', 'useNewNavigation']), + // useSidebarNavigation refers to whether the new left sidebar navigation is enabled + ...mapState(['useSidebarNavigation']), ...mapGetters(['currentScope']), showIssueAndMergeFilters() { return this.currentScope === SCOPE_ISSUES || this.currentScope === SCOPE_MERGE_REQUESTS; @@ -25,7 +26,9 @@ export default { showBlobFilter() { return this.currentScope === SCOPE_BLOB; }, - showOldNavigation() { + showScopeNavigation() { + // showLegacyNavigation refers to whether the scope navigation should be shown + // while the legacy navigation is being used and there are no search results the scope navigation has to be hidden return Boolean(this.currentScope); }, }, @@ -33,18 +36,18 @@ export default { </script> <template> - <section v-if="useNewNavigation"> + <section v-if="useSidebarNavigation"> <sidebar-portal> - <scope-new-navigation /> + <scope-sidebar-navigation /> <results-filters v-if="showIssueAndMergeFilters" /> <language-filter v-if="showBlobFilter" /> </sidebar-portal> </section> <section - v-else + v-else-if="showScopeNavigation" class="search-sidebar gl-display-flex gl-flex-direction-column gl-md-mr-5 gl-mb-6 gl-mt-5" > - <scope-navigation /> + <scope-legacy-navigation /> <results-filters v-if="showIssueAndMergeFilters" /> <language-filter v-if="showBlobFilter" /> </section> diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue index fc41baee831..e682369d60b 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue @@ -8,7 +8,7 @@ import { NAV_LINK_DEFAULT_CLASSES, NAV_LINK_COUNT_DEFAULT_CLASSES } from '../con import { slugifyWithUnderscore } from '../../../lib/utils/text_utility'; export default { - name: 'ScopeNavigation', + name: 'ScopeLegacyNavigation', i18n: { countOverLimitLabel: s__('GlobalSearch|Result count is over limit.'), }, diff --git a/app/assets/javascripts/search/sidebar/components/scope_new_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue index 86b7cc577a6..3707e152e47 100644 --- a/app/assets/javascripts/search/sidebar/components/scope_new_navigation.vue +++ b/app/assets/javascripts/search/sidebar/components/scope_sidebar_navigation.vue @@ -6,7 +6,7 @@ import NavItem from '~/super_sidebar/components/nav_item.vue'; import { NAV_LINK_DEFAULT_CLASSES, NAV_LINK_COUNT_DEFAULT_CLASSES } from '../constants'; export default { - name: 'ScopeNewNavigation', + name: 'ScopeSidebarNavigation', i18n: { countOverLimitLabel: s__('GlobalSearch|Result count is over limit.'), }, diff --git a/app/assets/javascripts/search/store/index.js b/app/assets/javascripts/search/store/index.js index 634f8f7a7fa..2478518c157 100644 --- a/app/assets/javascripts/search/store/index.js +++ b/app/assets/javascripts/search/store/index.js @@ -7,11 +7,11 @@ import createState from './state'; Vue.use(Vuex); -export const getStoreConfig = ({ query, navigation, useNewNavigation }) => ({ +export const getStoreConfig = (storeInitValues) => ({ actions, getters, mutations, - state: createState({ query, navigation, useNewNavigation }), + state: createState(storeInitValues), }); const createStore = (config) => new Vuex.Store(getStoreConfig(config)); diff --git a/app/assets/javascripts/search/store/state.js b/app/assets/javascripts/search/store/state.js index a62b6728819..c897d4108a8 100644 --- a/app/assets/javascripts/search/store/state.js +++ b/app/assets/javascripts/search/store/state.js @@ -1,7 +1,7 @@ import { cloneDeep } from 'lodash'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; -const createState = ({ query, navigation, useNewNavigation }) => ({ +const createState = ({ query, navigation, useSidebarNavigation }) => ({ urlQuery: cloneDeep(query), query, groups: [], @@ -14,7 +14,7 @@ const createState = ({ query, navigation, useNewNavigation }) => ({ }, sidebarDirty: false, navigation, - useNewNavigation, + useSidebarNavigation, aggregations: { error: false, fetching: false, diff --git a/app/graphql/types/ci/catalog/resource_type.rb b/app/graphql/types/ci/catalog/resource_type.rb index 7958a0ba558..b90ae111a3d 100644 --- a/app/graphql/types/ci/catalog/resource_type.rb +++ b/app/graphql/types/ci/catalog/resource_type.rb @@ -37,6 +37,10 @@ module Types description: 'Number of times the catalog resource has been forked.', alpha: { milestone: '16.1' } + field :root_namespace, Types::NamespaceType, null: true, + description: 'Root namespace of the catalog resource.', + alpha: { milestone: '16.1' } + def web_path ::Gitlab::Routing.url_helpers.project_path(object.project) end @@ -44,6 +48,29 @@ module Types def forks_count BatchLoader::GraphQL.wrap(object.forks_count) end + + def root_namespace + BatchLoader::GraphQL.for(object.project_id).batch do |project_ids, loader| + projects = Project.id_in(project_ids) + + # This preloader uses traversal_ids to obtain Group-type root namespaces. + # It also preloads each project's immediate parent namespace, which effectively + # preloads the User-type root namespaces since they cannot be nested (parent == root). + Preloaders::ProjectRootAncestorPreloader.new(projects, :group).execute + root_namespaces = projects.map(&:root_ancestor) + + # NamespaceType requires the `:read_namespace` ability. We must preload the policy for + # Group-type namespaces to avoid N+1 queries caused by the authorization requests. + group_root_namespaces = root_namespaces.select { |n| n.type == ::Group.sti_name } + Preloaders::GroupPolicyPreloader.new(group_root_namespaces, current_user).execute + + # For User-type namespaces, the authorization request requires preloading the owner objects. + user_root_namespaces = root_namespaces.select { |n| n.type == ::Namespaces::UserNamespace.sti_name } + ActiveRecord::Associations::Preloader.new(records: user_root_namespaces, associations: :owner).call + + projects.each { |project| loader.call(project.id, project.root_ancestor) } + end + end end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/services/packages/ml_model/create_package_file_service.rb b/app/services/packages/ml_model/create_package_file_service.rb new file mode 100644 index 00000000000..574f70940fc --- /dev/null +++ b/app/services/packages/ml_model/create_package_file_service.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Packages + module MlModel + class CreatePackageFileService < BaseService + def execute + ::Packages::Package.transaction do + create_package_file(find_or_create_package) + end + end + + private + + def find_or_create_package + package_params = { + name: params[:package_name], + version: params[:package_version], + build: params[:build], + status: params[:status] + } + + package = ::Packages::MlModel::FindOrCreatePackageService + .new(project, current_user, package_params) + .execute + + package.update_column(:status, params[:status]) if params[:status] && params[:status] != package.status + + package.create_build_infos!(params[:build]) + + package + end + + def create_package_file(package) + file_params = { + file: params[:file], + size: params[:file].size, + file_sha256: params[:file].sha256, + file_name: params[:file_name], + build: params[:build] + } + + ::Packages::CreatePackageFileService.new(package, file_params).execute + end + end + end +end diff --git a/app/services/packages/ml_model/find_or_create_package_service.rb b/app/services/packages/ml_model/find_or_create_package_service.rb new file mode 100644 index 00000000000..cab99e1b008 --- /dev/null +++ b/app/services/packages/ml_model/find_or_create_package_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Packages + module MlModel + class FindOrCreatePackageService < ::Packages::CreatePackageService + def execute + find_or_create_package!(::Packages::Package.package_types['ml_model']) + end + end + end +end diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index 5d693ac603d..a0f47f375f7 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -8,10 +8,10 @@ - hidden = @hidden_commit_count - commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, daily_commits| - %li.js-commit-header.gl-mt-7.gl-pb-2.gl-border-b{ data: { day: day } } + %li.js-commit-header.gl-py-2.gl-border-b{ data: { day: day } } %span.day.font-weight-bold= l(day, format: '%b %d, %Y') - %li.commits-row{ data: { day: day } } + %li.commits-row.gl-mb-6{ data: { day: day } } %ul.content-list.commit-list.flex-list - if Feature.enabled?(:cached_commits, project) = render partial: 'projects/commits/commit', collection: daily_commits, locals: { project: project, ref: ref, merge_request: merge_request }, cached: ->(commit) { commit_partial_cache_key(commit, ref: ref, merge_request: merge_request, request: request) } @@ -19,13 +19,13 @@ = render partial: 'projects/commits/commit', collection: daily_commits, locals: { project: project, ref: ref, merge_request: merge_request } - if context_commits.present? - %li.js-commit-header.gl-mt-7.gl-pb-2.gl-border-b + %li.js-commit-header.gl-py-2.gl-border-b %span.font-weight-bold= n_("%d previously merged commit", "%d previously merged commits", context_commits.count) % context_commits.count - if can_update_merge_request = render Pajamas::ButtonComponent.new(button_options: { class: 'gl-ml-3 add-review-item-modal-trigger', data: { context_commits_empty: 'false' } }) do = _('Add/remove') - %li.commits-row + %li.commits-row.gl-mb-6 %ul.content-list.commit-list.flex-list - if Feature.enabled?(:cached_commits, project) = render partial: 'projects/commits/commit', collection: context_commits, locals: { project: project, ref: ref, merge_request: merge_request }, cached: ->(commit) { commit_partial_cache_key(commit, ref: ref, merge_request: merge_request, request: request) } diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index f3785078393..093d8aa624d 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -12455,6 +12455,7 @@ Represents the total number of issues and their weights for a particular day. | <a id="cicatalogresourceicon"></a>`icon` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. Icon for the catalog resource. | | <a id="cicatalogresourceid"></a>`id` **{warning-solid}** | [`ID!`](#id) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. ID of the catalog resource. | | <a id="cicatalogresourcename"></a>`name` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.11. This feature is an Experiment. It can be changed or removed at any time. Name of the catalog resource. | +| <a id="cicatalogresourcerootnamespace"></a>`rootNamespace` **{warning-solid}** | [`Namespace`](#namespace) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Root namespace of the catalog resource. | | <a id="cicatalogresourcestarcount"></a>`starCount` **{warning-solid}** | [`Int!`](#int) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Number of times the catalog resource has been starred. | | <a id="cicatalogresourcewebpath"></a>`webPath` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.1. This feature is an Experiment. It can be changed or removed at any time. Web path of the catalog resource. | diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md index 651fa2d2ce9..610e68dd2fb 100644 --- a/doc/development/pipelines/index.md +++ b/doc/development/pipelines/index.md @@ -442,7 +442,7 @@ This [GitLab JH validation](https://gitlab.com/gitlab-org-sandbox/gitlab-jh-vali project variables set. It's a pull mirror pulling from [GitLab JH mirror](https://gitlab.com/gitlab-org/gitlab-jh-mirrors/gitlab), -mirroring only protected branches, `master` and `main-jh`, overriding +mirroring specific branches: `(master|main-jh)`, overriding divergent refs, triggering no pipelines when mirror is updated. The pulling user is [`@gitlab-jh-validation-bot`](https://gitlab.com/gitlab-jh-validation-bot), who is a maintainer in the project, and also a diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md index 4bebe4c1a97..97c56b9158d 100644 --- a/doc/user/project/repository/reducing_the_repo_size_using_git.md +++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md @@ -160,8 +160,10 @@ To purge files from a GitLab repository: Refer to the Git [`replace`](https://git-scm.com/book/en/v2/Git-Tools-Replace) documentation for information on how this works. -1. Wait at least 30 minutes, because the repository cleanup process only processes object older than 30 minutes. -1. Run [repository cleanup](#repository-cleanup). +1. Wait at least 30 minutes before attempting the next step. +1. Run [repository cleanup](#repository-cleanup). This process only cleans up objects + that are more than 30 minutes old. See [Space not being freed](#space-not-being-freed) + for more information. ## Repository cleanup @@ -300,3 +302,17 @@ end puts "#{artifact_storage} bytes" ``` + +### Space not being freed + +The process defined on this page can decrease the size of repository exports +decreasing, but the usage in the file system appearing unchanged in both the Web UI and terminal. + +The process leaves many unreachable objects remaining in the repository. +Because they are unreachable, they are not included in the export, but they are +still stored in the file system. These files are pruned after a grace period of +two weeks. Pruning deletes these files and ensures your storage usage statistics +are accurate. + +To expedite this process, see the +['Prune Unreachable Objects' housekeeping task](../../../administration/housekeeping.md). diff --git a/glfm_specification/output_example_snapshots/html.yml b/glfm_specification/output_example_snapshots/html.yml index c34ba47a004..d47d1d71b27 100644 --- a/glfm_specification/output_example_snapshots/html.yml +++ b/glfm_specification/output_example_snapshots/html.yml @@ -5494,7 +5494,7 @@ canonical: | <p><strong>foo, <strong>bar</strong>, baz</strong></p> static: |- - <p data-sourcepos="1:1-1:21" dir="auto"><strong>foo, <strong>bar</strong>, baz</strong></p> + <p data-sourcepos="1:1-1:21" dir="auto"><strong>foo, bar, baz</strong></p> wysiwyg: |- <p><strong>foo, bar</strong>, baz</p> 06_05_00__inlines__emphasis_and_strong_emphasis__040: @@ -5696,7 +5696,7 @@ canonical: | <p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p> static: |- - <p data-sourcepos="1:1-1:24" dir="auto">foo<strong><strong><strong>bar</strong></strong></strong>***baz</p> + <p data-sourcepos="1:1-1:24" dir="auto">foo<strong>bar</strong>***baz</p> wysiwyg: |- <p>foo<strong>bar</strong>***baz</p> 06_05_00__inlines__emphasis_and_strong_emphasis__068: @@ -5755,21 +5755,21 @@ canonical: | <p><strong>foo <strong>bar</strong> baz</strong></p> static: |- - <p data-sourcepos="1:1-1:19" dir="auto"><strong>foo <strong>bar</strong> baz</strong></p> + <p data-sourcepos="1:1-1:19" dir="auto"><strong>foo bar baz</strong></p> wysiwyg: |- <p><strong>foo bar</strong> baz</p> 06_05_00__inlines__emphasis_and_strong_emphasis__076: canonical: | <p><strong><strong>foo</strong> bar</strong></p> static: |- - <p data-sourcepos="1:1-1:15" dir="auto"><strong><strong>foo</strong> bar</strong></p> + <p data-sourcepos="1:1-1:15" dir="auto"><strong>foo bar</strong></p> wysiwyg: |- <p><strong>foo</strong> bar</p> 06_05_00__inlines__emphasis_and_strong_emphasis__077: canonical: | <p><strong>foo <strong>bar</strong></strong></p> static: |- - <p data-sourcepos="1:1-1:15" dir="auto"><strong>foo <strong>bar</strong></strong></p> + <p data-sourcepos="1:1-1:15" dir="auto"><strong>foo bar</strong></p> wysiwyg: |- <p><strong>foo bar</strong></p> 06_05_00__inlines__emphasis_and_strong_emphasis__078: @@ -6031,21 +6031,21 @@ canonical: | <p><strong><strong>foo</strong></strong></p> static: |- - <p data-sourcepos="1:1-1:11" dir="auto"><strong><strong>foo</strong></strong></p> + <p data-sourcepos="1:1-1:11" dir="auto"><strong>foo</strong></p> wysiwyg: |- <p><strong>foo</strong></p> 06_05_00__inlines__emphasis_and_strong_emphasis__115: canonical: | <p><strong><strong>foo</strong></strong></p> static: |- - <p data-sourcepos="1:1-1:11" dir="auto"><strong><strong>foo</strong></strong></p> + <p data-sourcepos="1:1-1:11" dir="auto"><strong>foo</strong></p> wysiwyg: |- <p><strong>foo</strong></p> 06_05_00__inlines__emphasis_and_strong_emphasis__116: canonical: | <p><strong><strong><strong>foo</strong></strong></strong></p> static: |- - <p data-sourcepos="1:1-1:15" dir="auto"><strong><strong><strong>foo</strong></strong></strong></p> + <p data-sourcepos="1:1-1:15" dir="auto"><strong>foo</strong></p> wysiwyg: |- <p><strong>foo</strong></p> 06_05_00__inlines__emphasis_and_strong_emphasis__117: @@ -6059,7 +6059,7 @@ canonical: | <p><em><strong><strong>foo</strong></strong></em></p> static: |- - <p data-sourcepos="1:1-1:13" dir="auto"><em><strong><strong>foo</strong></strong></em></p> + <p data-sourcepos="1:1-1:13" dir="auto"><em><strong>foo</strong></em></p> wysiwyg: |- <p><strong><em>foo</em></strong></p> 06_05_00__inlines__emphasis_and_strong_emphasis__119: @@ -7392,7 +7392,7 @@ canonical: | <p>foo <!-- not a comment -- two hyphens --></p> static: |- - <p data-sourcepos="1:1-1:41" dir="auto">foo <!-- not a comment -- two hyphens --></p> + <p data-sourcepos="1:1-1:41" dir="auto">foo <!-- not a comment -- two hyphens --></p> wysiwyg: |- <p>foo <!-- not a comment -- two hyphens --></p> 06_11_00__inlines__raw_html__015: @@ -7400,7 +7400,7 @@ <p>foo <!--> foo --></p> <p>foo <!-- foo---></p> static: |- - <p data-sourcepos="1:1-1:17" dir="auto">foo <!--> foo --></p> + <p data-sourcepos="1:1-1:17" dir="auto">foo <!----> foo --></p> <p data-sourcepos="3:1-3:16" dir="auto">foo <!-- foo---></p> wysiwyg: |- <p>foo <!--> foo --></p> @@ -8238,10 +8238,10 @@ <section data-footnotes class="footnotes"> <ol> <li id="fn-1-42"> - <p data-sourcepos="5:7-5:41">This is the text inside a footnote. <a href="#fnref-1-42" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <p data-sourcepos="5:7-5:41">This is the text inside a footnote. <a href="#fnref-1-42" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> <li id="fn-footnote-42"> - <p data-sourcepos="7:14-7:38">This is another footnote. <a href="#fnref-footnote-42" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <p data-sourcepos="7:14-7:38">This is another footnote. <a href="#fnref-footnote-42" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> </ol> </section> @@ -8643,7 +8643,7 @@ <section data-footnotes class="footnotes"> <ol> <li id="fn-fortytwo-42"> - <p data-sourcepos="3:14-3:26">footnote text <a href="#fnref-fortytwo-42" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <p data-sourcepos="3:14-3:26">footnote text <a href="#fnref-fortytwo-42" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> </ol> </section> diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index b119c2ffccf..c2cad237d6f 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -34,7 +34,7 @@ module Banzai # Allow section elements with data-footnotes attribute allowlist[:elements].push('section') allowlist[:attributes]['section'] = %w(data-footnotes) - allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref') + allowlist[:attributes]['a'].push('data-footnote-ref', 'data-footnote-backref', 'data-footnote-backref-idx') allowlist end diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 83dc8c5dcf8..846bb934a3d 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -42,11 +42,14 @@ module Gitlab clear_memoized with_finished_at(:recording_ce_finished_at) do - { recorded_at: recorded_at } - .merge(usage_data_metrics) + usage_data_metrics end end + def license_usage_data + { recorded_at: recorded_at } + end + def recorded_at @recorded_at ||= Time.current end @@ -549,7 +552,8 @@ module Gitlab end def usage_data_metrics - system_usage_data_license + license_usage_data + .merge(system_usage_data_license) .merge(system_usage_data_settings) .merge(system_usage_data) .merge(system_usage_data_monthly) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 40516f87ad7..8ec3ee9bfe7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5196,6 +5196,9 @@ msgstr "" msgid "Analytics|Browser Family" msgstr "" +msgid "Analytics|By GitLab" +msgstr "" + msgid "Analytics|Cancel" msgstr "" @@ -16443,9 +16446,6 @@ msgstr "" msgid "Edit Release" msgstr "" -msgid "Edit Requirement" -msgstr "" - msgid "Edit Slack integration" msgstr "" @@ -36887,6 +36887,9 @@ msgstr "" msgid "ProtectedEnvironments|Number of approvals must be between 1 and 5" msgstr "" +msgid "ProtectedEnvironments|Remove approval rule" +msgstr "" + msgid "ProtectedEnvironments|Required approval count" msgstr "" diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb index 769f8accfca..a04fd9092d1 100644 --- a/qa/qa/page/search/results.rb +++ b/qa/qa/page/search/results.rb @@ -4,7 +4,7 @@ module QA module Page module Search class Results < QA::Page::Base - view 'app/assets/javascripts/search/sidebar/components/scope_navigation.vue' do + view 'app/assets/javascripts/search/sidebar/components/scope_legacy_navigation.vue' do element :code_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern element :projects_tab, ':data-qa-selector="qaSelectorValue(item)"' # rubocop:disable QA/ElementWithPattern end diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 537424093fb..a721722a5c3 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -59,7 +59,7 @@ RSpec.describe Admin::ApplicationSettingsController, :do_not_mock_admin_mode_set end end - describe 'GET #usage_data', :with_license, feature_category: :service_ping do + describe 'GET #usage_data', feature_category: :service_ping do before do stub_usage_data_connections stub_database_flavor_check diff --git a/spec/controllers/admin/instance_review_controller_spec.rb b/spec/controllers/admin/instance_review_controller_spec.rb index b69d22ba1e3..f0225a71e00 100644 --- a/spec/controllers/admin/instance_review_controller_spec.rb +++ b/spec/controllers/admin/instance_review_controller_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Admin::InstanceReviewController, feature_category: :service_ping subject { post :index } - context 'with usage ping enabled', :with_license do + context 'with usage ping enabled' do before do stub_application_setting(usage_ping_enabled: true) stub_usage_data_connections diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index d3b43029f2a..1f43caf37e7 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -977,7 +977,7 @@ RSpec.describe 'Admin updates settings', feature_category: :shared do end end - context 'Service usage data page', :with_license do + context 'Service usage data page' do before do stub_usage_data_connections stub_database_flavor_check diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index c68ae443231..3d108951c64 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -502,13 +502,13 @@ RSpec.describe ProjectsFinder do end end - describe 'without CTE flag enabled' do + describe 'without CTE flag enabled', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408387' do let(:use_cte) { false } it_behaves_like 'ProjectFinder#execute examples' end - describe 'with CTE flag enabled' do + describe 'with CTE flag enabled', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/408387' do let(:use_cte) { true } it_behaves_like 'ProjectFinder#execute examples' diff --git a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js index f3e536de703..ce696ee321b 100644 --- a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js +++ b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js @@ -99,6 +99,9 @@ describe('Access Level Dropdown', () => { const findDropdownItemWithText = (items, text) => items.filter((item) => item.text().includes(text)).at(0); + const findSelected = (type) => + wrapper.findAllByTestId(`${type}-dropdown-item`).filter((w) => w.props('isChecked')); + describe('data request', () => { it('should make an api call for users, groups && deployKeys when user has a license', () => { createComponent(); @@ -305,9 +308,6 @@ describe('Access Level Dropdown', () => { { id: 122, type: 'deploy_key', deploy_key_id: 12 }, ]; - const findSelected = (type) => - wrapper.findAllByTestId(`${type}-dropdown-item`).filter((w) => w.props('isChecked')); - beforeEach(async () => { createComponent({ preselectedItems }); await waitForPromises(); @@ -339,6 +339,34 @@ describe('Access Level Dropdown', () => { }); }); + describe('handling two-way data binding', () => { + it('emits a formatted update on selection', async () => { + createComponent(); + await waitForPromises(); + const dropdownItems = findAllDropdownItems(); + // select new item from each group + findDropdownItemWithText(dropdownItems, 'role1').trigger('click'); + findDropdownItemWithText(dropdownItems, 'group4').trigger('click'); + findDropdownItemWithText(dropdownItems, 'user7').trigger('click'); + findDropdownItemWithText(dropdownItems, 'key10').trigger('click'); + + await wrapper.setProps({ items: [{ user_id: 7 }] }); + + const selectedUsers = findSelected(LEVEL_TYPES.USER); + expect(selectedUsers).toHaveLength(1); + expect(selectedUsers.at(0).text()).toBe('user7'); + + const selectedRoles = findSelected(LEVEL_TYPES.ROLE); + expect(selectedRoles).toHaveLength(0); + + const selectedGroups = findSelected(LEVEL_TYPES.GROUP); + expect(selectedGroups).toHaveLength(0); + + const selectedDeployKeys = findSelected(LEVEL_TYPES.DEPLOY_KEY); + expect(selectedDeployKeys).toHaveLength(0); + }); + }); + describe('on dropdown open', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index 963b73aeae5..4bdb034d4bb 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -4,7 +4,8 @@ import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import GlobalSearchSidebar from '~/search/sidebar/components/app.vue'; import ResultsFilters from '~/search/sidebar/components/results_filters.vue'; -import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; +import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue'; +import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue'; import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue'; Vue.use(Vuex); @@ -12,22 +13,16 @@ Vue.use(Vuex); describe('GlobalSearchSidebar', () => { let wrapper; - const actionSpies = { - applyQuery: jest.fn(), - resetQuery: jest.fn(), - }; - const getterSpies = { currentScope: jest.fn(() => 'issues'), }; - const createComponent = (initialState, featureFlags) => { + const createComponent = (initialState = {}, featureFlags = {}) => { const store = new Vuex.Store({ state: { urlQuery: MOCK_QUERY, ...initialState, }, - actions: actionSpies, getters: getterSpies, }); @@ -43,13 +38,14 @@ describe('GlobalSearchSidebar', () => { const findSidebarSection = () => wrapper.find('section'); const findFilters = () => wrapper.findComponent(ResultsFilters); - const findSidebarNavigation = () => wrapper.findComponent(ScopeNavigation); + const findScopeLegacyNavigation = () => wrapper.findComponent(ScopeLegacyNavigation); + const findScopeSidebarNavigation = () => wrapper.findComponent(ScopeSidebarNavigation); const findLanguageAggregation = () => wrapper.findComponent(LanguageFilter); describe('renders properly', () => { describe('always', () => { beforeEach(() => { - createComponent({}); + createComponent(); }); it(`shows section`, () => { expect(findSidebarSection().exists()).toBe(true); @@ -77,12 +73,24 @@ describe('GlobalSearchSidebar', () => { }); }); - describe('renders navigation', () => { + describe.each` + currentScope | sidebarNavShown | legacyNavShown + ${'issues'} | ${false} | ${true} + ${''} | ${false} | ${false} + ${'issues'} | ${true} | ${false} + ${''} | ${true} | ${false} + `('renders navigation', ({ currentScope, sidebarNavShown, legacyNavShown }) => { beforeEach(() => { - createComponent({}); + getterSpies.currentScope = jest.fn(() => currentScope); + createComponent({ useSidebarNavigation: sidebarNavShown }); }); - it('shows the vertical navigation', () => { - expect(findSidebarNavigation().exists()).toBe(true); + + it(`${!legacyNavShown ? 'hides' : 'shows'} the legacy navigation`, () => { + expect(findScopeLegacyNavigation().exists()).toBe(legacyNavShown); + }); + + it(`${!sidebarNavShown ? 'hides' : 'shows'} the sidebar navigation`, () => { + expect(findScopeSidebarNavigation().exists()).toBe(sidebarNavShown); }); }); }); diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js index e8737384f27..6a94da31a1b 100644 --- a/spec/frontend/search/sidebar/components/scope_navigation_spec.js +++ b/spec/frontend/search/sidebar/components/scope_legacy_navigation_spec.js @@ -3,11 +3,11 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import { MOCK_QUERY, MOCK_NAVIGATION } from 'jest/search/mock_data'; -import ScopeNavigation from '~/search/sidebar/components/scope_navigation.vue'; +import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue'; Vue.use(Vuex); -describe('ScopeNavigation', () => { +describe('ScopeLegacyNavigation', () => { let wrapper; const actionSpies = { @@ -29,7 +29,7 @@ describe('ScopeNavigation', () => { getters: getterSpies, }); - wrapper = shallowMount(ScopeNavigation, { + wrapper = shallowMount(ScopeLegacyNavigation, { store, }); }; diff --git a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js index 5207665f883..f31a7c8fa5d 100644 --- a/spec/frontend/search/sidebar/components/scope_new_navigation_spec.js +++ b/spec/frontend/search/sidebar/components/scope_sidebar_navigation_spec.js @@ -1,13 +1,13 @@ import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; -import ScopeNewNavigation from '~/search/sidebar/components/scope_new_navigation.vue'; +import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue'; import NavItem from '~/super_sidebar/components/nav_item.vue'; import { MOCK_QUERY, MOCK_NAVIGATION, MOCK_NAVIGATION_ITEMS } from '../../mock_data'; Vue.use(Vuex); -describe('ScopeNewNavigation', () => { +describe('ScopeSidebarNavigation', () => { let wrapper; const actionSpies = { @@ -30,7 +30,7 @@ describe('ScopeNewNavigation', () => { getters: getterSpies, }); - wrapper = mount(ScopeNewNavigation, { + wrapper = mount(ScopeSidebarNavigation, { store, stubs: { NavItem, diff --git a/spec/graphql/types/ci/catalog/resource_type_spec.rb b/spec/graphql/types/ci/catalog/resource_type_spec.rb index cdc863e0d90..d1f8adcd4fb 100644 --- a/spec/graphql/types/ci/catalog/resource_type_spec.rb +++ b/spec/graphql/types/ci/catalog/resource_type_spec.rb @@ -15,6 +15,7 @@ RSpec.describe Types::Ci::Catalog::ResourceType, feature_category: :pipeline_com versions star_count forks_count + root_namespace ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/lib/banzai/filter/footnote_filter_spec.rb b/spec/lib/banzai/filter/footnote_filter_spec.rb index 4b765191449..5dd5074801f 100644 --- a/spec/lib/banzai/filter/footnote_filter_spec.rb +++ b/spec/lib/banzai/filter/footnote_filter_spec.rb @@ -18,12 +18,12 @@ RSpec.describe Banzai::Filter::FootnoteFilter, feature_category: :team_planning <section data-footnotes> <ol> <li id="fn-1"> - <p>one <a href="#fnref-1" aria-label="Back to content" data-footnote-backref>↩</a></p> + <p>one <a href="#fnref-1" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p> </li> <li id="fn-second"> - <p>two <a href="#fnref-second" aria-label="Back to content" data-footnote-backref>↩</a></p> + <p>two <a href="#fnref-second" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p> </li>\n<li id="fn-_%F0%9F%98%84_"> - <p>three <a href="#fnref-_%F0%9F%98%84_" aria-label="Back to content" data-footnote-backref>↩</a></p> + <p>three <a href="#fnref-_%F0%9F%98%84_" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p> </li> </ol> EOF @@ -35,13 +35,13 @@ RSpec.describe Banzai::Filter::FootnoteFilter, feature_category: :team_planning <section data-footnotes class=\"footnotes\"> <ol> <li id="fn-1-#{identifier}"> - <p>one <a href="#fnref-1-#{identifier}" aria-label="Back to content" data-footnote-backref class="footnote-backref">↩</a></p> + <p>one <a href="#fnref-1-#{identifier}" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1" class="footnote-backref">↩</a></p> </li> <li id="fn-second-#{identifier}"> - <p>two <a href="#fnref-second-#{identifier}" aria-label="Back to content" data-footnote-backref class="footnote-backref">↩</a></p> + <p>two <a href="#fnref-second-#{identifier}" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2" class="footnote-backref">↩</a></p> </li> <li id="fn-_%F0%9F%98%84_-#{identifier}"> - <p>three <a href="#fnref-_%F0%9F%98%84_-#{identifier}" aria-label="Back to content" data-footnote-backref class="footnote-backref">↩</a></p> + <p>three <a href="#fnref-_%F0%9F%98%84_-#{identifier}" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3" class="footnote-backref">↩</a></p> </li> </ol> </section> diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 51832e60754..bad09732e00 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -144,6 +144,18 @@ RSpec.describe Banzai::Filter::SanitizationFilter, feature_category: :team_plann end describe 'footnotes' do + it 'allows the footnote attributes' do + exp = <<~HTML + <section data-footnotes> + <a href="#fn-first" id="fnref-first" data-footnote-ref data-footnote-backref data-footnote-backref-idx>foo/bar.md</a> + </section> + HTML + + act = filter(exp) + + expect(act.to_html).to eq exp + end + it 'allows correct footnote id property on links' do exp = %q(<a href="#fn-first" id="fnref-first">foo/bar.md</a>) act = filter(exp) diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb index ca05a353d47..6654eec7241 100644 --- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb +++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb @@ -49,13 +49,13 @@ RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning <section data-footnotes class="footnotes"> <ol> <li id="fn-1-#{identifier}"> - <p>one <a href="#fnref-1-#{identifier}" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <p>one <a href="#fnref-1-#{identifier}" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> <li id="fn-%F0%9F%98%84second-#{identifier}"> - <p>two <a href="#fnref-%F0%9F%98%84second-#{identifier}" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <p>two <a href="#fnref-%F0%9F%98%84second-#{identifier}" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> <li id="fn-_twenty-#{identifier}"> - <p>twenty <a href="#fnref-_twenty-#{identifier}" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> + <p>twenty <a href="#fnref-_twenty-#{identifier}" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> </li> </ol> </section> diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb index 49e02fe5cec..8cdee727d3d 100644 --- a/spec/lib/gitlab/checks/force_push_spec.rb +++ b/spec/lib/gitlab/checks/force_push_spec.rb @@ -6,14 +6,34 @@ RSpec.describe Gitlab::Checks::ForcePush do let_it_be(:project) { create(:project, :repository) } describe '.force_push?' do - it 'returns false if the repo is empty' do - allow(project).to receive(:empty_repo?).and_return(true) + let(:old_rev) { 'HEAD~' } + let(:new_rev) { 'HEAD' } - expect(described_class.force_push?(project, 'HEAD', 'HEAD~')).to be(false) + subject(:force_push) { described_class.force_push?(project, old_rev, new_rev) } + + context 'when the repo is empty' do + before do + allow(project).to receive(:empty_repo?).and_return(true) + end + + it 'returns false' do + expect(force_push).to be(false) + end end - it 'checks if old rev is an anchestor' do - expect(described_class.force_push?(project, 'HEAD', 'HEAD~')).to be(true) + context 'when new rev is a descendant of old rev' do + it 'returns false' do + expect(force_push).to be(false) + end + end + + context 'when new rev is not a descendant of old rev' do + let(:old_rev) { 'HEAD' } + let(:new_rev) { 'HEAD~' } + + it 'returns true' do + expect(force_push).to be(true) + end end end end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 4da5f899ea9..4544cb2eb26 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -661,6 +661,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures, feature_category: :servic end end + describe '.license_usage_data' do + subject { described_class.license_usage_data } + + it 'gathers license data' do + expect(subject[:recorded_at]).to be_a(Time) + end + end + context 'when not relying on database records' do describe '.features_usage_data_ce' do subject { described_class.features_usage_data_ce } diff --git a/spec/requests/api/usage_data_non_sql_metrics_spec.rb b/spec/requests/api/usage_data_non_sql_metrics_spec.rb index 4ca6c5cace3..b2929caf676 100644 --- a/spec/requests/api/usage_data_non_sql_metrics_spec.rb +++ b/spec/requests/api/usage_data_non_sql_metrics_spec.rb @@ -12,7 +12,7 @@ RSpec.describe API::UsageDataNonSqlMetrics, :aggregate_failures, feature_categor stub_usage_data_connections end - describe 'GET /usage_data/non_sql_metrics', :with_license do + describe 'GET /usage_data/non_sql_metrics' do let(:endpoint) { '/usage_data/non_sql_metrics' } context 'with authentication' do diff --git a/spec/requests/api/usage_data_queries_spec.rb b/spec/requests/api/usage_data_queries_spec.rb index 584b0f31a07..ab3c38adb81 100644 --- a/spec/requests/api/usage_data_queries_spec.rb +++ b/spec/requests/api/usage_data_queries_spec.rb @@ -14,7 +14,7 @@ RSpec.describe API::UsageDataQueries, :aggregate_failures, feature_category: :se stub_database_flavor_check end - describe 'GET /usage_data/usage_data_queries', :with_license do + describe 'GET /usage_data/usage_data_queries' do let(:endpoint) { '/usage_data/queries' } context 'with authentication' do diff --git a/spec/services/packages/ml_model/create_package_file_service_spec.rb b/spec/services/packages/ml_model/create_package_file_service_spec.rb new file mode 100644 index 00000000000..d749aee227a --- /dev/null +++ b/spec/services/packages/ml_model/create_package_file_service_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::MlModel::CreatePackageFileService, feature_category: :mlops do + describe '#execute' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + let_it_be(:pipeline) { create(:ci_pipeline, user: user, project: project) } + let_it_be(:file_name) { 'myfile.tar.gz.1' } + + let(:build) { instance_double(Ci::Build, pipeline: pipeline) } + + let(:sha256) { '440e5e148a25331bbd7991575f7d54933c0ebf6cc735a18ee5066ac1381bb590' } + let(:temp_file) { Tempfile.new("test") } + let(:file) { UploadedFile.new(temp_file.path, sha256: sha256) } + let(:package_service) { double } + + subject(:execute_service) { described_class.new(project, user, params).execute } + + before do + FileUtils.touch(temp_file) + end + + after do + FileUtils.rm_f(temp_file) + end + + context 'without existing package' do + let(:params) do + { + package_name: 'new_model', + package_version: '1.0.0', + file: file, + file_name: file_name + } + end + + it 'creates package file', :aggregate_failures do + expect { execute_service } + .to change { project.packages.ml_model.count }.by(1) + .and change { Packages::PackageFile.count }.by(1) + .and change { Packages::PackageFileBuildInfo.count }.by(0) + + new_model = project.packages.ml_model.last + package_file = new_model.package_files.last + + aggregate_failures do + expect(new_model.name).to eq('new_model') + expect(new_model.version).to eq('1.0.0') + expect(new_model.status).to eq('default') + expect(package_file.package).to eq(new_model) + expect(package_file.file_name).to eq(file_name) + expect(package_file.size).to eq(file.size) + expect(package_file.file_sha256).to eq(sha256) + end + end + end + + context 'with existing package' do + let_it_be(:model) { create(:ml_model_package, creator: user, project: project, version: '0.1.0') } + + let(:params) do + { + package_name: model.name, + package_version: model.version, + file: file, + file_name: file_name, + status: :hidden, + build: build + } + end + + it 'adds the package file and updates status and ci_build', :aggregate_failures do + expect { execute_service } + .to change { project.packages.ml_model.count }.by(0) + .and change { model.package_files.count }.by(1) + .and change { Packages::PackageFileBuildInfo.count }.by(1) + + model.reload + + package_file = model.package_files.last + + expect(model.build_infos.first.pipeline).to eq(build.pipeline) + expect(model.status).to eq('hidden') + + expect(package_file.package).to eq(model) + expect(package_file.file_name).to eq(file_name) + expect(package_file.size).to eq(file.size) + expect(package_file.file_sha256).to eq(sha256) + end + end + end +end diff --git a/spec/services/packages/ml_model/find_or_create_package_service_spec.rb b/spec/services/packages/ml_model/find_or_create_package_service_spec.rb new file mode 100644 index 00000000000..6e1e17da0e6 --- /dev/null +++ b/spec/services/packages/ml_model/find_or_create_package_service_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::MlModel::FindOrCreatePackageService, feature_category: :mlops do + let_it_be(:project) { create(:project) } + let_it_be(:user) { project.creator } + let_it_be(:ci_build) { create(:ci_build, :running, user: user, project: project) } + + let(:base_params) do + { + name: 'mymodel', + version: '0.0.1' + } + end + + let(:params) { base_params } + + describe '#execute' do + subject(:execute_service) { described_class.new(project, user, params).execute } + + context 'when model does not exist' do + it 'creates the model' do + expect { subject }.to change { project.packages.ml_model.count }.by(1) + + package = project.packages.ml_model.last + + aggregate_failures do + expect(package.creator).to eq(user) + expect(package.package_type).to eq('ml_model') + expect(package.name).to eq('mymodel') + expect(package.version).to eq('0.0.1') + expect(package.build_infos.count).to eq(0) + end + end + + context 'when build is provided' do + let(:params) { base_params.merge(build: ci_build) } + + it 'creates package and package build info' do + expect { subject }.to change { project.packages.ml_model.count }.by(1) + + package = project.packages.ml_model.last + + aggregate_failures do + expect(package.creator).to eq(user) + expect(package.package_type).to eq('ml_model') + expect(package.name).to eq('mymodel') + expect(package.version).to eq('0.0.1') + expect(package.build_infos.first.pipeline).to eq(ci_build.pipeline) + end + end + end + end + + context 'when model already exists' do + it 'does not create a new model', :aggregate_failures do + model = project.packages.ml_model.create!(params) + + expect do + new_model = subject + expect(new_model).to eq(model) + end.not_to change { project.packages.ml_model.count } + end + end + end +end diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb index 75853371c0f..30ffe3d6228 100644 --- a/spec/support/helpers/search_helpers.rb +++ b/spec/support/helpers/search_helpers.rb @@ -35,6 +35,8 @@ module SearchHelpers end def has_search_scope?(scope) + return false unless page.has_selector?('[data-testid="search-filter"]') + page.within '[data-testid="search-filter"]' do has_link?(scope) end diff --git a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb index b34d95519a2..cd792ccc4e3 100644 --- a/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb +++ b/spec/support/shared_contexts/services/service_ping/stubbed_service_ping_metrics_definitions_shared_context.rb @@ -3,8 +3,9 @@ RSpec.shared_context 'stubbed service ping metrics definitions' do include UsageDataHelpers - let(:metrics_definitions) { standard_metrics + operational_metrics + optional_metrics } + let(:metrics_definitions) { standard_metrics + subscription_metrics + operational_metrics + optional_metrics } # ToDo: remove during https://gitlab.com/gitlab-org/gitlab/-/issues/396824 (license metrics migration) + let(:subscription_metrics) { [] } let(:standard_metrics) do [ metric_attributes('recorded_at', 'standard'), diff --git a/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb b/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb index fd3c53f3675..8dcff99fb6f 100644 --- a/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb +++ b/spec/support/shared_examples/services/service_ping/complete_service_ping_payload_shared_examples.rb @@ -3,7 +3,7 @@ RSpec.shared_examples 'complete service ping payload' do it_behaves_like 'service ping payload with all expected metrics' do let(:expected_metrics) do - standard_metrics + operational_metrics + optional_metrics + standard_metrics + subscription_metrics + operational_metrics + optional_metrics end end end diff --git a/spec/tasks/gitlab/usage_data_rake_spec.rb b/spec/tasks/gitlab/usage_data_rake_spec.rb index 11aab1b1b42..72f284b0b7f 100644 --- a/spec/tasks/gitlab/usage_data_rake_spec.rb +++ b/spec/tasks/gitlab/usage_data_rake_spec.rb @@ -2,7 +2,7 @@ require 'rake_helper' -RSpec.describe 'gitlab:usage data take tasks', :silence_stdout, :with_license, feature_category: :service_ping do +RSpec.describe 'gitlab:usage data take tasks', :silence_stdout, feature_category: :service_ping do include StubRequests include UsageDataHelpers |