Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
234dc40a12
commit
e9322e019b
|
@ -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';
|
||||
|
||||
/**
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {})
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
||||
```
|
||||
|
||||
|
|
|
@ -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**.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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' });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue