Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-08-24 18:10:53 +00:00
parent 234dc40a12
commit e9322e019b
23 changed files with 311 additions and 40 deletions

View File

@ -1,7 +1,7 @@
// NOTE: This module will be used in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52044
import { memoize } from 'lodash';
export const RECAPTCHA_API_URL_PREFIX = 'https://www.google.com/recaptcha/api.js';
export const RECAPTCHA_API_URL_PREFIX = window.gon.recaptcha_api_server_url;
export const RECAPTCHA_ONLOAD_CALLBACK_NAME = 'recaptchaOnloadCallback';
/**

View File

@ -40,24 +40,31 @@ export default {
return this.item.type === ITEM_TYPE.GROUP;
},
},
methods: {
displayValue(value) {
return this.isGroup && value !== undefined;
},
},
};
</script>
<template>
<div class="stats gl-text-gray-500">
<item-stats-value
v-if="isGroup"
v-if="displayValue(item.subgroupCount)"
:title="__('Subgroups')"
:value="item.subgroupCount"
css-class="number-subgroups gl-ml-5"
icon-name="folder-o"
data-testid="subgroups-count"
/>
<item-stats-value
v-if="isGroup"
v-if="displayValue(item.projectCount)"
:title="__('Projects')"
:value="item.projectCount"
css-class="number-projects gl-ml-5"
icon-name="bookmark"
data-testid="projects-count"
/>
<item-stats-value
v-if="isGroup"

View File

@ -64,6 +64,8 @@ import syntaxHighlight from './syntax_highlight';
// </div>
//
// <100ms is typically indistinguishable from "instant" for users, but allows for re-rendering
const FAST_DELAY_FOR_RERENDER = 75;
// Store the `location` object, allowing for easier stubbing in tests
let { location } = window;
@ -83,6 +85,8 @@ export default class MergeRequestTabs {
this.peek = document.getElementById('js-peek');
this.paddingTop = 16;
this.scrollPositions = {};
this.commitsTab = document.querySelector('.tab-content .commits.tab-pane');
this.currentTab = null;
@ -136,11 +140,30 @@ export default class MergeRequestTabs {
}
}
storeScroll() {
if (this.currentTab) {
this.scrollPositions[this.currentTab] = document.documentElement.scrollTop;
}
}
recallScroll(action) {
const storedPosition = this.scrollPositions[action];
setTimeout(() => {
window.scrollTo({
top: storedPosition && storedPosition > 0 ? storedPosition : 0,
left: 0,
behavior: 'auto',
});
}, FAST_DELAY_FOR_RERENDER);
}
clickTab(e) {
if (e.currentTarget) {
e.stopImmediatePropagation();
e.preventDefault();
this.storeScroll();
const { action } = e.currentTarget.dataset || {};
if (isMetaClick(e)) {
@ -210,8 +233,14 @@ export default class MergeRequestTabs {
this.resetViewContainer();
this.mountPipelinesView();
} else {
this.mergeRequestTabPanes.querySelector('#notes').style.display = 'block';
this.mergeRequestTabs.querySelector('.notes-tab').classList.add('active');
const notesTab = this.mergeRequestTabs.querySelector('.notes-tab');
const notesPane = this.mergeRequestTabPanes.querySelector('#notes');
if (notesPane) {
notesPane.style.display = 'block';
}
if (notesTab) {
notesTab.classList.add('active');
}
if (bp.getBreakpointSize() !== 'xs') {
this.expandView();
@ -221,6 +250,8 @@ export default class MergeRequestTabs {
}
$('.detail-page-description').renderGFM();
this.recallScroll(action);
} else if (action === this.currentAction) {
// ContentTop is used to handle anything at the top of the page before the main content
const mainContentContainer = document.querySelector('.content-wrapper');

View File

@ -111,8 +111,11 @@ class GroupPolicy < BasePolicy
enable :read_issue_board
enable :read_group_member
enable :read_custom_emoji
enable :read_counts
end
rule { ~public_group & ~has_access }.prevent :read_counts
rule { ~can?(:read_group) }.policy do
prevent :read_design_activity
end

View File

@ -37,9 +37,13 @@ class GroupChildEntity < Grape::Entity
if: lambda { |_instance, _options| project? }
# Group only attributes
expose :children_count, :parent_id, :project_count, :subgroup_count,
expose :children_count, :parent_id,
unless: lambda { |_instance, _options| project? }
expose :subgroup_count, if: lambda { |group| access_group_counts?(group) }
expose :project_count, if: lambda { |group| access_group_counts?(group) }
expose :leave_path, unless: lambda { |_instance, _options| project? } do |instance|
leave_group_members_path(instance)
end
@ -52,10 +56,6 @@ class GroupChildEntity < Grape::Entity
end
end
expose :number_projects_with_delimiter, unless: lambda { |_instance, _options| project? } do |instance|
number_with_delimiter(instance.project_count)
end
expose :number_users_with_delimiter, unless: lambda { |_instance, _options| project? } do |instance|
number_with_delimiter(instance.member_count)
end
@ -66,6 +66,10 @@ class GroupChildEntity < Grape::Entity
private
def access_group_counts?(group)
!project? && can?(request.current_user, :read_counts, group)
end
# rubocop: disable CodeReuse/ActiveRecord
def membership
return unless request.current_user

View File

@ -40,10 +40,6 @@ class GroupEntity < Grape::Entity
GroupsFinder.new(request.current_user, parent: group).execute.any?
end
expose :number_projects_with_delimiter do |group|
number_with_delimiter(GroupProjectsFinder.new(group: group, current_user: request.current_user).execute.count)
end
expose :number_users_with_delimiter do |group|
number_with_delimiter(group.users.count)
end

View File

@ -5,15 +5,19 @@ module Packages
protected
def find_or_create_package!(package_type, name: params[:name], version: params[:version])
# safe_find_or_create_by! was originally called here.
# We merely switched to `find_or_create_by!`
# rubocop: disable CodeReuse/ActiveRecord
project
.packages
.with_package_type(package_type)
.safe_find_or_create_by!(name: name, version: version) do |package|
.find_or_create_by!(name: name, version: version) do |package|
package.status = params[:status] if params[:status]
package.creator = package_creator
add_build_info(package)
end
# rubocop: enable CodeReuse/ActiveRecord
end
def create_package!(package_type, attrs = {})

View File

@ -23,8 +23,3 @@ running on.
[strace-parser](https://gitlab.com/wchandler/strace-parser) is a small tool to analyze
and summarize raw `strace` data.
## Pritaly
[Pritaly](https://gitlab.com/wchandler/pritaly) takes Gitaly logs and colorizes output
or converts the logs to JSON.

View File

@ -10,7 +10,8 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/50144) in GitLab 11.3.
Interactive web terminals give the user access to a terminal in GitLab for
running one-off commands for their CI pipeline, enabling debugging with SSH. Since this is giving the user
running one-off commands for their CI pipeline. You can think of it like a method for
debugging with SSH, but done directly from the job page. Since this is giving the user
shell access to the environment where [GitLab Runner](https://docs.gitlab.com/runner/)
is deployed, some [security precautions](../../administration/integration/terminal.md#security) were
taken to protect the users.

View File

@ -99,7 +99,8 @@ are very appreciative of the work done by translators and proofreaders!
- André Gama - [GitLab](https://gitlab.com/andregamma), [Crowdin](https://crowdin.com/profile/ToeOficial)
- Eduardo Addad de Oliveira - [GitLab](https://gitlab.com/eduardoaddad), [Crowdin](https://crowdin.com/profile/eduardoaddad)
- Romanian
- Proofreaders needed.
- Mircea Pop - [GitLab](https://gitlab.com/eeex)[Crowdin](https://crowdin.com/profile/eex)
- Rareș Pița - [GitLab](https://gitlab.com/dlphin)[Crowdin](https://crowdin.com/profile/dlphin)
- Russian
- Nikita Grylov - [GitLab](https://gitlab.com/nixel2007), [Crowdin](https://crowdin.com/profile/nixel2007)
- Alexy Lustin - [GitLab](https://gitlab.com/allustin), [Crowdin](https://crowdin.com/profile/lustin)

View File

@ -60,6 +60,8 @@ Redis version 6.0 or higher is recommended, as this is what ships with
The necessary hard drive space largely depends on the size of the repositories you want to store in GitLab but as a *rule of thumb* you should have at least as much free space as all your repositories combined take up.
The Omnibus GitLab package requires about 2.5 GB of storage space for installation.
If you want to be flexible about growing your hard drive space in the future consider mounting it using [logical volume management (LVM)](https://en.wikipedia.org/wiki/Logical_volume_management) so you can add more hard drives when you need them.
Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume.

View File

@ -4,7 +4,7 @@ group: Integrations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Configure the Jira integration in GitLab
# Configure the Jira integration in GitLab **(FREE)**
You can set up the [Jira integration](index.md#jira-integration)
by configuring your project settings in GitLab.

View File

@ -108,3 +108,68 @@ clicking the **Resolve** button near the top of the page.
Marking an error as resolved indicates that the error has stopped firing events. If a GitLab issue is linked to the error, then the issue closes.
If another event occurs, the error reverts to unresolved.
## Integrated error tracking
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329596) in GitLab 14.3.
Integrated error tracking is a lightweight alternative to Sentry backend.
You still use Sentry SDK with your application. But you don't need to deploy Sentry
or set up for cloud-hosted Sentry. Instead, you use GitLab as a backend for it.
Sentry backend automatically assigns a Data Source Name (DSN) for every project you create.
GitLab does the same. You should be able to find a DSN for your project in the GitLab error tracking
settings. By using a GitLab-provided DSN, your application connects to GitLab to report an error.
Those errors are stored in the GitLab database and rendered by the GitLab UI, in the same way as
Sentry integration.
### Feature flag
The integrated error tracking feature is behind a feature flag that is disabled by default.
To enable it:
```ruby
Feature.enable(:integrated_error_tracking)
```
To disable it:
```ruby
Feature.disable(:integrated_error_tracking)
```
### Project settings
The feature should be enabled on the project level. However, there is no UI to enable this feature yet.
You must use the GitLab API to enable it.
#### How to enable
1. Enable the `integrated` error tracking setting for your project:
```shell
curl --request PATCH --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/projects/PROJECT_ID/error_tracking/settings?active=true&integrated=true"
```
1. Create a client key (DSN) to use with Sentry SDK in your application. Make sure to save the
response, as it contains a DSN:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/projects/PROJECT_ID/error_tracking/client_keys"
```
1. Take the DSN from the previous step and configure your Sentry SDK with it. Errors are now
reported to the GitLab collector and are visible in the [GitLab UI](#error-tracking-list).
#### How to disable
To disable the feature, run this command. This is the same command as the one that enables the
feature, but with a `false` value instead:
```shell
curl --request PATCH --header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/projects/PROJECT_ID/error_tracking/settings?active=false&integrated=false"
```

View File

@ -107,11 +107,11 @@ Download and install Go (for Linux, 64-bit):
# Remove former Go installation folder
sudo rm -rf /usr/local/go
curl --remote-name --progress "https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz"
echo '512103d7ad296467814a6e3f635631bd35574cab3369a97a323c9a585ccaa569 go1.13.5.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.13.5.linux-amd64.tar.gz
curl --remote-name --progress-bar "https://dl.google.com/go/go1.15.12.linux-amd64.tar.gz"
echo 'bbdb935699e0b24d90e2451346da76121b2412d30930eabcd80907c230d098b7 go1.15.12.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.15.12.linux-amd64.tar.gz
sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
rm go1.13.5.linux-amd64.tar.gz
rm go1.15.12.linux-amd64.tar.gz
```

View File

@ -24,9 +24,11 @@ When a fast-forward merge is not possible, the user is given the option to rebas
## Enabling fast-forward merges
1. Navigate to your project's **Settings** and search for the 'Merge method'
1. Select the **Fast-forward merge** option
1. Hit **Save changes** for the changes to take effect
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Settings > General**.
1. Expand **Merge requests**.
1. In the **Merge method** section, select **Fast-forward merge**.
1. Select **Save changes**.
Now, when you visit the merge request page, you can accept it
**only if a fast-forward merge is possible**.

View File

@ -21,6 +21,8 @@ module Gitlab
gon.sentry_environment = Gitlab.config.sentry.environment
end
gon.recaptcha_api_server_url = ::Recaptcha.configuration.api_server_url
gon.recaptcha_sitekey = Gitlab::CurrentSettings.recaptcha_site_key
gon.gitlab_url = Gitlab.config.gitlab.url
gon.revision = Gitlab.revision
gon.feature_category = Gitlab::ApplicationContext.current_context_attribute(:feature_category).presence

View File

@ -29646,6 +29646,9 @@ msgstr ""
msgid "SecurityOrchestration|Enforce security for this project. %{linkStart}More information.%{linkEnd}"
msgstr ""
msgid "SecurityOrchestration|If you are using Auto DevOps, your %{monospacedStart}auto-deploy-values.yaml%{monospacedEnd} file will not be updated if you change a policy in this section. Auto DevOps users should make changes by following the %{linkStart}Container Network Policy documentation%{linkEnd}."
msgstr ""
msgid "SecurityOrchestration|Network"
msgstr ""
@ -29676,9 +29679,18 @@ msgstr ""
msgid "SecurityOrchestration|Select security project"
msgstr ""
msgid "SecurityOrchestration|Sorry, your filter produced no results."
msgstr ""
msgid "SecurityOrchestration|There was a problem creating the new security policy"
msgstr ""
msgid "SecurityOrchestration|This project does not contain any security policies."
msgstr ""
msgid "SecurityOrchestration|To widen your search, change filters above or select a different security policy project."
msgstr ""
msgid "SecurityOrchestration|Update scan execution policies"
msgstr ""

View File

@ -227,8 +227,8 @@ RSpec.describe Groups::ChildrenController do
context 'when rendering hierarchies' do
# When loading hierarchies we load the all the ancestors for matched projects
# in 1 separate query
let(:extra_queries_for_hierarchies) { 1 }
# in 2 separate queries
let(:extra_queries_for_hierarchies) { 2 }
def get_filtered_list
get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json

View File

@ -90,9 +90,10 @@ RSpec.describe 'Group Packages & Registries settings' do
expect(page).to have_content('Do not allow duplicates')
fill_in 'Exceptions', with: ')'
# simulate blur event
find('#maven-duplicated-settings-regex-input').native.send_keys(:tab)
end
# simulate blur event
find('body').click
expect(page).to have_content('is an invalid regexp')
end

View File

@ -1,4 +1,4 @@
import { shallowMount } from '@vue/test-utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ItemStats from '~/groups/components/item_stats.vue';
import ItemStatsValue from '~/groups/components/item_stats_value.vue';
@ -12,7 +12,7 @@ describe('ItemStats', () => {
};
const createComponent = (props = {}) => {
wrapper = shallowMount(ItemStats, {
wrapper = shallowMountExtended(ItemStats, {
propsData: { ...defaultProps, ...props },
});
};
@ -46,5 +46,31 @@ describe('ItemStats', () => {
expect(findItemStatsValue().props('cssClass')).toBe('project-stars');
expect(wrapper.find('.last-updated').exists()).toBe(true);
});
describe('group specific rendering', () => {
describe.each`
provided | state | data
${true} | ${'displays'} | ${null}
${false} | ${'does not display'} | ${{ subgroupCount: undefined, projectCount: undefined }}
`('when provided = $provided', ({ provided, state, data }) => {
beforeEach(() => {
const item = {
...mockParentGroupItem,
...data,
type: ITEM_TYPE.GROUP,
};
createComponent({ item });
});
it.each`
entity | testId
${'subgroups'} | ${'subgroups-count'}
${'projects'} | ${'projects-count'}
`(`${state} $entity count`, ({ testId }) => {
expect(wrapper.findByTestId(testId).exists()).toBe(provided);
});
});
});
});
});

View File

@ -34,6 +34,44 @@ describe('MergeRequestTabs', () => {
gl.mrWidget = {};
});
describe('clickTab', () => {
let params;
beforeEach(() => {
document.documentElement.scrollTop = 100;
params = {
metaKey: false,
ctrlKey: false,
which: 1,
stopImmediatePropagation() {},
preventDefault() {},
currentTarget: {
getAttribute(attr) {
return attr === 'href' ? 'a/tab/url' : null;
},
},
};
});
it("stores the current scroll position if there's an active tab", () => {
testContext.class.currentTab = 'someTab';
testContext.class.clickTab(params);
expect(testContext.class.scrollPositions.someTab).toBe(100);
});
it("doesn't store a scroll position if there's no active tab", () => {
// this happens on first load, and we just don't want to store empty values in the `null` property
testContext.class.currentTab = null;
testContext.class.clickTab(params);
expect(testContext.class.scrollPositions).toEqual({});
});
});
describe('opensInNewTab', () => {
const windowTarget = '_blank';
let clickTabParams;
@ -258,6 +296,7 @@ describe('MergeRequestTabs', () => {
beforeEach(() => {
jest.spyOn(mainContent, 'getBoundingClientRect').mockReturnValue({ top: 10 });
jest.spyOn(tabContent, 'getBoundingClientRect').mockReturnValue({ top: 100 });
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
jest.spyOn(document, 'querySelector').mockImplementation((selector) => {
return selector === '.content-wrapper' ? mainContent : tabContent;
});
@ -267,8 +306,6 @@ describe('MergeRequestTabs', () => {
it('calls window scrollTo with options if document has scrollBehavior', () => {
document.documentElement.style.scrollBehavior = '';
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
testContext.class.tabShown('commits', 'foobar');
expect(window.scrollTo.mock.calls[0][0]).toEqual({ top: 39, behavior: 'smooth' });
@ -276,11 +313,50 @@ describe('MergeRequestTabs', () => {
it('calls window scrollTo with two args if document does not have scrollBehavior', () => {
jest.spyOn(document.documentElement, 'style', 'get').mockReturnValue({});
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
testContext.class.tabShown('commits', 'foobar');
expect(window.scrollTo.mock.calls[0]).toEqual([0, 39]);
});
describe('when switching tabs', () => {
const SCROLL_TOP = 100;
beforeAll(() => {
jest.useFakeTimers();
});
beforeEach(() => {
jest.spyOn(window, 'scrollTo').mockImplementation(() => {});
testContext.class.mergeRequestTabs = document.createElement('div');
testContext.class.mergeRequestTabPanes = document.createElement('div');
testContext.class.currentTab = 'tab';
testContext.class.scrollPositions = { newTab: SCROLL_TOP };
});
afterAll(() => {
jest.useRealTimers();
});
it('scrolls to the stored position, if one is stored', () => {
testContext.class.tabShown('newTab');
jest.advanceTimersByTime(250);
expect(window.scrollTo.mock.calls[0][0]).toEqual({
top: SCROLL_TOP,
left: 0,
behavior: 'auto',
});
});
it('scrolls to 0, if no position is stored', () => {
testContext.class.tabShown('unknownTab');
jest.advanceTimersByTime(250);
expect(window.scrollTo.mock.calls[0][0]).toEqual({ top: 0, left: 0, behavior: 'auto' });
});
});
});
});

View File

@ -11,6 +11,7 @@ RSpec.describe GroupPolicy do
it do
expect_allowed(:read_group)
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions)
expect_disallowed(:upload_file)
expect_disallowed(*reporter_permissions)
@ -30,6 +31,7 @@ RSpec.describe GroupPolicy do
end
it { expect_disallowed(:read_group) }
it { expect_disallowed(:read_counts) }
it { expect_disallowed(*read_group_permissions) }
end
@ -42,6 +44,7 @@ RSpec.describe GroupPolicy do
end
it { expect_disallowed(:read_group) }
it { expect_disallowed(:read_counts) }
it { expect_disallowed(*read_group_permissions) }
end
@ -245,6 +248,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { nil }
it do
expect_disallowed(:read_counts)
expect_disallowed(*read_group_permissions)
expect_disallowed(*guest_permissions)
expect_disallowed(*reporter_permissions)
@ -258,6 +262,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { guest }
it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_disallowed(*reporter_permissions)
@ -271,6 +276,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { reporter }
it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@ -284,6 +290,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { developer }
it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@ -297,6 +304,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { maintainer }
it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
@ -310,6 +318,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { owner }
it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)

View File

@ -87,7 +87,7 @@ RSpec.describe GroupChildEntity do
expect(json[:children_count]).to eq(2)
end
%w[children_count leave_path parent_id number_projects_with_delimiter number_users_with_delimiter project_count subgroup_count].each do |attribute|
%w[children_count leave_path parent_id number_users_with_delimiter project_count subgroup_count].each do |attribute|
it "includes #{attribute}" do
expect(json[attribute.to_sym]).to be_present
end
@ -114,6 +114,40 @@ RSpec.describe GroupChildEntity do
it_behaves_like 'group child json'
end
describe 'for a private group' do
let(:object) do
create(:group, :private)
end
describe 'user is member of the group' do
before do
object.add_owner(user)
end
it 'includes the counts' do
expect(json.keys).to include(*%i(project_count subgroup_count))
end
end
describe 'user is not a member of the group' do
it 'does not include the counts' do
expect(json.keys).not_to include(*%i(project_count subgroup_count))
end
end
describe 'user is only a member of a project in the group' do
let(:project) { create(:project, namespace: object) }
before do
project.add_guest(user)
end
it 'does not include the counts' do
expect(json.keys).not_to include(*%i(project_count subgroup_count))
end
end
end
describe 'for a project with external authorization enabled' do
let(:object) do
create(:project, :with_avatar,