Commit 8a7aaf86 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖

Add latest changes from gitlab-org/[email protected]

parent 96781283
Pipeline #128991168 passed with stages
in 64 minutes and 34 seconds
......@@ -251,9 +251,10 @@ class GfmAutoComplete {
});
// Cache assignees list for easier filtering later
assignees = SidebarMediator.singleton?.store?.assignees?.map(
assignee => `${assignee.username} ${assignee.name}`,
);
assignees =
SidebarMediator.singleton?.store?.assignees?.map(
assignee => `${assignee.username} ${assignee.name}`,
) || [];
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
return match && match.length ? match[1] : null;
......
......@@ -48,6 +48,7 @@ document.addEventListener('DOMContentLoaded', () => {
leaveByUrl('project');
if (document.getElementById('js-tree-list')) {
initBlob();
import('ee_else_ce/repository')
.then(m => m.default())
.catch(e => {
......
<script>
import _ from 'underscore';
import { isString, isEmpty } from 'lodash';
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
......@@ -56,7 +56,7 @@ export default {
required: false,
default: undefined,
validator: ref =>
_.isUndefined(ref) || (_.isFinite(ref.iid) && _.isString(ref.path) && !_.isEmpty(ref.path)),
ref === undefined || (Number.isFinite(ref.iid) && isString(ref.path) && !isEmpty(ref.path)),
},
/**
......
<script>
import _ from 'underscore';
import { throttle } from 'lodash';
import { numberToHumanSize } from '../../../../lib/utils/number_utils';
export default {
......@@ -48,7 +48,7 @@ export default {
mounted() {
// The onImgLoad may have happened before the control was actually mounted
this.onImgLoad();
this.resizeThrottled = _.throttle(this.onImgLoad, 400);
this.resizeThrottled = throttle(this.onImgLoad, 400);
window.addEventListener('resize', this.resizeThrottled, false);
},
methods: {
......
<script>
import _ from 'underscore';
import { throttle } from 'lodash';
import { pixeliseValue } from '../../../lib/utils/dom_utils';
import ImageViewer from '../../../content_viewer/viewers/image_viewer.vue';
......@@ -98,7 +98,7 @@ export default {
this.swipeOldImgInfo = imgInfo;
this.prepareSwipe();
},
resize: _.throttle(function throttledResize() {
resize: throttle(function throttledResize() {
this.swipeBarPos = 0;
this.swipeWrapWidth = 0;
this.prepareSwipe();
......
<script>
import { GlLink } from '@gitlab/ui';
import _ from 'underscore';
import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale';
import icon from '../../../vue_shared/components/icon.vue';
function buildDocsLinkStart(path) {
return `<a href="${_.escape(path)}" target="_blank" rel="noopener noreferrer">`;
return `<a href="${esc(path)}" target="_blank" rel="noopener noreferrer">`;
}
export default {
......
<script>
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import _ from 'underscore';
import { unescape as unesc } from 'lodash';
import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
import Flash from '../../../flash';
......@@ -115,7 +115,7 @@ export default {
return text;
}
return _.unescape(stripHtml(richText).replace(/\n/g, ''));
return unesc(stripHtml(richText).replace(/\n/g, ''));
}
return '';
......
<script>
import { GlButton } from '@gitlab/ui';
import _ from 'underscore';
import { isString } from 'lodash';
import Icon from '~/vue_shared/components/icon.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar/default.vue';
import highlight from '~/lib/utils/highlight';
......@@ -17,7 +17,7 @@ export default {
project: {
type: Object,
required: true,
validator: p => _.isFinite(p.id) && _.isString(p.name) && _.isString(p.name_with_namespace),
validator: p => Number.isFinite(p.id) && isString(p.name) && isString(p.name_with_namespace),
},
selected: {
type: Boolean,
......
<script>
import _ from 'underscore';
import { debounce } from 'lodash';
import { GlLoadingIcon, GlSearchBoxByType, GlInfiniteScroll } from '@gitlab/ui';
import ProjectListItem from './project_list_item.vue';
......@@ -61,9 +61,9 @@ export default {
this.$emit('bottomReached');
},
isSelected(project) {
return Boolean(_.find(this.selectedProjects, { id: project.id }));
return this.selectedProjects.some(({ id }) => project.id === id);
},
onInput: _.debounce(function debouncedOnInput() {
onInput: debounce(function debouncedOnInput() {
this.$emit('searched', this.searchQuery);
}, SEARCH_INPUT_TIMEOUT_MS),
},
......
<script>
import _ from 'underscore';
import { isString } from 'lodash';
import { GlDropdown, GlDropdownDivider, GlDropdownItem } from '@gitlab/ui';
const isValidItem = item =>
_.isString(item.eventName) && _.isString(item.title) && _.isString(item.description);
isString(item.eventName) && isString(item.title) && isString(item.description);
export default {
components: {
......
import _ from 'underscore';
import { isEmpty } from 'lodash';
import { sprintf, __ } from '~/locale';
import { formatDate } from '~/lib/utils/datetime_utility';
import tooltip from '~/vue_shared/directives/tooltip';
......@@ -130,7 +130,7 @@ const mixins = {
return this.assignees.length > 0;
},
hasMilestone() {
return !_.isEmpty(this.milestone);
return !isEmpty(this.milestone);
},
iconName() {
if (this.isMergeRequest && this.isMerged) {
......
......@@ -50,6 +50,7 @@ module Snippets
snippet_saved
rescue => e # Rescuing all because we can receive Creation exceptions, GRPC exceptions, Git exceptions, ...
snippet.errors.add(:base, e.message)
log_error(e.message)
# If the commit action failed we need to remove the repository if exists
snippet.repository.remove if snippet.repository_exists?
......
......@@ -51,8 +51,9 @@ module Snippets
# the changes
create_commit(snippet) if snippet.repository_exists?
end
rescue
rescue => e
snippet.errors.add(:repository, 'Error updating the snippet')
log_error(e.message)
false
end
......
---
title: Fix not working File upload from Project overview page.
merge_request: 26828
author: Gilang Gumilar
type: fixed
---
title: Fix assignee dropdown on new issue page
merge_request: 26971
author:
type: fixed
---
title: Fix issue/MR state not being preserved when importing a project using Project
Import/Export
merge_request: 27816
author:
type: fixed
---
title: Replace underscore with lodash for ./app/assets/javascripts/vue_shared
merge_request: 25108
author: Tobias Spagert
type: other
......@@ -189,6 +189,38 @@ to explicitly add `-DskipTests` to your options.
If you still need to run tests during `mvn install`, add `-DskipTests=false` to
`MAVEN_CLI_OPTS`.
#### Using private Maven repos
If you have a private Maven repository that requires login credentials, you can use the
`MAVEN_CLI_OPTS` variable to specify a custom [`settings.xml`](http://maven.apache.org/settings.html)
file.
For example, you may have a settings file like this in your project source:
```xml
<settings>
<servers>
<server>
<id>my-server</id>
<username>${private.username}</username>
<username>${private.password}</username>
</server>
</servers>
</settings>
```
You can use this file through the following declaration in your `gitlab-ci.yml` file:
```yaml
license_scanning:
variables:
MAVEN_CLI_OPTS: --settings settings.xml -Dprivate.username=foo -Dprivate.password=bar
```
NOTE: **Note:**
If you don't want to expose the credentials in your `.gitlab-ci.yml` file, then
you can [set the variable in your project's settings](../../../ci/variables/README.md#via-the-ui).
### Selecting the version of Python
> - [Introduced](https://gitlab.com/gitlab-org/security-products/license-management/-/merge_requests/36) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
......
......@@ -340,6 +340,7 @@ methods:
- :diff_head_sha
- :source_branch_sha
- :target_branch_sha
- :state
events:
- :action
push_event_payload:
......@@ -350,6 +351,8 @@ methods:
- :list_type
ci_pipelines:
- :notes
issues:
- :state
preloads:
statuses:
......
......@@ -16433,6 +16433,9 @@ msgstr ""
msgid "Release|Something went wrong while saving the release details"
msgstr ""
msgid "Remediated: needs review"
msgstr ""
msgid "Remember me"
msgstr ""
......@@ -19961,6 +19964,9 @@ msgstr ""
msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."
msgstr ""
msgid "The vulnerability is no longer detected. Verify the vulnerability has been fixed or removed before changing its status."
msgstr ""
msgid "There are no GPG keys associated with this account."
msgstr ""
......@@ -22364,6 +22370,9 @@ msgstr ""
msgid "Vulnerability List"
msgstr ""
msgid "Vulnerability remediated. Review before resolving."
msgstr ""
msgid "Vulnerability-Check"
msgstr ""
......
......@@ -284,16 +284,17 @@ describe 'GFM autocomplete', :js do
context 'assignees' do
let(:issue_assignee) { create(:issue, project: project) }
let(:unassigned_user) { create(:user) }
before do
issue_assignee.update(assignees: [user])
visit project_issue_path(project, issue_assignee)
wait_for_requests
project.add_maintainer(unassigned_user)
end
it 'lists users who are currently not assigned to the issue when using /assign' do
visit project_issue_path(project, issue_assignee)
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/as')
......@@ -305,6 +306,19 @@ describe 'GFM autocomplete', :js do
wait_for_requests
expect(find('#at-view-users .atwho-view-ul')).not_to have_content(user.username)
expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
end
it 'shows dropdown on new issue form' do
visit new_project_issue_path(project)
textarea = find('#issue_description')
textarea.native.send_keys('/ass')
find('.atwho-view li', text: '/assign')
textarea.native.send_keys(:tab)
expect(find('#at-view-users .atwho-view-ul')).to have_content(unassigned_user.username)
expect(find('#at-view-users .atwho-view-ul')).to have_content(user.username)
end
end
......
......@@ -5,103 +5,32 @@ require 'spec_helper'
describe 'Projects > Files > User uploads files' do
include DropzoneHelper
let(:fork_message) do
"You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
let(:user) { create(:user) }
let(:project) { create(:project, :repository, name: 'Shop', creator: user) }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
let(:project_tree_path_root_ref) { project_tree_path(project, project.repository.root_ref) }
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
before do
project.add_maintainer(user)
sign_in(user)
end
context 'when an user has write access' do
context 'when a user has write access' do
before do
visit(project_tree_path_root_ref)
visit(project_tree_path(project))
end
it 'uploads and commit a new text file', :js do
find('.add-to-tree').click
click_link('Upload file')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
end
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Upload file')
expect(page).to have_content('New commit message')
expect(current_path).to eq(project_new_merge_request_path(project))
include_examples 'it uploads and commit a new text file'
click_link('Changes')
find("a[data-action='diffs']", text: 'Changes').click
wait_for_requests
expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis')
end
it 'uploads and commit a new image file', :js do
find('.add-to-tree').click
click_link('Upload file')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
fill_in(:branch_name, with: 'new_branch_name', visible: true)
click_button('Upload file')
end
wait_for_all_requests
visit(project_blob_path(project, 'new_branch_name/logo_sample.svg'))
expect(page).to have_css('.file-content img')
end
include_examples 'it uploads and commit a new image file'
end
context 'when an user does not have write access' do
context 'when a user does not have write access' do
before do
project2.add_reporter(user)
visit(project2_tree_path_root_ref)
end
it 'uploads and commit a new file to a forked project', :js, :sidekiq_might_not_need_inline do
find('.add-to-tree').click
click_link('Upload file')
expect(page).to have_content(fork_message)
find('.add-to-tree').click
click_link('Upload file')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
end
click_button('Upload file')
expect(page).to have_content('New commit message')
fork = user.fork_of(project2.reload)
expect(current_path).to eq(project_new_merge_request_path(fork))
find("a[data-action='diffs']", text: 'Changes').click
wait_for_requests
expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis')
visit(project_tree_path(project2))
end
include_examples 'it uploads and commit a new file to a forked project'
end
end
# frozen_string_literal: true
require 'spec_helper'
describe 'Projects > Show > User uploads files' do
include DropzoneHelper
let(:user) { create(:user) }
let(:project) { create(:project, :repository, name: 'Shop', creator: user) }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
before do
project.add_maintainer(user)
sign_in(user)
end
context 'when a user has write access' do
before do
visit(project_path(project))
end
include_examples 'it uploads and commit a new text file'
include_examples 'it uploads and commit a new image file'
end
context 'when a user does not have write access' do
before do
project2.add_reporter(user)
visit(project_path(project2))
end
include_examples 'it uploads and commit a new file to a forked project'
end
end
......@@ -359,31 +359,16 @@ describe('common_utils', () => {
});
describe('parseBoolean', () => {
const { parseBoolean } = commonUtils;
it('returns true for "true"', () => {
expect(parseBoolean('true')).toEqual(true);
});
it('returns false for "false"', () => {
expect(parseBoolean('false')).toEqual(false);
});
it('returns false for "something"', () => {
expect(parseBoolean('something')).toEqual(false);
});
it('returns false for null', () => {
expect(parseBoolean(null)).toEqual(false);
});
it('is idempotent', () => {
const input = ['true', 'false', 'something', null];
input.forEach(value => {
const result = parseBoolean(value);
expect(parseBoolean(result)).toBe(result);
});
it.each`
input | expected
${'true'} | ${true}
${'false'} | ${false}
${'something'} | ${false}
${null} | ${false}
${true} | ${true}
${false} | ${false}
`('returns $expected for $input', ({ input, expected }) => {
expect(commonUtils.parseBoolean(input)).toBe(expected);
});
});
......
import Vue from 'vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
import mountComponent from 'helpers/vue_mount_component_helper';
import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue';
describe('DeleteAccountModal component', () => {
const actionUrl = `${gl.TEST_HOST}/delete/user`;
const actionUrl = `${TEST_HOST}/delete/user`;
const username = 'hasnoname';
let Component;
let vm;
......@@ -43,7 +44,7 @@ describe('DeleteAccountModal component', () => {
it('does not accept empty password', done => {
const { form, input, submitButton } = findElements();
spyOn(form, 'submit');
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = '';
input.dispatchEvent(new Event('input'));
......@@ -61,7 +62,7 @@ describe('DeleteAccountModal component', () => {
it('submits form with password', done => {
const { form, input, submitButton } = findElements();
spyOn(form, 'submit');
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'anything';
input.dispatchEvent(new Event('input'));
......@@ -95,7 +96,7 @@ describe('DeleteAccountModal component', () => {
it('does not accept wrong username', done => {
const { form, input, submitButton } = findElements();
spyOn(form, 'submit');
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'this is wrong';
input.dispatchEvent(new Event('input'));
......@@ -113,7 +114,7 @@ describe('DeleteAccountModal component', () => {
it('submits form with correct username', done => {
const { form, input, submitButton } = findElements();
spyOn(form, 'submit');
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = username;
input.dispatchEvent(new Event('input'));
......
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'helpers/test_constants';
import mountComponent from 'helpers/vue_mount_component_helper';
import axios from '~/lib/utils/axios_utils';
import updateUsername from '~/profile/account/components/update_username.vue';
describe('UpdateUsername component', () => {
const rootUrl = gl.TEST_HOST;
const actionUrl = `${gl.TEST_HOST}/update/username`;
const rootUrl = TEST_HOST;
const actionUrl = `${TEST_HOST}/update/username`;
const username = 'hasnoname';
const newUsername = 'new_username';
let Component;
......@@ -106,7 +107,7 @@ describe('UpdateUsername component', () => {
const { confirmModalBtn } = findElements();
axiosMock.onPut(actionUrl).replyOnce(() => [200, { message: 'Username changed' }]);
spyOn(axios, 'put').and.callThrough();
jest.spyOn(axios, 'put');
vm.newUsername = newUsername;
......
......@@ -22,6 +22,7 @@ describe('ProjectSelector component', () => {
beforeEach(() => {
jasmine.clock().install();
jasmine.clock().mockDate();
wrapper = mount(Vue.extend(ProjectSelector), {
localVue,
......
......@@ -35,7 +35,7 @@ describe Gitlab::Asciidoc::IncludeProcessor do
expect(processor.send(:include_allowed?, 'foo.adoc', reader)).to be_truthy
end
it 'allows the Nth + 1 include' do
it 'allows the Nth include' do
(max_includes - 1).times { processor.send(:read_blob, ref, 'a.adoc') }
expect(processor.send(:include_allowed?, 'foo.adoc', reader)).to be_truthy
......
......@@ -199,6 +199,12 @@ describe Snippets::CreateService do
expect(SnippetRepository.count).to be_zero
end
it 'logs the error' do
expect(Gitlab::AppLogger).to receive(:error).with('foobar')
subject
end
it 'returns the error' do
response = subject
......
......@@ -167,13 +167,23 @@ describe Snippets::UpdateService do
expect(blob.data).to eq(options[:content])
end
it 'returns error when the commit action fails' do
allow(snippet.snippet_repository).to receive(:multi_files_action).and_raise(SnippetRepository::CommitError)
context 'when an error is raised' do
before do
allow(snippet.snippet_repository).to receive(:multi_files_action).and_raise(SnippetRepository::CommitError, 'foobar')
end
response = subject
it 'logs the error' do
expect(Gitlab::AppLogger).to receive(:error).with('foobar')
expect(response).to be_error
expect(response.payload[:snippet].errors.full_messages).to eq ['Repository Error updating the snippet']
subject
end
it 'returns error with generic error message' do
response = subject
expect(response).to be_error
expect(response.payload[:snippet].errors.full_messages).to eq ['Repository Error updating the snippet']
end
end