Commit 8e94dad3 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent ab128cc1
import Vue from 'vue'; import Vue from 'vue';
import ConfirmModal from '~/vue_shared/components/confirm_modal.vue'; import ConfirmModal from '~/vue_shared/components/confirm_modal.vue';
const mountConfirmModal = button => { const mountConfirmModal = () => {
const props = {
path: button.dataset.path,
method: button.dataset.method,
modalAttributes: JSON.parse(button.dataset.modalAttributes),
};
return new Vue({ return new Vue({
data() {
return {
path: '',
method: '',
modalAttributes: null,
showModal: false,
};
},
mounted() {
document.querySelectorAll('.js-confirm-modal-button').forEach(button => {
button.addEventListener('click', e => {
e.preventDefault();
this.path = button.dataset.path;
this.method = button.dataset.method;
this.modalAttributes = JSON.parse(button.dataset.modalAttributes);
this.showModal = true;
});
});
},
methods: {
dismiss() {
this.showModal = false;
},
},
render(h) { render(h) {
return h(ConfirmModal, { props }); return h(ConfirmModal, {
props: {
path: this.path,
method: this.method,
modalAttributes: this.modalAttributes,
showModal: this.showModal,
},
on: { dismiss: this.dismiss },
});
}, },
}).$mount(); }).$mount();
}; };
export default () => { export default () => mountConfirmModal();
document.getElementsByClassName('js-confirm-modal-button').forEach(button => {
button.addEventListener('click', e => {
e.preventDefault();
mountConfirmModal(button);
});
});
};
import _ from 'underscore'; import { last } from 'lodash';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
import FilteredSearchTokenizer from './filtered_search_tokenizer'; import FilteredSearchTokenizer from './filtered_search_tokenizer';
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager'; import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
...@@ -70,11 +70,11 @@ export default class DropdownUtils { ...@@ -70,11 +70,11 @@ export default class DropdownUtils {
if (!allowMultiple && itemInExistingTokens) { if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true; updatedItem.droplab_hidden = true;
} else if (!isSearchItem && (!lastKey || _.last(searchInput.split('')) === ' ')) { } else if (!isSearchItem && (!lastKey || last(searchInput.split('')) === ' ')) {
updatedItem.droplab_hidden = false; updatedItem.droplab_hidden = false;
} else if (lastKey) { } else if (lastKey) {
const split = lastKey.split(':'); const split = lastKey.split(':');
const tokenName = _.last(split[0].split(' ')); const tokenName = last(split[0].split(' '));
const match = isSearchItem const match = isSearchItem
? allowedKeys.some(key => key.startsWith(tokenName.toLowerCase())) ? allowedKeys.some(key => key.startsWith(tokenName.toLowerCase()))
...@@ -129,7 +129,7 @@ export default class DropdownUtils { ...@@ -129,7 +129,7 @@ export default class DropdownUtils {
const values = []; const values = [];
if (untilInput) { if (untilInput) {
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token')); const inputIndex = tokens.findIndex(t => t.classList.contains('input-token'));
// Add one to include input-token to the tokens array // Add one to include input-token to the tokens array
tokens.splice(inputIndex + 1); tokens.splice(inputIndex + 1);
} }
......
import { last } from 'lodash';
import AvailableDropdownMappings from 'ee_else_ce/filtered_search/available_dropdown_mappings'; import AvailableDropdownMappings from 'ee_else_ce/filtered_search/available_dropdown_mappings';
import _ from 'underscore';
import DropLab from '~/droplab/drop_lab'; import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
import FilteredSearchTokenKeys from './filtered_search_token_keys'; import FilteredSearchTokenKeys from './filtered_search_token_keys';
...@@ -184,8 +184,8 @@ export default class FilteredSearchDropdownManager { ...@@ -184,8 +184,8 @@ export default class FilteredSearchDropdownManager {
// Eg. token = 'label:' // Eg. token = 'label:'
const split = lastToken.split(':'); const split = lastToken.split(':');
const dropdownName = _.last(split[0].split(' ')); const dropdownName = last(split[0].split(' '));
const possibleOperatorToken = _.last(split[1]); const possibleOperatorToken = last(split[1]);
const hasOperator = FilteredSearchVisualTokens.permissibleOperatorValues.includes( const hasOperator = FilteredSearchVisualTokens.permissibleOperatorValues.includes(
possibleOperatorToken && possibleOperatorToken.trim(), possibleOperatorToken && possibleOperatorToken.trim(),
......
import _ from 'underscore'; import { last } from 'lodash';
import recentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys'; import recentSearchesStorageKeys from 'ee_else_ce/filtered_search/recent_searches_storage_keys';
import { getParameterByName, getUrlParamsArray } from '~/lib/utils/common_utils'; import { getParameterByName, getUrlParamsArray } from '~/lib/utils/common_utils';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
...@@ -456,7 +456,7 @@ export default class FilteredSearchManager { ...@@ -456,7 +456,7 @@ export default class FilteredSearchManager {
if (fragments.length > 1) { if (fragments.length > 1) {
const inputValues = fragments[0].split(' '); const inputValues = fragments[0].split(' ');
const tokenKey = _.last(inputValues); const tokenKey = last(inputValues);
if (inputValues.length > 1) { if (inputValues.length > 1) {
inputValues.pop(); inputValues.pop();
......
import { flatten } from 'underscore'; import { flattenDeep } from 'lodash';
import FilteredSearchTokenKeys from './filtered_search_token_keys'; import FilteredSearchTokenKeys from './filtered_search_token_keys';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -73,7 +73,7 @@ export const alternativeTokenKeys = [ ...@@ -73,7 +73,7 @@ export const alternativeTokenKeys = [
}, },
]; ];
export const conditions = flatten( export const conditions = flattenDeep(
[ [
{ {
url: 'assignee_id=None', url: 'assignee_id=None',
......
import _ from 'underscore'; import { uniq } from 'lodash';
class RecentSearchesStore { class RecentSearchesStore {
constructor(initialState = {}, allowedKeys) { constructor(initialState = {}, allowedKeys) {
...@@ -20,7 +20,7 @@ class RecentSearchesStore { ...@@ -20,7 +20,7 @@ class RecentSearchesStore {
setRecentSearches(searches = []) { setRecentSearches(searches = []) {
const trimmedSearches = searches.map(search => search.trim()); const trimmedSearches = searches.map(search => search.trim());
this.state.recentSearches = _.uniq(trimmedSearches).slice(0, 5); this.state.recentSearches = uniq(trimmedSearches).slice(0, 5);
return this.state.recentSearches; return this.state.recentSearches;
} }
} }
......
import _ from 'underscore'; import { escape as esc } from 'lodash';
import { USER_TOKEN_TYPES } from 'ee_else_ce/filtered_search/constants'; import { USER_TOKEN_TYPES } from 'ee_else_ce/filtered_search/constants';
import FilteredSearchContainer from '~/filtered_search/container'; import FilteredSearchContainer from '~/filtered_search/container';
import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens'; import FilteredSearchVisualTokens from '~/filtered_search/filtered_search_visual_tokens';
...@@ -48,7 +48,7 @@ export default class VisualTokenValue { ...@@ -48,7 +48,7 @@ export default class VisualTokenValue {
tokenValueContainer.dataset.originalValue = tokenValue; tokenValueContainer.dataset.originalValue = tokenValue;
tokenValueElement.innerHTML = ` tokenValueElement.innerHTML = `
<img class="avatar s20" src="${user.avatar_url}" alt=""> <img class="avatar s20" src="${user.avatar_url}" alt="">
${_.escape(user.name)} ${esc(user.name)}
`; `;
/* eslint-enable no-param-reassign */ /* eslint-enable no-param-reassign */
}) })
......
...@@ -9,34 +9,43 @@ export default { ...@@ -9,34 +9,43 @@ export default {
props: { props: {
modalAttributes: { modalAttributes: {
type: Object, type: Object,
required: true, required: false,
default: () => {
return {};
},
}, },
path: { path: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
method: { method: {
type: String, type: String,
required: true, required: false,
default: '',
},
showModal: {
type: Boolean,
required: false,
default: false,
}, },
}, },
data() { watch: {
return { showModal(val) {
isDismissed: false, if (val) {
}; // Wait for v-if to render
}, this.$nextTick(() => {
mounted() { this.openModal();
this.openModal(); });
}
},
}, },
methods: { methods: {
openModal() { openModal() {
this.$refs.modal.show(); this.$refs.modal.show();
}, },
submitModal() { submitModal() {
this.$refs.form.requestSubmit(); this.$refs.form.submit();
},
dismiss() {
this.isDismissed = true;
}, },
}, },
csrf, csrf,
...@@ -45,11 +54,11 @@ export default { ...@@ -45,11 +54,11 @@ export default {
<template> <template>
<gl-modal <gl-modal
v-if="!isDismissed" v-if="showModal"
ref="modal" ref="modal"
v-bind="modalAttributes" v-bind="modalAttributes"
@primary="submitModal" @primary="submitModal"
@canceled="dismiss" @canceled="$emit('dismiss')"
> >
<form ref="form" :action="path" method="post"> <form ref="form" :action="path" method="post">
<!-- Rails workaround for <form method="delete" /> <!-- Rails workaround for <form method="delete" />
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
module Clusters module Clusters
module Applications module Applications
class CertManager < ApplicationRecord class CertManager < ApplicationRecord
VERSION = 'v0.9.1' VERSION = 'v0.10.1'
CRD_VERSION = '0.9' CRD_VERSION = '0.10'
self.table_name = 'clusters_applications_cert_managers' self.table_name = 'clusters_applications_cert_managers'
......
---
title: Ensure RepositoryLinkFilter handles Gitaly failures gracefully
merge_request: 26531
author:
type: performance
---
title: Fixed bug where processing NuGet packages are returned from the Packages API
merge_request: 26270
author:
type: fixed
---
title: Support Rails 6 `insert_all!`
merge_request: 26595
author:
type: fixed
---
title: Use cert-manager 0.10 instead of 0.9 for new chart installations
merge_request: 26345
author:
type: changed
...@@ -70,14 +70,9 @@ module API ...@@ -70,14 +70,9 @@ module API
params do params do
requires :repository_id, type: Integer, desc: 'The ID of the repository' requires :repository_id, type: Integer, desc: 'The ID of the repository'
optional :name_regex_delete, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' optional :name_regex_delete, type: String, desc: 'The tag name regexp to delete, specify .* to delete all'
# require either name_regex (deprecated) or name_regex_delete, it is ok to have both
given name_regex_delete: ->(val) { val.nil? } do
requires :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all'
end
optional :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' optional :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all'
given name_regex: ->(val) { val.nil? } do # require either name_regex (deprecated) or name_regex_delete, it is ok to have both
requires :name_regex_delete, type: String, desc: 'The tag name regexp to delete, specify .* to delete all' at_least_one_of :name_regex, :name_regex_delete
end
optional :name_regex_keep, type: String, desc: 'The tag name regexp to retain' optional :name_regex_keep, type: String, desc: 'The tag name regexp to retain'
optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name' optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name'
optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month' optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month'
......
...@@ -80,6 +80,13 @@ module Banzai ...@@ -80,6 +80,13 @@ module Banzai
end end
Gitlab::GitalyClient::BlobService.new(repository).get_blob_types(revision_paths, 1) Gitlab::GitalyClient::BlobService.new(repository).get_blob_types(revision_paths, 1)
rescue GRPC::Unavailable, GRPC::DeadlineExceeded => e
# Handle Gitaly connection issues gracefully
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
# Return all links as blob types
paths.collect do |path|
[path, :blob]
end
end end
def get_uri(html_attr) def get_uri(html_attr)
......
...@@ -9250,6 +9250,12 @@ msgstr "" ...@@ -9250,6 +9250,12 @@ msgstr ""
msgid "Geo|Remove" msgid "Geo|Remove"
msgstr "" msgstr ""
msgid "Geo|Remove entry"
msgstr ""
msgid "Geo|Remove tracking database entry"
msgstr ""
msgid "Geo|Repository sync capacity" msgid "Geo|Repository sync capacity"
msgstr "" msgstr ""
...@@ -9301,13 +9307,13 @@ msgstr "" ...@@ -9301,13 +9307,13 @@ msgstr ""
msgid "Geo|This is a primary node" msgid "Geo|This is a primary node"
msgstr "" msgstr ""
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed." msgid "Geo|Tracking database entry will be removed. Are you sure?"
msgstr "" msgstr ""
msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed." msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "" msgstr ""
msgid "Geo|Tracking entry will be removed. Are you sure?" msgid "Geo|Tracking entry for upload (%{type}/%{id}) was successfully removed."
msgstr "" msgstr ""
msgid "Geo|URL" msgid "Geo|URL"
......
HTMLFormElement.prototype.submit = jest.fn();
import './element_scroll_into_view'; import './element_scroll_into_view';
import './form_element';
import './get_client_rects'; import './get_client_rects';
import './inner_text'; import './inner_text';
import './window_scroll_to'; import './window_scroll_to';
......
...@@ -3,6 +3,8 @@ import { GlModal } from '@gitlab/ui'; ...@@ -3,6 +3,8 @@ import { GlModal } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import ConfirmModal from '~/vue_shared/components/confirm_modal.vue'; import ConfirmModal from '~/vue_shared/components/confirm_modal.vue';
jest.mock('~/lib/utils/csrf', () => ({ token: 'test-csrf-token' }));
describe('vue_shared/components/confirm_modal', () => { describe('vue_shared/components/confirm_modal', () => {
const testModalProps = { const testModalProps = {
path: `${TEST_HOST}/1`, path: `${TEST_HOST}/1`,
...@@ -39,45 +41,61 @@ describe('vue_shared/components/confirm_modal', () => { ...@@ -39,45 +41,61 @@ describe('vue_shared/components/confirm_modal', () => {
}); });
const findModal = () => wrapper.find(GlModal); const findModal = () => wrapper.find(GlModal);
const findForm = () => wrapper.find('form');
const findFormData = () =>
findForm()
.findAll('input')
.wrappers.map(x => ({ name: x.attributes('name'), value: x.attributes('value') }));
describe('template', () => { describe('template', () => {
beforeEach(() => { describe('when showModal is false', () => {
createComponent(); beforeEach(() => {
}); createComponent();
});
it('calls openModal on mount', () => { it('does not render GlModal', () => {
expect(actionSpies.openModal).toHaveBeenCalled(); expect(findModal().exists()).toBeFalsy();
});
}); });
it('renders GlModal', () => { describe('when showModal is true', () => {
expect(findModal().exists()).toBeTruthy(); beforeEach(() => {
createComponent({ showModal: true });
});
it('renders GlModal', () => {
expect(findModal().exists()).toBeTruthy();
expect(findModal().attributes()).toEqual(
expect.objectContaining({
modalid: testModalProps.modalAttributes.modalId,
oktitle: testModalProps.modalAttributes.okTitle,
okvariant: testModalProps.modalAttributes.okVariant,
}),
);
});
}); });
}); });
describe('methods', () => { describe('methods', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent({ showModal: true });
}); });
describe('submitModal', () => { it('does not submit form', () => {
beforeEach(() => { expect(findForm().element.submit).not.toHaveBeenCalled();
wrapper.vm.$refs.form.requestSubmit = jest.fn();
});
it('calls requestSubmit', () => {
wrapper.vm.submitModal();
expect(wrapper.vm.$refs.form.requestSubmit).toHaveBeenCalled();
});
}); });
describe('dismiss', () => { describe('when modal submitted', () => {
it('removes gl-modal', () => { beforeEach(() => {
expect(findModal().exists()).toBeTruthy(); findModal().vm.$emit('primary');
wrapper.vm.dismiss(); });
return wrapper.vm.$nextTick(() => { it('submits form', () => {
expect(findModal().exists()).toBeFalsy(); expect(findFormData()).toEqual([
}); { name: '_method', value: testModalProps.method },
{ name: 'authenticity_token', value: 'test-csrf-token' },
]);
expect(findForm().element.submit).toHaveBeenCalled();
}); });
}); });
}); });
......
...@@ -149,6 +149,34 @@ describe Banzai::Filter::RepositoryLinkFilter do ...@@ -149,6 +149,34 @@ describe Banzai::Filter::RepositoryLinkFilter do
end end
shared_examples :valid_repository do shared_examples :valid_repository do
it 'handles Gitaly unavailable exceptions gracefully' do
allow_next_instance_of(Gitlab::GitalyClient::BlobService) do |blob_service|
allow(blob_service).to receive(:get_blob_types).and_raise(GRPC::Unavailable)
end
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
an_instance_of(GRPC::Unavailable), project_id: project.id
)
doc = ""
expect { doc = filter(link('doc/api/README.md')) }.not_to raise_error
expect(doc.at_css('a')['href'])
.to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md"
end
it 'handles Gitaly timeout exceptions gracefully' do
allow_next_instance_of(Gitlab::GitalyClient::BlobService) do |blob_service|
allow(blob_service).to receive(:get_blob_types).and_raise(GRPC::DeadlineExceeded)
end
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
an_instance_of(GRPC::DeadlineExceeded), project_id: project.id
)
doc = ""
expect { doc = filter(link('doc/api/README.md')) }.not_to raise_error
expect(doc.at_css('a')['href'])
.to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md"
end
it 'rebuilds absolute URL for a file in the repo' do it 'rebuilds absolute URL for a file in the repo' do
doc = filter(link('/doc/api/README.md')) doc = filter(link('/doc/api/README.md'))
expect(doc.at_css('a')['href']) expect(doc.at_css('a')['href'])
......
...@@ -46,11 +46,11 @@ describe Clusters::Applications::CertManager do ...@@ -46,11 +46,11 @@ describe Clusters::Applications::CertManager do
expect(subject.name).to eq('certmanager') expect(subject.name).to eq('certmanager')
expect(subject.chart).to eq('certmanager/cert-manager') expect(subject.chart).to eq('certmanager/cert-manager')
expect(subject.repository).to eq('https://charts.jetstack.io') expect(subject.repository).to eq('https://charts.jetstack.io')
expect(subject.version).to eq('v0.9.1') expect(subject.version).to eq('v0.10.1')
expect(subject).to be_rbac expect(subject).to be_rbac
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
expect(subject.preinstall).to eq([ expect(subject.preinstall).to eq([
'kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.9/deploy/manifests/00-crds.yaml', 'kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.10/deploy/manifests/00-crds.yaml',
'kubectl label --overwrite namespace gitlab-managed-apps certmanager.k8s.io/disable-validation=true' 'kubectl label --overwrite namespace gitlab-managed-apps certmanager.k8s.io/disable-validation=true'
]) ])
expect(subject.postinstall).to eq([ expect(subject.postinstall).to eq([
...@@ -82,7 +82,7 @@ describe Clusters::Applications::CertManager do ...@@ -82,7 +82,7 @@ describe Clusters::Applications::CertManager do
let(:cert_manager) { create(:clusters_applications_cert_manager, :errored, version: '0.0.1') } let(:cert_manager) { create(:clusters_applications_cert_manager, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do it 'is initialized with the locked version' do
expect(subject.version).to eq('v0.9.1') expect(subject.version).to eq('v0.10.1')
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment