Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-01-15 06:31:33 +00:00
parent afaae32aba
commit a009356633
50 changed files with 601 additions and 596 deletions

View File

@ -714,7 +714,7 @@ lib/gitlab/checks/**
/doc/api/boards.md @msedlakjakubowski
/doc/api/branches.md @brendan777
/doc/api/bulk_imports.md @ashrafkhamis
/doc/api/chat.md @fneill
/doc/api/chat.md @jglassman1
/doc/api/cluster_agents.md @phillipwells
/doc/api/code_suggestions.md @jglassman1
/doc/api/commits.md @brendan777
@ -1096,7 +1096,7 @@ lib/gitlab/checks/**
/doc/user/get_started/get_started_projects.md @emily.sahlani
/doc/user/get_started/getting_started_gitlab_duo.md @sselhorn
/doc/user/gitlab_duo/ @sselhorn
/doc/user/gitlab_duo_chat/ @fneill
/doc/user/gitlab_duo_chat/ @jglassman1
/doc/user/glql/ @msedlakjakubowski
/doc/user/group/access_and_permissions.md @emily.sahlani
/doc/user/group/clusters/ @phillipwells

View File

@ -9,9 +9,7 @@ Gitlab/DocumentationLinks/Link:
- 'ee/app/helpers/projects/learn_gitlab_helper.rb'
- 'ee/lib/ee/gitlab/namespace_storage_size_error_message.rb'
- 'ee/lib/gitlab/checks/secrets_check.rb'
- 'ee/lib/gitlab/llm/embeddings/utils/base_content_parser.rb'
- 'ee/spec/helpers/projects/learn_gitlab_helper_spec.rb'
- 'ee/spec/lib/gitlab/llm/embeddings/utils/base_content_parser_spec.rb'
- 'ee/spec/support/shared_contexts/secrets_check_shared_contexts.rb'
- 'lib/backup/tasks/database.rb'
- 'lib/system_check/helpers.rb'

View File

@ -531,8 +531,6 @@ Layout/EmptyLineAfterMagicComment:
- 'spec/requests/api/group_debian_distributions_spec.rb'
- 'spec/requests/api/helm_packages_spec.rb'
- 'spec/requests/api/maven_packages_spec.rb'
- 'spec/requests/api/nuget_group_packages_spec.rb'
- 'spec/requests/api/nuget_project_packages_spec.rb'
- 'spec/requests/api/project_debian_distributions_spec.rb'
- 'spec/requests/api/pypi_packages_spec.rb'
- 'spec/requests/api/rpm_project_packages_spec.rb'

View File

@ -2004,8 +2004,6 @@ Layout/LineLength:
- 'lib/api/notes.rb'
- 'lib/api/notification_settings.rb'
- 'lib/api/npm_project_packages.rb'
- 'lib/api/nuget_group_packages.rb'
- 'lib/api/nuget_project_packages.rb'
- 'lib/api/pages_domains.rb'
- 'lib/api/project_clusters.rb'
- 'lib/api/project_container_repositories.rb'
@ -3729,7 +3727,6 @@ Layout/LineLength:
- 'spec/requests/api/notes_spec.rb'
- 'spec/requests/api/notification_settings_spec.rb'
- 'spec/requests/api/npm_project_packages_spec.rb'
- 'spec/requests/api/nuget_project_packages_spec.rb'
- 'spec/requests/api/pages/internal_access_spec.rb'
- 'spec/requests/api/pages/private_access_spec.rb'
- 'spec/requests/api/pages/public_access_spec.rb'
@ -4096,7 +4093,6 @@ Layout/LineLength:
- 'spec/support/shared_contexts/fixtures/analytics_shared_context.rb'
- 'spec/support/shared_contexts/graphql/resolvers/runners_resolver_shared_context.rb'
- 'spec/support/shared_contexts/lib/gitlab/sidekiq_middleware/server_metrics_shared_context.rb'
- 'spec/support/shared_contexts/presenters/nuget_shared_context.rb'
- 'spec/support/shared_contexts/requests/api/debian_repository_shared_context.rb'
- 'spec/support/shared_contexts/requests/api/go_modules_shared_context.rb'
- 'spec/support/shared_contexts/requests/api/npm_packages_shared_context.rb'
@ -4199,7 +4195,6 @@ Layout/LineLength:
- 'spec/support/shared_examples/requests/api/notes_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/read_user_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb'

View File

@ -120,7 +120,6 @@ Lint/AssignmentInCondition:
- 'ee/lib/ee/gitlab/repo_path.rb'
- 'ee/lib/gem_extensions/elasticsearch/model/indexing/instance_methods.rb'
- 'ee/lib/gitlab/group_plans_preloader.rb'
- 'ee/lib/gitlab/llm/embeddings/utils/base_content_parser.rb'
- 'ee/lib/gitlab/path_locks_finder.rb'
- 'ee/lib/gitlab/subscription_portal/clients/graphql.rb'
- 'ee/lib/system_check/geo/authorized_keys_check.rb'

View File

@ -217,7 +217,6 @@ Performance/StringIdentifierArgument:
- 'spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/graphql/projects/branch_protections/access_level_request_examples.rb'
- 'spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb'

View File

@ -1119,7 +1119,6 @@ RSpec/BeforeAllRoleAssignment:
- 'spec/requests/api/maven_packages_spec.rb'
- 'spec/requests/api/merge_request_approvals_spec.rb'
- 'spec/requests/api/merge_requests_spec.rb'
- 'spec/requests/api/nuget_group_packages_spec.rb'
- 'spec/requests/api/package_files_spec.rb'
- 'spec/requests/api/pages/internal_access_spec.rb'
- 'spec/requests/api/pages/private_access_spec.rb'

View File

@ -106,7 +106,6 @@ RSpec/ChangeByZero:
- 'spec/support/shared_examples/controllers/todos_shared_examples.rb'
- 'spec/support/shared_examples/graphql/mutations/timelogs/create_shared_examples.rb'
- 'spec/support/shared_examples/models/relative_positioning_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/services/clusters/create_service_shared_examples.rb'
- 'spec/support/shared_examples/services/schedule_bulk_repository_shard_moves_shared_examples.rb'

View File

@ -2219,7 +2219,6 @@ RSpec/ContextWording:
- 'spec/requests/api/merge_requests_spec.rb'
- 'spec/requests/api/notes_spec.rb'
- 'spec/requests/api/npm_project_packages_spec.rb'
- 'spec/requests/api/nuget_group_packages_spec.rb'
- 'spec/requests/api/oauth_tokens_spec.rb'
- 'spec/requests/api/package_files_spec.rb'
- 'spec/requests/api/pages/internal_access_spec.rb'
@ -2737,7 +2736,6 @@ RSpec/ContextWording:
- 'spec/support/shared_examples/requests/api/multiple_and_scoped_issue_boards_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/notes_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/repositories_shared_context.rb'

View File

@ -855,7 +855,6 @@ RSpec/VerifiedDoubles:
- 'spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/debian_common_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/rack_attack_shared_examples.rb'

View File

@ -449,7 +449,6 @@ Style/IfUnlessModifier:
- 'lib/api/invitations.rb'
- 'lib/api/maven_packages.rb'
- 'lib/api/merge_requests.rb'
- 'lib/api/nuget_project_packages.rb'
- 'lib/api/pages_domains.rb'
- 'lib/api/project_snippets.rb'
- 'lib/api/projects.rb'

View File

@ -1724,7 +1724,6 @@ Style/InlineDisableAnnotation:
- 'lib/api/metadata.rb'
- 'lib/api/namespaces.rb'
- 'lib/api/notes.rb'
- 'lib/api/nuget_project_packages.rb'
- 'lib/api/pages_domains.rb'
- 'lib/api/project_container_repositories.rb'
- 'lib/api/project_packages.rb'
@ -2428,7 +2427,6 @@ Style/InlineDisableAnnotation:
- 'spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/ml_model_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb'
- 'spec/support/shared_examples/requests/api/terraform/modules/v1/packages_shared_examples.rb'

View File

@ -50,4 +50,3 @@ Style/NumericLiteralPrefix:
- 'spec/models/personal_access_token_spec.rb'
- 'spec/requests/api/personal_access_tokens_spec.rb'
- 'spec/support/import_export/export_file_helper.rb'
- 'spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb'

View File

@ -45,7 +45,6 @@ Style/RedundantReturn:
- 'ee/app/workers/ee/repository_check/single_repository_worker.rb'
- 'ee/lib/ee/api/entities/billable_member.rb'
- 'ee/lib/ee/gitlab/checks/diff_check.rb'
- 'lib/api/nuget_project_packages.rb'
- 'lib/api/pagination_params.rb'
- 'lib/feature/gitaly.rb'
- 'lib/gitlab/auth/database/authentication.rb'

View File

@ -1 +1 @@
c9ad6326777be8ab69ddb1cf4b58e91a0fa38c81
70e2fc09262d3087f8bca60a680dabc7bb328164

View File

@ -15,8 +15,8 @@ const Template = (args, { argTypes }) => ({
<template #body>
<p><code>#body</code> slot content</p>
</template>
<template #alert-popover>
<div><code>#alert-popover</code> slot content</div>
<template #alert-message>
<div><code>#alert-message</code> slot content</div>
</template>
</panels-base>
`,

View File

@ -1,29 +1,15 @@
<script>
import {
GlDisclosureDropdown,
GlIcon,
GlLoadingIcon,
GlPopover,
GlSprintf,
GlLink,
} from '@gitlab/ui';
import { GlPopover, GlDashboardPanel } from '@gitlab/ui';
import { alertVariantIconMap } from '@gitlab/ui/src/utils/constants';
import uniqueId from 'lodash/uniqueId';
import { isObject } from 'lodash';
import { VARIANT_DANGER, VARIANT_WARNING, VARIANT_INFO } from '~/alert';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { PANEL_POPOVER_DELAY } from './constants';
export default {
name: 'PanelsBase',
components: {
GlDisclosureDropdown,
GlLoadingIcon,
GlIcon,
GlPopover,
TooltipOnTruncate,
GlSprintf,
GlLink,
GlDashboardPanel,
},
props: {
title: {
@ -76,28 +62,29 @@ export default {
},
data() {
return {
popoverId: uniqueId('panel-alert-popover-'),
titleTooltipId: uniqueId('title-tooltip-id-'),
dropdownOpen: false,
};
},
computed: {
alertClasses() {
const borderColor = this.showAlertState
? this.$options.alertBorderColorMap[this.alertVariant]
: '';
return `gl-border-t-2 gl-border-t-solid ${borderColor}`;
borderColor() {
return this.showAlertState ? this.$options.alertBorderColorMap[this.alertVariant] : '';
},
alertIconClasses() {
return this.$options.alertIconClassMap[this.alertVariant];
return this.showAlertState ? this.$options.alertIconClassMap[this.alertVariant] : '';
},
alertIcon() {
return this.$options.alertVariantIconMap[this.alertVariant] ?? alertVariantIconMap.danger;
if (this.showAlertState) {
return this.$options.alertVariantIconMap[this.alertVariant] ?? alertVariantIconMap.danger;
}
return '';
},
showAlertPopover() {
return this.showAlertState && !this.dropdownOpen;
},
editingActions() {
return this.editing ? this.actions : [];
},
},
PANEL_POPOVER_DELAY,
alertVariantIconMap,
@ -115,106 +102,41 @@ export default {
</script>
<template>
<div
:id="popoverId"
class="grid-stack-item-content gl-border gl-h-full !gl-overflow-visible gl-rounded-base gl-bg-white gl-p-4"
:class="alertClasses"
<gl-dashboard-panel
container-class="grid-stack-item-content"
:title="title"
:title-icon-class="alertIconClasses"
:title-icon="alertIcon"
:title-popover="tooltip"
:loading="loading"
:loading-delayed="loadingDelayed"
:loading-delayed-text="__('Still loading...')"
:actions="editingActions"
:actions-toggle-text="__('Actions')"
:border-color-class="borderColor"
@dropdownOpen="dropdownOpen = true"
@dropdownClosed="dropdownOpen = false"
>
<div class="gl-flex gl-h-full gl-flex-col">
<div class="gl-flex gl-items-start gl-justify-between" data-testid="panel-title">
<tooltip-on-truncate
v-if="title"
:title="title"
placement="top"
boundary="viewport"
class="gl-truncate gl-pb-3"
>
<gl-icon
v-if="showAlertState"
class="gl-mr-1"
:class="alertIconClasses"
:name="alertIcon"
data-testid="panel-title-alert-icon"
/>
<strong class="gl-text-subtle">{{ title }}</strong>
<template v-if="tooltip && tooltip.description">
<gl-icon
:id="titleTooltipId"
data-testid="panel-title-tooltip-icon"
name="information-o"
variant="info"
/>
<gl-popover
data-testid="panel-title-popover"
boundary="viewport"
:target="titleTooltipId"
>
<gl-sprintf v-if="tooltip.descriptionLink" :message="tooltip.description">
<template #link="{ content }">
<gl-link :href="tooltip.descriptionLink" class="gl-text-sm">{{
content
}}</gl-link>
</template>
</gl-sprintf>
<template v-else>
{{ tooltip.description }}
</template>
</gl-popover>
</template>
</tooltip-on-truncate>
<gl-disclosure-dropdown
v-if="editing"
:items="actions"
icon="ellipsis_v"
:toggle-text="__('Actions')"
text-sr-only
no-caret
placement="bottom-end"
fluid-width
toggle-class="gl-ml-1"
category="tertiary"
positioning-strategy="fixed"
@shown="dropdownOpen = true"
@hidden="dropdownOpen = false"
>
<template #list-item="{ item }">
<span> <gl-icon :name="item.icon" /> {{ item.text }}</span>
</template>
</gl-disclosure-dropdown>
</div>
<div
class="gl-grow gl-overflow-y-auto gl-overflow-x-hidden"
:class="{ 'gl-flex gl-flex-wrap gl-content-center gl-text-center': loading }"
>
<template v-if="loading">
<gl-loading-icon size="lg" class="gl-w-full" />
<div
v-if="loadingDelayed"
class="gl-w-full gl-text-subtle"
data-testId="panel-loading-delayed-indicator"
>
{{ __('Still loading...') }}
</div>
</template>
<!-- @slot The panel body to display when not loading. -->
<slot v-else name="body"></slot>
</div>
<template #body>
<slot name="body"></slot>
</template>
<template #alert-message="{ panelId }">
<gl-popover
v-if="showAlertPopover"
data-test-id="panel-alert-popover"
:aria-describedby="panelId"
triggers="hover focus"
:title="alertPopoverTitle"
:show-close-button="false"
placement="top"
:css-classes="['gl-max-w-1/2']"
:target="popoverId"
:target="panelId"
:delay="$options.PANEL_POPOVER_DELAY"
boundary="viewport"
>
<!-- @slot The panel error popover body to display when showAlertState is true. -->
<slot name="alert-popover"></slot>
</gl-popover>
</div>
</div>
</template>
</gl-dashboard-panel>
</template>

View File

@ -4,7 +4,7 @@ import { GlIcon, GlBadge, GlTooltipDirective } from '@gitlab/ui';
import {
renderDeleteSuccessToast,
deleteParams,
} from 'ee_else_ce/vue_shared/components/resource_lists/utils';
} from 'ee_else_ce/vue_shared/components/projects_list/utils';
import ProjectListItemDescription from 'ee_else_ce/vue_shared/components/projects_list/project_list_item_description.vue';
import ProjectListItemActions from 'ee_else_ce/vue_shared/components/projects_list/project_list_item_actions.vue';
import ProjectListItemInactiveBadge from 'ee_else_ce/vue_shared/components/projects_list/project_list_item_inactive_badge.vue';

View File

@ -1,3 +1,5 @@
import toast from '~/vue_shared/plugins/global_toast';
import { sprintf, __ } from '~/locale';
import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants';
export const availableGraphQLProjectActions = ({ userPermissions }) => {
@ -13,3 +15,16 @@ export const availableGraphQLProjectActions = ({ userPermissions }) => {
return baseActions;
};
export const renderDeleteSuccessToast = (project) => {
toast(
sprintf(__("Project '%{project_name}' is being deleted."), {
project_name: project.name,
}),
);
};
export const deleteParams = () => {
// Overridden in EE
return {};
};

View File

@ -89,3 +89,5 @@ module Organizations
end
end
end
Organizations::OrganizationsController.prepend_mod

View File

@ -98,7 +98,7 @@ module ReleasesHelper
commit = project.repository.commit(@release.tag)
deployments.map do |deployment|
user = deployment.deployable.user
user = deployment.deployable&.user
environment = deployment.environment
{
@ -118,11 +118,15 @@ module ReleasesHelper
short_sha: commit.short_id,
title: commit.title
},
triggerer: {
name: user&.name,
web_url: user ? user_url(user) : nil,
avatar_url: user&.avatar_url
},
triggerer: if user
{
name: user.name,
web_url: user_url(user),
avatar_url: user.avatar_url
}
end,
created_at: deployment.created_at,
finished_at: deployment.finished_at
}

View File

@ -3,6 +3,14 @@
module WorkItems
module Widgets
class Development < Base
def self.quick_action_commands
[:create_merge_request]
end
def self.quick_action_params
[:branch_name]
end
def closing_merge_requests
work_item.merge_requests_closing_issues
end

View File

@ -46,7 +46,7 @@
.row
= form.label :merge_after, s_('MergeRequests|Merge can start'), class: 'col-12'
.js-merge-request-schedule-input{ data: {
merge_after: issuable.merge_schedule&.merge_after&.strftime('%Y-%m-%dT%H:%MZ'),
merge_after: issuable.merge_schedule&.merge_after&.strftime('%Y-%m-%dT%H:%M%z'),
param_key: issuable.class.model_name.param_key
} }
%p.gl-text-subtle.col-12= s_('MergeRequests|Requires that merge checks pass.')

View File

@ -51,14 +51,14 @@ The process for configuring TLS support depends on your installation type.
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Gitaly clients, edit `git_data_dirs` in `/etc/gitlab/gitlab.rb` as follows:
1. On the Gitaly clients, edit `gitlab_rails['repositories_storages']` in `/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
})
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).

View File

@ -89,7 +89,7 @@ Here are common errors and potential causes:
- 500 response code
- `ActionView::Template::Error (7:permission denied)`
- `praefect['configuration'][:auth][:token]` and `gitlab_rails['gitaly_token']` do not match on the GitLab server.
- `git_data_dirs` storage configuration is missing on the Sidekiq server.
- `gitlab_rails['repositories_storages']` storage configuration is missing on the Sidekiq server.
- `Unable to save project. Error: 7:permission denied`
- Secret token in `praefect['configuration'][:virtual_storage]` on GitLab server does not match the
value in `gitaly['auth_token']` on one or more Gitaly servers.

View File

@ -1448,7 +1448,7 @@ To configure the Praefect nodes, on each one:
password: '<praefect_postgresql_password>',
},
# Praefect Virtual Storage config
# Name of storage hash must match storage name in git_data_dirs on GitLab
# Name of storage hash must match storage name in gitlab_rails['repositories_storages'] on GitLab
# server ('praefect') and in gitaly['configuration'][:storage] on Gitaly nodes ('gitaly-1')
virtual_storage: [
{
@ -1733,16 +1733,16 @@ To configure Praefect with TLS:
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
1. On the Praefect clients (except Gitaly servers), edit `gitlab_rails['repositories_storages']` in
`/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => 'tls://LOAD_BALANCER_SERVER_ADDRESS:3305',
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
@ -1823,15 +1823,15 @@ To configure the Sidekiq nodes, on each one:
]
# Gitaly Cluster
## git_data_dirs get configured for the Praefect virtual storage
## Address is Internal Load Balancer for Praefect
## Token is praefect_external_token
git_data_dirs({
## gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
## Address is the Internal Load Balancer for Praefect
## Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
# PostgreSQL
gitlab_rails['db_host'] = '10.6.0.40' # internal load balancer IP
@ -1949,15 +1949,15 @@ On each node perform the following:
```ruby
external_url 'https://gitlab.example.com'
# git_data_dirs get configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
# gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
# Address is the Internal Load Balancer for Praefect
# Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
## Disable components that will not be on the GitLab application server
roles(['application_role'])
@ -2042,15 +2042,15 @@ On each node perform the following:
```
1. If you're using [Gitaly with TLS support](#gitaly-cluster-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
`gitlab_rails['repositories_storages']` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
```
1. Copy the cert into `/etc/gitlab/trusted-certs`:

View File

@ -1454,7 +1454,7 @@ To configure the Praefect nodes, on each one:
password: '<praefect_postgresql_password>',
},
# Praefect Virtual Storage config
# Name of storage hash must match storage name in git_data_dirs on GitLab
# Name of storage hash must match storage name in gitlab_rails['repositories_storages'] on GitLab
# server ('praefect') and in gitaly['configuration'][:storage] on Gitaly nodes ('gitaly-1')
virtual_storage: [
{
@ -1739,16 +1739,16 @@ To configure Praefect with TLS:
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
1. On the Praefect clients (except Gitaly servers), edit `gitlab_rails['repositories_storages']` in
`/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => 'tls://LOAD_BALANCER_SERVER_ADDRESS:3305',
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
@ -1829,15 +1829,15 @@ To configure the Sidekiq nodes, on each one:
]
# Gitaly Cluster
## git_data_dirs get configured for the Praefect virtual storage
## Address is Internal Load Balancer for Praefect
## Token is praefect_external_token
git_data_dirs({
## gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
## Address is the Internal Load Balancer for Praefect
## Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
# PostgreSQL
gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
@ -1957,15 +1957,15 @@ On each node perform the following:
```ruby
external_url 'https://gitlab.example.com'
# git_data_dirs get configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
# gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
# Address is the Internal Load Balancer for Praefect
# Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
## Disable components that will not be on the GitLab application server
roles(['application_role'])
@ -2050,15 +2050,15 @@ On each node perform the following:
```
1. If you're using [Gitaly with TLS support](#gitaly-cluster-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
`gitlab_rails['repositories_storages']` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
```
1. Copy the cert into `/etc/gitlab/trusted-certs`:

View File

@ -663,11 +663,11 @@ To configure the Sidekiq server, on the server node you want to use for Sidekiq:
# of the Gitaly setup
gitlab_rails['gitaly_token'] = 'gitalysecret'
git_data_dirs({
gitlab_rails['repositories_storages'] = {
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
}
## PostgreSQL connection details
gitlab_rails['db_adapter'] = 'postgresql'
@ -784,11 +784,11 @@ On each node perform the following:
# of the Gitaly setup
gitlab_rails['gitaly_token'] = 'gitalysecret'
git_data_dirs({
gitlab_rails['repositories_storages'] = {
'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
}
## Disable components that will not be on the GitLab application server
roles(['application_role'])
@ -867,14 +867,14 @@ On each node perform the following:
```
1. If you're using [Gitaly with TLS support](#gitaly-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
`gitlab_rails['repositories_storages']` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
})
}
```
1. Copy the cert into `/etc/gitlab/trusted-certs`:

View File

@ -1284,7 +1284,7 @@ To configure the Praefect nodes, on each one:
password: '<praefect_postgresql_password>',
},
# Praefect Virtual Storage config
# Name of storage hash must match storage name in git_data_dirs on GitLab
# Name of storage hash must match storage name in gitlab_rails['repositories_storages'] on the GitLab
# server ('praefect') and in gitaly['configuration'][:storage] on Gitaly nodes ('gitaly-1')
virtual_storage: [
{
@ -1573,16 +1573,16 @@ To configure Praefect with TLS:
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
1. On the Praefect clients (except Gitaly servers), edit `gitlab_rails['repositories_storages']` in
`/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => 'tls://LOAD_BALANCER_SERVER_ADDRESS:3305',
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
@ -1655,15 +1655,15 @@ To configure the Sidekiq nodes, on each one:
]
# Gitaly Cluster
## git_data_dirs get configured for the Praefect virtual storage
## Address is Internal Load Balancer for Praefect
## Token is praefect_external_token
git_data_dirs({
## repositories_storages gets configured for the Praefect virtual storage
## Address is the Internal Load Balancer for Praefect
## Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
# PostgreSQL
gitlab_rails['db_host'] = '10.6.0.40' # internal load balancer IP
@ -1790,15 +1790,15 @@ On each node perform the following:
```ruby
external_url 'https://gitlab.example.com'
# git_data_dirs get configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
# gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
# Address is the Internal Load Balancer for Praefect
# Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
## Disable components that will not be on the GitLab application server
roles(['application_role'])
@ -1901,15 +1901,15 @@ On each node perform the following:
```
1. If you're using [Gitaly with TLS support](#gitaly-cluster-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
`gitlab_rails['repositories_storages']` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
```
1. Copy the cert into `/etc/gitlab/trusted-certs`:

View File

@ -1461,7 +1461,7 @@ To configure the Praefect nodes, on each one:
password: '<praefect_postgresql_password>',
},
# Praefect Virtual Storage config
# Name of storage hash must match storage name in git_data_dirs on GitLab
# Name of storage hash must match storage name in gitlab_rails['repositories_storages'] on GitLab
# server ('praefect') and in gitaly['configuration'][:storage] on Gitaly nodes ('gitaly-1')
virtual_storage: [
{
@ -1746,16 +1746,16 @@ To configure Praefect with TLS:
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
1. On the Praefect clients (except Gitaly servers), edit `gitlab_rails['repositories_storages']` in
`/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => 'tls://LOAD_BALANCER_SERVER_ADDRESS:3305',
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
@ -1837,15 +1837,15 @@ To configure the Sidekiq nodes, on each one:
]
# Gitaly
# git_data_dirs get configured for the Praefect virtual storage
# gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
# PostgreSQL
gitlab_rails['db_host'] = '10.6.0.20' # internal load balancer IP
@ -1975,15 +1975,15 @@ On each node perform the following:
```ruby
external_url 'https://gitlab.example.com'
# git_data_dirs get configured for the Praefect virtual storage
# gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
## Disable components that will not be on the GitLab application server
roles(['application_role'])
@ -2069,15 +2069,15 @@ On each node perform the following:
```
1. If you're using [Gitaly with TLS support](#gitaly-cluster-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
`gitlab_rails['repositories_storages']` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
```
1. Copy the cert into `/etc/gitlab/trusted-certs`:

View File

@ -1285,7 +1285,7 @@ To configure the Praefect nodes, on each one:
password: '<praefect_postgresql_password>',
},
# Praefect Virtual Storage config
# Name of storage hash must match storage name in git_data_dirs on GitLab
# Name of storage hash must match storage name in gitlab_rails['repositories_storages'] on GitLab
# server ('praefect') and in gitaly['configuration'][:storage] on Gitaly nodes ('gitaly-1')
virtual_storage: [
{
@ -1570,16 +1570,16 @@ To configure Praefect with TLS:
sudo cp cert.pem /etc/gitlab/trusted-certs/
```
1. On the Praefect clients (except Gitaly servers), edit `git_data_dirs` in
1. On the Praefect clients (except Gitaly servers), edit `gitlab_rails['repositories_storages']` in
`/etc/gitlab/gitlab.rb` as follows:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => 'tls://LOAD_BALANCER_SERVER_ADDRESS:3305',
"gitaly_token" => 'PRAEFECT_EXTERNAL_TOKEN'
}
})
}
```
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#reconfigure-a-linux-package-installation).
@ -1652,15 +1652,15 @@ To configure the Sidekiq nodes, on each one:
]
# Gitaly Cluster
## git_data_dirs get configured for the Praefect virtual storage
## Address is Internal Load Balancer for Praefect
## Token is praefect_external_token
git_data_dirs({
## gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
## Address is the Internal Load Balancer for Praefect
## Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
# PostgreSQL
gitlab_rails['db_host'] = '10.6.0.40' # internal load balancer IP
@ -1787,15 +1787,15 @@ On each node perform the following:
```ruby
external_url 'https://gitlab.example.com'
# git_data_dirs get configured for the Praefect virtual storage
# Address is Internal Load Balancer for Praefect
# Token is praefect_external_token
git_data_dirs({
# gitlab_rails['repositories_storages'] gets configured for the Praefect virtual storage
# Address is the Internal Load Balancer for Praefect
# Token is the praefect_external_token
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tcp://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
## Disable components that will not be on the GitLab application server
roles(['application_role'])
@ -1901,15 +1901,15 @@ On each node perform the following:
```
1. If you're using [Gitaly with TLS support](#gitaly-cluster-tls-support), make sure the
`git_data_dirs` entry is configured with `tls` instead of `tcp`:
`gitlab_rails['repositories_storages']` entry is configured with `tls` instead of `tcp`:
```ruby
git_data_dirs({
gitlab_rails['repositories_storages'] = {
"default" => {
"gitaly_address" => "tls://10.6.0.40:2305", # internal load balancer IP
"gitaly_token" => '<praefect_external_token>'
}
})
}
```
1. Copy the cert into `/etc/gitlab/trusted-certs`:

View File

@ -191,6 +191,6 @@ To start using Internal Events Tracking, follow these steps:
### Frontend
If you are calling `trackRedisHllUserEvent` in the frontend to track the frontend event, you can convert this to Internal events by using mixin, raw JavaScript or data tracking attribute,
You can convert `trackRedisHllUserEvent` calls to Internal events by using the mixin, raw JavaScript, or the `data-event-tracking` attribute.
[Quick start guide](quick_start.md#frontend-tracking) has example for each methods.
[Quick start guide](quick_start.md#frontend-tracking) has examples for each method.

View File

@ -56,7 +56,8 @@ module API
end
params do
requires :id, types: [Integer, String], desc: 'The group ID or full group path.', regexp: ::API::Concerns::Packages::Nuget::PrivateEndpoints::POSITIVE_INTEGER_REGEX
requires :id, types: [Integer, String], desc: 'The group ID or full group path.',
regexp: ::API::Concerns::Packages::Nuget::PrivateEndpoints::POSITIVE_INTEGER_REGEX
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
@ -66,8 +67,11 @@ module API
end
authenticate_with do |accept|
accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username)
.sent_through(:http_basic_auth)
accept.token_types(
:personal_access_token_with_username,
:deploy_token_with_username,
:job_token_with_username
).sent_through(:http_basic_auth)
end
namespace '/nuget' do

View File

@ -34,7 +34,8 @@ module API
include ::Gitlab::Utils::StrongMemoize
params :file_params do
requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
requires :package, type: ::API::Validations::Types::WorkhorseFile,
desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
end
def project_or_group
@ -82,7 +83,9 @@ module API
def legacy_upload_nuget_package_file(symbol_package: false)
project = project_or_group
authorize_upload!(project)
bad_request!('File is too large') if project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
bad_request!('File is too large') if project.actual_limits.exceeded?(:nuget_max_file_size,
params[:package].size)
file_params = params.merge(
file: params[:package],
@ -95,15 +98,19 @@ module API
project, current_user, declared_params.merge(build: current_authenticated_job)
).execute(:nuget, name: temp_file_name(symbol_package))
package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job))
.execute
package_file = ::Packages::CreatePackageFileService
.new(package, file_params.merge(build: current_authenticated_job))
.execute
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker -- not newly introduced
end
def upload_nuget_package_file(symbol_package: false)
authorize_upload!(project_or_group)
bad_request!('File is too large') if project_or_group.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
if project_or_group.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
bad_request!('File is too large')
end
file_params = params.merge(
file: params[:package],
@ -112,7 +119,8 @@ module API
)
if !symbol_package && nuspec_file_service.success?
# Create or update package on the fly if nuspec file is extracted successfully, otherwise fallback to the background job
# Create or update package on the fly if nuspec file is extracted successfully,
# otherwise fallback to the background job
create_or_update_package(file_params)
elsif symbol_package || nuspec_file_service.cause.nuspec_extraction_failed?
create_temp_package_and_enqueue_worker(file_params, symbol_package)
@ -139,7 +147,7 @@ module API
package_file = ::Packages::CreatePackageFileService.new(package, file_params)
.execute
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker -- not newly introduced
end
def nuspec_file_service
@ -177,7 +185,10 @@ module API
)
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id })
Gitlab::ErrorTracking.track_exception(
e,
extra: { file_name: params[:file_name], project_id: project_or_group.id }
)
forbidden!
end
@ -188,7 +199,10 @@ module API
def format_filename(package)
return "#{params[:package_filename]}.#{params[:format]}" if package.version == params[:package_version]
return "#{params[:package_filename].sub(params[:package_version], package.version)}.#{params[:format]}" if package.normalized_nuget_version == params[:package_version]
return unless package.normalized_nuget_version == params[:package_version]
"#{params[:package_filename].sub(params[:package_version], package.version)}.#{params[:format]}"
end
def present_odata_entry
@ -219,7 +233,8 @@ module API
end
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', regexp: ::API::Concerns::Packages::Nuget::PrivateEndpoints::POSITIVE_INTEGER_REGEX
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project',
regexp: ::API::Concerns::Packages::Nuget::PrivateEndpoints::POSITIVE_INTEGER_REGEX
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/packages' do
@ -228,8 +243,11 @@ module API
end
authenticate_with do |accept|
accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username)
.sent_through(:http_basic_auth)
accept.token_types(
:personal_access_token_with_username,
:deploy_token_with_username,
:job_token_with_username
).sent_through(:http_basic_auth)
end
namespace '/nuget' do
@ -237,7 +255,8 @@ module API
# https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource
params do
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' }
requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX,
documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' }
end
namespace '/download/*package_name' do
after_validation do
@ -270,8 +289,10 @@ module API
tags %w[nuget_packages]
end
params do
requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.3.0.17' }
requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' }
requires :package_version, type: String, desc: 'The NuGet package version',
regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.3.0.17' }
requires :package_filename, type: String, desc: 'The NuGet package filename',
regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' }
end
get '*package_version/*package_filename', format: [:nupkg, :snupkg], urgency: :low do
package = find_package
@ -297,8 +318,11 @@ module API
# we redefine the `authenticate_with` method by combining the previous
# authentication option with the new one.
authenticate_with do |accept|
accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username)
.sent_through(:http_basic_auth)
accept.token_types(
:personal_access_token_with_username,
:deploy_token_with_username,
:job_token_with_username
).sent_through(:http_basic_auth)
accept.token_types(:personal_access_token, :deploy_token, :job_token)
.sent_through(http_header: API_KEY_HEADER)
end
@ -382,8 +406,10 @@ module API
tags %w[nuget_packages]
end
params do
requires :package_name, type: String, allow_blank: false, desc: 'The NuGet package name', regexp: Gitlab::Regex.nuget_package_name_regex, documentation: { example: 'mynugetpkg' }
requires :package_version, type: String, allow_blank: false, desc: 'The NuGet package version', regexp: Gitlab::Regex.nuget_version_regex, documentation: { example: '1.0.1' }
requires :package_name, type: String, allow_blank: false, desc: 'The NuGet package name',
regexp: Gitlab::Regex.nuget_package_name_regex, documentation: { example: 'mynugetpkg' }
requires :package_version, type: String, allow_blank: false, desc: 'The NuGet package version',
regexp: Gitlab::Regex.nuget_version_regex, documentation: { example: '1.0.1' }
end
delete '*package_name/*package_version', format: false, urgency: :low do
authorize_destroy_package!(project_or_group)
@ -391,7 +417,13 @@ module API
destroy_conditionally!(find_package) do |package|
::Packages::MarkPackageForDestructionService.new(container: package, current_user: current_user).execute
track_package_event('delete_package', :nuget, category: 'API::NugetPackages', project: package.project, namespace: package.project.namespace)
track_package_event(
'delete_package',
:nuget,
category: 'API::NugetPackages',
project: package.project,
namespace: package.project.namespace
)
end
end

View File

@ -42,7 +42,7 @@ namespace :tw do
CodeOwnerRule.new('Distribution', '@axil'),
CodeOwnerRule.new('Distribution (Charts)', '@axil'),
CodeOwnerRule.new('Distribution (Omnibus)', '@eread'),
CodeOwnerRule.new('Duo Chat', '@fneill'),
CodeOwnerRule.new('Duo Chat', '@jglassman1'),
CodeOwnerRule.new('Duo Workflow', '@sselhorn'),
CodeOwnerRule.new('Dynamic Analysis', '@phillipwells'),
CodeOwnerRule.new('Editor Extensions', '@aqualls'),

View File

@ -43971,6 +43971,9 @@ msgstr ""
msgid "Project '%{project_name}' was successfully updated."
msgstr ""
msgid "Project '%{project_name}' will be deleted on %{date}."
msgstr ""
msgid "Project Badges"
msgstr ""

View File

@ -62,7 +62,7 @@
"@gitlab/fonts": "^1.3.0",
"@gitlab/query-language-rust": "0.3.2",
"@gitlab/svgs": "3.121.0",
"@gitlab/ui": "106.2.0",
"@gitlab/ui": "106.2.2",
"@gitlab/vue-router-vue3": "npm:vue-router@4.1.6",
"@gitlab/vuex-vue3": "npm:vuex@4.0.0",
"@gitlab/web-ide": "^0.0.1-dev-20250109231656",

View File

@ -133,7 +133,6 @@ spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items
spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js
spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js
spec/frontend/ci/pipelines_page/components/pipelines_artifacts_spec.js
spec/frontend/ci/runner/components/registration/runner_instructions/runner_instructions_modal_spec.js
spec/frontend/ci/runner/components/runner_filtered_search_bar_spec.js
spec/frontend/ci/runner/components/runner_form_fields_spec.js

View File

@ -1,83 +1,139 @@
import { nextTick } from 'vue';
import {
GlDisclosureDropdown,
GlDisclosureDropdownItem,
GlLoadingIcon,
GlPopover,
GlIcon,
GlLink,
GlSprintf,
} from '@gitlab/ui';
import { GlDashboardPanel, GlPopover } from '@gitlab/ui';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import PanelsBase from '~/vue_shared/components/customizable_dashboard/panels_base.vue';
import { VARIANT_DANGER, VARIANT_WARNING, VARIANT_INFO } from '~/alert';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { PANEL_POPOVER_DELAY } from '~/vue_shared/components/customizable_dashboard/constants';
import { stubComponent } from 'helpers/stub_component';
describe('PanelsBase', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
const createWrapper = ({ props = {}, slots = {}, mountFn = shallowMountExtended } = {}) => {
const createWrapper = ({
props = {},
slots = {},
scopedSlots = {},
mountFn = shallowMountExtended,
} = {}) => {
wrapper = mountFn(PanelsBase, {
propsData: {
...props,
},
slots,
stubs: { GlSprintf },
scopedSlots,
stubs: {
GlPopover: stubComponent(GlPopover, {
props: { ...GlPopover.props, delay: {} },
}),
},
});
};
const findPanelTitle = () => wrapper.findComponent(TooltipOnTruncate);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findLoadingDelayedIndicator = () => wrapper.findByTestId('panel-loading-delayed-indicator');
const findPanelTitleTooltipIcon = () => wrapper.findByTestId('panel-title-tooltip-icon');
const findPanelTitleAlertIcon = () => wrapper.findByTestId('panel-title-alert-icon');
const findPanelTitlePopover = () => wrapper.findByTestId('panel-title-popover');
const findPanelTitlePopoverLink = () => findPanelTitlePopover().findComponent(GlLink);
const findDashboardPanel = () => wrapper.findComponent(GlDashboardPanel);
const findPanelAlertPopover = () => wrapper.findComponent(GlPopover);
const findPanelActionsDropdown = () => wrapper.findComponent(GlDisclosureDropdown);
const findDropdownItemByText = (text) =>
findPanelActionsDropdown()
.findAllComponents(GlDisclosureDropdownItem)
.filter((w) => w.text() === text)
.at(0);
describe('default behaviour', () => {
beforeEach(() => {
createWrapper();
});
it('does not render a title', () => {
expect(findPanelTitle().exists()).toBe(false);
});
it('does not render a loading icon', () => {
expect(findLoadingIcon().exists()).toBe(false);
expect(findLoadingDelayedIndicator().exists()).toBe(false);
});
it('does not render a disclosure dropdown', () => {
expect(findPanelActionsDropdown().exists()).toBe(false);
it('sets the default props for the dashboard panel', () => {
expect(findDashboardPanel().props()).toStrictEqual({
containerClass: 'grid-stack-item-content',
borderColorClass: '',
title: '',
titleIcon: '',
titleIconClass: '',
titlePopover: {},
loading: false,
loadingDelayed: false,
loadingDelayedText: 'Still loading...',
actions: [],
actionsToggleText: 'Actions',
});
});
it('does not render an error popover', () => {
expect(findPanelAlertPopover().exists()).toBe(false);
});
});
it('does not render the tooltip icon', () => {
expect(findPanelTitleTooltipIcon().exists()).toBe(false);
describe('with a title', () => {
beforeEach(() => {
createWrapper({
props: {
title: 'Panel title',
},
});
});
it('does not set an alert border color', () => {
const alertClasses = [
'gl-border-t-red-500',
'gl-border-t-orange-500',
'gl-border-t-blue-500',
];
it('sets the title prop', () => {
expect(findDashboardPanel().props('title')).toBe('Panel title');
});
});
alertClasses.forEach((alertClass) => {
expect(wrapper.attributes('class')).not.toContain(alertClass);
describe('with a tooltip', () => {
beforeEach(() => {
createWrapper({
props: {
tooltip: {
description: 'This is a description',
descriptionLink: '#',
},
},
});
});
it('sets the titlePopover prop', () => {
expect(findDashboardPanel().props('titlePopover')).toStrictEqual({
description: 'This is a description',
descriptionLink: '#',
});
});
});
describe('with actions', () => {
describe('when not editing', () => {
beforeEach(() => {
createWrapper({
props: {
actions: [
{
text: 'Delete',
icon: 'remove',
},
],
},
});
});
it('does not set the actions prop', () => {
expect(findDashboardPanel().props('actions')).toStrictEqual([]);
});
});
describe('when editing', () => {
beforeEach(() => {
createWrapper({
props: {
editing: true,
actions: [
{
text: 'Delete',
icon: 'remove',
},
],
},
});
});
it('sets the actions prop', () => {
expect(findDashboardPanel().props('actions')).toStrictEqual([
{
text: 'Delete',
icon: 'remove',
},
]);
});
});
});
@ -105,201 +161,15 @@ describe('PanelsBase', () => {
});
});
it('renders a loading icon', () => {
expect(findLoadingIcon().exists()).toBe(true);
expect(findLoadingDelayedIndicator().exists()).toBe(false);
it('sets the dashboard panel loading prop', () => {
expect(findDashboardPanel().props('loading')).toBe(true);
});
it('renders the additional "Still loading" indicator if the data source is slow', async () => {
await wrapper.setProps({ loadingDelayed: true });
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
expect(findLoadingDelayedIndicator().exists()).toBe(true);
});
});
describe('when loading with a body slot', () => {
beforeEach(() => {
createWrapper({
props: {
loading: true,
},
slots: {
body: '<div data-testid="panel-body-slot"></div>',
},
});
});
it('does not render the panel body', () => {
expect(wrapper.findByTestId('panel-body-slot').exists()).toBe(false);
});
});
describe('when there is a title', () => {
beforeEach(() => {
createWrapper({
props: {
title: 'Panel Title',
},
});
});
it('renders the panel title', () => {
expect(findPanelTitle().text()).toBe('Panel Title');
});
});
describe('when there is a title with a tooltip', () => {
describe('with description and link', () => {
beforeEach(() => {
createWrapper({
props: {
title: 'Panel Title',
tooltip: {
description: 'This is just information, %{linkStart}learn more%{linkEnd}',
descriptionLink: '/foo',
},
},
});
});
it('renders the panel title tooltip icon with special content', () => {
expect(findPanelTitleTooltipIcon().exists()).toBe(true);
expect(findPanelTitlePopover().text()).toBe('This is just information, learn more');
expect(findPanelTitlePopoverLink().attributes('href')).toBe('/foo');
});
});
describe('without description link', () => {
beforeEach(() => {
createWrapper({
props: {
title: 'Panel Title',
tooltip: {
description: 'This is just information.',
},
},
});
});
it('renders the panel title tooltip icon with description only', () => {
expect(findPanelTitleTooltipIcon().exists()).toBe(true);
expect(findPanelTitlePopoverLink().exists()).toBe(false);
expect(findPanelTitlePopover().text()).toBe('This is just information.');
});
});
describe('without description', () => {
beforeEach(() => {
createWrapper({
props: {
title: 'Panel Title',
tooltip: {
descriptionLink: '/foo',
},
},
});
});
it('does not render the panel title tooltip icon', () => {
expect(findPanelTitleTooltipIcon().exists()).toBe(false);
});
});
});
describe('when there is a title with an error alert', () => {
beforeEach(() => {
createWrapper({
props: {
title: 'Panel Title',
showAlertState: true,
alertVariant: VARIANT_DANGER,
},
});
});
it('renders the panel title error icon', () => {
expect(findPanelTitleAlertIcon().exists()).toBe(true);
expect(findPanelTitleAlertIcon().attributes('name')).toBe('error');
});
});
describe('when editing and there are actions', () => {
const actions = [
{
icon: 'pencil',
text: 'Edit',
action: () => {},
},
];
beforeEach(() => {
createWrapper({
props: {
editing: true,
actions,
},
mountFn: mountExtended,
});
});
it('renders the panel actions dropdown', () => {
expect(findPanelActionsDropdown().props('items')).toStrictEqual(actions);
});
it('renders the panel action dropdown item and icon', () => {
const dropdownItem = findDropdownItemByText(actions[0].text);
expect(dropdownItem.exists()).toBe(true);
expect(dropdownItem.findComponent(GlIcon).props('name')).toBe(actions[0].icon);
});
});
describe('when there is an error alert title and the alert state is true', () => {
beforeEach(() => {
createWrapper({
props: {
alertPopoverTitle: 'Some error',
showAlertState: true,
},
slots: {
'alert-popover': '<div data-testid="alert-popover-slot"></div>',
},
});
});
it('renders the error popover', () => {
const popover = findPanelAlertPopover();
expect(popover.exists()).toBe(true);
expect(popover.props('title')).toBe('Some error');
// TODO: Replace with .props() once GitLab-UI adds all supported props.
// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/428
expect(popover.vm.$attrs.delay).toStrictEqual(PANEL_POPOVER_DELAY);
});
it('renders the error popover slot', () => {
expect(wrapper.findByTestId('alert-popover-slot').exists()).toBe(true);
});
});
describe('when the editing and error state are true', () => {
beforeEach(() => {
createWrapper({
props: {
showAlertState: true,
editing: true,
},
});
});
it('hides the error popover when the dropdown is shown', async () => {
expect(findPanelAlertPopover().exists()).toBe(true);
await findPanelActionsDropdown().vm.$emit('shown');
expect(findPanelAlertPopover().exists()).toBe(false);
expect(findDashboardPanel().props('loadingDelayed')).toBe(true);
});
});
@ -318,22 +188,55 @@ describe('PanelsBase', () => {
showAlertState: true,
alertVariant,
},
slots: {
'alert-popover': '<div data-testid="alert-popover-slot"></div>',
scopedSlots: {
'alert-popover': '<div data-testid="panel-alert-popover-slot">Alert popover</div>',
},
mountFn: mountExtended,
});
});
it('sets the panel colors', () => {
['gl-border-t-2', 'gl-border-t-solid', borderColor].forEach((cssClass) => {
expect(wrapper.attributes('class')).toContain(cssClass);
});
expect(findDashboardPanel().props('borderColorClass')).toBe(borderColor);
});
it('sets the alert icon', () => {
expect(findPanelTitleAlertIcon().attributes('name')).toBe(iconName);
expect(findPanelTitleAlertIcon().attributes('class')).toContain(iconColor);
expect(findDashboardPanel().props('titleIcon')).toBe(iconName);
expect(findDashboardPanel().props('titleIconClass')).toBe(iconColor);
});
it('renders the alert message slot', () => {
expect(findPanelAlertPopover().props()).toMatchObject({
title: 'Some error',
triggers: 'hover focus',
delay: { hide: 500 },
showCloseButton: false,
placement: 'top',
cssClasses: ['gl-max-w-1/2'],
target: expect.stringContaining('gl-dashboard-panel-id-'),
boundary: 'viewport',
});
expect(findPanelAlertPopover().attributes()).toMatchObject({
'aria-describedby': expect.stringContaining('gl-dashboard-panel-id-'),
});
});
it('renders the alert popover slot', () => {
expect(wrapper.findByTestId('panel-alert-popover-slot').exists()).toBe(true);
});
it.each`
eventName | alertPopoverShown
${`dropdownOpen`} | ${false}
${`dropdownClosed`} | ${true}
`(
'when the dropdown event $eventName is emitted, the alert popover is $alertPopoverShown',
async ({ eventName, alertPopoverShown }) => {
findDashboardPanel().vm.$emit(eventName);
await nextTick();
expect(findPanelAlertPopover().exists()).toBe(alertPopoverShown);
},
);
});
});
});

View File

@ -27,7 +27,7 @@ import {
import {
renderDeleteSuccessToast,
deleteParams,
} from 'ee_else_ce/vue_shared/components/resource_lists/utils';
} from 'ee_else_ce/vue_shared/components/projects_list/utils';
import { deleteProject } from '~/api/projects_api';
import { createAlert } from '~/alert';
import CiIcon from '~/vue_shared/components/ci_icon/ci_icon.vue';
@ -36,8 +36,8 @@ const MOCK_DELETE_PARAMS = {
testParam: true,
};
jest.mock('ee_else_ce/vue_shared/components/resource_lists/utils', () => ({
...jest.requireActual('ee_else_ce/vue_shared/components/resource_lists/utils'),
jest.mock('ee_else_ce/vue_shared/components/projects_list/utils', () => ({
...jest.requireActual('ee_else_ce/vue_shared/components/projects_list/utils'),
renderDeleteSuccessToast: jest.fn(),
deleteParams: jest.fn(() => MOCK_DELETE_PARAMS),
}));

View File

@ -1,7 +1,24 @@
import { availableGraphQLProjectActions } from '~/vue_shared/components/projects_list/utils';
import organizationProjectsGraphQlResponse from 'test_fixtures/graphql/organizations/projects.query.graphql.json';
import {
availableGraphQLProjectActions,
deleteParams,
renderDeleteSuccessToast,
} from '~/vue_shared/components/projects_list/utils';
import { ACTION_EDIT, ACTION_DELETE } from '~/vue_shared/components/list_actions/constants';
import { formatGraphQLProjects } from '~/vue_shared/components/projects_list/formatter';
import toast from '~/vue_shared/plugins/global_toast';
describe('Projects list utils', () => {
jest.mock('~/vue_shared/plugins/global_toast');
const {
data: {
organization: {
projects: { nodes: projects },
},
},
} = organizationProjectsGraphQlResponse;
describe('availableGraphQLProjectActions', () => {
describe.each`
userPermissions | availableActions
${{ viewEditPage: false, removeProject: false }} | ${[]}
@ -14,3 +31,19 @@ describe('Projects list utils', () => {
});
});
});
describe('renderDeleteSuccessToast', () => {
const [project] = formatGraphQLProjects(projects);
it('calls toast correctly', () => {
renderDeleteSuccessToast(project);
expect(toast).toHaveBeenCalledWith(`Project '${project.name}' is being deleted.`);
});
});
describe('deleteParams', () => {
it('returns empty object', () => {
expect(deleteParams()).toStrictEqual({});
});
});

View File

@ -166,6 +166,23 @@ RSpec.describe ReleasesHelper, feature_category: :release_orchestration do
end
end
context 'when deployable is nil' do
let_it_be(:deployment_with_user) do
create(:deployment, environment: environment, project: project, sha: project.repository.commit.id,
deployable: nil)
end
before do
allow(release).to receive(:related_deployments).and_return([deployment_with_user])
end
it 'sets triggerer as nil' do
deployment_data = Gitlab::Json.parse(helper.data_for_show_page[:deployments]).first
expect(deployment_data['triggerer']).to be_nil
end
end
context 'when user cannot read deployments' do
# rubocop: disable CodeReuse/ActiveRecord -- mock for can? is incorrectly flagged
before do

View File

@ -250,7 +250,8 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
:assignees,
:labels,
:start_and_due_date,
:current_user_todos
:current_user_todos,
:development
])
end
@ -300,6 +301,10 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
it 'returns quick action commands from current user todos widget' do
is_expected.to include(:todo, :done)
end
it 'returns quick action commands from development widget' do
is_expected.to include(:create_merge_request)
end
end
end

View File

@ -9,6 +9,12 @@ RSpec.describe WorkItems::Widgets::Development, feature_category: :team_planning
it { is_expected.to eq(:development) }
end
describe '.quick_action_params' do
subject { described_class.quick_action_params }
it { is_expected.to include(:branch_name) }
end
describe '#type' do
subject { described_class.new(build_stubbed(:work_item)).type }

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do
@ -105,7 +106,7 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do
end
end
context 'a group' do
context 'for a group' do
let(:target) { group }
it_behaves_like 'handling all endpoints'
@ -171,8 +172,11 @@ RSpec.describe API::NugetGroupPackages, feature_category: :package_registry do
subject { get api(url), headers: headers }
before do
before_all do
subgroup.add_reporter(user)
end
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value('private'))
subgroup.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value('private'))
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value('private'))

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do
@ -111,7 +112,8 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
it_behaves_like 'process nuget v2 $metadata service request', params[:user_role], params[:expected_status], params[:member]
it_behaves_like 'process nuget v2 $metadata service request', params[:user_role], params[:expected_status],
params[:member]
end
end
end
@ -199,11 +201,18 @@ RSpec.describe API::NugetProjectPackages, feature_category: :package_registry do
end
describe 'GET /api/v4/projects/:id/packages/nuget/download/*package_name/*package_version/*package_filename' do
let_it_be(:package) { create(:nuget_package, :with_symbol_package, :with_metadatum, project: project, name: package_name, version: '0.1') }
let_it_be(:package) do
create(:nuget_package, :with_symbol_package, :with_metadatum, project: project, name: package_name,
version: '0.1')
end
let_it_be(:package_version) { package.version }
let(:format) { 'nupkg' }
let(:url) { "/projects/#{target.id}/packages/nuget/download/#{package.name}/#{package_version}/#{package.name}.#{package_version}.#{format}" }
let(:url) do
"/projects/#{target.id}/packages/nuget/download/" \
"#{package.name}/#{package_version}/#{package.name}.#{package_version}.#{format}"
end
subject { get api(url), headers: headers }

View File

@ -3,19 +3,18 @@
require 'spec_helper'
RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
let_it_be_with_reload(:project) { create(:project, :repository) }
shared_context 'note on noteable' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:maintainer) { create(:user, maintainer_of: project) }
let_it_be(:assignee) { create(:user) }
before do
before_all do
project.add_maintainer(assignee)
end
end
shared_examples 'note on noteable that supports quick actions' do
include_context 'note on noteable'
before do
note.note = note_text
end
@ -206,7 +205,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
describe '/confidential' do
let_it_be_with_reload(:noteable) { create(:work_item, :issue, project: project) }
let_it_be(:note_text) { '/confidential' }
let_it_be(:note) { create(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { create(:note, noteable: noteable, project: project, note: note_text) }
context 'when work item does not have children' do
it 'leaves the note empty' do
@ -383,12 +382,12 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
end
describe '/add_child' do
let_it_be(:noteable) { create(:work_item, :objective, project: project) }
let_it_be_with_reload(:noteable) { create(:work_item, :objective, project: project) }
let_it_be(:child) { create(:work_item, :objective, project: project) }
let_it_be(:second_child) { create(:work_item, :objective, project: project) }
let_it_be(:note_text) { "/add_child #{child.to_reference}, #{second_child.to_reference}" }
let_it_be(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let_it_be(:children) { [child, second_child] }
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let(:children) { [child, second_child] }
it_behaves_like 'adds child work items'
@ -411,7 +410,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
let_it_be_with_reload(:noteable) { create(:work_item, :objective, project: project) }
let_it_be_with_reload(:child) { create(:work_item, :objective, project: project) }
let_it_be(:note_text) { "/remove_child #{child.to_reference}" }
let_it_be(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
before do
create(:parent_link, work_item_parent: noteable, work_item: child)
@ -453,7 +452,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
let_it_be_with_reload(:noteable) { create(:work_item, :objective, project: project) }
let_it_be_with_reload(:parent) { create(:work_item, :objective, project: project) }
let_it_be(:note_text) { "/set_parent #{parent.to_reference}" }
let_it_be(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
context 'when using work item reference' do
let_it_be(:note_text) { "/set_parent #{project.full_path}#{parent.to_reference}" }
@ -479,7 +478,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
let_it_be_with_reload(:parent) { create(:work_item, :objective, project: project) }
let_it_be_with_reload(:noteable) { create(:work_item, :objective, project: project) }
let_it_be(:note_text) { "/remove_parent" }
let_it_be(:note) { create(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { create(:note, noteable: noteable, project: project, note: note_text) }
before do
create(:parent_link, work_item_parent: parent, work_item: noteable)
@ -511,7 +510,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
context 'when user is not allowed to promote work item' do
let_it_be_with_reload(:noteable) { create(:work_item, :task, project: project) }
let_it_be(:note_text) { '/promote_to issue' }
let_it_be(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
before do
project.team.find_member(maintainer.id).destroy!
@ -526,7 +525,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
context 'on a task' do
let_it_be_with_reload(:noteable) { create(:work_item, :task, project: project) }
let_it_be(:note_text) { '/promote_to Issue' }
let_it_be(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
it_behaves_like 'promotes work item', from: 'task', to: 'issue'
@ -540,7 +539,7 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
context 'on an issue' do
let_it_be_with_reload(:noteable) { create(:work_item, :issue, project: project) }
let_it_be(:note_text) { '/promote_to Incident' }
let_it_be(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
it_behaves_like 'promotes work item', from: 'issue', to: 'incident'
@ -619,6 +618,8 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
end
describe '#execute' do
include_context 'note on noteable'
let(:service) { described_class.new(project, maintainer) }
it_behaves_like 'note on noteable that supports quick actions' do
@ -636,9 +637,44 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
let(:note) { build(:note_on_merge_request, project: project, noteable: merge_request) }
end
context 'note on work item that supports quick actions' do
include_context 'note on noteable'
describe '/create_merge_request' do
let(:note) { build(:note, noteable: noteable, project: project, note: note_text) }
context 'when noteable is a work item' do
let_it_be(:noteable) { create(:work_item, project: project) }
context 'when no branch name is provided' do
let(:note_text) { '/create_merge_request' }
it 'creates a merge request with default branch name', :aggregate_failures do
expect { execute(note) }.to change { MergeRequest.count }.by(1)
expect(MergeRequest.last.source_branch).to eq(noteable.to_branch_name)
end
context 'when work item type does not have the development widget' do
let_it_be(:work_item_type) { create(:work_item_type, :non_default) }
let_it_be(:noteable) { create(:work_item, project: project, work_item_type: work_item_type) }
it 'does not create a merge request' do
expect { execute(note) }.to not_change { MergeRequest.count }
end
end
end
context 'when a branch name is provided' do
let(:note_text) { '/create_merge_request test-branch-1' }
it 'creates a merge request with default branch name', :aggregate_failures do
expect { execute(note) }.to change { MergeRequest.count }.by(1)
expect(MergeRequest.last.source_branch).to eq('test-branch-1')
end
end
end
end
context 'note on work item that supports quick actions' do
let_it_be(:work_item, reload: true) { create(:work_item, project: project) }
let(:note) { build(:note_on_work_item, project: project, noteable: work_item) }
@ -720,13 +756,13 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
describe '#apply_updates' do
include_context 'note on noteable'
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:work_item, reload: true) { create(:work_item, :issue, project: project) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let_it_be(:issue_note) { create(:note_on_issue, project: project, noteable: issue) }
let_it_be(:work_item_note) { create(:note, project: project, noteable: work_item) }
let_it_be(:mr_note) { create(:note_on_merge_request, project: project, noteable: merge_request) }
let_it_be(:commit_note) { create(:note_on_commit, project: project) }
let_it_be_with_reload(:issue) { create(:issue, project: project) }
let_it_be_with_reload(:work_item) { create(:work_item, :issue, project: project) }
let_it_be_with_reload(:merge_request) { create(:merge_request, source_project: project) }
let_it_be_with_reload(:issue_note) { create(:note_on_issue, project: project, noteable: issue) }
let_it_be_with_reload(:work_item_note) { create(:note, project: project, noteable: work_item) }
let_it_be_with_reload(:mr_note) { create(:note_on_merge_request, project: project, noteable: merge_request) }
let_it_be_with_reload(:commit_note) { create(:note_on_commit, project: project) }
let(:update_params) { {} }
subject(:apply_updates) { described_class.new(project, maintainer).apply_updates(update_params, note) }
@ -777,8 +813,9 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
create(:parent_link, work_item: task, work_item_parent: work_item)
expect(apply_updates.success?).to be false
expect(apply_updates.message)
.to include("All child items must be confidential in order to turn on confidentiality.")
expect(apply_updates.message).to include(
"A confidential issue must have only confidential children. Make any child items confidential and try again."
)
end
end
@ -800,9 +837,8 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
context 'CE restriction for issue assignees' do
describe '/assign' do
let(:project) { create(:project) }
let(:assignee) { create(:user) }
let(:maintainer) { create(:user) }
let_it_be(:assignee) { create(:user) }
let_it_be(:maintainer) { create(:user) }
let(:service) { described_class.new(project, maintainer) }
let(:note) { create(:note_on_issue, note: note_text, project: project) }
@ -810,12 +846,15 @@ RSpec.describe Notes::QuickActionsService, feature_category: :text_editors do
%(/assign @#{assignee.username} @#{maintainer.username}\n")
end
before do
stub_licensed_features(multiple_issue_assignees: false)
before_all do
project.add_maintainer(maintainer)
project.add_maintainer(assignee)
end
before do
stub_licensed_features(multiple_issue_assignees: false)
end
it 'adds only one assignee from the list' do
execute(note)

View File

@ -32,8 +32,10 @@ RSpec.shared_context 'with expected presenters dependency groups' do
end
def create_dependencies_for(package)
dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') || create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') || create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
dependency1 = Packages::Dependency.find_by(name: 'Newtonsoft.Json', version_pattern: '12.0.3') ||
create(:packages_dependency, name: 'Newtonsoft.Json', version_pattern: '12.0.3')
dependency2 = Packages::Dependency.find_by(name: 'Castle.Core', version_pattern: '4.4.1') ||
create(:packages_dependency, name: 'Castle.Core', version_pattern: '4.4.1')
create(:packages_dependency_link, :with_nuget_metadatum, package: package, dependency: dependency1)
create(:packages_dependency_link, package: package, dependency: dependency2)

View File

@ -3,7 +3,7 @@
RSpec.shared_examples 'rejects nuget packages access' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -21,7 +21,7 @@ end
RSpec.shared_examples 'process nuget service index request' do |user_type, status, add_member = true, v2 = false|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -54,7 +54,7 @@ end
RSpec.shared_examples 'process nuget v2 $metadata service request' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -92,12 +92,13 @@ end
RSpec.shared_examples 'process nuget metadata request at package name level' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
it_behaves_like 'returning nuget metadata json response with json schema',
'public_api/v4/packages/nuget/packages_metadata'
context 'with invalid format' do
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/metadata/#{package_name}/index.xls" }
@ -110,20 +111,24 @@ RSpec.shared_examples 'process nuget metadata request at package name level' do
it_behaves_like 'returning response status', status
it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/packages_metadata'
it_behaves_like 'returning nuget metadata json response with json schema',
'public_api/v4/packages/nuget/packages_metadata'
end
end
end
RSpec.shared_examples 'process nuget metadata request at package name and package version level' do |user_type, status, add_member = true|
RSpec.shared_examples \
'process nuget metadata request at package name and package version level' \
do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
it_behaves_like 'returning nuget metadata json response with json schema',
'public_api/v4/packages/nuget/package_metadata'
context 'with invalid format' do
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/metadata/#{package_name}/#{package.version}.xls" }
@ -136,7 +141,8 @@ RSpec.shared_examples 'process nuget metadata request at package name and packag
it_behaves_like 'returning response status', status
it_behaves_like 'returning nuget metadata json response with json schema', 'public_api/v4/packages/nuget/package_metadata'
it_behaves_like 'returning nuget metadata json response with json schema',
'public_api/v4/packages/nuget/package_metadata'
end
end
end
@ -144,7 +150,7 @@ end
RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -172,7 +178,7 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta
end
RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member = true, symbol_package = false|
shared_context 'stub nuspec extraction service' do
shared_context 'with nuspec extraction service stub' do
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:nuspec_file_service).and_return(service_result)
@ -190,7 +196,9 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
before do
allow_next_instance_of(::Packages::Nuget::ExtractRemoteMetadataFileService) do |service|
allow(service).to receive(:execute).and_return(ServiceResponse.success(payload: fixture_file('packages/nuget/with_metadata.nuspec')))
allow(service).to receive(:execute).and_return(
ServiceResponse.success(payload: fixture_file('packages/nuget/with_metadata.nuspec'))
)
end
end
@ -224,7 +232,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
end
context 'when nuspec extraction fails' do
include_context 'stub nuspec extraction service' do
include_context 'with nuspec extraction service stub' do
let(:service_result) { ServiceResponse.error(message: 'error', reason: :nuspec_extraction_failed) }
end
@ -241,12 +249,12 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
end
context 'when nuspec extraction fails with a different error', unless: symbol_package do
include_context 'stub nuspec extraction service' do
include_context 'with nuspec extraction service stub' do
let(:service_result) { ServiceResponse.error(message: 'error', reason: :bad_request) }
end
it 'returns a bad request' do
expect { subject }.to change { target.packages.count }.by(0)
expect { subject }.not_to change { target.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
@ -260,7 +268,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
end
it 'returns a bad request' do
expect { subject }.to change { target.packages.count }.by(0)
expect { subject }.not_to change { target.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
@ -268,7 +276,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
context 'with object storage disabled' do
@ -296,7 +304,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
context 'with object storage enabled' do
let(:tmp_object) do
fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang
fog_connection.directories.new(key: 'packages').files.create( # rubocop:disable Rails/SaveBang -- not the AR method
key: "tmp/uploads/#{file_name}",
body: 'content'
)
@ -314,8 +322,10 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
['123123', '../../123123'].each do |remote_id|
context "with invalid remote_id: #{remote_id}" do
include_context 'stub nuspec extraction service' do
let(:service_result) { ServiceResponse.success(payload: fixture_file('packages/nuget/with_metadata.nuspec')) }
include_context 'with nuspec extraction service stub' do
let(:service_result) do
ServiceResponse.success(payload: fixture_file('packages/nuget/with_metadata.nuspec'))
end
end
let(:params) do
@ -336,7 +346,7 @@ RSpec.shared_examples 'process nuget upload' do |user_type, status, add_member =
let(:file_key) { :file }
it 'does not create a package file' do
expect { subject }.to change { ::Packages::PackageFile.count }.by(0)
expect { subject }.not_to change { ::Packages::PackageFile.count }
end
it_behaves_like 'returning response status', :bad_request
@ -368,7 +378,7 @@ RSpec.shared_examples 'process nuget download versions request' do |user_type, s
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -394,7 +404,7 @@ end
RSpec.shared_examples 'process nuget download content request' do |user_type, status, add_member = true|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -410,7 +420,10 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st
end
context 'with invalid format' do
let(:url) { "/#{target_type}/#{target.id}/packages/nuget/download/#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls" }
let(:url) do
"/#{target_type}/#{target.id}/packages/nuget/download/" \
"#{package.name}/#{package.version}/#{package.name}.#{package.version}.xls"
end
it_behaves_like 'rejects nuget packages access', :anonymous, :not_found
end
@ -474,7 +487,7 @@ RSpec.shared_examples 'process nuget search request' do |user_type, status, add_
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returns a valid json search response', status, 4, [1, 5, 5, 1]
@ -515,7 +528,7 @@ end
RSpec.shared_examples 'process empty nuget search request' do |user_type, status, add_member = true|
before do
target.send("add_#{user_type}", user) if add_member && user_type != :anonymous
target.send(:"add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
@ -537,7 +550,7 @@ RSpec.shared_examples 'rejects nuget access with invalid target id' do |not_foun
context 'with a target id with invalid integers' do
using RSpec::Parameterized::TableSyntax
let(:target) { double(id: id) }
let(:target) { instance_double(Group, id:) }
where(:id, :status) do
'/../' | :bad_request
@ -545,7 +558,7 @@ RSpec.shared_examples 'rejects nuget access with invalid target id' do |not_foun
'%20' | :bad_request
'%2e%2e%2f' | :bad_request
'NaN' | :bad_request
00002345 | not_found_response
0o0002345 | not_found_response
'anything25' | :bad_request
end
@ -557,7 +570,7 @@ end
RSpec.shared_examples 'rejects nuget access with unknown target id' do |not_found_response: :unauthorized|
context 'with an unknown target' do
let(:target) { double(id: non_existing_record_id) }
let(:target) { instance_double(Group, id: non_existing_record_id) }
context 'as anonymous' do
it_behaves_like 'rejects nuget packages access', :anonymous, not_found_response
@ -642,7 +655,8 @@ RSpec.shared_examples 'nuget authorize upload endpoint' do
it { is_expected.to have_request_urgency(:low) }
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :sent_through, :shared_examples_name, :expected_status) do
where(:visibility_level, :user_role, :member, :user_token, :sent_through, :shared_examples_name,
:expected_status) do
'PUBLIC' | :developer | true | true | :basic_auth | 'process nuget workhorse authorization' | :success
'PUBLIC' | :guest | true | true | :basic_auth | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized
@ -740,7 +754,8 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
it { is_expected.to have_request_urgency(:low) }
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :sent_through, :shared_examples_name, :expected_status) do
where(:visibility_level, :user_role, :member, :user_token, :sent_through, :shared_examples_name,
:expected_status) do
'PUBLIC' | :developer | true | true | :basic_auth | 'process nuget upload' | :created
'PUBLIC' | :guest | true | true | :basic_auth | 'rejects nuget packages access' | :forbidden
'PUBLIC' | :developer | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized
@ -805,7 +820,8 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member], symbol_package
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member],
symbol_package
end
end
@ -819,7 +835,7 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
it_behaves_like 'rejects nuget access with invalid target id'
context 'file size above maximum limit' do
context 'when file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
before do
@ -846,7 +862,10 @@ RSpec.shared_examples 'nuget upload endpoint' do |symbol_package: false|
context 'when package duplicates are not allowed', unless: symbol_package do
let(:params) { { package: fixture_file_upload('spec/fixtures/packages/nuget/package.nupkg') } }
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
let!(:existing_package) { create(:nuget_package, project: project, name: 'DummyProject.DummyPackage', version: '1.0.0') }
let!(:existing_package) do
create(:nuget_package, project: project, name: 'DummyProject.DummyPackage', version: '1.0.0')
end
let_it_be(:package_settings) do
create(:namespace_package_setting, :group, namespace: project.namespace, nuget_duplicates_allowed: false)
end
@ -866,7 +885,7 @@ end
RSpec.shared_examples 'process nuget delete request' do |user_type, status, auth|
context "for user type #{user_type}" do
before do
target.send("add_#{user_type}", user) if user_type
target.send(:"add_#{user_type}", user) if user_type
end
it_behaves_like 'returning response status', status
@ -928,7 +947,7 @@ RSpec.shared_examples 'nuget symbol file endpoint' do
end
context 'when target does not exist' do
let(:target) { double(id: non_existing_record_id) }
let(:target) { instance_double(Group, id: non_existing_record_id) }
it_behaves_like 'returning response status', :not_found
end

View File

@ -1436,10 +1436,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.121.0.tgz#57cacc895929aef4320632396373797a64b230ff"
integrity sha512-ZekVjdMZrjrNEjdrOHsJYCu7A+ea3AkuNUxWIZ3FaNgJj4Oh21RlTP7bQKnRSXVhBbV1jg1PgzQ1ANEoCW8t4g==
"@gitlab/ui@106.2.0":
version "106.2.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-106.2.0.tgz#d69f4d886c30703dde593a699a882d8e4b0df3eb"
integrity sha512-Kpl5TWEIaQXFaoFZTyw8HaeQLgeohaWAinTPHOIr5zPB8J4TryAuGe92jg5LxoJ6/iNRBHhfZ5dYs25TSoiYlg==
"@gitlab/ui@106.2.2":
version "106.2.2"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-106.2.2.tgz#369f87b8d8bf621cdb4f149fd97c8d5d6d977233"
integrity sha512-9GKTaNb0pDgxgPgIHoOvmZDRV56rQrnqt0UexBaiDlbY/GNTMJbLaybjy7/VVp+GjpE5XT3jmekjRTRjC7oIxg==
dependencies:
"@floating-ui/dom" "1.4.3"
echarts "^5.3.2"