Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-02 00:09:56 +00:00
parent f7bc7dc5ea
commit 926711e454
75 changed files with 783 additions and 290 deletions

View File

@ -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'

View File

@ -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)

View File

@ -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 },
});
},
},

View File

@ -136,7 +136,5 @@ export default {
<!-- TODO add actions to update runners -->
</template>
</gl-table>
<!-- TODO implement pagination -->
</div>
</template>

View File

@ -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>

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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;
};

View File

@ -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>

View File

@ -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')

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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',

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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).

View File

@ -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"
}
]
```

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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",

View File

@ -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 .

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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 },
},
]);
});

View File

@ -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);
});
});
});

View File

@ -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',
},
},

View File

@ -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', () => {

View File

@ -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,
});
});
});
});

View File

@ -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

View File

@ -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)) }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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/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.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/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"