Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-24 21:10:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-24 21:10:53 +0300
commite9322e019bfeb7f33ce4c17662d93e6579303c2b (patch)
tree0c1dd4ac4e8f60164f7e21ba33bf0b1ee867c347
parent234dc40a12a1cdaef0cdb825ca4acc3f271c6394 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/captcha/init_recaptcha_script.js2
-rw-r--r--app/assets/javascripts/groups/components/item_stats.vue11
-rw-r--r--app/assets/javascripts/merge_request_tabs.js35
-rw-r--r--app/policies/group_policy.rb3
-rw-r--r--app/serializers/group_child_entity.rb14
-rw-r--r--app/serializers/group_entity.rb4
-rw-r--r--app/services/packages/create_package_service.rb6
-rw-r--r--doc/administration/troubleshooting/diagnostics_tools.md5
-rw-r--r--doc/ci/interactive_web_terminal/index.md3
-rw-r--r--doc/development/i18n/proofreader.md3
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/integration/jira/configure.md2
-rw-r--r--doc/operations/error_tracking.md65
-rw-r--r--doc/update/upgrading_from_source.md8
-rw-r--r--doc/user/project/merge_requests/fast_forward_merge.md8
-rw-r--r--lib/gitlab/gon_helper.rb2
-rw-r--r--locale/gitlab.pot12
-rw-r--r--spec/controllers/groups/children_controller_spec.rb4
-rw-r--r--spec/features/groups/settings/packages_and_registries_spec.rb5
-rw-r--r--spec/frontend/groups/components/item_stats_spec.js30
-rw-r--r--spec/frontend/merge_request_tabs_spec.js82
-rw-r--r--spec/policies/group_policy_spec.rb9
-rw-r--r--spec/serializers/group_child_entity_spec.rb36
23 files changed, 311 insertions, 40 deletions
diff --git a/app/assets/javascripts/captcha/init_recaptcha_script.js b/app/assets/javascripts/captcha/init_recaptcha_script.js
index f546eef7d84..28aef22873d 100644
--- a/app/assets/javascripts/captcha/init_recaptcha_script.js
+++ b/app/assets/javascripts/captcha/init_recaptcha_script.js
@@ -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';
/**
diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue
index 7a37d1eb93d..46e9d2bec99 100644
--- a/app/assets/javascripts/groups/components/item_stats.vue
+++ b/app/assets/javascripts/groups/components/item_stats.vue
@@ -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"
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 21fe4356fe0..540959b3a46 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -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');
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 1d0aa54c1c0..785104145cb 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -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
diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb
index 619ca0b5f82..0ca6e7b40d9 100644
--- a/app/serializers/group_child_entity.rb
+++ b/app/serializers/group_child_entity.rb
@@ -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
diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb
index 0e1bc9a6b3d..66919c49320 100644
--- a/app/serializers/group_entity.rb
+++ b/app/serializers/group_entity.rb
@@ -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
diff --git a/app/services/packages/create_package_service.rb b/app/services/packages/create_package_service.rb
index 3dc06497d9f..7e1b6ecbe51 100644
--- a/app/services/packages/create_package_service.rb
+++ b/app/services/packages/create_package_service.rb
@@ -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 = {})
diff --git a/doc/administration/troubleshooting/diagnostics_tools.md b/doc/administration/troubleshooting/diagnostics_tools.md
index fe85b5d5803..53d4810b920 100644
--- a/doc/administration/troubleshooting/diagnostics_tools.md
+++ b/doc/administration/troubleshooting/diagnostics_tools.md
@@ -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.
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 4c90a4e06db..312b4a0553b 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -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.
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 50de1d253ba..ac8af0f9420 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -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)
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 3957828ba30..e8ea456e022 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -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.
diff --git a/doc/integration/jira/configure.md b/doc/integration/jira/configure.md
index b11f367258d..d4e5a1bfca1 100644
--- a/doc/integration/jira/configure.md
+++ b/doc/integration/jira/configure.md
@@ -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.
diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md
index 9234ca36634..5400e92ce3c 100644
--- a/doc/operations/error_tracking.md
+++ b/doc/operations/error_tracking.md
@@ -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"
+```
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index dd7ef27feca..44fee84b850 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -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
```
diff --git a/doc/user/project/merge_requests/fast_forward_merge.md b/doc/user/project/merge_requests/fast_forward_merge.md
index b1e8d761f5c..7edc379399b 100644
--- a/doc/user/project/merge_requests/fast_forward_merge.md
+++ b/doc/user/project/merge_requests/fast_forward_merge.md
@@ -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**.
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 16a8b5f959e..452f8d8f72e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bce2f06d5b9..12a590d87e6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index e97fe50c468..04cf7785f1e 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -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
diff --git a/spec/features/groups/settings/packages_and_registries_spec.rb b/spec/features/groups/settings/packages_and_registries_spec.rb
index 835555480dd..d3141da9160 100644
--- a/spec/features/groups/settings/packages_and_registries_spec.rb
+++ b/spec/features/groups/settings/packages_and_registries_spec.rb
@@ -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
diff --git a/spec/frontend/groups/components/item_stats_spec.js b/spec/frontend/groups/components/item_stats_spec.js
index f350012ebed..49f3f5da43c 100644
--- a/spec/frontend/groups/components/item_stats_spec.js
+++ b/spec/frontend/groups/components/item_stats_spec.js
@@ -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);
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/merge_request_tabs_spec.js b/spec/frontend/merge_request_tabs_spec.js
index 23e9bf8b447..ced9b71125b 100644
--- a/spec/frontend/merge_request_tabs_spec.js
+++ b/spec/frontend/merge_request_tabs_spec.js
@@ -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' });
+ });
+ });
});
});
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 9fac5521aa6..c95363a76cc 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -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)
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index 7f330da44a7..e4844c25067 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -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,