Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f7bc7dc5ea
commit
926711e454
2
Gemfile
2
Gemfile
|
@ -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'
|
||||
|
||||
|
|
126
Gemfile.lock
126
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)
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -136,7 +136,5 @@ export default {
|
|||
<!-- TODO add actions to update runners -->
|
||||
</template>
|
||||
</gl-table>
|
||||
|
||||
<!-- TODO implement pagination -->
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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,19 +66,21 @@ export default {
|
|||
return this.$apollo.queries.runners.loading;
|
||||
},
|
||||
noRunnersFound() {
|
||||
return !this.runnersLoading && !this.runners.length;
|
||||
return !this.runnersLoading && !this.runners.items.length;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
search() {
|
||||
search: {
|
||||
deep: true,
|
||||
handler() {
|
||||
// TODO Implement back button reponse using onpopstate
|
||||
|
||||
updateHistory({
|
||||
url: fromSearchToUrl(this.search),
|
||||
title: document.title,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
errorCaptured(err) {
|
||||
this.captureException(err);
|
||||
},
|
||||
|
@ -99,11 +110,13 @@ export default {
|
|||
<div v-if="noRunnersFound" class="gl-text-center gl-p-5">
|
||||
{{ __('No runners found') }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<runner-list
|
||||
v-else
|
||||
:runners="runners"
|
||||
:runners="runners.items"
|
||||
:loading="runnersLoading"
|
||||
:active-runners-count="activeRunnersCount"
|
||||
/>
|
||||
<runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -123,9 +123,23 @@ module TabHelper
|
|||
|
||||
def route_matches_pages?(pages)
|
||||
Array(pages).compact.any? do |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
|
||||
|
||||
def route_matches_controllers_and_or_actions?(controller, action)
|
||||
if controller && action
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 .
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -254,23 +254,11 @@ 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
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project has configured kubernetes from CI/CD > Clusters' do
|
||||
let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
let(:platform_kubernetes) { cluster.platform_kubernetes }
|
||||
|
@ -311,23 +299,11 @@ 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
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is not the cluster\'s management project' do
|
||||
let(:another_project) { create(:project, group: group) }
|
||||
let!(:cluster_with_management_project) { create(:cluster, :provided_by_user, management_project: another_project) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
it 'defines an uncached header response' do
|
||||
get api(route, current_user)
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
16
yarn.lock
16
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/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"
|
||||
|
|
Loading…
Reference in New Issue