Skip to content
Snippets Groups Projects
Commit 7671216b authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent c2367afb
No related branches found
No related tags found
No related merge requests found
Showing
with 533 additions and 43 deletions
---
title: Fix Group Import API file upload when object storage is disabled
merge_request: 25715
author:
type: fixed
# frozen_string_literal: true
 
Gitlab.ee do
begin
public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
public_key = OpenSSL::PKey::RSA.new(public_key_file)
Gitlab::License.encryption_key = public_key
rescue
warn "WARNING: No valid license encryption key provided."
end
# Needed to run migration
if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
if ::License.block_changes? && message.present?
warn "WARNING: #{message}"
end
end
public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
public_key = OpenSSL::PKey::RSA.new(public_key_file)
Gitlab::License.encryption_key = public_key
rescue
warn "WARNING: No valid license encryption key provided."
end
Loading
Loading
@@ -3,6 +3,9 @@
module API
class Version < Grape::API
helpers ::API::Helpers::GraphqlHelpers
include APIGuard
allow_access_with_scope :read_user, if: -> (request) { request.get? }
 
before { authenticate! }
 
Loading
Loading
Loading
Loading
@@ -9448,6 +9448,9 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
 
msgid "Go to Pipelines"
msgstr ""
msgid "Go to Webhooks"
msgstr ""
 
Loading
Loading
@@ -11687,6 +11690,12 @@ msgstr ""
msgid "MERGED"
msgstr ""
 
msgid "MR widget|Take a look at our %{beginnerLinkStart}Beginner's Guide to Continuous Integration%{beginnerLinkEnd} and our %{exampleLinkStart}examples of GitLab CI/CD%{exampleLinkEnd} to see all the cool stuff you can do with it."
msgstr ""
msgid "MR widget|The pipeline will now run automatically every time you commit code. Pipelines are useful for deploying static web pages, detecting vulnerabilities in dependencies, static or dynamic application security testing (SAST and DAST), and so much more!"
msgstr ""
msgid "MRApprovals|Approved by"
msgstr ""
 
Loading
Loading
@@ -12960,6 +12969,9 @@ msgstr ""
msgid "No template"
msgstr ""
 
msgid "No thanks, don't show this again"
msgstr ""
msgid "No value set by top-level parent group."
msgstr ""
 
Loading
Loading
@@ -17650,6 +17662,9 @@ msgstr ""
msgid "Show latest version"
msgstr ""
 
msgid "Show me how"
msgstr ""
msgid "Show only direct members"
msgstr ""
 
Loading
Loading
@@ -19023,6 +19038,9 @@ msgstr ""
msgid "Thanks! Don't show me this again"
msgstr ""
 
msgid "That's it, well done!%{celebrate}"
msgstr ""
msgid "The \"%{group_path}\" group allows you to sign in with your Single Sign-On Account"
msgstr ""
 
Loading
Loading
@@ -23346,6 +23364,9 @@ msgstr ""
msgid "mrWidget|Approved by"
msgstr ""
 
msgid "mrWidget|Are you adding technical debt or code vulnerabilities?"
msgstr ""
msgid "mrWidget|Cancel automatic merge"
msgstr ""
 
Loading
Loading
@@ -23379,6 +23400,9 @@ msgstr ""
msgid "mrWidget|Deployment statistics are not available currently"
msgstr ""
 
msgid "mrWidget|Detect issues before deployment with a CI pipeline"
msgstr ""
msgid "mrWidget|Did not close"
msgstr ""
 
Loading
Loading
@@ -23556,6 +23580,9 @@ msgstr ""
msgid "mrWidget|Your password"
msgstr ""
 
msgid "mrWidget|a quick guide that'll show you how to create"
msgstr ""
msgid "mrWidget|branch does not exist."
msgstr ""
 
Loading
Loading
@@ -23565,6 +23592,15 @@ msgstr ""
msgid "mrWidget|into"
msgstr ""
 
msgid "mrWidget|one. Make your code more secure and more"
msgstr ""
msgid "mrWidget|robust in just a minute."
msgstr ""
msgid "mrWidget|that continuously tests your code. We created"
msgstr ""
msgid "mrWidget|to be added to the merge train when the pipeline succeeds"
msgstr ""
 
Loading
Loading
import pipelineTourSuccess from '~/blob/pipeline_tour_success_modal.vue';
import { shallowMount } from '@vue/test-utils';
import Cookies from 'js-cookie';
import { GlSprintf, GlModal } from '@gitlab/ui';
describe('PipelineTourSuccessModal', () => {
let wrapper;
let cookieSpy;
const goToPipelinesPath = 'some_pipeline_path';
const commitCookie = 'some_cookie';
beforeEach(() => {
wrapper = shallowMount(pipelineTourSuccess, {
propsData: {
goToPipelinesPath,
commitCookie,
},
});
cookieSpy = jest.spyOn(Cookies, 'remove');
});
afterEach(() => {
wrapper.destroy();
});
it('has expected structure', () => {
const modal = wrapper.find(GlModal);
const sprintf = modal.find(GlSprintf);
expect(modal.attributes('title')).toContain("That's it, well done!");
expect(sprintf.exists()).toBe(true);
});
it('calls to remove cookie', () => {
wrapper.vm.disableModalFromRenderingAgain();
expect(cookieSpy).toHaveBeenCalledWith(commitCookie);
});
});
Loading
Loading
@@ -8,7 +8,7 @@ let handlers;
export function mockTracking(category = '_category_', documentOverride, spyMethod) {
document = documentOverride || window.document;
window.snowplow = () => {};
Tracking.bindDocument(category, document);
handlers = Tracking.bindDocument(category, document);
return spyMethod ? spyMethod(Tracking, 'event') : null;
}
 
Loading
Loading
Loading
Loading
@@ -226,6 +226,14 @@ describe('Tracking', () => {
};
});
 
it('calls the event method with no category or action defined', () => {
mixin.trackingCategory = mixin.trackingCategory();
mixin.trackingOptions = mixin.trackingOptions();
mixin.track();
expect(eventSpy).toHaveBeenCalledWith(undefined, undefined, {});
});
it('calls the event method', () => {
mixin.trackingCategory = mixin.trackingCategory();
mixin.trackingOptions = mixin.trackingOptions();
Loading
Loading
import { mount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import suggestPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
import stubChildren from 'helpers/stub_children';
import PipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
 
describe('MRWidgetHeader', () => {
let wrapper;
const pipelinePath = '/foo/bar/add/pipeline/path';
const pipelineSvgPath = '/foo/bar/pipeline/svg/path';
const humanAccess = 'maintainer';
const iconName = 'status_notfound';
 
beforeEach(() => {
wrapper = mount(suggestPipelineComponent, {
propsData: { pipelinePath },
propsData: { pipelinePath, pipelineSvgPath, humanAccess },
stubs: {
...stubChildren(PipelineTourState),
},
});
});
 
Loading
Loading
@@ -22,30 +30,47 @@ describe('MRWidgetHeader', () => {
it('renders add pipeline file link', () => {
const link = wrapper.find(GlLink);
 
return wrapper.vm.$nextTick().then(() => {
expect(link.exists()).toBe(true);
expect(link.attributes().href).toBe(pipelinePath);
});
expect(link.exists()).toBe(true);
expect(link.attributes().href).toBe(pipelinePath);
});
 
it('renders the expected text', () => {
const messageText = /\s*No pipeline\s*Add the .gitlab-ci.yml file\s*to create one./;
 
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.text()).toMatch(messageText);
});
expect(wrapper.text()).toMatch(messageText);
});
 
it('renders widget icon', () => {
const icon = wrapper.find(MrWidgetIcon);
 
return wrapper.vm.$nextTick().then(() => {
expect(icon.exists()).toBe(true);
expect(icon.props()).toEqual(
expect.objectContaining({
name: iconName,
}),
);
expect(icon.exists()).toBe(true);
expect(icon.props()).toEqual(
expect.objectContaining({
name: iconName,
}),
);
});
describe('tracking', () => {
let spy;
beforeEach(() => {
spy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
});
it('send an event when ok button is clicked', () => {
const link = wrapper.find(GlLink);
triggerEvent(link.element);
expect(spy).toHaveBeenCalledWith('_category_', 'click_link', {
label: 'no_pipeline_noticed',
property: humanAccess,
value: '30',
});
});
});
});
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import { GlPopover } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
import pipelineTourState from '~/vue_merge_request_widget/components/states/mr_widget_pipeline_tour.vue';
import { popoverProps, cookieKey } from './pipeline_tour_mock_data';
describe('MRWidgetPipelineTour', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
describe(`when ${cookieKey} cookie is set`, () => {
beforeEach(() => {
Cookies.set(cookieKey, true);
wrapper = shallowMount(pipelineTourState, {
propsData: popoverProps,
});
});
it('does not render the popover', () => {
const popover = wrapper.find(GlPopover);
expect(popover.exists()).toBe(false);
});
describe('tracking', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
});
it('does not call tracking', () => {
expect(trackingSpy).not.toHaveBeenCalled();
});
});
});
describe(`when ${cookieKey} cookie is not set`, () => {
const findOkBtn = () => wrapper.find({ ref: 'ok' });
const findDismissBtn = () => wrapper.find({ ref: 'no-thanks' });
beforeEach(() => {
Cookies.remove(cookieKey);
wrapper = shallowMount(pipelineTourState, {
propsData: popoverProps,
});
});
it('renders the popover', () => {
const popover = wrapper.find(GlPopover);
expect(popover.exists()).toBe(true);
});
it('renders the show me how button', () => {
const button = findOkBtn();
expect(button.exists()).toBe(true);
expect(button.attributes().category).toBe('primary');
});
it('renders the dismiss button', () => {
const button = findDismissBtn();
expect(button.exists()).toBe(true);
expect(button.attributes().category).toBe('secondary');
});
it('renders the empty pipelines image', () => {
const image = wrapper.find('img');
expect(image.exists()).toBe(true);
expect(image.attributes().src).toBe(popoverProps.pipelineSvgPath);
});
describe('tracking', () => {
let trackingSpy;
beforeEach(() => {
trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn);
});
afterEach(() => {
unmockTracking();
});
it('send event for basic view of popover', () => {
document.body.dataset.page = 'projects:merge_requests:show';
wrapper.vm.trackOnShow();
expect(trackingSpy).toHaveBeenCalledWith(undefined, undefined, {
label: popoverProps.trackLabel,
property: popoverProps.humanAccess,
});
});
it('send an event when ok button is clicked', () => {
const okBtn = findOkBtn();
triggerEvent(okBtn.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: popoverProps.trackLabel,
property: popoverProps.humanAccess,
value: '10',
});
});
it('send an event when dismiss button is clicked', () => {
const dismissBtn = findDismissBtn();
triggerEvent(dismissBtn.element);
expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', {
label: popoverProps.trackLabel,
property: popoverProps.humanAccess,
value: '20',
});
});
});
describe('dismissPopover', () => {
it('updates popoverDismissed', () => {
const button = findDismissBtn();
const popover = wrapper.find(GlPopover);
button.vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(Cookies.get(cookieKey)).toBe('true');
expect(popover.exists()).toBe(false);
});
});
});
});
});
});
export const popoverProps = {
pipelinePath: '/foo/bar/add/pipeline/path',
pipelineSvgPath: 'assets/illustrations/something.svg',
humanAccess: 'maintainer',
popoverTarget: 'suggest-popover',
popoverContainer: 'suggest-pipeline',
trackLabel: 'some_tracking_label',
};
export const cookieKey = 'suggest_pipeline_dismissed';
import Vue from 'vue';
import { file } from 'spec/ide/helpers';
import { file } from 'jest/ide/helpers';
import FileRow from '~/vue_shared/components/file_row.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
import { mount } from '@vue/test-utils';
 
describe('File row component', () => {
let vm;
let wrapper;
 
function createComponent(propsData) {
const FileRowComponent = Vue.extend(FileRow);
vm = mountComponent(FileRowComponent, propsData);
wrapper = mount(FileRow, {
propsData,
});
}
 
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
 
it('renders name', () => {
const fileName = 't4';
createComponent({
file: file('t4'),
file: file(fileName),
level: 0,
});
 
const name = vm.$el.querySelector('.file-row-name');
const name = wrapper.find('.file-row-name');
 
expect(name.textContent.trim()).toEqual(vm.file.name);
expect(name.text().trim()).toEqual(fileName);
});
 
it('emits toggleTreeOpen on click', () => {
const fileName = 't3';
createComponent({
file: {
...file('t3'),
...file(fileName),
type: 'tree',
},
level: 0,
});
spyOn(vm, '$emit').and.stub();
jest.spyOn(wrapper.vm, '$emit');
 
vm.$el.click();
wrapper.element.click();
 
expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
expect(wrapper.vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', fileName);
});
 
it('calls scrollIntoView if made active', done => {
it('calls scrollIntoView if made active', () => {
createComponent({
file: {
...file(),
Loading
Loading
@@ -52,14 +53,16 @@ describe('File row component', () => {
level: 0,
});
 
spyOn(vm, 'scrollIntoView').and.stub();
jest.spyOn(wrapper.vm, 'scrollIntoView');
 
vm.file.active = true;
vm.$nextTick(() => {
expect(vm.scrollIntoView).toHaveBeenCalled();
wrapper.setProps({
file: Object.assign({}, wrapper.props('file'), {
active: true,
}),
});
 
done();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.vm.scrollIntoView).toHaveBeenCalled();
});
});
 
Loading
Loading
@@ -69,7 +72,7 @@ describe('File row component', () => {
level: 2,
});
 
expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
expect(wrapper.find('.file-row-name').element.style.marginLeft).toBe('32px');
});
 
it('renders header for file', () => {
Loading
Loading
@@ -82,6 +85,6 @@ describe('File row component', () => {
level: 0,
});
 
expect(vm.$el.classList).toContain('js-file-row-header');
expect(wrapper.element.classList).toContain('js-file-row-header');
});
});
Loading
Loading
@@ -27,7 +27,7 @@ describe BlobHelper do
end
 
describe "#edit_blob_link" do
let(:namespace) { create(:namespace, name: 'gitlab' )}
let(:namespace) { create(:namespace, name: 'gitlab') }
let(:project) { create(:project, :repository, namespace: namespace) }
 
before do
Loading
Loading
@@ -202,6 +202,90 @@ describe BlobHelper do
end
end
end
describe '#show_suggest_pipeline_creation_celebration?' do
let(:blob) { fake_blob(path: Gitlab::FileDetector::PATTERNS[:gitlab_ci]) }
let(:current_user) { create(:user) }
before do
assign(:project, project)
assign(:blob, blob)
assign(:commit, double('Commit', sha: 'whatever'))
helper.request.cookies["suggest_gitlab_ci_yml_commit_#{project.id}"] = 'true'
allow(blob).to receive(:auxiliary_viewer).and_return(double('viewer', valid?: true))
allow(helper).to receive(:current_user).and_return(current_user)
end
context 'experiment enabled' do
before do
allow(helper).to receive(:experiment_enabled?).and_return(true)
end
it 'is true' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_truthy
end
context 'file is invalid format' do
before do
allow(blob).to receive(:auxiliary_viewer).and_return(double('viewer', valid?: false))
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'path is not a ci file' do
before do
allow(blob).to receive(:path).and_return('something_bad')
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'does not use the default ci config' do
before do
project.ci_config_path = 'something_bad'
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
context 'does not have the needed cookie' do
before do
helper.request.cookies.delete "suggest_gitlab_ci_yml_commit_#{project.id}"
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
end
context 'experiment disabled' do
before do
allow(helper).to receive(:experiment_enabled?).and_return(false)
end
it 'is false' do
expect(helper.show_suggest_pipeline_creation_celebration?).to be_falsey
end
end
end
end
describe 'suggest_pipeline_commit_cookie_name' do
let(:project) { create(:project) }
it 'uses project id to make up the cookie name' do
assign(:project, project)
expect(helper.suggest_pipeline_commit_cookie_name).to eq "suggest_gitlab_ci_yml_commit_#{project.id}"
end
end
 
describe '#ide_edit_path' do
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ let handlers;
export function mockTracking(category = '_category_', documentOverride, spyMethod) {
document = documentOverride || window.document;
window.snowplow = () => {};
Tracking.bindDocument(category, document);
handlers = Tracking.bindDocument(category, document);
return spyMethod ? spyMethod(Tracking, 'event') : null;
}
 
Loading
Loading
Loading
Loading
@@ -28,6 +28,7 @@ export default {
},
merge_status: 'can_be_merged',
merge_user_id: null,
pipelines_empty_svg_path: '/path/to/svg',
source_branch: 'daaaa',
source_branch_link: 'daaaa',
source_project_id: 19,
Loading
Loading
Loading
Loading
@@ -96,5 +96,11 @@ describe('MergeRequestStore', () => {
 
expect(store.humanAccess).toEqual('Maintainer');
});
it('should set pipelinesEmptySvgPath', () => {
store.setData({ ...mockData });
expect(store.pipelinesEmptySvgPath).toBe('/path/to/svg');
});
});
});
Loading
Loading
@@ -19,12 +19,14 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
context 'with a metrics charts placeholder' do
let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) }
 
it_behaves_like 'a supported metrics dashboard url'
it_behaves_like 'redacts the embed placeholder'
it_behaves_like 'retains the embed placeholder when applicable'
 
context 'for a grafana dashboard' do
let(:url) { urls.project_grafana_api_metrics_dashboard_url(project, embedded: true) }
 
it_behaves_like 'a supported metrics dashboard url'
it_behaves_like 'redacts the embed placeholder'
it_behaves_like 'retains the embed placeholder when applicable'
end
 
context 'the user has requisite permissions' do
Loading
Loading
Loading
Loading
@@ -12,17 +12,55 @@ describe API::Version do
end
end
 
context 'when authenticated' do
context 'when authenticated as user' do
let(:user) { create(:user) }
 
it 'returns the version information' do
get api('/version', user)
 
expect(response).to have_gitlab_http_status(200)
expect(json_response['version']).to eq(Gitlab::VERSION)
expect(json_response['revision']).to eq(Gitlab.revision)
expect_version
end
end
context 'when authenticated with token' do
let(:personal_access_token) { create(:personal_access_token, scopes: scopes) }
context 'with api scope' do
let(:scopes) { %i(api) }
it 'returns the version information' do
get api('/version', personal_access_token: personal_access_token)
expect_version
end
end
context 'with read_user scope' do
let(:scopes) { %i(read_user) }
it 'returns the version information' do
get api('/version', personal_access_token: personal_access_token)
expect_version
end
end
context 'with neither api nor read_user scope' do
let(:scopes) { %i(read_repository) }
it 'returns authorization error' do
get api('/version', personal_access_token: personal_access_token)
expect(response).to have_gitlab_http_status(403)
end
end
end
def expect_version
expect(response).to have_gitlab_http_status(200)
expect(json_response['version']).to eq(Gitlab::VERSION)
expect(json_response['revision']).to eq(Gitlab.revision)
end
end
 
context 'with graphql enabled' do
Loading
Loading
Loading
Loading
@@ -75,8 +75,9 @@ describe MergeRequestWidgetEntity do
let(:role) { :developer }
 
it 'has add ci config path' do
expect(subject[:merge_request_add_ci_config_path])
.to eq("/#{resource.project.full_path}/-/new/#{resource.source_branch}?commit_message=Add+.gitlab-ci.yml&file_name=.gitlab-ci.yml")
expected_path = "/#{resource.project.full_path}/-/new/#{resource.source_branch}?commit_message=Add+.gitlab-ci.yml&file_name=.gitlab-ci.yml&suggest_gitlab_ci_yml=true"
expect(subject[:merge_request_add_ci_config_path]).to eq(expected_path)
end
 
context 'when source project is missing' do
Loading
Loading
# frozen_string_literal: true
 
RSpec.shared_examples 'a supported metrics dashboard url' do
RSpec.shared_examples 'redacts the embed placeholder' do
context 'no user is logged in' do
it 'redacts the placeholder' do
expect(doc.to_s).to be_empty
Loading
Loading
@@ -14,7 +14,9 @@ RSpec.shared_examples 'a supported metrics dashboard url' do
expect(doc.to_s).to be_empty
end
end
end
 
RSpec.shared_examples 'retains the embed placeholder when applicable' do
context 'the user has requisite permissions' do
let(:user) { create(:user) }
let(:doc) { filter(input, current_user: user) }
Loading
Loading
@@ -22,7 +24,7 @@ RSpec.shared_examples 'a supported metrics dashboard url' do
it 'leaves the placeholder' do
project.add_maintainer(user)
 
expect(doc.to_s).to eq(input)
expect(CGI.unescapeHTML(doc.to_s)).to eq(input)
end
end
end
Loading
Loading
@@ -51,4 +51,10 @@ describe ImportExportUploader do
end
end
end
describe '.workhorse_local_upload_path' do
it 'returns path that includes uploads dir' do
expect(described_class.workhorse_local_upload_path).to end_with('/uploads/tmp/uploads')
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment