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 {
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"
......
......@@ -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
......
......@@ -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.
......
......@@ -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
```
......
<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 {
<transition name="fade">
<gl-button
v-if="canRestore"
data-testid="vsa-reset-button"
class="gl-ml-3"
variant="link"
@click="handleResetDefaults"
......
......@@ -7,7 +7,7 @@ import CiCdAnalyticsApp from './components/app.vue';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
export default () => {
......
......@@ -54,7 +54,7 @@ RSpec.describe 'Promotions', :js do
it 'appears in repository settings page' do
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
it 'does not show when cookie is set' do
......
import { GlModal, GlFormInput } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
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 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';
......@@ -44,7 +47,7 @@ describe('ValueStreamForm', () => {
name: 'Editable value stream',
};
const initialPreset = PRESET_OPTIONS_BLANK;
const initialPreset = PRESET_OPTIONS_DEFAULT;
const fakeStore = () =>
new Vuex.Store({
......@@ -86,13 +89,14 @@ describe('ValueStreamForm', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findExtendedFormFields = () => wrapper.findByTestId('extended-form-fields');
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 findBtn = (btn) => findModal().props(btn);
const clickSubmit = () => findModal().vm.$emit('primary', 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 = '') =>
expect(wrapper.findByTestId(testId).attributes('invalid-feedback')).toBe(error);
......@@ -118,6 +122,18 @@ describe('ValueStreamForm', () => {
it('has the preset button', () => {
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', () => {
......@@ -215,6 +231,20 @@ describe('ValueStreamForm', () => {
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', () => {
const hiddenStages = defaultStageConfig.map((s) => ({ ...s, hidden: true }));
......
......@@ -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', () => {
it(`commits the ${types.RECEIVE_VALUE_STREAM_DATA_ERROR} mutation on a 403 response`, () => {
const response = { status: 403 };
......
......@@ -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
......
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);
});
});
});
});
});
......@@ -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,
......
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