Commit b41907d7 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 38064e4b 7ddb45bf
...@@ -40,24 +40,31 @@ export default { ...@@ -40,24 +40,31 @@ export default {
return this.item.type === ITEM_TYPE.GROUP; return this.item.type === ITEM_TYPE.GROUP;
}, },
}, },
methods: {
displayValue(value) {
return this.isGroup && value !== undefined;
},
},
}; };
</script> </script>
<template> <template>
<div class="stats gl-text-gray-500"> <div class="stats gl-text-gray-500">
<item-stats-value <item-stats-value
v-if="isGroup" v-if="displayValue(item.subgroupCount)"
:title="__('Subgroups')" :title="__('Subgroups')"
:value="item.subgroupCount" :value="item.subgroupCount"
css-class="number-subgroups gl-ml-5" css-class="number-subgroups gl-ml-5"
icon-name="folder-o" icon-name="folder-o"
data-testid="subgroups-count"
/> />
<item-stats-value <item-stats-value
v-if="isGroup" v-if="displayValue(item.projectCount)"
:title="__('Projects')" :title="__('Projects')"
:value="item.projectCount" :value="item.projectCount"
css-class="number-projects gl-ml-5" css-class="number-projects gl-ml-5"
icon-name="bookmark" icon-name="bookmark"
data-testid="projects-count"
/> />
<item-stats-value <item-stats-value
v-if="isGroup" v-if="isGroup"
......
...@@ -111,8 +111,11 @@ class GroupPolicy < BasePolicy ...@@ -111,8 +111,11 @@ class GroupPolicy < BasePolicy
enable :read_issue_board enable :read_issue_board
enable :read_group_member enable :read_group_member
enable :read_custom_emoji enable :read_custom_emoji
enable :read_counts
end end
rule { ~public_group & ~has_access }.prevent :read_counts
rule { ~can?(:read_group) }.policy do rule { ~can?(:read_group) }.policy do
prevent :read_design_activity prevent :read_design_activity
end end
......
...@@ -37,9 +37,13 @@ class GroupChildEntity < Grape::Entity ...@@ -37,9 +37,13 @@ class GroupChildEntity < Grape::Entity
if: lambda { |_instance, _options| project? } if: lambda { |_instance, _options| project? }
# Group only attributes # Group only attributes
expose :children_count, :parent_id, :project_count, :subgroup_count, expose :children_count, :parent_id,
unless: lambda { |_instance, _options| project? } 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| expose :leave_path, unless: lambda { |_instance, _options| project? } do |instance|
leave_group_members_path(instance) leave_group_members_path(instance)
end end
...@@ -52,10 +56,6 @@ class GroupChildEntity < Grape::Entity ...@@ -52,10 +56,6 @@ class GroupChildEntity < Grape::Entity
end end
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| expose :number_users_with_delimiter, unless: lambda { |_instance, _options| project? } do |instance|
number_with_delimiter(instance.member_count) number_with_delimiter(instance.member_count)
end end
...@@ -66,6 +66,10 @@ class GroupChildEntity < Grape::Entity ...@@ -66,6 +66,10 @@ class GroupChildEntity < Grape::Entity
private private
def access_group_counts?(group)
!project? && can?(request.current_user, :read_counts, group)
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def membership def membership
return unless request.current_user return unless request.current_user
......
...@@ -40,10 +40,6 @@ class GroupEntity < Grape::Entity ...@@ -40,10 +40,6 @@ class GroupEntity < Grape::Entity
GroupsFinder.new(request.current_user, parent: group).execute.any? GroupsFinder.new(request.current_user, parent: group).execute.any?
end 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| expose :number_users_with_delimiter do |group|
number_with_delimiter(group.users.count) number_with_delimiter(group.users.count)
end end
......
...@@ -10,7 +10,8 @@ type: reference ...@@ -10,7 +10,8 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/50144) in GitLab 11.3. > [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 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/) shell access to the environment where [GitLab Runner](https://docs.gitlab.com/runner/)
is deployed, some [security precautions](../../administration/integration/terminal.md#security) were is deployed, some [security precautions](../../administration/integration/terminal.md#security) were
taken to protect the users. taken to protect the users.
......
...@@ -99,7 +99,8 @@ are very appreciative of the work done by translators and proofreaders! ...@@ -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) - 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) - Eduardo Addad de Oliveira - [GitLab](https://gitlab.com/eduardoaddad), [Crowdin](https://crowdin.com/profile/eduardoaddad)
- Romanian - 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 - Russian
- Nikita Grylov - [GitLab](https://gitlab.com/nixel2007), [Crowdin](https://crowdin.com/profile/nixel2007) - 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) - 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 ...@@ -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 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. 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. 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.
......
...@@ -107,11 +107,11 @@ Download and install Go (for Linux, 64-bit): ...@@ -107,11 +107,11 @@ Download and install Go (for Linux, 64-bit):
# Remove former Go installation folder # Remove former Go installation folder
sudo rm -rf /usr/local/go sudo rm -rf /usr/local/go
curl --remote-name --progress "https://dl.google.com/go/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 '512103d7ad296467814a6e3f635631bd35574cab3369a97a323c9a585ccaa569 go1.13.5.linux-amd64.tar.gz' | shasum -a256 -c - && \ echo 'bbdb935699e0b24d90e2451346da76121b2412d30930eabcd80907c230d098b7 go1.15.12.linux-amd64.tar.gz' | shasum -a256 -c - && \
sudo tar -C /usr/local -xzf go1.13.5.linux-amd64.tar.gz 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/ 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
``` ```
......
<script>
import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { n__ } from '~/locale';
import { EVENTS_LIST_ITEM_LIMIT } from '../constants';
export default {
components: {
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
count: {
type: Number,
required: true,
},
},
eventsListItemLimit: EVENTS_LIST_ITEM_LIMIT,
tooltipTitle: n__(
'Limited to showing %d event at most',
'Limited to showing %d events at most',
EVENTS_LIST_ITEM_LIMIT,
),
};
</script>
<template>
<!-- TODO: im not sure why this is rendered only for exactly 50 items, why not >= 50? -->
<span v-if="count >= $options.eventsListItemLimit" class="events-info float-right">
<gl-icon v-gl-tooltip="{ title: $options.tooltipTitle }" name="warning" />
{{ n__('Showing %d event', 'Showing %d events', $options.eventsListItemLimit) }}
</span>
</template>
...@@ -336,6 +336,7 @@ export default { ...@@ -336,6 +336,7 @@ export default {
<transition name="fade"> <transition name="fade">
<gl-button <gl-button
v-if="canRestore" v-if="canRestore"
data-testid="vsa-reset-button"
class="gl-ml-3" class="gl-ml-3"
variant="link" variant="link"
@click="handleResetDefaults" @click="handleResetDefaults"
......
...@@ -7,7 +7,7 @@ import CiCdAnalyticsApp from './components/app.vue'; ...@@ -7,7 +7,7 @@ import CiCdAnalyticsApp from './components/app.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(), defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
}); });
export default () => { export default () => {
......
...@@ -54,7 +54,7 @@ RSpec.describe 'Promotions', :js do ...@@ -54,7 +54,7 @@ RSpec.describe 'Promotions', :js do
it 'appears in repository settings page' do it 'appears in repository settings page' do
visit project_settings_repository_path(project) visit project_settings_repository_path(project)
expect(find('#promote_repository_features')).to have_content 'Improve repositories with GitLab Enterprise Edition' expect(find('#promote_repository_features')).to have_content(s_('Promotions|Improve repositories with GitLab Enterprise Edition.'))
end end
it 'does not show when cookie is set' do it 'does not show when cookie is set' do
......
import { GlModal, GlFormInput } from '@gitlab/ui'; import { GlModal, GlFormInput } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { PRESET_OPTIONS_BLANK } from 'ee/analytics/cycle_analytics/components/create_value_stream_form/constants'; import {
PRESET_OPTIONS_BLANK,
PRESET_OPTIONS_DEFAULT,
} from 'ee/analytics/cycle_analytics/components/create_value_stream_form/constants';
import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue'; import CustomStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue';
import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue'; import DefaultStageFields from 'ee/analytics/cycle_analytics/components/create_value_stream_form/default_stage_fields.vue';
import ValueStreamForm from 'ee/analytics/cycle_analytics/components/value_stream_form.vue'; import ValueStreamForm from 'ee/analytics/cycle_analytics/components/value_stream_form.vue';
...@@ -44,7 +47,7 @@ describe('ValueStreamForm', () => { ...@@ -44,7 +47,7 @@ describe('ValueStreamForm', () => {
name: 'Editable value stream', name: 'Editable value stream',
}; };
const initialPreset = PRESET_OPTIONS_BLANK; const initialPreset = PRESET_OPTIONS_DEFAULT;
const fakeStore = () => const fakeStore = () =>
new Vuex.Store({ new Vuex.Store({
...@@ -86,13 +89,14 @@ describe('ValueStreamForm', () => { ...@@ -86,13 +89,14 @@ describe('ValueStreamForm', () => {
const findModal = () => wrapper.findComponent(GlModal); const findModal = () => wrapper.findComponent(GlModal);
const findExtendedFormFields = () => wrapper.findByTestId('extended-form-fields'); const findExtendedFormFields = () => wrapper.findByTestId('extended-form-fields');
const findPresetSelector = () => wrapper.findByTestId('vsa-preset-selector'); const findPresetSelector = () => wrapper.findByTestId('vsa-preset-selector');
const findRestoreButton = (index) => wrapper.findByTestId(`stage-action-restore-${index}`); const findRestoreButton = () => wrapper.findByTestId('vsa-reset-button');
const findRestoreStageButton = (index) => wrapper.findByTestId(`stage-action-restore-${index}`);
const findHiddenStages = () => wrapper.findAllByTestId('vsa-hidden-stage').wrappers; const findHiddenStages = () => wrapper.findAllByTestId('vsa-hidden-stage').wrappers;
const findBtn = (btn) => findModal().props(btn); const findBtn = (btn) => findModal().props(btn);
const clickSubmit = () => findModal().vm.$emit('primary', mockEvent); const clickSubmit = () => findModal().vm.$emit('primary', mockEvent);
const clickAddStage = () => findModal().vm.$emit('secondary', mockEvent); const clickAddStage = () => findModal().vm.$emit('secondary', mockEvent);
const clickRestoreStageAtIndex = (index) => findRestoreButton(index).vm.$emit('click'); const clickRestoreStageAtIndex = (index) => findRestoreStageButton(index).vm.$emit('click');
const expectFieldError = (testId, error = '') => const expectFieldError = (testId, error = '') =>
expect(wrapper.findByTestId(testId).attributes('invalid-feedback')).toBe(error); expect(wrapper.findByTestId(testId).attributes('invalid-feedback')).toBe(error);
...@@ -118,6 +122,18 @@ describe('ValueStreamForm', () => { ...@@ -118,6 +122,18 @@ describe('ValueStreamForm', () => {
it('has the preset button', () => { it('has the preset button', () => {
expect(findPresetSelector().exists()).toBe(true); expect(findPresetSelector().exists()).toBe(true);
}); });
it('will toggle between the blank and default templates', () => {
expect(wrapper.vm.stages).toHaveLength(defaultStageConfig.length);
findPresetSelector().vm.$emit('input', PRESET_OPTIONS_BLANK);
expect(wrapper.vm.stages).toHaveLength(1);
findPresetSelector().vm.$emit('input', PRESET_OPTIONS_DEFAULT);
expect(wrapper.vm.stages).toHaveLength(defaultStageConfig.length);
});
}); });
it('does not display any hidden stages', () => { it('does not display any hidden stages', () => {
...@@ -215,6 +231,20 @@ describe('ValueStreamForm', () => { ...@@ -215,6 +231,20 @@ describe('ValueStreamForm', () => {
expect(findHiddenStages().length).toBe(0); expect(findHiddenStages().length).toBe(0);
}); });
describe('restore defaults button', () => {
it('will clear the form fields', async () => {
expect(wrapper.vm.stages).toHaveLength(stageCount);
await clickAddStage();
expect(wrapper.vm.stages).toHaveLength(stageCount + 1);
findRestoreButton().vm.$emit('click');
expect(wrapper.vm.stages).toHaveLength(stageCount);
});
});
describe('with hidden stages', () => { describe('with hidden stages', () => {
const hiddenStages = defaultStageConfig.map((s) => ({ ...s, hidden: true })); const hiddenStages = defaultStageConfig.map((s) => ({ ...s, hidden: true }));
......
...@@ -181,6 +181,18 @@ describe('Value Stream Analytics actions', () => { ...@@ -181,6 +181,18 @@ describe('Value Stream Analytics actions', () => {
}); });
}); });
describe('receiveCycleAnalyticsDataSuccess', () => {
it(`commits the ${types.RECEIVE_VALUE_STREAM_DATA_SUCCESS} and dispatches the 'typeOfWork/fetchTopRankedGroupLabels' action`, () => {
return testAction(
actions.receiveCycleAnalyticsDataSuccess,
null,
state,
[{ type: types.RECEIVE_VALUE_STREAM_DATA_SUCCESS }],
[{ type: 'typeOfWork/fetchTopRankedGroupLabels' }],
);
});
});
describe('receiveCycleAnalyticsDataError', () => { describe('receiveCycleAnalyticsDataError', () => {
it(`commits the ${types.RECEIVE_VALUE_STREAM_DATA_ERROR} mutation on a 403 response`, () => { it(`commits the ${types.RECEIVE_VALUE_STREAM_DATA_ERROR} mutation on a 403 response`, () => {
const response = { status: 403 }; const response = { status: 403 };
......
...@@ -227,8 +227,8 @@ RSpec.describe Groups::ChildrenController do ...@@ -227,8 +227,8 @@ RSpec.describe Groups::ChildrenController do
context 'when rendering hierarchies' do context 'when rendering hierarchies' do
# When loading hierarchies we load the all the ancestors for matched projects # When loading hierarchies we load the all the ancestors for matched projects
# in 1 separate query # in 2 separate queries
let(:extra_queries_for_hierarchies) { 1 } let(:extra_queries_for_hierarchies) { 2 }
def get_filtered_list def get_filtered_list
get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json get :index, params: { group_id: group.to_param, filter: 'filter' }, format: :json
......
import { shallowMount } from '@vue/test-utils'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ItemStats from '~/groups/components/item_stats.vue'; import ItemStats from '~/groups/components/item_stats.vue';
import ItemStatsValue from '~/groups/components/item_stats_value.vue'; import ItemStatsValue from '~/groups/components/item_stats_value.vue';
...@@ -12,7 +12,7 @@ describe('ItemStats', () => { ...@@ -12,7 +12,7 @@ describe('ItemStats', () => {
}; };
const createComponent = (props = {}) => { const createComponent = (props = {}) => {
wrapper = shallowMount(ItemStats, { wrapper = shallowMountExtended(ItemStats, {
propsData: { ...defaultProps, ...props }, propsData: { ...defaultProps, ...props },
}); });
}; };
...@@ -46,5 +46,31 @@ describe('ItemStats', () => { ...@@ -46,5 +46,31 @@ describe('ItemStats', () => {
expect(findItemStatsValue().props('cssClass')).toBe('project-stars'); expect(findItemStatsValue().props('cssClass')).toBe('project-stars');
expect(wrapper.find('.last-updated').exists()).toBe(true); 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);
});
});
});
}); });
}); });
...@@ -11,6 +11,7 @@ RSpec.describe GroupPolicy do ...@@ -11,6 +11,7 @@ RSpec.describe GroupPolicy do
it do it do
expect_allowed(:read_group) expect_allowed(:read_group)
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions) expect_allowed(*read_group_permissions)
expect_disallowed(:upload_file) expect_disallowed(:upload_file)
expect_disallowed(*reporter_permissions) expect_disallowed(*reporter_permissions)
...@@ -30,6 +31,7 @@ RSpec.describe GroupPolicy do ...@@ -30,6 +31,7 @@ RSpec.describe GroupPolicy do
end end
it { expect_disallowed(:read_group) } it { expect_disallowed(:read_group) }
it { expect_disallowed(:read_counts) }
it { expect_disallowed(*read_group_permissions) } it { expect_disallowed(*read_group_permissions) }
end end
...@@ -42,6 +44,7 @@ RSpec.describe GroupPolicy do ...@@ -42,6 +44,7 @@ RSpec.describe GroupPolicy do
end end
it { expect_disallowed(:read_group) } it { expect_disallowed(:read_group) }
it { expect_disallowed(:read_counts) }
it { expect_disallowed(*read_group_permissions) } it { expect_disallowed(*read_group_permissions) }
end end
...@@ -245,6 +248,7 @@ RSpec.describe GroupPolicy do ...@@ -245,6 +248,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { nil } let(:current_user) { nil }
it do it do
expect_disallowed(:read_counts)
expect_disallowed(*read_group_permissions) expect_disallowed(*read_group_permissions)
expect_disallowed(*guest_permissions) expect_disallowed(*guest_permissions)
expect_disallowed(*reporter_permissions) expect_disallowed(*reporter_permissions)
...@@ -258,6 +262,7 @@ RSpec.describe GroupPolicy do ...@@ -258,6 +262,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { guest } let(:current_user) { guest }
it do it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions) expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
expect_disallowed(*reporter_permissions) expect_disallowed(*reporter_permissions)
...@@ -271,6 +276,7 @@ RSpec.describe GroupPolicy do ...@@ -271,6 +276,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { reporter } let(:current_user) { reporter }
it do it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions) expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions) expect_allowed(*reporter_permissions)
...@@ -284,6 +290,7 @@ RSpec.describe GroupPolicy do ...@@ -284,6 +290,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { developer } let(:current_user) { developer }
it do it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions) expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions) expect_allowed(*reporter_permissions)
...@@ -297,6 +304,7 @@ RSpec.describe GroupPolicy do ...@@ -297,6 +304,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { maintainer } let(:current_user) { maintainer }
it do it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions) expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions) expect_allowed(*reporter_permissions)
...@@ -310,6 +318,7 @@ RSpec.describe GroupPolicy do ...@@ -310,6 +318,7 @@ RSpec.describe GroupPolicy do
let(:current_user) { owner } let(:current_user) { owner }
it do it do
expect_allowed(:read_counts)
expect_allowed(*read_group_permissions) expect_allowed(*read_group_permissions)
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions) expect_allowed(*reporter_permissions)
......
...@@ -87,7 +87,7 @@ RSpec.describe GroupChildEntity do ...@@ -87,7 +87,7 @@ RSpec.describe GroupChildEntity do
expect(json[:children_count]).to eq(2) expect(json[:children_count]).to eq(2)
end 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 it "includes #{attribute}" do
expect(json[attribute.to_sym]).to be_present expect(json[attribute.to_sym]).to be_present
end end
...@@ -114,6 +114,40 @@ RSpec.describe GroupChildEntity do ...@@ -114,6 +114,40 @@ RSpec.describe GroupChildEntity do
it_behaves_like 'group child json' it_behaves_like 'group child json'
end 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 describe 'for a project with external authorization enabled' do
let(:object) do let(:object) do
create(:project, :with_avatar, create(:project, :with_avatar,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment