Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-04 12:10:30 +00:00
parent eada495948
commit 415f502f73
26 changed files with 237 additions and 352 deletions

View File

@ -34,7 +34,7 @@ export default {
selectedStage: {
type: Object,
required: false,
default: () => {},
default: () => ({}),
},
withStageCounts: {
type: Boolean,

View File

@ -15,7 +15,7 @@ export default {
issuableTemplates: {
type: [Object, Array],
required: false,
default: () => {},
default: () => ({}),
},
projectPath: {
type: String,

View File

@ -33,7 +33,7 @@ export default {
issuableTemplates: {
type: [Object, Array],
required: false,
default: () => {},
default: () => [],
},
issuableType: {
type: String,

View File

@ -1,3 +1,3 @@
import UsersSelect from '~/users_select';
document.addEventListener('DOMContentLoaded', () => new UsersSelect());
new UsersSelect(); // eslint-disable-line no-new

View File

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

View File

@ -1,6 +1,6 @@
import { initClose2faSuccessMessage } from '~/authentication/two_factor_auth';
import initProfileAccount from '~/profile/account';
document.addEventListener('DOMContentLoaded', initProfileAccount);
initProfileAccount();
initClose2faSuccessMessage();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,12 +42,12 @@ export default {
itemsCount: {
type: Object,
required: false,
default: () => {},
default: () => ({}),
},
pageInfo: {
type: Object,
required: false,
default: () => {},
default: () => ({}),
},
statusTabs: {
type: Array,

View File

@ -69,3 +69,5 @@ module Projects
end
end
end
Projects::ProtectDefaultBranchService.prepend_mod

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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