Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2024-01-15 21:08:29 +00:00
parent 854a0164ea
commit 2cfe59f72a
14 changed files with 425 additions and 12 deletions

View File

@ -0,0 +1,67 @@
<script>
import { GlAlert } from '@gitlab/ui';
import StorageUsageStatistics from 'ee_else_ce/usage_quotas/storage/components/storage_usage_statistics.vue';
export default {
name: 'NamespaceStorageApp',
components: {
GlAlert,
StorageUsageStatistics,
},
props: {
namespaceLoadingError: {
type: Boolean,
required: false,
default: false,
},
projectsLoadingError: {
type: Boolean,
required: false,
default: false,
},
isNamespaceStorageStatisticsLoading: {
type: Boolean,
required: false,
default: false,
},
namespace: {
type: Object,
required: false,
default: () => ({}),
},
},
computed: {
usedStorage() {
return (
// This is the coefficient adjusted forked repo size, only used in EE
this.namespace.rootStorageStatistics?.costFactoredStorageSize ??
// This is the actual storage size value, used in CE or when the above is disabled
this.namespace.rootStorageStatistics?.storageSize
);
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="namespaceLoadingError || projectsLoadingError"
variant="danger"
:dismissible="false"
class="gl-mt-4"
>
{{
s__(
'UsageQuota|An error occured while loading the storage usage details. Please refresh the page to try again.',
)
}}
</gl-alert>
<storage-usage-statistics
:additional-purchased-storage-size="namespace.additionalPurchasedStorageSize"
:used-storage="usedStorage"
:loading="isNamespaceStorageStatisticsLoading"
/>
<slot name="ee-storage-app"></slot>
</div>
</template>

View File

@ -0,0 +1,51 @@
<script>
import { GlCard, GlSkeletonLoader } from '@gitlab/ui';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
export default {
name: 'StorageUsageOverviewCard',
components: {
GlCard,
GlSkeletonLoader,
NumberToHumanSize,
},
props: {
usedStorage: {
type: Number,
required: false,
default: null,
},
loading: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<gl-card>
<gl-skeleton-loader v-if="loading" :height="64">
<rect width="140" height="30" x="5" y="0" rx="4" />
<rect width="240" height="10" x="5" y="40" rx="4" />
<rect width="340" height="10" x="5" y="54" rx="4" />
</gl-skeleton-loader>
<div v-else>
<div class="gl-font-weight-bold" data-testid="namespace-storage-card-title">
{{ s__('UsageQuota|Namespace storage used') }}
</div>
<div class="gl-font-size-h-display gl-font-weight-bold gl-line-height-ratio-1000 gl-my-3">
<number-to-human-size label-class="gl-font-lg" :value="Number(usedStorage)" plain-zero />
</div>
<hr class="gl-my-4" />
<p>
{{
s__(
'UsageQuota|Namespace total storage represents the sum of storage consumed by all projects, Container Registry, and Dependency Proxy.',
)
}}
</p>
</div>
</gl-card>
</template>

View File

@ -0,0 +1,33 @@
<script>
import StorageUsageOverviewCard from './storage_usage_overview_card.vue';
export default {
name: 'StorageUsageStatistics',
components: {
StorageUsageOverviewCard,
},
props: {
usedStorage: {
type: Number,
required: false,
default: 0,
},
loading: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div>
<h3 data-testid="overview-subtitle">{{ s__('UsageQuota|Namespace overview') }}</h3>
<div class="gl-display-grid gl-md-grid-template-columns-2 gl-gap-5 gl-py-4">
<storage-usage-overview-card
:used-storage="usedStorage"
:loading="loading"
data-testid="namespace-usage-total"
/>
</div>
</div>
</template>

View File

@ -131,7 +131,7 @@ export default {
class="item-body work-item-link-child gl-relative gl-display-flex gl-flex-grow-1 gl-overflow-break-word gl-min-w-0 gl-pl-3 gl-pr-2 gl-py-2 gl-mx-n2 gl-rounded-base gl-gap-3"
data-testid="links-child"
>
<div class="item-contents gl-display-flex gl-flex-grow-1 gl-flex-wrap gl-min-w-0">
<div class="gl-display-flex gl-flex-grow-1 gl-flex-wrap gl-min-w-0">
<div
class="gl-display-flex gl-flex-grow-1 gl-flex-wrap flex-xl-nowrap gl-align-items-center gl-justify-content-space-between gl-gap-3 gl-min-w-0"
>

View File

@ -205,6 +205,95 @@ If you have any concerns, put the new ability behind a feature flag.
- When you enable the ability by default, add the `feature_flag_enabled_milestone` and `feature_flag_enabled_mr` attributes to the appropriate ability YAML file and regenerate the documentation.
- You do not have to include these attributes in the YAML file if the feature flag is enabled by default in the same release as the ability is introduced.
#### Testing
Unit tests are preferred to test out changes to any policies affected by the
addition of new custom permissions. Custom Roles is an Ultimate tier feature so
these tests can be found in the `ee/spec/policies` directory. The [spec file](https://gitlab.com/gitlab-org/gitlab/-/blob/13baa4e8c92a56260591a5bf0a58d3339890ee10/ee/spec/policies/project_policy_spec.rb#L2726-2740) for
the `ProjectPolicy` contains shared examples that can be used to test out the
following conditions:
- when the `custom_roles` licensed feature is not enabled
- when the `custom_roles` licensed feature is enabled
- when a user is a member of a custom role via an inherited group member
- when a user is a member of a custom role via a direct group member
- when a user is a member of a custom role via a direct project membership
Below is an example for testing out `ProjectPolicy` related changes.
```ruby
context 'for a role with `custom_permission` enabled' do
let(:member_role_abilities) { { custom_permission: true } }
let(:allowed_abilities) { [:custom_permission] }
it_behaves_like 'custom roles abilities'
end
```
Request specs are preferred to test out any endpoint that allow access via a custom role permission.
This includes controllers, REST API, and GraphQL. Examples of request specs can be found in `ee/spec/requests/custom_roles/`. In this directory you will find a sub-directory named after each permission that can be enabled via a custom role.
The `custom_roles` licensed feature must be enabled to test this functionality.
Below is an example of the typical setup that is required to test out a
Rails Controller endpoint.
```ruby
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :in_group) }
let_it_be(:role) { create(:member_role, :guest, namespace: project.group, custom_permission: true) }
let_it_be(:membership) { create(:project_member, :guest, member_role: role, user: user, project: project) }
before do
stub_licensed_features(custom_roles: true)
sign_in(user)
end
describe MyController do
describe '#show' do
it 'allows access' do
get my_controller_path(project)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
end
end
```
Below is an example of the typical setup that is required to test out a GraphQL
mutation.
```ruby
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :in_group) }
let_it_be(:role) { create(:member_role, :guest, namespace: project.group, custom_permission: true) }
let_it_be(:membership) { create(:project_member, :guest, member_role: role, user: user, project: project) }
before do
stub_licensed_features(custom_roles: true)
end
describe MyMutation do
include GraphqlHelpers
describe '#show' do
it 'allows access' do
post_graphql_mutation(graphql_mutation(:my_mutation, {
example: "Example"
}), current_user: user)
expect(response).to have_gitlab_http_status(:success)
mutation_response = graphql_mutation_response(:my_mutation)
expect(mutation_response).to be_present
expect(mutation_response["errors"]).to be_empty
end
end
end
```
[`GITLAB_DEBUG_POLICIES=true`](#finding-existing-abilities-checks) can be used
to troubleshoot runtime policy decisions.
## Custom abilities definition
All new custom abilities must have a type definition stored in `ee/config/custom_abilities` that contains a single source of truth for every ability that is part of custom roles feature.
@ -217,6 +306,7 @@ To add a new custom ability:
- Use the `ee/bin/custom-ability` CLI to create the YAML definition automatically.
- Perform manual steps to create a new file in `ee/config/custom_abilities/` with the filename matching the name of the ability name.
1. Add contents to the file that conform to the [schema](#schema) defined in `ee/config/custom_abilities/types/type_schema.json`.
1. Add [tests](#testing) for the new ability in `ee/spec/requests/custom_roles/` with a new directory named after the ability name.
### Schema

View File

@ -83,10 +83,10 @@ module):
- Maintainer (`40`)
- Owner (`50`)
If a user is the member of both a project and the project parent groups, the
If a user is a member of both a project and the project parent groups, the
highest permission is the applied access level for the project.
If a user is the member of a project, but not the parent groups, they
If a user is a member of a project, but not the parent groups, they
can still view the groups and their entities (like epics).
Project membership (where the group membership is already taken into account)

View File

@ -92,9 +92,6 @@ the following sections and tables provide an alternative.
> The `approval_settings` fields were [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/418752) in GitLab 16.4 [with flags](../../../administration/feature_flags.md) named `scan_result_policies_block_unprotecting_branches`, `scan_result_any_merge_request`, or `scan_result_policies_block_force_push`. See the `approval_settings` section below for more information.
FLAG:
On self-managed GitLab, by default the `approval_settings` field is available. To hide the feature, an administrator can [disable the feature flags](../../../administration/feature_flags.md) named `scan_result_policies_block_unprotecting_branches`, `scan_result_any_merge_request` and `scan_result_policies_block_force_push`. See the `approval_settings` section below for more information. On GitLab.com, the `approval_settings` field is available.
| Field | Type | Required | Possible values | Description |
|---------------------|--------------------|----------|-----------------|----------------------------------------------------------|
| `name` | `string` | true | | Name of the policy. Maximum of 255 characters. |
@ -195,7 +192,6 @@ the defined policy.
FLAG:
On self-managed GitLab, by default the `block_branch_modification` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `scan_result_policies_block_unprotecting_branches`. On GitLab.com, this feature is available.
On self-managed GitLab, by default the `prevent_approval_by_author`, `prevent_approval_by_commit_author`, `remove_approvals_with_new_commit`, and `require_password_to_approve` fields are available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `scan_result_any_merge_request`. On GitLab.com, this feature is available.
On self-managed GitLab, by default the `prevent_pushing_and_force_pushing` field is available. To hide the feature, an administrator can [disable the feature flag](../../../administration/feature_flags.md) named `scan_result_policies_block_force_push`. On GitLab.com, this feature is available.
The settings set in the policy overwrite settings in the project.

View File

@ -143,7 +143,8 @@ then
ruby -r './tooling/lib/tooling/find_changes' -e "Tooling::FindChanges.new(
from: :api,
changed_files_pathname: '${DOC_CHANGES_FILE}',
file_filter: ->(file) { !file['deleted_file'] && file['new_path'] =~ %r{doc/.*\.md|lint-doc\.sh|docs\.gitlab-ci\.yml} }
file_filter: ->(file) { !file['deleted_file'] && file['new_path'] =~ %r{doc/.*\.md|\.vale|\.markdownlint|lint-doc\.sh|docs\.gitlab-ci\.yml} },
only_new_paths: true
).execute"
if grep -E "\.vale|\.markdownlint|lint-doc\.sh|docs\.gitlab-ci\.yml" < $DOC_CHANGES_FILE
then

View File

@ -0,0 +1,51 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NamespaceStorageApp from '~/usage_quotas/storage/components/namespace_storage_app.vue';
import StorageUsageStatistics from '~/usage_quotas/storage/components/storage_usage_statistics.vue';
import { defaultNamespaceProvideValues } from '../mock_data';
const defaultProps = {
namespaceLoadingError: false,
projectsLoadingError: false,
isNamespaceStorageStatisticsLoading: false,
// hardcoding object until we move test_fixtures from ee/ to here
namespace: {
rootStorageStatistics: {
storageSize: 1234,
},
},
};
describe('NamespaceStorageApp', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const findStorageUsageStatistics = () => wrapper.findComponent(StorageUsageStatistics);
const createComponent = ({ provide = {}, props = {} } = {}) => {
wrapper = shallowMountExtended(NamespaceStorageApp, {
provide: {
...defaultNamespaceProvideValues,
...provide,
},
propsData: {
...defaultProps,
...props,
},
});
};
describe('Namespace usage overview', () => {
describe('StorageUsageStatistics', () => {
beforeEach(() => {
createComponent();
});
it('passes the correct props to StorageUsageStatistics', () => {
expect(findStorageUsageStatistics().props()).toMatchObject({
usedStorage: defaultProps.namespace.rootStorageStatistics.storageSize,
loading: false,
});
});
});
});
});

View File

@ -0,0 +1,44 @@
import { GlSkeletonLoader } from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import StorageUsageOverviewCard from '~/usage_quotas/storage/components/storage_usage_overview_card.vue';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
describe('StorageUsageOverviewCard', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const defaultProps = {
purchasedStorage: 0,
// hardcoding value until we move test_fixtures from ee/ to here
usedStorage: 1234,
loading: false,
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(StorageUsageOverviewCard, {
propsData: { ...defaultProps, ...props },
stubs: {
NumberToHumanSize,
},
});
};
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
it('displays the used storage value', () => {
createComponent();
expect(wrapper.text()).toContain(numberToHumanSize(defaultProps.usedStorage, 1));
});
describe('skeleton loader', () => {
it('renders skeleton loader when loading prop is true', () => {
createComponent({ props: { loading: true } });
expect(findSkeletonLoader().exists()).toBe(true);
});
it('does not render skeleton loader when loading prop is false', () => {
createComponent({ props: { loading: false } });
expect(findSkeletonLoader().exists()).toBe(false);
});
});
});

View File

@ -0,0 +1,43 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import StorageUsageStatistics from '~/usage_quotas/storage/components/storage_usage_statistics.vue';
import StorageUsageOverviewCard from '~/usage_quotas/storage/components/storage_usage_overview_card.vue';
const defaultProps = {
// hardcoding value until we move test_fixtures from ee/ to here
usedStorage: 1234,
loading: false,
};
describe('StorageUsageStatistics', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(StorageUsageStatistics, {
propsData: {
...defaultProps,
...props,
},
});
};
const findOverviewSubtitle = () => wrapper.findByTestId('overview-subtitle');
const findStorageUsageOverviewCard = () => wrapper.findComponent(StorageUsageOverviewCard);
beforeEach(() => {
createComponent();
});
it('shows the namespace storage overview subtitle', () => {
expect(findOverviewSubtitle().text()).toBe('Namespace overview');
});
describe('StorageStatisticsCard', () => {
it('passes the correct props to StorageUsageOverviewCard', () => {
expect(findStorageUsageOverviewCard().props()).toEqual({
usedStorage: defaultProps.usedStorage,
loading: false,
});
});
});
});

View File

@ -6,3 +6,5 @@ export const mockEmptyResponse = { data: { project: null } };
export const defaultProjectProvideValues = {
projectPath: '/project-path',
};
export const defaultNamespaceProvideValues = {};

View File

@ -16,7 +16,8 @@ RSpec.describe Tooling::FindChanges, feature_category: :tooling do
predictive_tests_pathname: predictive_tests_pathname,
frontend_fixtures_mapping_pathname: frontend_fixtures_mapping_pathname,
from: from,
file_filter: file_filter)
file_filter: file_filter,
only_new_paths: only_new_paths)
end
let(:changed_files_pathname) { changed_files_file.path }
@ -25,6 +26,7 @@ RSpec.describe Tooling::FindChanges, feature_category: :tooling do
let(:from) { :api }
let(:gitlab_client) { double('GitLab') } # rubocop:disable RSpec/VerifiedDoubles
let(:file_filter) { ->(_) { true } }
let(:only_new_paths) { false }
around do |example|
self.changed_files_file = Tempfile.new('changed_files_file')
@ -122,6 +124,37 @@ RSpec.describe Tooling::FindChanges, feature_category: :tooling do
expect(File.read(changed_files_file)).to eq('doc/index.md')
end
end
context 'when used with only_new_paths' do
let(:only_new_paths) { true }
let(:mr_changes_array) do
[
{
"new_path" => "scripts/test.js",
"old_path" => "scripts/test.js"
},
{
"new_path" => "doc/renamed_index.md",
"old_path" => "doc/index.md"
}
]
end
before do
# rubocop:disable RSpec/VerifiedDoubles -- The class from the GitLab gem isn't public, so we cannot use verified doubles for it.
allow(gitlab_client).to receive(:merge_request_changes)
.with('dummy-project', '1234')
.and_return(double(changes: mr_changes_array))
# rubocop:enable RSpec/VerifiedDoubles
end
it 'only writes new file paths to output' do
subject
expect(File.read(changed_files_file)).to eq('doc/renamed_index.md scripts/test.js')
end
end
end
context 'when fetching changes from changed files' do

View File

@ -15,7 +15,8 @@ module Tooling
changed_files_pathname: nil,
predictive_tests_pathname: nil,
frontend_fixtures_mapping_pathname: nil,
file_filter: ->(_) { true }
file_filter: ->(_) { true },
only_new_paths: false
)
raise ArgumentError, ':from can only be :api or :changed_files' unless
@ -30,6 +31,7 @@ module Tooling
@frontend_fixtures_mapping_pathname = frontend_fixtures_mapping_pathname
@from = from
@file_filter = file_filter
@api_path_attributes = only_new_paths ? %w[new_path] : %w[new_path old_path]
end
def execute
@ -53,7 +55,7 @@ module Tooling
attr_reader :gitlab_token, :gitlab_endpoint, :mr_project_path,
:mr_iid, :changed_files_pathname, :predictive_tests_pathname,
:frontend_fixtures_mapping_pathname, :file_filter
:frontend_fixtures_mapping_pathname, :file_filter, :api_path_attributes
def gitlab
@gitlab ||= begin
@ -86,7 +88,7 @@ module Tooling
case @from
when :api
mr_changes.changes.select(&file_filter).flat_map do |change|
change.to_h.values_at('old_path', 'new_path')
change.to_h.values_at(*api_path_attributes)
end.uniq
else
read_array_from_file(changed_files_pathname)