diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-02 00:09:56 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-02 00:09:56 +0000 |
commit | 926711e4546e0cf845c6cbe5773076f2195357f0 (patch) | |
tree | 92edf881d2be503589848c218a85d2a584cf0d19 | |
parent | f7bc7dc5eafc4eef9043a3d1b2dcbc15ca76a571 (diff) | |
download | gitlab-ce-926711e4546e0cf845c6cbe5773076f2195357f0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
75 files changed, 784 insertions, 291 deletions
@@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'rails', '~> 6.0.3.7' +gem 'rails', '~> 6.1.3.2' gem 'bootsnap', '~> 1.4.6' diff --git a/Gemfile.lock b/Gemfile.lock index f30b1cde549..6e5f7edfd73 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,64 +12,68 @@ GEM abstract_type (0.0.7) acme-client (2.0.6) faraday (>= 0.17, < 2.0.0) - actioncable (6.0.3.7) - actionpack (= 6.0.3.7) + actioncable (6.1.3.2) + actionpack (= 6.1.3.2) + activesupport (= 6.1.3.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.7) - actionpack (= 6.0.3.7) - activejob (= 6.0.3.7) - activerecord (= 6.0.3.7) - activestorage (= 6.0.3.7) - activesupport (= 6.0.3.7) + actionmailbox (6.1.3.2) + actionpack (= 6.1.3.2) + activejob (= 6.1.3.2) + activerecord (= 6.1.3.2) + activestorage (= 6.1.3.2) + activesupport (= 6.1.3.2) mail (>= 2.7.1) - actionmailer (6.0.3.7) - actionpack (= 6.0.3.7) - actionview (= 6.0.3.7) - activejob (= 6.0.3.7) + actionmailer (6.1.3.2) + actionpack (= 6.1.3.2) + actionview (= 6.1.3.2) + activejob (= 6.1.3.2) + activesupport (= 6.1.3.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.7) - actionview (= 6.0.3.7) - activesupport (= 6.0.3.7) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.3.2) + actionview (= 6.1.3.2) + activesupport (= 6.1.3.2) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.7) - actionpack (= 6.0.3.7) - activerecord (= 6.0.3.7) - activestorage (= 6.0.3.7) - activesupport (= 6.0.3.7) + actiontext (6.1.3.2) + actionpack (= 6.1.3.2) + activerecord (= 6.1.3.2) + activestorage (= 6.1.3.2) + activesupport (= 6.1.3.2) nokogiri (>= 1.8.5) - actionview (6.0.3.7) - activesupport (= 6.0.3.7) + actionview (6.1.3.2) + activesupport (= 6.1.3.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.7) - activesupport (= 6.0.3.7) + activejob (6.1.3.2) + activesupport (= 6.1.3.2) globalid (>= 0.3.6) - activemodel (6.0.3.7) - activesupport (= 6.0.3.7) - activerecord (6.0.3.7) - activemodel (= 6.0.3.7) - activesupport (= 6.0.3.7) + activemodel (6.1.3.2) + activesupport (= 6.1.3.2) + activerecord (6.1.3.2) + activemodel (= 6.1.3.2) + activesupport (= 6.1.3.2) activerecord-explain-analyze (0.1.0) activerecord (>= 4) pg - activestorage (6.0.3.7) - actionpack (= 6.0.3.7) - activejob (= 6.0.3.7) - activerecord (= 6.0.3.7) + activestorage (6.1.3.2) + actionpack (= 6.1.3.2) + activejob (= 6.1.3.2) + activerecord (= 6.1.3.2) + activesupport (= 6.1.3.2) marcel (~> 1.0.0) - activesupport (6.0.3.7) + mini_mime (~> 1.0.2) + activesupport (6.1.3.2) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) acts-as-taggable-on (7.0.0) activerecord (>= 5.0, < 6.2) adamantium (0.2.0) @@ -744,7 +748,7 @@ GEM mime-types-data (3.2020.0512) mini_histogram (0.3.1) mini_magick (4.10.1) - mini_mime (1.1.0) + mini_mime (1.0.2) mini_portile2 (2.5.0) minitest (5.11.3) mixlib-cli (2.1.8) @@ -963,20 +967,20 @@ GEM rack-test (1.1.0) rack (>= 1.0, < 3) rack-timeout (0.5.2) - rails (6.0.3.7) - actioncable (= 6.0.3.7) - actionmailbox (= 6.0.3.7) - actionmailer (= 6.0.3.7) - actionpack (= 6.0.3.7) - actiontext (= 6.0.3.7) - actionview (= 6.0.3.7) - activejob (= 6.0.3.7) - activemodel (= 6.0.3.7) - activerecord (= 6.0.3.7) - activestorage (= 6.0.3.7) - activesupport (= 6.0.3.7) - bundler (>= 1.3.0) - railties (= 6.0.3.7) + rails (6.1.3.2) + actioncable (= 6.1.3.2) + actionmailbox (= 6.1.3.2) + actionmailer (= 6.1.3.2) + actionpack (= 6.1.3.2) + actiontext (= 6.1.3.2) + actionview (= 6.1.3.2) + activejob (= 6.1.3.2) + activemodel (= 6.1.3.2) + activerecord (= 6.1.3.2) + activestorage (= 6.1.3.2) + activesupport (= 6.1.3.2) + bundler (>= 1.15.0) + railties (= 6.1.3.2) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -990,12 +994,12 @@ GEM rails-i18n (6.0.0) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 7) - railties (6.0.3.7) - actionpack (= 6.0.3.7) - activesupport (= 6.0.3.7) + railties (6.1.3.2) + actionpack (= 6.1.3.2) + activesupport (= 6.1.3.2) method_source rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) + thor (~> 1.0) rainbow (3.0.0) raindrops (0.19.1) rake (13.0.3) @@ -1306,8 +1310,8 @@ GEM tty-screen (~> 0.8) wisper (~> 2.0) tty-screen (0.8.1) - tzinfo (1.2.9) - thread_safe (~> 0.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) u2f (0.2.1) uber (0.1.0) unf (0.1.4) @@ -1585,7 +1589,7 @@ DEPENDENCIES rack-oauth2 (~> 1.16.0) rack-proxy (~> 0.6.0) rack-timeout (~> 0.5.1) - rails (~> 6.0.3.7) + rails (~> 6.1.3.2) rails-controller-testing rails-i18n (~> 6.0) rainbow (~> 3.0) diff --git a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue index 8bcaa5df7b6..bec33ce2f44 100644 --- a/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue +++ b/app/assets/javascripts/runner/components/runner_filtered_search_bar.vue @@ -113,6 +113,7 @@ export default { this.$emit('input', { filters, sort, + pagination: { page: 1 }, }); }, onSort(sort) { @@ -121,6 +122,7 @@ export default { this.$emit('input', { filters, sort, + pagination: { page: 1 }, }); }, }, diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue index f58f271c9ee..39d354db46d 100644 --- a/app/assets/javascripts/runner/components/runner_list.vue +++ b/app/assets/javascripts/runner/components/runner_list.vue @@ -136,7 +136,5 @@ export default { <!-- TODO add actions to update runners --> </template> </gl-table> - - <!-- TODO implement pagination --> </div> </template> diff --git a/app/assets/javascripts/runner/components/runner_pagination.vue b/app/assets/javascripts/runner/components/runner_pagination.vue new file mode 100644 index 00000000000..8645b90f5cd --- /dev/null +++ b/app/assets/javascripts/runner/components/runner_pagination.vue @@ -0,0 +1,57 @@ +<script> +import { GlPagination } from '@gitlab/ui'; + +export default { + components: { + GlPagination, + }, + props: { + value: { + required: false, + type: Object, + default: () => ({ + page: 1, + }), + }, + pageInfo: { + required: false, + type: Object, + default: () => ({}), + }, + }, + computed: { + prevPage() { + return this.pageInfo?.hasPreviousPage ? this.value?.page - 1 : null; + }, + nextPage() { + return this.pageInfo?.hasNextPage ? this.value?.page + 1 : null; + }, + }, + methods: { + handlePageChange(page) { + if (page > this.value.page) { + this.$emit('input', { + page, + after: this.pageInfo.endCursor, + }); + } else { + this.$emit('input', { + page, + before: this.pageInfo.startCursor, + }); + } + }, + }, +}; +</script> + +<template> + <gl-pagination + :value="value.page" + :prev-page="prevPage" + :next-page="nextPage" + align="center" + class="gl-pagination gl-mt-3" + @input="handlePageChange" + /> +</template> diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js index 51dc0afdd0b..c51825a0846 100644 --- a/app/assets/javascripts/runner/constants.js +++ b/app/assets/javascripts/runner/constants.js @@ -1,5 +1,7 @@ import { s__ } from '~/locale'; +export const RUNNER_PAGE_SIZE = 20; + export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}'); export const RUNNER_ENTITY_TYPE = 'Ci::Runner'; @@ -11,6 +13,9 @@ export const RUNNER_ENTITY_TYPE = 'Ci::Runner'; export const PARAM_KEY_STATUS = 'status'; export const PARAM_KEY_RUNNER_TYPE = 'runner_type'; export const PARAM_KEY_SORT = 'sort'; +export const PARAM_KEY_PAGE = 'page'; +export const PARAM_KEY_AFTER = 'after'; +export const PARAM_KEY_BEFORE = 'before'; // CiRunnerType diff --git a/app/assets/javascripts/runner/graphql/get_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_runners.query.graphql index 1f094b72e79..3864dd5bf37 100644 --- a/app/assets/javascripts/runner/graphql/get_runners.query.graphql +++ b/app/assets/javascripts/runner/graphql/get_runners.query.graphql @@ -1,5 +1,23 @@ -query getRunners($status: CiRunnerStatus, $type: CiRunnerType, $sort: CiRunnerSort) { - runners(status: $status, type: $type, sort: $sort) { +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" + +query getRunners( + $before: String + $after: String + $first: Int + $last: Int + $status: CiRunnerStatus + $type: CiRunnerType + $sort: CiRunnerSort +) { + runners( + before: $before + after: $after + first: $first + last: $last + status: $status + type: $type + sort: $sort + ) { nodes { id description @@ -13,5 +31,8 @@ query getRunners($status: CiRunnerStatus, $type: CiRunnerType, $sort: CiRunnerSo tagList contactedAt } + pageInfo { + ...PageInfo + } } } diff --git a/app/assets/javascripts/runner/runner_list/filtered_search_utils.js b/app/assets/javascripts/runner/runner_list/filtered_search_utils.js index 4ae068c3eb6..2303dd587e1 100644 --- a/app/assets/javascripts/runner/runner_list/filtered_search_utils.js +++ b/app/assets/javascripts/runner/runner_list/filtered_search_utils.js @@ -3,7 +3,11 @@ import { PARAM_KEY_STATUS, PARAM_KEY_RUNNER_TYPE, PARAM_KEY_SORT, + PARAM_KEY_PAGE, + PARAM_KEY_AFTER, + PARAM_KEY_BEFORE, DEFAULT_SORT, + RUNNER_PAGE_SIZE, } from '../constants'; const getValuesFromFilters = (paramKey, filters) => { @@ -30,6 +34,23 @@ const getFilterFromParams = (paramKey, params) => { }); }; +const getPaginationFromParams = (params) => { + const page = parseInt(params[PARAM_KEY_PAGE], 10); + const after = params[PARAM_KEY_AFTER]; + const before = params[PARAM_KEY_BEFORE]; + + if (page && (before || after)) { + return { + page, + before, + after, + }; + } + return { + page: 1, + }; +}; + export const fromUrlQueryToSearch = (query = window.location.search) => { const params = queryToObject(query, { gatherArrays: true }); @@ -39,10 +60,14 @@ export const fromUrlQueryToSearch = (query = window.location.search) => { ...getFilterFromParams(PARAM_KEY_RUNNER_TYPE, params), ], sort: params[PARAM_KEY_SORT] || DEFAULT_SORT, + pagination: getPaginationFromParams(params), }; }; -export const fromSearchToUrl = ({ filters = [], sort = null }, url = window.location.href) => { +export const fromSearchToUrl = ( + { filters = [], sort = null, pagination = {} }, + url = window.location.href, +) => { const urlParams = { [PARAM_KEY_STATUS]: getValuesFromFilters(PARAM_KEY_STATUS, filters), [PARAM_KEY_RUNNER_TYPE]: getValuesFromFilters(PARAM_KEY_RUNNER_TYPE, filters), @@ -52,10 +77,21 @@ export const fromSearchToUrl = ({ filters = [], sort = null }, url = window.loca urlParams[PARAM_KEY_SORT] = sort; } + // Remove pagination params for first page + if (pagination?.page === 1) { + urlParams[PARAM_KEY_PAGE] = null; + urlParams[PARAM_KEY_BEFORE] = null; + urlParams[PARAM_KEY_AFTER] = null; + } else { + urlParams[PARAM_KEY_PAGE] = pagination.page; + urlParams[PARAM_KEY_BEFORE] = pagination.before; + urlParams[PARAM_KEY_AFTER] = pagination.after; + } + return setUrlParams(urlParams, url, false, true, true); }; -export const fromSearchToVariables = ({ filters = [], sort = null } = {}) => { +export const fromSearchToVariables = ({ filters = [], sort = null, pagination = {} } = {}) => { const variables = {}; // TODO Get more than one value when GraphQL API supports OR for "status" @@ -68,5 +104,13 @@ export const fromSearchToVariables = ({ filters = [], sort = null } = {}) => { variables.sort = sort; } + if (pagination.before) { + variables.before = pagination.before; + variables.last = RUNNER_PAGE_SIZE; + } else { + variables.after = pagination.after; + variables.first = RUNNER_PAGE_SIZE; + } + return variables; }; diff --git a/app/assets/javascripts/runner/runner_list/runner_list_app.vue b/app/assets/javascripts/runner/runner_list/runner_list_app.vue index e0f3330fef5..93d1cf38b9b 100644 --- a/app/assets/javascripts/runner/runner_list/runner_list_app.vue +++ b/app/assets/javascripts/runner/runner_list/runner_list_app.vue @@ -4,6 +4,7 @@ import { updateHistory } from '~/lib/utils/url_utility'; import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue'; import RunnerList from '../components/runner_list.vue'; import RunnerManualSetupHelp from '../components/runner_manual_setup_help.vue'; +import RunnerPagination from '../components/runner_pagination.vue'; import RunnerTypeHelp from '../components/runner_type_help.vue'; import getRunnersQuery from '../graphql/get_runners.query.graphql'; import { @@ -18,6 +19,7 @@ export default { RunnerList, RunnerManualSetupHelp, RunnerTypeHelp, + RunnerPagination, }, props: { activeRunnersCount: { @@ -32,7 +34,10 @@ export default { data() { return { search: fromUrlQueryToSearch(), - runners: [], + runners: { + items: [], + pageInfo: {}, + }, }; }, apollo: { @@ -41,8 +46,12 @@ export default { variables() { return this.variables; }, - update({ runners }) { - return runners?.nodes || []; + update(data) { + const { runners } = data; + return { + items: runners?.nodes || [], + pageInfo: runners?.pageInfo || {}, + }; }, error(err) { this.captureException(err); @@ -57,17 +66,19 @@ export default { return this.$apollo.queries.runners.loading; }, noRunnersFound() { - return !this.runnersLoading && !this.runners.length; + return !this.runnersLoading && !this.runners.items.length; }, }, watch: { - search() { - // TODO Implement back button reponse using onpopstate - - updateHistory({ - url: fromSearchToUrl(this.search), - title: document.title, - }); + search: { + deep: true, + handler() { + // TODO Implement back button reponse using onpopstate + updateHistory({ + url: fromSearchToUrl(this.search), + title: document.title, + }); + }, }, }, errorCaptured(err) { @@ -99,11 +110,13 @@ export default { <div v-if="noRunnersFound" class="gl-text-center gl-p-5"> {{ __('No runners found') }} </div> - <runner-list - v-else - :runners="runners" - :loading="runnersLoading" - :active-runners-count="activeRunnersCount" - /> + <template v-else> + <runner-list + :runners="runners.items" + :loading="runnersLoading" + :active-runners-count="activeRunnersCount" + /> + <runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" /> + </template> </div> </template> diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index c2f7fa2074c..0092743f96e 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -102,7 +102,7 @@ module DropdownsHelper def dropdown_filter(placeholder, search_id: nil) content_tag :div, class: "dropdown-input" do - filter_output = search_field_tag search_id, nil, data: { qa_selector: "dropdown_input_field" }, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off' + filter_output = search_field_tag search_id, nil, data: { qa_selector: "dropdown_input_field" }, id: nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off' filter_output << sprite_icon('search', css_class: 'dropdown-input-search') filter_output << sprite_icon('close', size: 16, css_class: 'dropdown-input-clear js-dropdown-input-clear') diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 1d3242ca44a..e64e1c935dd 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -123,7 +123,21 @@ module TabHelper def route_matches_pages?(pages) Array(pages).compact.any? do |single_page| - current_page?(single_page) + # We need to distinguish between Hash argument and other types of + # arguments (for example String) in order to fix deprecation kwargs + # warning. + # + # This can be refactored to + # + # current_page?(single_page) + # + # When we migrate to Ruby 3 or the Rails version contains the following: + # https://github.com/rails/rails/commit/81d90d81d0ee1fc1a649ab705119a71f2d04c8a2 + if single_page.is_a?(Hash) + current_page?(**single_page) + else + current_page?(single_page) + end end end diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb index 719511bbb8a..aaeff8bdd14 100644 --- a/app/models/ci/build_trace_chunk.rb +++ b/app/models/ci/build_trace_chunk.rb @@ -85,16 +85,10 @@ module Ci # change the behavior in CE. # def with_read_consistency(build, &block) - return yield unless consistent_reads_enabled?(build) - ::Gitlab::Database::Consistency .with_read_consistency(&block) end - def consistent_reads_enabled?(build) - Feature.enabled?(:gitlab_ci_trace_read_consistency, build.project, type: :development, default_enabled: true) - end - ## # Sometimes we do not want to read raw data. This method makes it easier # to find attributes that are just metadata excluding raw data. diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb index 125783e6ee1..162a1a3290d 100644 --- a/app/models/clusters/clusters_hierarchy.rb +++ b/app/models/clusters/clusters_hierarchy.rb @@ -4,9 +4,8 @@ module Clusters class ClustersHierarchy DEPTH_COLUMN = :depth - def initialize(clusterable, include_management_project: true) + def initialize(clusterable) @clusterable = clusterable - @include_management_project = include_management_project end # Returns clusters in order from deepest to highest group @@ -25,7 +24,7 @@ module Clusters private - attr_reader :clusterable, :include_management_project + attr_reader :clusterable def recursive_cte cte = Gitlab::SQL::RecursiveCTE.new(:clusters_cte) @@ -39,7 +38,7 @@ module Clusters raise ArgumentError, "unknown type for #{clusterable}" end - if clusterable.is_a?(::Project) && include_management_project + if clusterable.is_a?(::Project) cte << same_namespace_management_clusters_query end @@ -71,7 +70,7 @@ module Clusters # Only applicable if the clusterable is a project (most especially when # requesting project.deployment_platform). def depth_order_clause - return { DEPTH_COLUMN => :asc } unless clusterable.is_a?(::Project) && include_management_project + return { DEPTH_COLUMN => :asc } unless clusterable.is_a?(::Project) order = <<~SQL (CASE clusters.management_project_id diff --git a/app/models/concerns/bulk_insert_safe.rb b/app/models/concerns/bulk_insert_safe.rb index 3748e77e933..908f0b6a7e2 100644 --- a/app/models/concerns/bulk_insert_safe.rb +++ b/app/models/concerns/bulk_insert_safe.rb @@ -141,6 +141,12 @@ module BulkInsertSafe raise ArgumentError, "returns needs to be :ids or nil" end + # Handle insertions for tables with a composite primary key + primary_keys = connection.schema_cache.primary_keys(table_name) + if unique_by.blank? && primary_key != primary_keys + unique_by = primary_keys + end + transaction do items.each_slice(batch_size).flat_map do |item_batch| attributes = _bulk_insert_item_attributes( diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb index 02f7711e927..b6245e29746 100644 --- a/app/models/concerns/deployment_platform.rb +++ b/app/models/concerns/deployment_platform.rb @@ -10,10 +10,6 @@ module DeploymentPlatform private - def cluster_management_project_enabled? - Feature.enabled?(:cluster_management_project, self, default_enabled: true) - end - def find_deployment_platform(environment) find_platform_kubernetes_with_cte(environment) || find_instance_cluster_platform_kubernetes(environment: environment) @@ -21,13 +17,13 @@ module DeploymentPlatform def find_platform_kubernetes_with_cte(environment) if environment - ::Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?) + ::Clusters::ClustersHierarchy.new(self) .base_and_ancestors .enabled .on_environment(environment, relevant_only: true) .first&.platform_kubernetes else - Clusters::ClustersHierarchy.new(self, include_management_project: cluster_management_project_enabled?).base_and_ancestors + Clusters::ClustersHierarchy.new(self).base_and_ancestors .enabled.default_environment .first&.platform_kubernetes end diff --git a/app/models/concerns/enum_with_nil.rb b/app/models/concerns/enum_with_nil.rb index 6d0a21cf070..c66942025d7 100644 --- a/app/models/concerns/enum_with_nil.rb +++ b/app/models/concerns/enum_with_nil.rb @@ -11,14 +11,6 @@ module EnumWithNil # override auto-defined methods only for the # key which uses nil value definitions.each do |name, values| - next unless key_with_nil = values.key(nil) - - # E.g. for enum_with_nil failure_reason: { unknown_failure: nil } - # this overrides auto-generated method `unknown_failure?` - define_method("#{key_with_nil}?") do - self[name].nil? - end - # E.g. for enum_with_nil failure_reason: { unknown_failure: nil } # this overrides auto-generated method `failure_reason` define_method(name) do diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb index 1fed166e4d0..64e768007ee 100644 --- a/app/models/project_authorization.rb +++ b/app/models/project_authorization.rb @@ -29,6 +29,15 @@ class ProjectAuthorization < ApplicationRecord EOF end end + + # This method overrides its ActiveRecord's version in order to work correctly + # with composite primary keys and fix the tests for Rails 6.1 + # + # Consider using BulkInsertSafe module instead since we plan to refactor it in + # https://gitlab.com/gitlab-org/gitlab/-/issues/331264 + def self.insert_all(attributes) + super(attributes, unique_by: connection.schema_cache.primary_keys(table_name)) + end end ProjectAuthorization.prepend_mod_with('ProjectAuthorization') diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml index 7f508fd0a59..c84b3a923ca 100644 --- a/app/views/clusters/clusters/_advanced_settings.html.haml +++ b/app/views/clusters/clusters/_advanced_settings.html.haml @@ -16,7 +16,7 @@ .sub-section.form-group = form_for @cluster, url: clusterable.cluster_path(@cluster), as: :cluster, html: { class: 'cluster_management_form' } do |field| %h4 - = s_('ClusterIntegration|Cluster management project (alpha)') + = s_('ClusterIntegration|Cluster management project') %p = project_select_tag('cluster[management_project_id]', class: 'hidden-filter-value', toggle_class: 'js-project-search js-project-filter js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', diff --git a/app/views/projects/merge_requests/tabs/_tab.html.haml b/app/views/projects/merge_requests/tabs/_tab.html.haml index dcd8db90509..9d942da8098 100644 --- a/app/views/projects/merge_requests/tabs/_tab.html.haml +++ b/app/views/projects/merge_requests/tabs/_tab.html.haml @@ -2,6 +2,8 @@ - tab_class = local_assigns.fetch(:class, nil) - qa_selector = local_assigns.fetch(:qa_selector, nil) - id = local_assigns.fetch(:id, nil) +- attrs = { class: [tab_class, ("active" if params[:tab] == tab_name)], data: { qa_selector: qa_selector } } +- attrs[:id] = id if id.present? -%li{ class: [tab_class, ("active" if params[:tab] == tab_name)], id: id, data: { qa_selector: qa_selector } } +%li{ attrs } = yield diff --git a/cable/config.ru b/cable/config.ru index c50bc41511d..421aee38346 100644 --- a/cable/config.ru +++ b/cable/config.ru @@ -5,6 +5,6 @@ Rails.application.eager_load! ACTION_CABLE_SERVER = true -use ActionDispatch::RequestId +use ActionDispatch::RequestId, header: Rails.application.config.action_dispatch.request_id_header run ActionCable.server diff --git a/config/feature_flags/development/cluster_management_project.yml b/config/feature_flags/development/cluster_management_project.yml deleted file mode 100644 index bcce2e027ff..00000000000 --- a/config/feature_flags/development/cluster_management_project.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: cluster_management_project -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17866 -rollout_issue_url: -milestone: '12.4' -type: development -group: group::configure -default_enabled: true diff --git a/config/feature_flags/development/gitlab_ci_trace_read_consistency.yml b/config/feature_flags/development/gitlab_ci_trace_read_consistency.yml deleted file mode 100644 index ee0b4e46924..00000000000 --- a/config/feature_flags/development/gitlab_ci_trace_read_consistency.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: gitlab_ci_trace_read_consistency -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46976 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/320938 -milestone: '13.9' -type: development -group: group::continuous integration -default_enabled: true diff --git a/config/initializers/labkit_middleware.rb b/config/initializers/labkit_middleware.rb index 748666b6cd7..771adabfeeb 100644 --- a/config/initializers/labkit_middleware.rb +++ b/config/initializers/labkit_middleware.rb @@ -3,22 +3,6 @@ # partial backport of https://github.com/rails/rails/pull/38169 # this is in order to be able to re-order rack middlewares. -if ActionDispatch::MiddlewareStack.method_defined?(:move) - warn "`move` is now defined in in ActionDispatch itself: https://github.com/rails/rails/pull/38169, please remove this patch from #{__FILE__}" -else - module ActionDispatch - class MiddlewareStack - def move(target, source) - source_index = assert_index(source, :before) - source_middleware = middlewares.delete_at(source_index) - - target_index = assert_index(target, :before) - middlewares.insert(target_index, source_middleware) - end - end - end -end - unless Rails::Configuration::MiddlewareStackProxy.method_defined?(:move) module Rails module Configuration diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index a7f4fc10655..52aa0f5ab4e 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -147,6 +147,17 @@ recorded: Instance events can also be accessed via the [Instance Audit Events API](../api/audit_events.md#instance-audit-events). +### Sign-in events **(FREE)** + +Successful sign-in events are the only Audit Events available at all tiers. To see +successful sign-in events: + +1. Select your avatar. +1. Select **Edit profile > Authentication log**. + +After upgrading from GitLab Free to a paid tier, successful sign-in events are the only Audit +Events visible in Audit Events views until more events are logged. + ### Missing events Some events are not tracked in Audit Events. See the following diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md index e46a010222f..7badfa12e3d 100644 --- a/doc/administration/repository_storage_paths.md +++ b/doc/administration/repository_storage_paths.md @@ -154,5 +154,5 @@ often it is chosen. That is, `(storage weight) / (sum of all weights) * 100 = ch ## Move repositories -To move a repository to a different repository path, use the same process as -[migrating to Gitaly Cluster](gitaly/praefect.md#migrate-to-gitaly-cluster). +To move a repository to a different repository storage (for example, from `default` to `storage2`), use the +same process as [migrating to Gitaly Cluster](gitaly/praefect.md#migrate-to-gitaly-cluster). diff --git a/doc/api/projects.md b/doc/api/projects.md index 5b96bcb22e2..0f3d9cdd334 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -2597,3 +2597,28 @@ GET /projects/:id/snapshot |-----------|----------------|------------------------|-------------| | `id` | integer/string | **{check-circle}** Yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | | `wiki` | boolean | **{dotted-circle}** No | Whether to download the wiki, rather than project, repository. | + +## Get the path to repository storage + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/29861) in GitLab 14.0. + +Get the path to repository storage for specified project. Available for administrators only. + +```plaintext +GET /projects/:id/storage +``` + +| Attribute | Type | Required | Description | +|--------------|----------------|------------------------|-------------| +| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). | + +```json +[ + { + "project_id": 1, + "disk_path": "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b", + "created_at": "2012-10-12T17:04:47Z", + "repository_storage": "default" + } +] +``` diff --git a/doc/topics/git/bisect.md b/doc/topics/git/bisect.md index 8af77031c93..e587a51ba17 100644 --- a/doc/topics/git/bisect.md +++ b/doc/topics/git/bisect.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Bisect +# Bisect **(FREE)** - Find a commit that introduced a bug - Works through a process of elimination diff --git a/doc/topics/git/cherry_picking.md b/doc/topics/git/cherry_picking.md index 5a0867371bb..4a875e25e1b 100644 --- a/doc/topics/git/cherry_picking.md +++ b/doc/topics/git/cherry_picking.md @@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w comments: false --- -# Cherry Pick +# Cherry pick **(FREE)** Given an existing commit on one branch, apply the change to another branch. diff --git a/doc/topics/git/feature_branching.md b/doc/topics/git/feature_branching.md index f6233bddb18..f0ded5511ee 100644 --- a/doc/topics/git/feature_branching.md +++ b/doc/topics/git/feature_branching.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Feature branching +# Feature branching **(FREE)** - Efficient parallel workflow for teams - Develop each feature in a branch diff --git a/doc/topics/git/getting_started.md b/doc/topics/git/getting_started.md index 2c3d5fe15de..7e04eae622f 100644 --- a/doc/topics/git/getting_started.md +++ b/doc/topics/git/getting_started.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Getting Started +# Getting started **(FREE)** ## Instantiating Repositories diff --git a/doc/topics/git/git_add.md b/doc/topics/git/git_add.md index d136b9151bc..e15a1e9a60d 100644 --- a/doc/topics/git/git_add.md +++ b/doc/topics/git/git_add.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Git Add +# Git Add **(FREE)** Adds content to the index or staging area. diff --git a/doc/topics/git/git_log.md b/doc/topics/git/git_log.md index ae4ae69ce76..3988d7f7ac9 100644 --- a/doc/topics/git/git_log.md +++ b/doc/topics/git/git_log.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Git Log +# Git Log **(FREE)** Git log lists commit history. It allows searching and filtering. diff --git a/doc/topics/git/merge_conflicts.md b/doc/topics/git/merge_conflicts.md index 66771559298..bf69190030c 100644 --- a/doc/topics/git/merge_conflicts.md +++ b/doc/topics/git/merge_conflicts.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Merge conflicts +# Merge conflicts **(FREE)** - Happen often - Learning to fix conflicts is hard diff --git a/doc/topics/git/rollback_commits.md b/doc/topics/git/rollback_commits.md index 34c2d9687bb..478dce179d2 100644 --- a/doc/topics/git/rollback_commits.md +++ b/doc/topics/git/rollback_commits.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Rollback Commits +# Rollback commits **(FREE)** ## Undo Commits diff --git a/doc/topics/git/stash.md b/doc/topics/git/stash.md index 051103e5f4b..d321795e034 100644 --- a/doc/topics/git/stash.md +++ b/doc/topics/git/stash.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Git Stash +# Git Stash **(FREE)** We use `git stash` to store our changes when they are not ready to be committed and we need to change to a different branch. diff --git a/doc/topics/git/subtree.md b/doc/topics/git/subtree.md index 54461915a05..0bf89668405 100644 --- a/doc/topics/git/subtree.md +++ b/doc/topics/git/subtree.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Subtree +# Subtree **(FREE)** - Used when there are nested repositories. - Not recommended when the amount of dependencies is too large. diff --git a/doc/topics/git/tags.md b/doc/topics/git/tags.md index 70580ecf778..6e0622273bb 100644 --- a/doc/topics/git/tags.md +++ b/doc/topics/git/tags.md @@ -4,7 +4,7 @@ group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# Tags +# Tags **(FREE)** Tags are useful for marking certain deployments and releases for later reference. Git supports two types of tags: diff --git a/doc/topics/git/unstage.md b/doc/topics/git/unstage.md index 30d26854135..b5f7c01de24 100644 --- a/doc/topics/git/unstage.md +++ b/doc/topics/git/unstage.md @@ -1,11 +1,11 @@ --- -stage: none -group: unassigned +stage: Create +group: Source Code info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments comments: false --- -# Unstage +# Unstage **(FREE)** - To remove files from stage use reset HEAD where HEAD is the last commit of the current branch. This unstages the file but maintain the modifications. diff --git a/doc/user/clusters/management_project.md b/doc/user/clusters/management_project.md index e728577e194..0a24bca67a5 100644 --- a/doc/user/clusters/management_project.md +++ b/doc/user/clusters/management_project.md @@ -6,10 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Cluster management project **(FREE)** -WARNING: -This is an _alpha_ feature, and it is subject to change at any time without -prior notice. - > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32810) in GitLab 12.5 A project can be designated as the management project for a cluster. diff --git a/lib/api/entities/project_repository_storage.rb b/lib/api/entities/project_repository_storage.rb new file mode 100644 index 00000000000..0816bebde2c --- /dev/null +++ b/lib/api/entities/project_repository_storage.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + class ProjectRepositoryStorage < Grape::Entity + include Gitlab::Routing + + expose :disk_path do |project| + project.repository.disk_path + end + + expose :id, as: :project_id + expose :repository_storage, :created_at + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 4e8786fbe1f..bf55618768c 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -660,6 +660,18 @@ module API render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400) end end + + desc 'Show the storage information' do + success Entities::ProjectRepositoryStorage + end + params do + requires :id, type: String, desc: 'ID of a project' + end + get ':id/storage', feature_category: :projects do + authenticated_as_admin! + + present user_project, with: Entities::ProjectRepositoryStorage, current_user: current_user + end end end end diff --git a/lib/gitlab/ci/trace/chunked_io.rb b/lib/gitlab/ci/trace/chunked_io.rb index 7c2e39b1e53..9f24ba99201 100644 --- a/lib/gitlab/ci/trace/chunked_io.rb +++ b/lib/gitlab/ci/trace/chunked_io.rb @@ -229,13 +229,8 @@ module Gitlab def next_chunk @chunks_cache[chunk_index] = begin - if ::Ci::BuildTraceChunk.consistent_reads_enabled?(build) - ::Ci::BuildTraceChunk - .safe_find_or_create_by(build: build, chunk_index: chunk_index) - else - ::Ci::BuildTraceChunk - .new(build: build, chunk_index: chunk_index) - end + ::Ci::BuildTraceChunk + .safe_find_or_create_by(build: build, chunk_index: chunk_index) end end diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 3583338c58a..1dba3811519 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -61,7 +61,7 @@ module Gitlab end def self.config - default_config_hash = ActiveRecord::Base.configurations.find_db_config(Rails.env)&.config || {} + default_config_hash = ActiveRecord::Base.configurations.find_db_config(Rails.env)&.configuration_hash || {} default_config_hash.with_indifferent_access.tap do |hash| # Match config/initializers/database_config.rb diff --git a/lib/gitlab/metrics/samplers/database_sampler.rb b/lib/gitlab/metrics/samplers/database_sampler.rb index 0a0ac6c5386..5d7f434b660 100644 --- a/lib/gitlab/metrics/samplers/database_sampler.rb +++ b/lib/gitlab/metrics/samplers/database_sampler.rb @@ -45,8 +45,8 @@ module Gitlab def labels_for_class(klass) { - host: klass.connection_config[:host], - port: klass.connection_config[:port], + host: klass.connection_db_config.host, + port: klass.connection_db_config.configuration_hash[:port], class: klass.to_s } end diff --git a/lib/release_highlights/validator/entry.rb b/lib/release_highlights/validator/entry.rb index 133afcb52ae..dff55eead2f 100644 --- a/lib/release_highlights/validator/entry.rb +++ b/lib/release_highlights/validator/entry.rb @@ -46,7 +46,10 @@ module ReleaseHighlights def add_line_numbers_to_errors! errors.messages.each do |attribute, messages| - messages.map! { |m| "#{m} (line #{line_number_for(attribute)})" } + extended_messages = messages.map { |m| "#{m} (line #{line_number_for(attribute)})" } + + errors.delete(attribute) + extended_messages.each { |extended_message| errors.add(attribute, extended_message) } end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c244c486c5d..1bd78281a6c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7075,7 +7075,7 @@ msgstr "" msgid "ClusterIntegration|Cluster Region" msgstr "" -msgid "ClusterIntegration|Cluster management project (alpha)" +msgid "ClusterIntegration|Cluster management project" msgstr "" msgid "ClusterIntegration|Cluster name is required." diff --git a/package.json b/package.json index 067a9745026..a0a136d75dc 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "@gitlab/tributejs": "1.0.0", "@gitlab/ui": "29.31.0", "@gitlab/visual-review-tools": "1.6.1", - "@rails/actioncable": "^6.0.3-4", - "@rails/ujs": "^6.0.3-4", + "@rails/actioncable": "6.1.3-2", + "@rails/ujs": "6.1.3-2", "@sentry/browser": "^5.22.3", "@sourcegraph/code-host-integration": "0.0.57", "@tiptap/core": "^2.0.0-beta.54", diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb index 635313b9fb0..9131cad7244 100644 --- a/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb @@ -31,7 +31,13 @@ module QA DOCKER_TLS_VERIFY: 1 DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" before_script: - - until docker info; do sleep 1; done + - | + echo "Waiting for docker to start..." + for i in $(seq 1 30) + do + docker info && break + sleep 1s + done script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $IMAGE_TAG . diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 0235d7eb95a..218aa04dd3f 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -704,7 +704,7 @@ RSpec.describe ApplicationController do get :index - expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store' + expect(response.headers['Cache-Control']).to eq 'no-store' expect(response.headers['Pragma']).to eq 'no-cache' end @@ -740,7 +740,7 @@ RSpec.describe ApplicationController do it 'sets no-cache headers', :aggregate_failures do subject - expect(response.headers['Cache-Control']).to eq 'no-cache, no-store' + expect(response.headers['Cache-Control']).to eq 'no-store' expect(response.headers['Pragma']).to eq 'no-cache' expect(response.headers['Expires']).to eq 'Fri, 01 Jan 1990 00:00:00 GMT' end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 32ac83847aa..3a2986f6cbe 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -258,7 +258,7 @@ RSpec.describe SearchController do expect(response).to have_gitlab_http_status(:ok) - expect(response.headers['Cache-Control']).to include('max-age=60, private') + expect(response.headers['Cache-Control']).to eq('no-store') end end diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index e0e2a012c81..325d675a68c 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -269,7 +269,7 @@ RSpec.describe 'Database schema' do sql = <<~SQL SELECT table_name, column_name, data_type FROM information_schema.columns - WHERE table_catalog = '#{ApplicationRecord.connection_config[:database]}' + WHERE table_catalog = '#{ApplicationRecord.connection_db_config.database}' AND table_schema = 'public' AND table_name NOT LIKE 'pg_%' AND data_type = 'jsonb' diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb index bfc924b5d9b..9d8f9872a1a 100644 --- a/spec/features/projects/badges/pipeline_badge_spec.rb +++ b/spec/features/projects/badges/pipeline_badge_spec.rb @@ -68,7 +68,7 @@ RSpec.describe 'Pipeline Badge' do visit pipeline_project_badges_path(project, ref: ref, format: :svg) expect(page.status_code).to eq(200) - expect(page.response_headers['Cache-Control']).to include 'no-cache' + expect(page.response_headers['Cache-Control']).to eq('no-store') end end diff --git a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js index aa1752d187f..61a8f821b30 100644 --- a/spec/frontend/runner/components/runner_filtered_search_bar_spec.js +++ b/spec/frontend/runner/components/runner_filtered_search_bar_spec.js @@ -118,6 +118,7 @@ describe('RunnerList', () => { { filters: mockFilters, sort: mockDefaultSort, + pagination: { page: 1 }, }, ]); }); @@ -129,6 +130,7 @@ describe('RunnerList', () => { { filters: [], sort: mockOtherSort, + pagination: { page: 1 }, }, ]); }); diff --git a/spec/frontend/runner/components/runner_pagination_spec.js b/spec/frontend/runner/components/runner_pagination_spec.js new file mode 100644 index 00000000000..59feb32dd2a --- /dev/null +++ b/spec/frontend/runner/components/runner_pagination_spec.js @@ -0,0 +1,160 @@ +import { GlPagination } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import RunnerPagination from '~/runner/components/runner_pagination.vue'; + +const mockStartCursor = 'START_CURSOR'; +const mockEndCursor = 'END_CURSOR'; + +describe('RunnerPagination', () => { + let wrapper; + + const findPagination = () => wrapper.findComponent(GlPagination); + + const createComponent = ({ page = 1, hasPreviousPage = false, hasNextPage = true } = {}) => { + wrapper = mount(RunnerPagination, { + propsData: { + value: { + page, + }, + pageInfo: { + hasPreviousPage, + hasNextPage, + startCursor: mockStartCursor, + endCursor: mockEndCursor, + }, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('When on the first page', () => { + beforeEach(() => { + createComponent({ + page: 1, + hasPreviousPage: false, + hasNextPage: true, + }); + }); + + it('Contains the current page information', () => { + expect(findPagination().props('value')).toBe(1); + expect(findPagination().props('prevPage')).toBe(null); + expect(findPagination().props('nextPage')).toBe(2); + }); + + it('Shows prev page disabled', () => { + expect(findPagination().find('[aria-disabled]').text()).toBe('Prev'); + }); + + it('Shows next page link', () => { + expect(findPagination().find('a').text()).toBe('Next'); + }); + + it('Goes to the second page', () => { + findPagination().vm.$emit('input', 2); + + expect(wrapper.emitted('input')[0]).toEqual([ + { + after: mockEndCursor, + page: 2, + }, + ]); + }); + }); + + describe('When in between pages', () => { + beforeEach(() => { + createComponent({ + page: 2, + hasPreviousPage: true, + hasNextPage: true, + }); + }); + + it('Contains the current page information', () => { + expect(findPagination().props('value')).toBe(2); + expect(findPagination().props('prevPage')).toBe(1); + expect(findPagination().props('nextPage')).toBe(3); + }); + + it('Shows the next and previous pages', () => { + const links = findPagination().findAll('a'); + + expect(links).toHaveLength(2); + expect(links.at(0).text()).toBe('Prev'); + expect(links.at(1).text()).toBe('Next'); + }); + + it('Goes to the last page', () => { + findPagination().vm.$emit('input', 3); + + expect(wrapper.emitted('input')[0]).toEqual([ + { + after: mockEndCursor, + page: 3, + }, + ]); + }); + + it('Goes to the first page', () => { + findPagination().vm.$emit('input', 1); + + expect(wrapper.emitted('input')[0]).toEqual([ + { + before: mockStartCursor, + page: 1, + }, + ]); + }); + }); + + describe('When in the last page', () => { + beforeEach(() => { + createComponent({ + page: 3, + hasPreviousPage: true, + hasNextPage: false, + }); + }); + + it('Contains the current page', () => { + expect(findPagination().props('value')).toBe(3); + expect(findPagination().props('prevPage')).toBe(2); + expect(findPagination().props('nextPage')).toBe(null); + }); + + it('Shows next page link', () => { + expect(findPagination().find('a').text()).toBe('Prev'); + }); + + it('Shows next page disabled', () => { + expect(findPagination().find('[aria-disabled]').text()).toBe('Next'); + }); + }); + + describe('When only one page', () => { + beforeEach(() => { + createComponent({ + page: 1, + hasPreviousPage: false, + hasNextPage: false, + }); + }); + + it('does not display pagination', () => { + expect(wrapper.html()).toBe(''); + }); + + it('Contains the current page', () => { + expect(findPagination().props('value')).toBe(1); + }); + + it('Shows no more page buttons', () => { + expect(findPagination().props('prevPage')).toBe(null); + expect(findPagination().props('nextPage')).toBe(null); + }); + }); +}); diff --git a/spec/frontend/runner/mock_data.js b/spec/frontend/runner/mock_data.js index 3c1b3a0b5e2..744942bfa73 100644 --- a/spec/frontend/runner/mock_data.js +++ b/spec/frontend/runner/mock_data.js @@ -31,6 +31,13 @@ export const runnersData = { __typename: 'CiRunner', }, ], + pageInfo: { + endCursor: 'GRAPHQL_END_CURSOR', + startCursor: 'GRAPHQL_START_CURSOR', + hasNextPage: true, + hasPreviousPage: false, + __typename: 'PageInfo', + }, __typename: 'CiRunnerConnection', }, }, diff --git a/spec/frontend/runner/runner_list/filtered_search_utils_spec.js b/spec/frontend/runner/runner_list/filtered_search_utils_spec.js index e46821d6504..abbe05452d1 100644 --- a/spec/frontend/runner/runner_list/filtered_search_utils_spec.js +++ b/spec/frontend/runner/runner_list/filtered_search_utils_spec.js @@ -1,3 +1,4 @@ +import { RUNNER_PAGE_SIZE } from '~/runner/constants'; import { fromUrlQueryToSearch, fromSearchToUrl, @@ -9,26 +10,28 @@ describe('search_params.js', () => { { name: 'a default query', urlQuery: '', - search: { filters: [], sort: 'CREATED_DESC' }, - graphqlVariables: { sort: 'CREATED_DESC' }, + search: { filters: [], pagination: { page: 1 }, sort: 'CREATED_DESC' }, + graphqlVariables: { sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'a single status', urlQuery: '?status[]=ACTIVE', search: { filters: [{ type: 'status', value: { data: 'ACTIVE', operator: '=' } }], + pagination: { page: 1 }, sort: 'CREATED_DESC', }, - graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC' }, + graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'single instance type', urlQuery: '?runner_type[]=INSTANCE_TYPE', search: { filters: [{ type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } }], + pagination: { page: 1 }, sort: 'CREATED_DESC', }, - graphqlVariables: { type: 'INSTANCE_TYPE', sort: 'CREATED_DESC' }, + graphqlVariables: { type: 'INSTANCE_TYPE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'multiple runner status', @@ -38,9 +41,10 @@ describe('search_params.js', () => { { type: 'status', value: { data: 'ACTIVE', operator: '=' } }, { type: 'status', value: { data: 'PAUSED', operator: '=' } }, ], + pagination: { page: 1 }, sort: 'CREATED_DESC', }, - graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC' }, + graphqlVariables: { status: 'ACTIVE', sort: 'CREATED_DESC', first: RUNNER_PAGE_SIZE }, }, { name: 'multiple status, a single instance type and a non default sort', @@ -50,9 +54,52 @@ describe('search_params.js', () => { { type: 'status', value: { data: 'ACTIVE', operator: '=' } }, { type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } }, ], + pagination: { page: 1 }, sort: 'CREATED_ASC', }, - graphqlVariables: { status: 'ACTIVE', type: 'INSTANCE_TYPE', sort: 'CREATED_ASC' }, + graphqlVariables: { + status: 'ACTIVE', + type: 'INSTANCE_TYPE', + sort: 'CREATED_ASC', + first: RUNNER_PAGE_SIZE, + }, + }, + { + name: 'the next page', + urlQuery: '?page=2&after=AFTER_CURSOR', + search: { filters: [], pagination: { page: 2, after: 'AFTER_CURSOR' }, sort: 'CREATED_DESC' }, + graphqlVariables: { sort: 'CREATED_DESC', after: 'AFTER_CURSOR', first: RUNNER_PAGE_SIZE }, + }, + { + name: 'the previous page', + urlQuery: '?page=2&before=BEFORE_CURSOR', + search: { + filters: [], + pagination: { page: 2, before: 'BEFORE_CURSOR' }, + sort: 'CREATED_DESC', + }, + graphqlVariables: { sort: 'CREATED_DESC', before: 'BEFORE_CURSOR', last: RUNNER_PAGE_SIZE }, + }, + { + name: + 'the next page filtered by multiple status, a single instance type and a non default sort', + urlQuery: + '?status[]=ACTIVE&runner_type[]=INSTANCE_TYPE&sort=CREATED_ASC&page=2&after=AFTER_CURSOR', + search: { + filters: [ + { type: 'status', value: { data: 'ACTIVE', operator: '=' } }, + { type: 'runner_type', value: { data: 'INSTANCE_TYPE', operator: '=' } }, + ], + pagination: { page: 2, after: 'AFTER_CURSOR' }, + sort: 'CREATED_ASC', + }, + graphqlVariables: { + status: 'ACTIVE', + type: 'INSTANCE_TYPE', + sort: 'CREATED_ASC', + after: 'AFTER_CURSOR', + first: RUNNER_PAGE_SIZE, + }, }, ]; @@ -62,6 +109,24 @@ describe('search_params.js', () => { expect(fromUrlQueryToSearch(urlQuery)).toEqual(search); }); }); + + it('When a page cannot be parsed as a number, it defaults to `1`', () => { + expect(fromUrlQueryToSearch('?page=NONSENSE&after=AFTER_CURSOR').pagination).toEqual({ + page: 1, + }); + }); + + it('When a page is less than 1, it defaults to `1`', () => { + expect(fromUrlQueryToSearch('?page=0&after=AFTER_CURSOR').pagination).toEqual({ + page: 1, + }); + }); + + it('When a page with no cursor is given, it defaults to `1`', () => { + expect(fromUrlQueryToSearch('?page=2').pagination).toEqual({ + page: 1, + }); + }); }); describe('fromSearchToUrl', () => { diff --git a/spec/frontend/runner/runner_list/runner_list_app_spec.js b/spec/frontend/runner/runner_list/runner_list_app_spec.js index 19a5a60d2c1..e908e62db4f 100644 --- a/spec/frontend/runner/runner_list/runner_list_app_spec.js +++ b/spec/frontend/runner/runner_list/runner_list_app_spec.js @@ -1,5 +1,5 @@ import * as Sentry from '@sentry/browser'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { createLocalVue, mount, shallowMount } from '@vue/test-utils'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import { TEST_HOST } from 'helpers/test_constants'; @@ -9,14 +9,17 @@ import { updateHistory } from '~/lib/utils/url_utility'; import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue'; import RunnerList from '~/runner/components/runner_list.vue'; import RunnerManualSetupHelp from '~/runner/components/runner_manual_setup_help.vue'; +import RunnerPagination from '~/runner/components/runner_pagination.vue'; import RunnerTypeHelp from '~/runner/components/runner_type_help.vue'; import { CREATED_ASC, + CREATED_DESC, DEFAULT_SORT, INSTANCE_TYPE, PARAM_KEY_STATUS, STATUS_ACTIVE, + RUNNER_PAGE_SIZE, } from '~/runner/constants'; import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql'; import RunnerListApp from '~/runner/runner_list/runner_list_app.vue'; @@ -26,6 +29,7 @@ import { runnersData } from '../mock_data'; const mockRegistrationToken = 'MOCK_REGISTRATION_TOKEN'; const mockActiveRunnersCount = 2; const mocKRunners = runnersData.data.runners.nodes; +const mockPageInfo = runnersData.data.runners.pageInfo; jest.mock('@sentry/browser'); jest.mock('~/lib/utils/url_utility', () => ({ @@ -44,6 +48,7 @@ describe('RunnerListApp', () => { const findRunnerTypeHelp = () => wrapper.findComponent(RunnerTypeHelp); const findRunnerManualSetupHelp = () => wrapper.findComponent(RunnerManualSetupHelp); const findRunnerList = () => wrapper.findComponent(RunnerList); + const findRunnerPagination = () => wrapper.findComponent(RunnerPagination); const findRunnerFilteredSearchBar = () => wrapper.findComponent(RunnerFilteredSearchBar); const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => { @@ -101,6 +106,7 @@ describe('RunnerListApp', () => { status: undefined, type: undefined, sort: DEFAULT_SORT, + first: RUNNER_PAGE_SIZE, }); }); @@ -128,6 +134,7 @@ describe('RunnerListApp', () => { { type: 'runner_type', value: { data: INSTANCE_TYPE, operator: '=' } }, ], sort: 'CREATED_DESC', + pagination: { page: 1 }, }); }); @@ -136,6 +143,7 @@ describe('RunnerListApp', () => { status: STATUS_ACTIVE, type: INSTANCE_TYPE, sort: DEFAULT_SORT, + first: RUNNER_PAGE_SIZE, }); }); }); @@ -159,6 +167,7 @@ describe('RunnerListApp', () => { expect(mockRunnersQuery).toHaveBeenLastCalledWith({ status: STATUS_ACTIVE, sort: CREATED_ASC, + first: RUNNER_PAGE_SIZE, }); }); }); @@ -193,4 +202,37 @@ describe('RunnerListApp', () => { expect(Sentry.captureException).toHaveBeenCalled(); }); }); + + describe('Pagination', () => { + beforeEach(() => { + createComponentWithApollo({ mountFn: mount }); + }); + + it('more pages can be selected', () => { + expect(findRunnerPagination().text()).toMatchInterpolatedText('Prev Next'); + }); + + it('cannot navigate to the previous page', () => { + expect(findRunnerPagination().find('[aria-disabled]').text()).toBe('Prev'); + }); + + it('navigates to the next page', async () => { + const nextPageBtn = findRunnerPagination().find('a'); + expect(nextPageBtn.text()).toBe('Next'); + + await nextPageBtn.trigger('click'); + + expect(mockRunnersQuery).toHaveBeenLastCalledWith({ + sort: CREATED_DESC, + first: RUNNER_PAGE_SIZE, + after: expect.any(String), + }); + + expect(mockRunnersQuery).toHaveBeenLastCalledWith({ + sort: CREATED_DESC, + first: RUNNER_PAGE_SIZE, + after: mockPageInfo.endCursor, + }); + }); + }); }); diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb index f878d24fe4b..63625244fe8 100644 --- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb +++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do let(:chunked_io) { described_class.new(build) } before do - stub_feature_flags(ci_enable_live_trace: true, gitlab_ci_trace_read_consistency: true) + stub_feature_flags(ci_enable_live_trace: true) end describe "#initialize" do diff --git a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb index 59f70165380..28d78c182ad 100644 --- a/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb +++ b/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb @@ -3,8 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do - let(:pool_spec) { ActiveRecord::Base.connection_pool.spec } - let(:pool) { ActiveRecord::ConnectionAdapters::ConnectionPool.new(pool_spec) } + let(:pool) { Gitlab::Database.create_connection_pool(2) } let(:conflict_error) { Class.new(RuntimeError) } let(:lb) { described_class.new(%w(localhost localhost)) } diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb index 79ddb450d7a..4f1d6302331 100644 --- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb +++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb @@ -580,7 +580,7 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe it 'idempotently cleans up after failed background migrations' do expect(partitioned_model.count).to eq(0) - partitioned_model.insert!(record2.attributes) + partitioned_model.insert(record2.attributes, unique_by: [:id, :created_at]) expect_next_instance_of(Gitlab::Database::PartitioningMigrationHelpers::BackfillPartitionedTable) do |backfill| allow(backfill).to receive(:transaction_open?).and_return(false) diff --git a/spec/lib/gitlab/database/with_lock_retries_spec.rb b/spec/lib/gitlab/database/with_lock_retries_spec.rb index b08f39fc92a..df2c506e163 100644 --- a/spec/lib/gitlab/database/with_lock_retries_spec.rb +++ b/spec/lib/gitlab/database/with_lock_retries_spec.rb @@ -242,10 +242,10 @@ RSpec.describe Gitlab::Database::WithLockRetries do let(:timing_configuration) { [[0.015.seconds, 0.025.seconds], [0.015.seconds, 0.025.seconds]] } # 15ms, 25ms it 'executes `SET LOCAL lock_timeout` using the configured timeout value in milliseconds' do - expect(ActiveRecord::Base.connection).to receive(:execute).with("SAVEPOINT active_record_1").and_call_original - expect(ActiveRecord::Base.connection).to receive(:execute).with('RESET idle_in_transaction_session_timeout; RESET lock_timeout').and_call_original + expect(ActiveRecord::Base.connection).to receive(:execute).with("RESET idle_in_transaction_session_timeout; RESET lock_timeout").and_call_original + expect(ActiveRecord::Base.connection).to receive(:execute).with("SAVEPOINT active_record_1", "TRANSACTION").and_call_original expect(ActiveRecord::Base.connection).to receive(:execute).with("SET LOCAL lock_timeout TO '15ms'").and_call_original - expect(ActiveRecord::Base.connection).to receive(:execute).with("RELEASE SAVEPOINT active_record_1").and_call_original + expect(ActiveRecord::Base.connection).to receive(:execute).with("RELEASE SAVEPOINT active_record_1", "TRANSACTION").and_call_original subject.run { } end diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index 2b31f3b4dee..5c9af1206c0 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -329,7 +329,7 @@ RSpec.describe Gitlab::Database do expect(pool) .to be_kind_of(ActiveRecord::ConnectionAdapters::ConnectionPool) - expect(pool.spec.config[:pool]).to eq(5) + expect(pool.db_config.pool).to eq(5) ensure pool.disconnect! end @@ -339,7 +339,7 @@ RSpec.describe Gitlab::Database do pool = described_class.create_connection_pool(5, '127.0.0.1') begin - expect(pool.spec.config[:host]).to eq('127.0.0.1') + expect(pool.db_config.host).to eq('127.0.0.1') ensure pool.disconnect! end @@ -349,8 +349,8 @@ RSpec.describe Gitlab::Database do pool = described_class.create_connection_pool(5, '127.0.0.1', 5432) begin - expect(pool.spec.config[:host]).to eq('127.0.0.1') - expect(pool.spec.config[:port]).to eq(5432) + expect(pool.db_config.host).to eq('127.0.0.1') + expect(pool.db_config.configuration_hash[:port]).to eq(5432) ensure pool.disconnect! end diff --git a/spec/lib/gitlab/import_export/import_failure_service_spec.rb b/spec/lib/gitlab/import_export/import_failure_service_spec.rb index c8bb067d40c..51f1fc9c6a2 100644 --- a/spec/lib/gitlab/import_export/import_failure_service_spec.rb +++ b/spec/lib/gitlab/import_export/import_failure_service_spec.rb @@ -43,7 +43,7 @@ RSpec.describe Gitlab::ImportExport::ImportFailureService do let(:importable) { create(:merge_request) } it 'raise exception' do - expect { subject }.to raise_exception(ActiveRecord::AssociationNotFoundError, "Association named 'import_failures' was not found on MergeRequest; perhaps you misspelled it?") + expect { subject }.to raise_exception(ActiveRecord::AssociationNotFoundError, /Association named 'import_failures' was not found on MergeRequest/) end end end diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb index 12bc5d9aa3c..c15910ef529 100644 --- a/spec/models/ci/build_trace_chunk_spec.rb +++ b/spec/models/ci/build_trace_chunk_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do it_behaves_like 'having unique enum values' before do - stub_feature_flags(ci_enable_live_trace: true, gitlab_ci_trace_read_consistency: true) + stub_feature_flags(ci_enable_live_trace: true) stub_artifacts_object_storage end diff --git a/spec/models/clusters/clusters_hierarchy_spec.rb b/spec/models/clusters/clusters_hierarchy_spec.rb index 5ac561eb2d0..5dd2fe98352 100644 --- a/spec/models/clusters/clusters_hierarchy_spec.rb +++ b/spec/models/clusters/clusters_hierarchy_spec.rb @@ -4,8 +4,8 @@ require 'spec_helper' RSpec.describe Clusters::ClustersHierarchy do describe '#base_and_ancestors' do - def base_and_ancestors(clusterable, include_management_project: true) - described_class.new(clusterable, include_management_project: include_management_project).base_and_ancestors + def base_and_ancestors(clusterable) + described_class.new(clusterable).base_and_ancestors end context 'project in nested group with clusters at every level' do @@ -101,10 +101,6 @@ RSpec.describe Clusters::ClustersHierarchy do expect(base_and_ancestors(management_project)).to eq([ancestor, child]) end - it 'returns clusters for management_project' do - expect(base_and_ancestors(management_project, include_management_project: false)).to eq([child, ancestor]) - end - it 'returns clusters for project' do expect(base_and_ancestors(project)).to eq([child, ancestor]) end diff --git a/spec/models/concerns/bulk_insert_safe_spec.rb b/spec/models/concerns/bulk_insert_safe_spec.rb index ca6df506ee8..209ee1264d5 100644 --- a/spec/models/concerns/bulk_insert_safe_spec.rb +++ b/spec/models/concerns/bulk_insert_safe_spec.rb @@ -20,6 +20,13 @@ RSpec.describe BulkInsertSafe do t.index :name, unique: true end + + create_table :bulk_insert_items_with_composite_pk, id: false, force: true do |t| + t.integer :id, null: true + t.string :name, null: true + end + + execute("ALTER TABLE bulk_insert_items_with_composite_pk ADD PRIMARY KEY (id,name);") end end @@ -27,6 +34,7 @@ RSpec.describe BulkInsertSafe do ActiveRecord::Schema.define do drop_table :bulk_insert_items, force: true drop_table :bulk_insert_parent_items, force: true + drop_table :bulk_insert_items_with_composite_pk, force: true end end @@ -227,5 +235,28 @@ RSpec.describe BulkInsertSafe do end end end + + context 'when a model with composite primary key is inserted' do + let_it_be(:bulk_insert_items_with_composite_pk_class) do + Class.new(ActiveRecord::Base) do + self.table_name = 'bulk_insert_items_with_composite_pk' + + include BulkInsertSafe + end + end + + let(:new_object) { bulk_insert_items_with_composite_pk_class.new(id: 1, name: 'composite') } + + it 'successfully inserts an item' do + expect(ActiveRecord::InsertAll).to receive(:new) + .with( + bulk_insert_items_with_composite_pk_class, [new_object.as_json], on_duplicate: :raise, returning: false, unique_by: %w[id name] + ).and_call_original + + expect { bulk_insert_items_with_composite_pk_class.bulk_insert!([new_object]) }.to( + change(bulk_insert_items_with_composite_pk_class, :count).from(0).to(1) + ) + end + end end end diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb index 2bb6aa27e21..7fa55184cf1 100644 --- a/spec/models/concerns/deployment_platform_spec.rb +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -254,20 +254,8 @@ RSpec.describe DeploymentPlatform do create(:cluster, :provided_by_user, projects: [another_project], management_project: project) end - context 'cluster_management_project feature is enabled' do - it 'returns the cluster with management project' do - is_expected.to eq(cluster_with_management_project.platform_kubernetes) - end - end - - context 'cluster_management_project feature is disabled' do - before do - stub_feature_flags(cluster_management_project: false) - end - - it 'returns nothing' do - is_expected.to be_nil - end + it 'returns the cluster with management project' do + is_expected.to eq(cluster_with_management_project.platform_kubernetes) end end @@ -311,20 +299,8 @@ RSpec.describe DeploymentPlatform do create(:cluster, :provided_by_user, projects: [another_project], management_project: project) end - context 'cluster_management_project feature is enabled' do - it 'returns the cluster with management project' do - is_expected.to eq(cluster_with_management_project.platform_kubernetes) - end - end - - context 'cluster_management_project feature is disabled' do - before do - stub_feature_flags(cluster_management_project: false) - end - - it 'returns the group cluster' do - is_expected.to eq(group_cluster.platform_kubernetes) - end + it 'returns the cluster with management project' do + is_expected.to eq(cluster_with_management_project.platform_kubernetes) end end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 71a4a1a2784..869df06b60c 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -558,8 +558,7 @@ RSpec.describe API::Files do get api(url, current_user), params: params - expect(response.headers["Cache-Control"]).to include("no-store") - expect(response.headers["Cache-Control"]).to include("no-cache") + expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache") expect(response.headers["Pragma"]).to eq("no-cache") expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 16619017dfe..e103aa3d6de 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3864,6 +3864,48 @@ RSpec.describe API::Projects do end end + describe 'GET /projects/:id/storage' do + context 'when unauthenticated' do + it 'does not return project storage data' do + get api("/projects/#{project.id}/storage") + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + + it 'returns project storage data when user is admin' do + get api("/projects/#{project.id}/storage", create(:admin)) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['project_id']).to eq(project.id) + expect(json_response['disk_path']).to eq(project.repository.disk_path) + expect(json_response['created_at']).to be_present + expect(json_response['repository_storage']).to eq(project.repository_storage) + end + + it 'does not return project storage data when user is not admin' do + get api("/projects/#{project.id}/storage", user3) + + expect(response).to have_gitlab_http_status(:forbidden) + end + + it 'responds with a 401 for unauthenticated users trying to access a non-existent project id' do + expect(Project.find_by(id: non_existing_record_id)).to be_nil + + get api("/projects/#{non_existing_record_id}/storage") + + expect(response).to have_gitlab_http_status(:unauthorized) + end + + it 'responds with a 403 for non-admin users trying to access a non-existent project id' do + expect(Project.find_by(id: non_existing_record_id)).to be_nil + + get api("/projects/#{non_existing_record_id}/storage", user3) + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + it_behaves_like 'custom attributes endpoints', 'projects' do let(:attributable) { project } let(:other_attributable) { project2 } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index a12b4dc9848..1b96efeca22 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -178,10 +178,12 @@ RSpec.describe API::Repositories do expect(headers['Content-Disposition']).to eq 'inline' end - it_behaves_like 'uncached response' do - before do - get api(route, current_user) - end + it 'defines an uncached header response' do + get api(route, current_user) + + expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache") + expect(response.headers["Pragma"]).to eq("no-cache") + expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") end context 'when sha does not exist' do diff --git a/spec/support/database_cleaner.rb b/spec/support/database_cleaner.rb index 60d82f7e92a..f6339d7343c 100644 --- a/spec/support/database_cleaner.rb +++ b/spec/support/database_cleaner.rb @@ -35,8 +35,6 @@ RSpec.configure do |config| puts "Recreating the database" start = Gitlab::Metrics::System.monotonic_time - ActiveRecord::AdvisoryLockBase.clear_all_connections! - ActiveRecord::Tasks::DatabaseTasks.drop_current ActiveRecord::Tasks::DatabaseTasks.create_current ActiveRecord::Tasks::DatabaseTasks.load_schema_current diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index bdfeb7a97f0..9af35c189d0 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -298,7 +298,7 @@ RSpec.shared_examples 'wiki controller actions' do expect(response.headers['Content-Disposition']).to match(/^inline/) expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true') expect(response.cache_control[:public]).to be(false) - expect(response.cache_control[:extras]).to include('no-store') + expect(response.headers['Cache-Control']).to eq('no-store') end end end diff --git a/spec/support/shared_examples/uncached_response_shared_examples.rb b/spec/support/shared_examples/uncached_response_shared_examples.rb deleted file mode 100644 index 3997017ff35..00000000000 --- a/spec/support/shared_examples/uncached_response_shared_examples.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true -# -# Pairs with lib/gitlab/no_cache_headers.rb -# - -RSpec.shared_examples 'uncached response' do - it 'defines an uncached header response' do - expect(response.headers["Cache-Control"]).to include("no-store", "no-cache") - expect(response.headers["Pragma"]).to eq("no-cache") - expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT") - end -end diff --git a/spec/views/shared/nav/_sidebar.html.haml_spec.rb b/spec/views/shared/nav/_sidebar.html.haml_spec.rb index cf9452ba68c..2eeebdff7a8 100644 --- a/spec/views/shared/nav/_sidebar.html.haml_spec.rb +++ b/spec/views/shared/nav/_sidebar.html.haml_spec.rb @@ -25,13 +25,11 @@ RSpec.describe 'shared/nav/_sidebar.html.haml' do context 'when sidebar does not have a scope menu' do let(:scope_menu_view_path) { 'shared/nav/' } let(:scope_menu_view_name) { 'scope_menu.html.haml' } - let(:scope_menu_view) { "#{scope_menu_view_path}#{scope_menu_view_name}" } let(:scope_menu_partial) { "#{scope_menu_view_path}_#{scope_menu_view_name}" } let(:content) { 'Custom test content' } context 'when sidebar has a custom scope menu partial defined' do it 'renders the custom partial' do - allow(sidebar).to receive(:render_raw_scope_menu_partial).and_return(scope_menu_view) allow(view).to receive(:scope_menu).and_return(nil) stub_template(scope_menu_partial => content) diff --git a/yarn.lock b/yarn.lock index 403ee6b3aac..fee3af0d725 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1176,15 +1176,15 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== -"@rails/actioncable@^6.0.3-4": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.0.tgz#f336f25450b1bc43b99bc60557a70b6e6bb1d3d2" - integrity sha512-eDgy+vcKN9RIzxmMBfSAe77rTj2cp6kJALiVQyKrW2O9EK2MdostOmP+99At/Dit3ur5+77NVnruxD7y14ZYFA== - -"@rails/ujs@^6.0.3-4": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.0.tgz#9a48df6511cb2b472c9f596c1f37dc0af022e751" - integrity sha512-kQNKyM4ePAc4u9eR1c4OqrbAHH+3SJXt++8izIjeaZeg+P7yBtgoF/dogMD/JPPowNC74ACFpM/4op0Ggp/fPw== +"@rails/actioncable@6.1.3-2": + version "6.1.3-2" + resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.3-2.tgz#de22e2d7474dcca051f7060829450412a17ecc04" + integrity sha512-3mBLDwM85oj0Ot+wgC3c0wsfx5qvf8XJwSbkJk4ZqW4bA7ctn8BFW+cRQxrnQau+NDfmJvSECY8mmNIANcpULA== + +"@rails/ujs@6.1.3-2": + version "6.1.3-2" + resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.3-2.tgz#5d7e161e7061654e738a116a7ec8b58b51721a11" + integrity sha512-Nd0Im4cW8tIX8ZR3jE/dS3wnJrN46RJSdCfU59Cji2puctIWohq63LjKFMufUwm21bCasISNGoLdkr3S7nwONw== "@sentry/browser@^5.22.3": version "5.30.0" |