Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
eada495948
commit
415f502f73
|
|
@ -34,7 +34,7 @@ export default {
|
|||
selectedStage: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
withStageCounts: {
|
||||
type: Boolean,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export default {
|
|||
issuableTemplates: {
|
||||
type: [Object, Array],
|
||||
required: false,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default {
|
|||
issuableTemplates: {
|
||||
type: [Object, Array],
|
||||
required: false,
|
||||
default: () => {},
|
||||
default: () => [],
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import UsersSelect from '~/users_select';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => new UsersSelect());
|
||||
new UsersSelect(); // eslint-disable-line no-new
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import mountImportProjectsTable from '~/import_entities/import_projects';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mountElement = document.getElementById('import-projects-mount-element');
|
||||
const mountElement = document.getElementById('import-projects-mount-element');
|
||||
|
||||
mountImportProjectsTable(mountElement);
|
||||
});
|
||||
mountImportProjectsTable(mountElement);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { initClose2faSuccessMessage } from '~/authentication/two_factor_auth';
|
||||
import initProfileAccount from '~/profile/account';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initProfileAccount);
|
||||
initProfileAccount();
|
||||
|
||||
initClose2faSuccessMessage();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import initConfirmModal from '~/confirm_modal';
|
||||
import AddSshKeyValidation from '~/profile/add_ssh_key_validation';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initConfirmModal();
|
||||
initConfirmModal();
|
||||
|
||||
function initSshKeyValidation() {
|
||||
const input = document.querySelector('.js-add-ssh-key-validation-input');
|
||||
if (!input) return;
|
||||
|
||||
|
|
@ -18,4 +18,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
confirmSubmit,
|
||||
);
|
||||
addSshKeyValidation.register();
|
||||
});
|
||||
}
|
||||
|
||||
initSshKeyValidation();
|
||||
|
|
|
|||
|
|
@ -2,17 +2,15 @@ import { mount2faRegistration } from '~/authentication/mount_2fa';
|
|||
import { initRecoveryCodes } from '~/authentication/two_factor_auth';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const twoFactorNode = document.querySelector('.js-two-factor-auth');
|
||||
const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
|
||||
const twoFactorNode = document.querySelector('.js-two-factor-auth');
|
||||
const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSkippable) : false;
|
||||
|
||||
if (skippable) {
|
||||
const button = `<a class="btn btn-sm btn-warning float-right" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
|
||||
const flashAlert = document.querySelector('.flash-alert');
|
||||
if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
|
||||
}
|
||||
if (skippable) {
|
||||
const button = `<a class="btn btn-sm btn-warning float-right" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
|
||||
const flashAlert = document.querySelector('.flash-alert');
|
||||
if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
|
||||
}
|
||||
|
||||
mount2faRegistration();
|
||||
});
|
||||
mount2faRegistration();
|
||||
|
||||
initRecoveryCodes();
|
||||
|
|
|
|||
|
|
@ -7,151 +7,149 @@ import SeriesDataMixin from './series_data_mixin';
|
|||
|
||||
const seriesDataToBarData = (raw) => Object.entries(raw).map(([name, data]) => ({ name, data }));
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
waitForCSSLoaded(() => {
|
||||
const languagesContainer = document.getElementById('js-languages-chart');
|
||||
const codeCoverageContainer = document.getElementById('js-code-coverage-chart');
|
||||
const monthContainer = document.getElementById('js-month-chart');
|
||||
const weekdayContainer = document.getElementById('js-weekday-chart');
|
||||
const hourContainer = document.getElementById('js-hour-chart');
|
||||
const LANGUAGE_CHART_HEIGHT = 300;
|
||||
const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => {
|
||||
if (firstDayOfWeek === 0) {
|
||||
return weekDays;
|
||||
}
|
||||
waitForCSSLoaded(() => {
|
||||
const languagesContainer = document.getElementById('js-languages-chart');
|
||||
const codeCoverageContainer = document.getElementById('js-code-coverage-chart');
|
||||
const monthContainer = document.getElementById('js-month-chart');
|
||||
const weekdayContainer = document.getElementById('js-weekday-chart');
|
||||
const hourContainer = document.getElementById('js-hour-chart');
|
||||
const LANGUAGE_CHART_HEIGHT = 300;
|
||||
const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => {
|
||||
if (firstDayOfWeek === 0) {
|
||||
return weekDays;
|
||||
}
|
||||
|
||||
return Object.keys(weekDays).reduce((acc, dayName, idx, arr) => {
|
||||
const reorderedDayName = arr[(idx + firstDayOfWeek) % arr.length];
|
||||
return Object.keys(weekDays).reduce((acc, dayName, idx, arr) => {
|
||||
const reorderedDayName = arr[(idx + firstDayOfWeek) % arr.length];
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[reorderedDayName]: weekDays[reorderedDayName],
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
return {
|
||||
...acc,
|
||||
[reorderedDayName]: weekDays[reorderedDayName],
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: languagesContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: languagesContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(languagesContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
seriesData() {
|
||||
return [{ name: 'full', data: this.chartData.map((d) => [d.label, d.value]) }];
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(languagesContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
seriesData() {
|
||||
return [{ name: 'full', data: this.chartData.map((d) => [d.label, d.value]) }];
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: this.seriesData,
|
||||
xAxisTitle: __('Used programming language'),
|
||||
yAxisTitle: __('Percentage'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: this.seriesData,
|
||||
xAxisTitle: __('Used programming language'),
|
||||
yAxisTitle: __('Percentage'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
attrs: {
|
||||
height: LANGUAGE_CHART_HEIGHT,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: codeCoverageContainer,
|
||||
render(h) {
|
||||
return h(CodeCoverage, {
|
||||
props: {
|
||||
graphEndpoint: codeCoverageContainer.dataset?.graphEndpoint,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: monthContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
},
|
||||
mixins: [SeriesDataMixin],
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(monthContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: seriesDataToBarData(this.seriesData),
|
||||
xAxisTitle: __('Day of month'),
|
||||
yAxisTitle: __('No. of commits'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: weekdayContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(weekdayContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
seriesData() {
|
||||
const weekDays = reorderWeekDays(this.chartData, gon.first_day_of_week);
|
||||
const data = Object.keys(weekDays).reduce((acc, key) => {
|
||||
acc.push([key, weekDays[key]]);
|
||||
return acc;
|
||||
}, []);
|
||||
return [{ name: 'full', data }];
|
||||
attrs: {
|
||||
height: LANGUAGE_CHART_HEIGHT,
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: this.seriesData,
|
||||
xAxisTitle: __('Weekday'),
|
||||
yAxisTitle: __('No. of commits'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: hourContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: codeCoverageContainer,
|
||||
render(h) {
|
||||
return h(CodeCoverage, {
|
||||
props: {
|
||||
graphEndpoint: codeCoverageContainer.dataset?.graphEndpoint,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: monthContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
},
|
||||
mixins: [SeriesDataMixin],
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(monthContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: seriesDataToBarData(this.seriesData),
|
||||
xAxisTitle: __('Day of month'),
|
||||
yAxisTitle: __('No. of commits'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: weekdayContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(weekdayContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
seriesData() {
|
||||
const weekDays = reorderWeekDays(this.chartData, gon.first_day_of_week);
|
||||
const data = Object.keys(weekDays).reduce((acc, key) => {
|
||||
acc.push([key, weekDays[key]]);
|
||||
return acc;
|
||||
}, []);
|
||||
return [{ name: 'full', data }];
|
||||
},
|
||||
mixins: [SeriesDataMixin],
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(hourContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: seriesDataToBarData(this.seriesData),
|
||||
xAxisTitle: __('Hour (UTC)'),
|
||||
yAxisTitle: __('No. of commits'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: this.seriesData,
|
||||
xAxisTitle: __('Weekday'),
|
||||
yAxisTitle: __('No. of commits'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: hourContainer,
|
||||
components: {
|
||||
GlColumnChart,
|
||||
},
|
||||
mixins: [SeriesDataMixin],
|
||||
data() {
|
||||
return {
|
||||
chartData: JSON.parse(hourContainer.dataset.chartData),
|
||||
};
|
||||
},
|
||||
render(h) {
|
||||
return h(GlColumnChart, {
|
||||
props: {
|
||||
bars: seriesDataToBarData(this.seriesData),
|
||||
xAxisTitle: __('Hour (UTC)'),
|
||||
yAxisTitle: __('No. of commits'),
|
||||
xAxisType: 'category',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ export default {
|
|||
isLoading() {
|
||||
return this.$apollo.queries.project.loading || this.isLoadingLegacyViewer;
|
||||
},
|
||||
isBinaryFileType() {
|
||||
return this.isBinary || this.viewer.fileType === 'download';
|
||||
},
|
||||
blobInfo() {
|
||||
const nodes = this.project?.repository?.blobs?.nodes;
|
||||
|
||||
|
|
@ -169,7 +172,7 @@ export default {
|
|||
<blob-header
|
||||
:blob="blobInfo"
|
||||
:hide-viewer-switcher="!hasRichViewer || isBinary"
|
||||
:is-binary="isBinary"
|
||||
:is-binary="isBinaryFileType"
|
||||
:active-viewer-type="viewer.type"
|
||||
:has-render-error="hasRenderError"
|
||||
@viewer-changed="switchViewer"
|
||||
|
|
|
|||
|
|
@ -29,15 +29,6 @@ export default {
|
|||
update(data) {
|
||||
return this.onContentUpdate(data);
|
||||
},
|
||||
result() {
|
||||
if (this.activeViewerType === RICH_BLOB_VIEWER) {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.blob.richViewer.renderError = null;
|
||||
} else {
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.blob.simpleViewer.renderError = null;
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
return this.viewer.renderError;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ export default function appFactory(el, Component) {
|
|||
}
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient({}, { batchMax: 1 }),
|
||||
defaultClient: createDefaultClient(
|
||||
{},
|
||||
{
|
||||
batchMax: 1,
|
||||
assumeImmutableResults: true,
|
||||
},
|
||||
),
|
||||
});
|
||||
|
||||
const {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { isEmpty } from 'lodash';
|
||||
import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql';
|
||||
|
||||
const blobsDefault = [];
|
||||
|
|
@ -12,20 +13,18 @@ export const getSnippetMixin = {
|
|||
};
|
||||
},
|
||||
update(data) {
|
||||
const res = data.snippets.nodes[0];
|
||||
const res = { ...data.snippets.nodes[0] };
|
||||
|
||||
// Set `snippet.blobs` since some child components are coupled to this.
|
||||
if (res) {
|
||||
if (!isEmpty(res)) {
|
||||
// It's possible for us to not get any blobs in a response.
|
||||
// In this case, we should default to current blobs.
|
||||
res.blobs = res.blobs ? res.blobs.nodes : this.blobs;
|
||||
res.blobs = res.blobs ? res.blobs.nodes : blobsDefault;
|
||||
res.description = res.description || '';
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
result(res) {
|
||||
this.blobs = res.data.snippets.nodes[0]?.blobs || blobsDefault;
|
||||
},
|
||||
skip() {
|
||||
return this.newSnippet;
|
||||
},
|
||||
|
|
@ -41,12 +40,14 @@ export const getSnippetMixin = {
|
|||
return {
|
||||
snippet: {},
|
||||
newSnippet: !this.snippetGid,
|
||||
blobs: blobsDefault,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isLoading() {
|
||||
return this.$apollo.queries.snippet.loading;
|
||||
},
|
||||
blobs() {
|
||||
return this.snippet?.blobs || [];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script>
|
||||
import { GlEmptyState, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { GlEmptyState, GlIcon, GlLink } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlEmptyState,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
image: {
|
||||
|
|
@ -14,6 +14,11 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
docsUrl() {
|
||||
return helpPagePath('user/infrastructure/terraform_state');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -21,23 +26,10 @@ export default {
|
|||
<gl-empty-state :svg-path="image" :title="s__('Terraform|Get started with Terraform')">
|
||||
<template #description>
|
||||
<p>
|
||||
<gl-sprintf
|
||||
:message="
|
||||
s__(
|
||||
'Terraform|Find out how to use the %{linkStart}GitLab managed Terraform State%{linkEnd}',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #link="{ content }">
|
||||
<gl-link
|
||||
href="https://docs.gitlab.com/ee/user/infrastructure/index.html"
|
||||
target="_blank"
|
||||
>
|
||||
{{ content }}
|
||||
<gl-icon name="external-link" />
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<gl-link :href="docsUrl" target="_blank"
|
||||
>{{ s__('Terraform|How to use GitLab-managed Terraform State?') }}
|
||||
<gl-icon name="external-link"
|
||||
/></gl-link>
|
||||
</p>
|
||||
</template>
|
||||
</gl-empty-state>
|
||||
|
|
|
|||
|
|
@ -42,12 +42,12 @@ export default {
|
|||
itemsCount: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
pageInfo: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
statusTabs: {
|
||||
type: Array,
|
||||
|
|
|
|||
|
|
@ -69,3 +69,5 @@ module Projects
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Projects::ProtectDefaultBranchService.prepend_mod
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330605
|
|||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: load_balancing_refine_load_balancer_methods
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65356
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335109
|
||||
milestone: '14.1'
|
||||
type: development
|
||||
group: group::memory
|
||||
default_enabled: false
|
||||
|
|
@ -12,7 +12,6 @@ module Gitlab
|
|||
# always returns a connection to the primary.
|
||||
class LoadBalancer
|
||||
CACHE_KEY = :gitlab_load_balancer_host
|
||||
VALID_HOSTS_CACHE_KEY = :gitlab_load_balancer_valid_hosts
|
||||
|
||||
attr_reader :host_list
|
||||
|
||||
|
|
@ -118,7 +117,7 @@ module Gitlab
|
|||
# Hosts are scoped per thread so that multiple threads don't
|
||||
# accidentally re-use the same host + connection.
|
||||
def host
|
||||
RequestStore[CACHE_KEY] ||= current_host_list.next
|
||||
RequestStore[CACHE_KEY] ||= @host_list.next
|
||||
end
|
||||
|
||||
# Releases the host and connection for the current thread.
|
||||
|
|
@ -129,7 +128,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
RequestStore.delete(CACHE_KEY)
|
||||
RequestStore.delete(VALID_HOSTS_CACHE_KEY)
|
||||
end
|
||||
|
||||
def release_primary_connection
|
||||
|
|
@ -148,39 +146,6 @@ module Gitlab
|
|||
end
|
||||
|
||||
# Returns true if there was at least one host that has caught up with the given transaction.
|
||||
#
|
||||
# In case of a retry, this method also stores the set of hosts that have caught up.
|
||||
#
|
||||
# UPD: `select_caught_up_hosts` seems to have redundant logic managing host list (`:gitlab_load_balancer_valid_hosts`),
|
||||
# while we only need a single host: https://gitlab.com/gitlab-org/gitlab/-/issues/326125#note_615271604
|
||||
# Also, shuffling the list afterwards doesn't seem to be necessary.
|
||||
# This may be improved by merging this method with `select_up_to_date_host`.
|
||||
# Could be removed when `:load_balancing_refine_load_balancer_methods` FF is rolled out
|
||||
def select_caught_up_hosts(location)
|
||||
all_hosts = @host_list.hosts
|
||||
valid_hosts = all_hosts.select { |host| host.caught_up?(location) }
|
||||
|
||||
return false if valid_hosts.empty?
|
||||
|
||||
# Hosts can come online after the time when this scan was done,
|
||||
# so we need to remember the ones that can be used. If the host went
|
||||
# offline, we'll just rely on the retry mechanism to use the primary.
|
||||
set_consistent_hosts_for_request(HostList.new(valid_hosts))
|
||||
|
||||
# Since we will be using a subset from the original list, let's just
|
||||
# pick a random host and mix up the original list to ensure we don't
|
||||
# only end up using one replica.
|
||||
RequestStore[CACHE_KEY] = valid_hosts.sample
|
||||
@host_list.shuffle
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Returns true if there was at least one host that has caught up with the given transaction.
|
||||
# Similar to `#select_caught_up_hosts`, picks a random host, to rotate replicas we use.
|
||||
# Unlike `#select_caught_up_hosts`, does not iterate over all hosts if finds any.
|
||||
#
|
||||
# It is going to be merged with `select_caught_up_hosts`, because they intend to do the same.
|
||||
def select_up_to_date_host(location)
|
||||
all_hosts = @host_list.hosts.shuffle
|
||||
host = all_hosts.find { |host| host.caught_up?(location) }
|
||||
|
|
@ -192,11 +157,6 @@ module Gitlab
|
|||
true
|
||||
end
|
||||
|
||||
# Could be removed when `:load_balancing_refine_load_balancer_methods` FF is rolled out
|
||||
def set_consistent_hosts_for_request(hosts)
|
||||
RequestStore[VALID_HOSTS_CACHE_KEY] = hosts
|
||||
end
|
||||
|
||||
# Yields a block, retrying it upon error using an exponential backoff.
|
||||
def retry_with_backoff(retries = 3, time = 2)
|
||||
retried = 0
|
||||
|
|
@ -268,10 +228,6 @@ module Gitlab
|
|||
@connection_db_roles_count.delete(connection)
|
||||
end
|
||||
end
|
||||
|
||||
def current_host_list
|
||||
RequestStore[VALID_HOSTS_CACHE_KEY] || @host_list
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -53,14 +53,8 @@ module Gitlab
|
|||
# write location. If no such location exists, err on the side of caution.
|
||||
return false unless location
|
||||
|
||||
if ::Feature.enabled?(:load_balancing_refine_load_balancer_methods)
|
||||
load_balancer.select_up_to_date_host(location).tap do |selected|
|
||||
unstick(namespace, id) if selected
|
||||
end
|
||||
else
|
||||
load_balancer.select_caught_up_hosts(location).tap do |selected|
|
||||
unstick(namespace, id) if selected
|
||||
end
|
||||
load_balancer.select_up_to_date_host(location).tap do |selected|
|
||||
unstick(namespace, id) if selected
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13145,6 +13145,9 @@ msgstr ""
|
|||
msgid "EscalationPolicies|A schedule is required for adding an escalation policy. Please create an on-call schedule first."
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|A user is required for adding an escalation policy."
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Add an escalation policy"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13169,6 +13172,9 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Email on-call user in schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Email user"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Escalation policies"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13178,7 +13184,7 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Failed to load oncall-schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{schedule}"
|
||||
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{scheduleOrUser}"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes"
|
||||
|
|
@ -13193,13 +13199,16 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Remove escalation rule"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Search for user"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Select schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Set up escalation policies to define who is paged, and when, in the event the first users paged don't respond."
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|THEN %{doAction} %{schedule}"
|
||||
msgid "EscalationPolicies|THEN %{doAction} %{scheduleOrUser}"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|The escalation policy could not be deleted. Please try again."
|
||||
|
|
@ -32513,15 +32522,15 @@ msgstr ""
|
|||
msgid "Terraform|Download JSON"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Find out how to use the %{linkStart}GitLab managed Terraform State%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Generating the report caused an error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Get started with Terraform"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|How to use GitLab-managed Terraform State?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Terraform|Job status"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -349,15 +349,23 @@ describe('Blob content viewer component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('passes the correct isBinary value to blob header when viewing a binary file', async () => {
|
||||
fullFactory({
|
||||
mockData: { blobInfo: richMockData, isBinary: true },
|
||||
stubs: { BlobContent: true, BlobReplace: true },
|
||||
});
|
||||
describe('blob header binary file', () => {
|
||||
it.each([richMockData, { simpleViewer: { fileType: 'download' } }])(
|
||||
'passes the correct isBinary value when viewing a binary file',
|
||||
async (blobInfo) => {
|
||||
fullFactory({
|
||||
mockData: {
|
||||
blobInfo,
|
||||
isBinary: true,
|
||||
},
|
||||
stubs: { BlobContent: true, BlobReplace: true },
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
expect(findBlobHeader().props('isBinary')).toBe(true);
|
||||
expect(findBlobHeader().props('isBinary')).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('BlobButtonGroup', () => {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@ describe('Snippet view app', () => {
|
|||
it('renders correct snippet-blob components', () => {
|
||||
createComponent({
|
||||
data: {
|
||||
blobs: [Blob, BinaryBlob],
|
||||
snippet: {
|
||||
blobs: [Blob, BinaryBlob],
|
||||
},
|
||||
},
|
||||
});
|
||||
const blobs = wrapper.findAll(SnippetBlob);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlEmptyState, GlSprintf } from '@gitlab/ui';
|
||||
import { GlEmptyState, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import EmptyState from '~/terraform/components/empty_state.vue';
|
||||
|
||||
|
|
@ -8,19 +8,20 @@ describe('EmptyStateComponent', () => {
|
|||
const propsData = {
|
||||
image: '/image/path',
|
||||
};
|
||||
const docsUrl = '/help/user/infrastructure/terraform_state';
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
const findLink = () => wrapper.findComponent(GlLink);
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallowMount(EmptyState, { propsData, stubs: { GlEmptyState, GlSprintf } });
|
||||
return wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
wrapper = shallowMount(EmptyState, { propsData, stubs: { GlEmptyState, GlLink } });
|
||||
});
|
||||
|
||||
it('should render content', () => {
|
||||
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
|
||||
expect(findEmptyState().exists()).toBe(true);
|
||||
expect(wrapper.text()).toContain('Get started with Terraform');
|
||||
});
|
||||
|
||||
it('should have a link to the GitLab managed Terraform States docs', () => {
|
||||
expect(findLink().attributes('href')).toBe(docsUrl);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -261,7 +261,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
|
||||
it 'stores the host in a thread-local variable' do
|
||||
RequestStore.delete(described_class::CACHE_KEY)
|
||||
RequestStore.delete(described_class::VALID_HOSTS_CACHE_KEY)
|
||||
|
||||
expect(lb.host_list).to receive(:next).once.and_call_original
|
||||
|
||||
|
|
@ -279,7 +278,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
lb.release_host
|
||||
|
||||
expect(RequestStore[described_class::CACHE_KEY]).to be_nil
|
||||
expect(RequestStore[described_class::VALID_HOSTS_CACHE_KEY]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -414,60 +412,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::LoadBalancer, :request_store do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#select_caught_up_hosts' do
|
||||
let(:location) { 'AB/12345'}
|
||||
let(:hosts) { lb.host_list.hosts }
|
||||
let(:valid_host_list) { RequestStore[described_class::VALID_HOSTS_CACHE_KEY] }
|
||||
let(:valid_hosts) { valid_host_list.hosts }
|
||||
|
||||
subject { lb.select_caught_up_hosts(location) }
|
||||
|
||||
context 'when all replicas are caught up' do
|
||||
before do
|
||||
expect(hosts).to all(receive(:caught_up?).with(location).and_return(true))
|
||||
end
|
||||
|
||||
it 'returns true and sets all hosts to valid' do
|
||||
expect(subject).to be true
|
||||
expect(valid_host_list).to be_a(Gitlab::Database::LoadBalancing::HostList)
|
||||
expect(valid_hosts).to contain_exactly(*hosts)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when none of the replicas are caught up' do
|
||||
before do
|
||||
expect(hosts).to all(receive(:caught_up?).with(location).and_return(false))
|
||||
end
|
||||
|
||||
it 'returns false and does not set the valid hosts' do
|
||||
expect(subject).to be false
|
||||
expect(valid_host_list).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when one of the replicas is caught up' do
|
||||
before do
|
||||
expect(hosts[0]).to receive(:caught_up?).with(location).and_return(false)
|
||||
expect(hosts[1]).to receive(:caught_up?).with(location).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns true and sets one host to valid' do
|
||||
expect(subject).to be true
|
||||
expect(valid_host_list).to be_a(Gitlab::Database::LoadBalancing::HostList)
|
||||
expect(valid_hosts).to contain_exactly(hosts[1])
|
||||
end
|
||||
|
||||
it 'host always returns the caught-up replica' do
|
||||
subject
|
||||
|
||||
3.times do
|
||||
expect(lb.host).to eq(hosts[1])
|
||||
RequestStore.delete(described_class::CACHE_KEY)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#select_up_to_date_host' do
|
||||
let(:location) { 'AB/12345'}
|
||||
let(:hosts) { lb.host_list.hosts }
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
|
|||
end
|
||||
|
||||
it 'returns false and does not try to find caught up hosts' do
|
||||
expect(described_class).not_to receive(:select_caught_up_hosts)
|
||||
expect(lb).not_to receive(:select_up_to_date_host)
|
||||
expect(described_class.select_caught_up_replicas(:project, 42)).to be false
|
||||
end
|
||||
end
|
||||
|
|
@ -329,18 +329,6 @@ RSpec.describe Gitlab::Database::LoadBalancing::Sticking, :redis do
|
|||
expect(described_class).to receive(:unstick).with(:project, 42)
|
||||
expect(described_class.select_caught_up_replicas(:project, 42)).to be true
|
||||
end
|
||||
|
||||
context 'when :load_balancing_refine_load_balancer_methods FF is disabled' do
|
||||
before do
|
||||
stub_feature_flags(load_balancing_refine_load_balancer_methods: false)
|
||||
end
|
||||
|
||||
it 'returns true, selects hosts, and unsticks if any secondary has caught up' do
|
||||
expect(lb).to receive(:select_caught_up_hosts).and_return(true)
|
||||
expect(described_class).to receive(:unstick).with(:project, 42)
|
||||
expect(described_class.select_caught_up_replicas(:project, 42)).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue