Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
377c02f959
commit
6021fa2fc6
|
@ -2,21 +2,6 @@
|
|||
# Cop supports --auto-correct.
|
||||
Layout/FirstArrayElementIndentation:
|
||||
Exclude:
|
||||
- 'app/controllers/abuse_reports_controller.rb'
|
||||
- 'app/controllers/admin/application_settings_controller.rb'
|
||||
- 'app/controllers/admin/broadcast_messages_controller.rb'
|
||||
- 'app/controllers/admin/plan_limits_controller.rb'
|
||||
- 'app/controllers/boards/issues_controller.rb'
|
||||
- 'app/controllers/groups_controller.rb'
|
||||
- 'app/controllers/projects/issues_controller.rb'
|
||||
- 'app/controllers/projects/merge_requests_controller.rb'
|
||||
- 'app/controllers/projects/pipelines_controller.rb'
|
||||
- 'app/controllers/projects_controller.rb'
|
||||
- 'app/finders/issuable_finder.rb'
|
||||
- 'app/finders/merge_requests/by_approvals_finder.rb'
|
||||
- 'app/finders/user_groups_counter.rb'
|
||||
- 'app/helpers/diff_helper.rb'
|
||||
- 'app/helpers/search_helper.rb'
|
||||
- 'config/initializers/postgres_partitioning.rb'
|
||||
- 'db/post_migrate/20210812013042_remove_duplicate_project_authorizations.rb'
|
||||
- 'ee/app/controllers/groups/settings/reporting_controller.rb'
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<script>
|
||||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { isEqual, get, isEmpty } from 'lodash';
|
||||
import { GlAlert, GlSprintf, GlLink, GlCard, GlButton } from '@gitlab/ui';
|
||||
import {
|
||||
CONTAINER_CLEANUP_POLICY_TITLE,
|
||||
CONTAINER_CLEANUP_POLICY_DESCRIPTION,
|
||||
CONTAINER_CLEANUP_POLICY_EDIT_RULES,
|
||||
CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION,
|
||||
CONTAINER_CLEANUP_POLICY_SET_RULES,
|
||||
FETCH_SETTINGS_ERROR_MESSAGE,
|
||||
UNAVAILABLE_FEATURE_TITLE,
|
||||
UNAVAILABLE_FEATURE_INTRO_TEXT,
|
||||
|
@ -13,20 +15,29 @@ import {
|
|||
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
|
||||
import SettingsBlock from '~/packages_and_registries/shared/components/settings_block.vue';
|
||||
|
||||
import ContainerExpirationPolicyForm from './container_expiration_policy_form.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SettingsBlock,
|
||||
GlAlert,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
ContainerExpirationPolicyForm,
|
||||
GlCard,
|
||||
GlButton,
|
||||
},
|
||||
inject: ['projectPath', 'isAdmin', 'adminSettingsPath', 'enableHistoricEntries', 'helpPagePath'],
|
||||
inject: [
|
||||
'projectPath',
|
||||
'isAdmin',
|
||||
'adminSettingsPath',
|
||||
'enableHistoricEntries',
|
||||
'helpPagePath',
|
||||
'cleanupSettingsPath',
|
||||
],
|
||||
i18n: {
|
||||
CONTAINER_CLEANUP_POLICY_TITLE,
|
||||
CONTAINER_CLEANUP_POLICY_DESCRIPTION,
|
||||
CONTAINER_CLEANUP_POLICY_EDIT_RULES,
|
||||
CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION,
|
||||
CONTAINER_CLEANUP_POLICY_SET_RULES,
|
||||
UNAVAILABLE_FEATURE_TITLE,
|
||||
UNAVAILABLE_FEATURE_INTRO_TEXT,
|
||||
FETCH_SETTINGS_ERROR_MESSAGE,
|
||||
|
@ -40,9 +51,6 @@ export default {
|
|||
};
|
||||
},
|
||||
update: (data) => data.project?.containerExpirationPolicy,
|
||||
result({ data }) {
|
||||
this.workingCopy = { ...get(data, 'project.containerExpirationPolicy', {}) };
|
||||
},
|
||||
error(e) {
|
||||
this.fetchSettingsError = e;
|
||||
},
|
||||
|
@ -52,29 +60,25 @@ export default {
|
|||
return {
|
||||
fetchSettingsError: false,
|
||||
containerExpirationPolicy: null,
|
||||
workingCopy: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isDisabled() {
|
||||
return !(this.containerExpirationPolicy || this.enableHistoricEntries);
|
||||
isCleanupEnabled() {
|
||||
return this.containerExpirationPolicy?.enabled ?? false;
|
||||
},
|
||||
isEnabled() {
|
||||
return this.containerExpirationPolicy || this.enableHistoricEntries;
|
||||
},
|
||||
showDisabledFormMessage() {
|
||||
return this.isDisabled && !this.fetchSettingsError;
|
||||
return !this.isEnabled && !this.fetchSettingsError;
|
||||
},
|
||||
unavailableFeatureMessage() {
|
||||
return this.isAdmin ? UNAVAILABLE_ADMIN_FEATURE_TEXT : UNAVAILABLE_USER_FEATURE_TEXT;
|
||||
},
|
||||
isEdited() {
|
||||
if (isEmpty(this.containerExpirationPolicy) && isEmpty(this.workingCopy)) {
|
||||
return false;
|
||||
}
|
||||
return !isEqual(this.containerExpirationPolicy, this.workingCopy);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
restoreOriginal() {
|
||||
this.workingCopy = { ...this.containerExpirationPolicy };
|
||||
cleanupRulesButtonText() {
|
||||
return this.isCleanupEnabled
|
||||
? this.$options.i18n.CONTAINER_CLEANUP_POLICY_EDIT_RULES
|
||||
: this.$options.i18n.CONTAINER_CLEANUP_POLICY_SET_RULES;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -93,13 +97,19 @@ export default {
|
|||
</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<container-expiration-policy-form
|
||||
v-if="!isDisabled"
|
||||
v-model="workingCopy"
|
||||
:is-loading="$apollo.queries.containerExpirationPolicy.loading"
|
||||
:is-edited="isEdited"
|
||||
@reset="restoreOriginal"
|
||||
/>
|
||||
<gl-card v-if="isEnabled">
|
||||
<p data-testid="description">
|
||||
{{ $options.i18n.CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION }}
|
||||
</p>
|
||||
<gl-button
|
||||
data-testid="rules-button"
|
||||
:href="cleanupSettingsPath"
|
||||
category="secondary"
|
||||
variant="confirm"
|
||||
>
|
||||
{{ cleanupRulesButtonText }}
|
||||
</gl-button>
|
||||
</gl-card>
|
||||
<template v-else>
|
||||
<gl-alert
|
||||
v-if="showDisabledFormMessage"
|
||||
|
|
|
@ -4,6 +4,12 @@ export const CONTAINER_CLEANUP_POLICY_TITLE = s__(`ContainerRegistry|Clean up im
|
|||
export const CONTAINER_CLEANUP_POLICY_DESCRIPTION = s__(
|
||||
`ContainerRegistry|Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}`,
|
||||
);
|
||||
export const CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION = s__(
|
||||
'ContainerRegistry|Set rules to automatically remove unused packages to save storage space.',
|
||||
);
|
||||
export const CONTAINER_CLEANUP_POLICY_EDIT_RULES = s__('ContainerRegistry|Edit cleanup rules');
|
||||
export const CONTAINER_CLEANUP_POLICY_SET_RULES = s__('ContainerRegistry|Set cleanup rules');
|
||||
|
||||
export const SET_CLEANUP_POLICY_BUTTON = __('Save changes');
|
||||
export const UNAVAILABLE_FEATURE_TITLE = s__(
|
||||
`ContainerRegistry|Cleanup policy for tags is disabled`,
|
||||
|
|
|
@ -18,6 +18,7 @@ export default () => {
|
|||
enableHistoricEntries,
|
||||
projectPath,
|
||||
adminSettingsPath,
|
||||
cleanupSettingsPath,
|
||||
tagsRegexHelpPagePath,
|
||||
helpPagePath,
|
||||
showContainerRegistrySettings,
|
||||
|
@ -34,6 +35,7 @@ export default () => {
|
|||
enableHistoricEntries: parseBoolean(enableHistoricEntries),
|
||||
projectPath,
|
||||
adminSettingsPath,
|
||||
cleanupSettingsPath,
|
||||
tagsRegexHelpPagePath,
|
||||
helpPagePath,
|
||||
showContainerRegistrySettings: parseBoolean(showContainerRegistrySettings),
|
||||
|
|
|
@ -19,6 +19,7 @@ const PERSISTENT_USER_CALLOUTS = [
|
|||
'.js-namespace-storage-alert',
|
||||
'.js-web-hook-disabled-callout',
|
||||
'.js-merge-request-settings-callout',
|
||||
'.js-ultimate-feature-removal-banner',
|
||||
];
|
||||
|
||||
const initCallouts = () => {
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
@return $string;
|
||||
}
|
||||
|
||||
@mixin dropzone-background($stroke-color, $stroke-width: 4, $stroke-linecap: 'butt') {
|
||||
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='#{encodecolor($stroke-color)}' stroke-width='#{$stroke-width}' stroke-dasharray='6%2c4' stroke-dashoffset='0' stroke-linecap='#{encodecolor($stroke-linecap)}'/%3e%3c/svg%3e");
|
||||
@mixin dropzone-background($stroke-color, $stroke-width: 4) {
|
||||
background-image: url("data:image/svg+xml, %3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='#{$border-radius-default}' ry='#{$border-radius-default}' stroke='#{encodecolor($stroke-color)}' stroke-width='#{$stroke-width}' stroke-dasharray='6%2c4' stroke-dashoffset='0' stroke-linecap='butt' /%3e %3c/svg%3e");
|
||||
}
|
||||
|
||||
.upload-dropzone-border {
|
||||
border: 0;
|
||||
@include dropzone-background($gray-400, 2, 'round');
|
||||
border-radius: 8px;
|
||||
@include dropzone-background($gray-400, 2);
|
||||
border-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
.upload-dropzone-card {
|
||||
|
|
|
@ -30,10 +30,7 @@ class AbuseReportsController < ApplicationController
|
|||
private
|
||||
|
||||
def report_params
|
||||
params.require(:abuse_report).permit(%i(
|
||||
message
|
||||
user_id
|
||||
))
|
||||
params.require(:abuse_report).permit(:message, :user_id)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
|
|
|
@ -18,23 +18,23 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
feature_category :not_owned, [ # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
|
||||
:general, :reporting, :metrics_and_profiling, :network,
|
||||
:preferences, :update, :reset_health_check_token
|
||||
]
|
||||
:general, :reporting, :metrics_and_profiling, :network,
|
||||
:preferences, :update, :reset_health_check_token
|
||||
]
|
||||
|
||||
feature_category :metrics, [
|
||||
:create_self_monitoring_project,
|
||||
:status_create_self_monitoring_project,
|
||||
:delete_self_monitoring_project,
|
||||
:status_delete_self_monitoring_project
|
||||
]
|
||||
:create_self_monitoring_project,
|
||||
:status_create_self_monitoring_project,
|
||||
:delete_self_monitoring_project,
|
||||
:status_delete_self_monitoring_project
|
||||
]
|
||||
urgency :low, [
|
||||
:create_self_monitoring_project,
|
||||
:status_create_self_monitoring_project,
|
||||
:delete_self_monitoring_project,
|
||||
:status_delete_self_monitoring_project,
|
||||
:reset_error_tracking_access_token
|
||||
]
|
||||
:create_self_monitoring_project,
|
||||
:status_create_self_monitoring_project,
|
||||
:delete_self_monitoring_project,
|
||||
:status_delete_self_monitoring_project,
|
||||
:reset_error_tracking_access_token
|
||||
]
|
||||
|
||||
feature_category :source_code_management, [:repository, :clear_repository_check_states]
|
||||
feature_category :continuous_integration, [:ci_cd, :reset_registration_token]
|
||||
|
|
|
@ -57,14 +57,15 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def broadcast_message_params
|
||||
params.require(:broadcast_message).permit(%i(
|
||||
theme
|
||||
ends_at
|
||||
message
|
||||
starts_at
|
||||
target_path
|
||||
broadcast_type
|
||||
dismissable
|
||||
), target_access_levels: []).reverse_merge!(target_access_levels: [])
|
||||
params.require(:broadcast_message)
|
||||
.permit(%i(
|
||||
theme
|
||||
ends_at
|
||||
message
|
||||
starts_at
|
||||
target_path
|
||||
broadcast_type
|
||||
dismissable
|
||||
), target_access_levels: []).reverse_merge!(target_access_levels: [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,24 +28,25 @@ class Admin::PlanLimitsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def plan_limits_params
|
||||
params.require(:plan_limits).permit(%i[
|
||||
plan_id
|
||||
conan_max_file_size
|
||||
helm_max_file_size
|
||||
maven_max_file_size
|
||||
npm_max_file_size
|
||||
nuget_max_file_size
|
||||
pypi_max_file_size
|
||||
terraform_module_max_file_size
|
||||
generic_packages_max_file_size
|
||||
ci_pipeline_size
|
||||
ci_active_jobs
|
||||
ci_active_pipelines
|
||||
ci_project_subscriptions
|
||||
ci_pipeline_schedules
|
||||
ci_needs_size_limit
|
||||
ci_registered_group_runners
|
||||
ci_registered_project_runners
|
||||
])
|
||||
params.require(:plan_limits)
|
||||
.permit(%i[
|
||||
plan_id
|
||||
conan_max_file_size
|
||||
helm_max_file_size
|
||||
maven_max_file_size
|
||||
npm_max_file_size
|
||||
nuget_max_file_size
|
||||
pypi_max_file_size
|
||||
terraform_module_max_file_size
|
||||
generic_packages_max_file_size
|
||||
ci_pipeline_size
|
||||
ci_active_jobs
|
||||
ci_active_pipelines
|
||||
ci_project_subscriptions
|
||||
ci_pipeline_schedules
|
||||
ci_needs_size_limit
|
||||
ci_registered_group_runners
|
||||
ci_registered_project_runners
|
||||
])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,10 +77,10 @@ module Boards
|
|||
:milestone,
|
||||
:assignees,
|
||||
project: [
|
||||
:route,
|
||||
{
|
||||
namespace: [:route]
|
||||
}
|
||||
:route,
|
||||
{
|
||||
namespace: [:route]
|
||||
}
|
||||
],
|
||||
labels: [:priorities],
|
||||
notes: [:award_emoji, :author]
|
||||
|
|
|
@ -49,9 +49,9 @@ class GroupsController < Groups::ApplicationController
|
|||
layout :determine_layout
|
||||
|
||||
feature_category :subgroups, [
|
||||
:index, :new, :create, :show, :edit, :update,
|
||||
:destroy, :details, :transfer, :activity
|
||||
]
|
||||
:index, :new, :create, :show, :edit, :update,
|
||||
:destroy, :details, :transfer, :activity
|
||||
]
|
||||
|
||||
feature_category :team_planning, [:issues, :issues_calendar, :preview_markdown]
|
||||
feature_category :code_review, [:merge_requests, :unfoldered_environment_names]
|
||||
|
|
|
@ -65,19 +65,19 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
alias_method :designs, :show
|
||||
|
||||
feature_category :team_planning, [
|
||||
:index, :calendar, :show, :new, :create, :edit, :update,
|
||||
:destroy, :move, :reorder, :designs, :toggle_subscription,
|
||||
:discussions, :bulk_update, :realtime_changes,
|
||||
:toggle_award_emoji, :mark_as_spam, :related_branches,
|
||||
:can_create_branch, :create_merge_request
|
||||
]
|
||||
:index, :calendar, :show, :new, :create, :edit, :update,
|
||||
:destroy, :move, :reorder, :designs, :toggle_subscription,
|
||||
:discussions, :bulk_update, :realtime_changes,
|
||||
:toggle_award_emoji, :mark_as_spam, :related_branches,
|
||||
:can_create_branch, :create_merge_request
|
||||
]
|
||||
urgency :low, [
|
||||
:index, :calendar, :show, :new, :create, :edit, :update,
|
||||
:destroy, :move, :reorder, :designs, :toggle_subscription,
|
||||
:discussions, :bulk_update, :realtime_changes,
|
||||
:toggle_award_emoji, :mark_as_spam, :related_branches,
|
||||
:can_create_branch, :create_merge_request
|
||||
]
|
||||
:index, :calendar, :show, :new, :create, :edit, :update,
|
||||
:destroy, :move, :reorder, :designs, :toggle_subscription,
|
||||
:discussions, :bulk_update, :realtime_changes,
|
||||
:toggle_award_emoji, :mark_as_spam, :related_branches,
|
||||
:can_create_branch, :create_merge_request
|
||||
]
|
||||
|
||||
feature_category :service_desk, [:service_desk]
|
||||
urgency :low, [:service_desk]
|
||||
|
|
|
@ -57,11 +57,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
after_action :log_merge_request_show, only: [:show]
|
||||
|
||||
feature_category :code_review, [
|
||||
:assign_related_issues, :bulk_update, :cancel_auto_merge,
|
||||
:commit_change_content, :commits, :context_commits, :destroy,
|
||||
:discussions, :edit, :index, :merge, :rebase, :remove_wip,
|
||||
:show, :toggle_award_emoji, :toggle_subscription, :update
|
||||
]
|
||||
:assign_related_issues, :bulk_update, :cancel_auto_merge,
|
||||
:commit_change_content, :commits, :context_commits, :destroy,
|
||||
:discussions, :edit, :index, :merge, :rebase, :remove_wip,
|
||||
:show, :toggle_award_emoji, :toggle_subscription, :update
|
||||
]
|
||||
|
||||
feature_category :code_testing, [:test_reports, :coverage_reports]
|
||||
feature_category :code_quality, [:codequality_reports, :codequality_mr_diff_reports]
|
||||
|
|
|
@ -51,10 +51,10 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
POLLING_INTERVAL = 10_000
|
||||
|
||||
feature_category :continuous_integration, [
|
||||
:charts, :show, :config_variables, :stage, :cancel, :retry,
|
||||
:builds, :dag, :failures, :status,
|
||||
:index, :create, :new, :destroy
|
||||
]
|
||||
:charts, :show, :config_variables, :stage, :cancel, :retry,
|
||||
:builds, :dag, :failures, :status,
|
||||
:index, :create, :new, :destroy
|
||||
]
|
||||
feature_category :code_testing, [:test_report]
|
||||
feature_category :build_artifacts, [:downloadable_artifacts]
|
||||
|
||||
|
|
|
@ -56,9 +56,9 @@ class ProjectsController < Projects::ApplicationController
|
|||
layout :determine_layout
|
||||
|
||||
feature_category :projects, [
|
||||
:index, :show, :new, :create, :edit, :update, :transfer,
|
||||
:destroy, :archive, :unarchive, :toggle_star, :activity
|
||||
]
|
||||
:index, :show, :new, :create, :edit, :update, :transfer,
|
||||
:destroy, :archive, :unarchive, :toggle_star, :activity
|
||||
]
|
||||
|
||||
feature_category :source_code_management, [:remove_fork, :housekeeping, :refs]
|
||||
feature_category :team_planning, [:preview_markdown, :new_issuable_address]
|
||||
|
|
|
@ -31,7 +31,7 @@ module IncidentManagement
|
|||
end
|
||||
|
||||
def sort(collection)
|
||||
collection.order_occurred_at_asc
|
||||
collection.order_occurred_at_asc_id_asc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,19 +58,19 @@ class IssuableFinder
|
|||
class << self
|
||||
def scalar_params
|
||||
@scalar_params ||= %i[
|
||||
assignee_id
|
||||
assignee_username
|
||||
author_id
|
||||
author_username
|
||||
crm_contact_id
|
||||
crm_organization_id
|
||||
label_name
|
||||
milestone_title
|
||||
release_tag
|
||||
my_reaction_emoji
|
||||
search
|
||||
in
|
||||
]
|
||||
assignee_id
|
||||
assignee_username
|
||||
author_id
|
||||
author_username
|
||||
crm_contact_id
|
||||
crm_organization_id
|
||||
label_name
|
||||
milestone_title
|
||||
release_tag
|
||||
my_reaction_emoji
|
||||
search
|
||||
in
|
||||
]
|
||||
end
|
||||
|
||||
def array_params
|
||||
|
|
|
@ -71,9 +71,7 @@ module MergeRequests
|
|||
#
|
||||
# @param [ActiveRecord::Relation] items the activerecord relation
|
||||
def with_any_approvals(items)
|
||||
items.select_from_union([
|
||||
items.with_approvals
|
||||
])
|
||||
items.select_from_union([items.with_approvals])
|
||||
end
|
||||
|
||||
# Merge requests approved by given usernames
|
||||
|
|
|
@ -8,9 +8,9 @@ class UserGroupsCounter
|
|||
def execute
|
||||
Namespace.unscoped do
|
||||
Namespace.from_union([
|
||||
groups,
|
||||
project_groups
|
||||
]).group(:user_id).count # rubocop: disable CodeReuse/ActiveRecord
|
||||
groups,
|
||||
project_groups
|
||||
]).group(:user_id).count # rubocop: disable CodeReuse/ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -140,12 +140,12 @@ module DiffHelper
|
|||
if compare_url
|
||||
|
||||
link_text = [
|
||||
_('Compare'),
|
||||
' ',
|
||||
content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'),
|
||||
'...',
|
||||
content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha')
|
||||
].join('').html_safe
|
||||
_('Compare'),
|
||||
' ',
|
||||
content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'),
|
||||
'...',
|
||||
content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha')
|
||||
].join('').html_safe
|
||||
|
||||
tooltip = _('Compare submodule commit revisions')
|
||||
link = content_tag(:span, link_to(link_text, compare_url, class: 'btn gl-button has-tooltip', title: tooltip), class: 'submodule-compare')
|
||||
|
|
|
@ -83,7 +83,8 @@ module PackagesHelper
|
|||
def settings_data
|
||||
cleanup_settings_data.merge(
|
||||
show_container_registry_settings: show_container_registry_settings(@project).to_s,
|
||||
show_package_registry_settings: show_package_registry_settings(@project).to_s
|
||||
show_package_registry_settings: show_package_registry_settings(@project).to_s,
|
||||
cleanup_settings_path: cleanup_image_tags_project_settings_packages_and_registries_path(@project)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -239,26 +239,26 @@ module SearchHelper
|
|||
|
||||
if can?(current_user, :download_code, @project)
|
||||
result.concat([
|
||||
{ category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) }
|
||||
])
|
||||
{ category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) }
|
||||
])
|
||||
end
|
||||
|
||||
if can?(current_user, :read_repository_graphs, @project)
|
||||
result.concat([
|
||||
{ category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) }
|
||||
])
|
||||
{ category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) }
|
||||
])
|
||||
end
|
||||
|
||||
result.concat([
|
||||
{ category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
|
||||
{ category: "In this project", label: _("Merge requests"), url: project_merge_requests_path(@project) },
|
||||
{ category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
|
||||
{ category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
|
||||
{ category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
|
||||
{ category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
|
||||
])
|
||||
{ category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
|
||||
{ category: "In this project", label: _("Merge requests"), url: project_merge_requests_path(@project) },
|
||||
{ category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
|
||||
{ category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
|
||||
{ category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
|
||||
{ category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
|
||||
])
|
||||
|
||||
if can?(current_user, :read_feature_flag, @project)
|
||||
result << { category: "In this project", label: _("Feature Flags"), url: project_feature_flags_path(@project) }
|
||||
|
@ -294,13 +294,13 @@ module SearchHelper
|
|||
return [] unless issue && Ability.allowed?(current_user, :read_issue, issue)
|
||||
|
||||
[
|
||||
{
|
||||
category: 'In this project',
|
||||
id: issue.id,
|
||||
label: search_result_sanitize("#{issue.title} (#{issue.to_reference})"),
|
||||
url: issue_path(issue),
|
||||
avatar_url: issue.project.avatar_url || ''
|
||||
}
|
||||
{
|
||||
category: 'In this project',
|
||||
id: issue.id,
|
||||
label: search_result_sanitize("#{issue.title} (#{issue.to_reference})"),
|
||||
url: issue_path(issue),
|
||||
avatar_url: issue.project.avatar_url || ''
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ module Users
|
|||
MERGE_REQUEST_SETTINGS_MOVED_CALLOUT = 'merge_request_settings_moved_callout'
|
||||
REGISTRATION_ENABLED_CALLOUT_ALLOWED_CONTROLLER_PATHS = [/^root/, /^dashboard\S*/, /^admin\S*/].freeze
|
||||
WEB_HOOK_DISABLED = 'web_hook_disabled'
|
||||
ULTIMATE_FEATURE_REMOVAL_BANNER = 'ultimate_feature_removal_banner'
|
||||
|
||||
def show_gke_cluster_integration_callout?(project)
|
||||
active_nav_link?(controller: sidebar_operations_paths) &&
|
||||
|
@ -79,6 +80,12 @@ module Users
|
|||
!user_dismissed?(MERGE_REQUEST_SETTINGS_MOVED_CALLOUT)
|
||||
end
|
||||
|
||||
def ultimate_feature_removal_banner_dismissed?(project)
|
||||
return false unless project
|
||||
|
||||
user_dismissed?(ULTIMATE_FEATURE_REMOVAL_BANNER, project: project)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_dismissed?(feature_name, ignore_dismissal_earlier_than = nil, project: nil)
|
||||
|
|
|
@ -598,6 +598,7 @@ class ContainerRepository < ApplicationRecord
|
|||
tags_response_body.map do |raw_tag|
|
||||
tag = ContainerRegistry::Tag.new(self, raw_tag['name'])
|
||||
tag.force_created_at_from_iso8601(raw_tag['created_at'])
|
||||
tag.updated_at = raw_tag['updated_at']
|
||||
tag
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,6 @@ module IncidentManagement
|
|||
validates :action, presence: true, length: { maximum: 128 }
|
||||
validates :note, :note_html, presence: true, length: { maximum: 10_000 }
|
||||
|
||||
scope :order_occurred_at_asc, -> { reorder(occurred_at: :asc) }
|
||||
scope :order_occurred_at_asc_id_asc, -> { reorder(occurred_at: :asc, id: :asc) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,7 +22,8 @@ class Packages::Package < ApplicationRecord
|
|||
debian: 9,
|
||||
rubygems: 10,
|
||||
helm: 11,
|
||||
terraform_module: 12
|
||||
terraform_module: 12,
|
||||
rpm: 13
|
||||
}
|
||||
|
||||
enum status: { default: 0, hidden: 1, processing: 2, error: 3, pending_destruction: 4 }
|
||||
|
@ -43,6 +44,7 @@ class Packages::Package < ApplicationRecord
|
|||
has_one :nuget_metadatum, inverse_of: :package, class_name: 'Packages::Nuget::Metadatum'
|
||||
has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum'
|
||||
has_one :rubygems_metadatum, inverse_of: :package, class_name: 'Packages::Rubygems::Metadatum'
|
||||
has_one :rpm_metadatum, inverse_of: :package, class_name: 'Packages::Rpm::Metadatum'
|
||||
has_one :npm_metadatum, inverse_of: :package, class_name: 'Packages::Npm::Metadatum'
|
||||
has_many :build_infos, inverse_of: :package
|
||||
has_many :pipelines, through: :build_infos, disable_joins: true
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Rpm
|
||||
def self.table_name_prefix
|
||||
'packages_rpm_'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Packages
|
||||
module Rpm
|
||||
class Metadatum < ApplicationRecord
|
||||
self.primary_key = :package_id
|
||||
|
||||
belongs_to :package, -> { where(package_type: :rpm) }, inverse_of: :rpm_metadatum
|
||||
|
||||
validates :package, presence: true
|
||||
|
||||
validates :release,
|
||||
presence: true,
|
||||
length: { maximum: 128 }
|
||||
|
||||
validates :summary,
|
||||
presence: true,
|
||||
length: { maximum: 1000 }
|
||||
|
||||
validates :description,
|
||||
presence: true,
|
||||
length: { maximum: 5000 }
|
||||
|
||||
validates :arch,
|
||||
presence: true,
|
||||
length: { maximum: 255 }
|
||||
|
||||
validates :license,
|
||||
allow_nil: true,
|
||||
length: { maximum: 1000 }
|
||||
|
||||
validates :url,
|
||||
allow_nil: true,
|
||||
length: { maximum: 1000 }
|
||||
|
||||
validate :rpm_package_type
|
||||
|
||||
private
|
||||
|
||||
def rpm_package_type
|
||||
return if package&.rpm?
|
||||
|
||||
errors.add(:base, _('Package type must be RPM'))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,7 +10,8 @@ module Users
|
|||
|
||||
enum feature_name: {
|
||||
awaiting_members_banner: 1, # EE-only
|
||||
web_hook_disabled: 2
|
||||
web_hook_disabled: 2,
|
||||
ultimate_feature_removal_banner: 3
|
||||
}
|
||||
|
||||
validates :project, presence: true
|
||||
|
|
|
@ -9,7 +9,7 @@ module Ci
|
|||
@user.owns_runner?(@subject)
|
||||
end
|
||||
|
||||
condition(:belongs_to_multiple_projects) do
|
||||
condition(:belongs_to_multiple_projects, scope: :subject) do
|
||||
@subject.belongs_to_more_than_one_project?
|
||||
end
|
||||
|
||||
|
|
|
@ -317,6 +317,8 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def autodevops_anchor_data(show_auto_devops_callout: false)
|
||||
return unless project.feature_available?(:builds, current_user)
|
||||
|
||||
if current_user && can?(current_user, :admin_pipeline, project) && repository.gitlab_ci_yml.blank? && !show_auto_devops_callout
|
||||
if auto_devops_enabled?
|
||||
AnchorData.new(false,
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
module Gitlab
|
||||
module Timeoutable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DISABLED_TIMEOUTS = [nil, 0].freeze
|
||||
|
||||
TimeoutError = Class.new(StandardError)
|
||||
|
||||
private
|
||||
|
||||
def timeout?(start_time)
|
||||
return false if service_timeout.in?(DISABLED_TIMEOUTS)
|
||||
|
||||
(Time.zone.now - start_time) > service_timeout
|
||||
end
|
||||
|
||||
def service_timeout
|
||||
::Gitlab::CurrentSettings.current_application_settings.container_registry_delete_tags_service_timeout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,118 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
class CleanupTagsBaseService
|
||||
include BaseServiceUtility
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
private
|
||||
|
||||
def filter_out_latest
|
||||
@tags.reject!(&:latest?)
|
||||
end
|
||||
|
||||
def filter_by_name
|
||||
regex_delete = ::Gitlab::UntrustedRegexp.new("\\A#{name_regex_delete || name_regex}\\z")
|
||||
regex_retain = ::Gitlab::UntrustedRegexp.new("\\A#{name_regex_keep}\\z")
|
||||
|
||||
@tags.select! do |tag|
|
||||
# regex_retain will override any overlapping matches by regex_delete
|
||||
regex_delete.match?(tag.name) && !regex_retain.match?(tag.name)
|
||||
end
|
||||
end
|
||||
|
||||
# Should return [tags_to_delete, tags_to_keep]
|
||||
def partition_by_keep_n
|
||||
return [@tags, []] unless keep_n
|
||||
|
||||
order_by_date_desc
|
||||
|
||||
@tags.partition.with_index { |_, index| index >= keep_n_as_integer }
|
||||
end
|
||||
|
||||
# Should return [tags_to_delete, tags_to_keep]
|
||||
def partition_by_older_than
|
||||
return [@tags, []] unless older_than
|
||||
|
||||
older_than_timestamp = older_than_in_seconds.ago
|
||||
|
||||
@tags.partition do |tag|
|
||||
timestamp = pushed_at(tag)
|
||||
|
||||
timestamp && timestamp < older_than_timestamp
|
||||
end
|
||||
end
|
||||
|
||||
def order_by_date_desc
|
||||
now = DateTime.current
|
||||
@tags.sort_by! { |tag| pushed_at(tag) || now }
|
||||
.reverse!
|
||||
end
|
||||
|
||||
def delete_tags
|
||||
return success(deleted: []) unless @tags.any?
|
||||
|
||||
service = Projects::ContainerRepository::DeleteTagsService.new(
|
||||
@project,
|
||||
@current_user,
|
||||
tags: @tags.map(&:name),
|
||||
container_expiration_policy: container_expiration_policy
|
||||
)
|
||||
|
||||
service.execute(@container_repository)
|
||||
end
|
||||
|
||||
def can_destroy?
|
||||
return true if container_expiration_policy
|
||||
|
||||
can?(@current_user, :destroy_container_image, @project)
|
||||
end
|
||||
|
||||
def valid_regex?
|
||||
%w[name_regex_delete name_regex name_regex_keep].each do |param_name|
|
||||
regex = @params[param_name]
|
||||
::Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
|
||||
end
|
||||
true
|
||||
rescue RegexpError => e
|
||||
::Gitlab::ErrorTracking.log_exception(e, project_id: @project.id)
|
||||
false
|
||||
end
|
||||
|
||||
def older_than
|
||||
@params['older_than']
|
||||
end
|
||||
|
||||
def name_regex_delete
|
||||
@params['name_regex_delete']
|
||||
end
|
||||
|
||||
def name_regex
|
||||
@params['name_regex']
|
||||
end
|
||||
|
||||
def name_regex_keep
|
||||
@params['name_regex_keep']
|
||||
end
|
||||
|
||||
def container_expiration_policy
|
||||
@params['container_expiration_policy']
|
||||
end
|
||||
|
||||
def keep_n
|
||||
@params['keep_n']
|
||||
end
|
||||
|
||||
def keep_n_as_integer
|
||||
keep_n.to_i
|
||||
end
|
||||
|
||||
def older_than_in_seconds
|
||||
strong_memoize(:older_than_in_seconds) do
|
||||
ChronicDuration.parse(older_than).seconds
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,10 +2,7 @@
|
|||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
class CleanupTagsService
|
||||
include BaseServiceUtility
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
class CleanupTagsService < CleanupTagsBaseService
|
||||
def initialize(container_repository, user = nil, params = {})
|
||||
@container_repository = container_repository
|
||||
@current_user = user
|
||||
|
@ -43,74 +40,20 @@ module Projects
|
|||
|
||||
private
|
||||
|
||||
def delete_tags
|
||||
return success(deleted: []) unless @tags.any?
|
||||
|
||||
service = Projects::ContainerRepository::DeleteTagsService.new(
|
||||
@project,
|
||||
@current_user,
|
||||
tags: @tags.map(&:name),
|
||||
container_expiration_policy: container_expiration_policy
|
||||
)
|
||||
|
||||
service.execute(@container_repository)
|
||||
end
|
||||
|
||||
def filter_out_latest
|
||||
@tags.reject!(&:latest?)
|
||||
end
|
||||
|
||||
def order_by_date
|
||||
now = DateTime.current
|
||||
@tags.sort_by! { |tag| tag.created_at || now }
|
||||
.reverse!
|
||||
end
|
||||
|
||||
def filter_by_name
|
||||
regex_delete = ::Gitlab::UntrustedRegexp.new("\\A#{name_regex_delete || name_regex}\\z")
|
||||
regex_retain = ::Gitlab::UntrustedRegexp.new("\\A#{name_regex_keep}\\z")
|
||||
|
||||
@tags.select! do |tag|
|
||||
# regex_retain will override any overlapping matches by regex_delete
|
||||
regex_delete.match?(tag.name) && !regex_retain.match?(tag.name)
|
||||
end
|
||||
end
|
||||
|
||||
def filter_keep_n
|
||||
return unless keep_n
|
||||
|
||||
order_by_date
|
||||
cache_tags(@tags.first(keep_n_as_integer))
|
||||
@tags = @tags.drop(keep_n_as_integer)
|
||||
end
|
||||
|
||||
def filter_by_older_than
|
||||
return unless older_than
|
||||
|
||||
older_than_timestamp = older_than_in_seconds.ago
|
||||
|
||||
@tags, tags_to_keep = @tags.partition do |tag|
|
||||
tag.created_at && tag.created_at < older_than_timestamp
|
||||
end
|
||||
@tags, tags_to_keep = partition_by_keep_n
|
||||
|
||||
cache_tags(tags_to_keep)
|
||||
end
|
||||
|
||||
def can_destroy?
|
||||
return true if container_expiration_policy
|
||||
def filter_by_older_than
|
||||
@tags, tags_to_keep = partition_by_older_than
|
||||
|
||||
can?(@current_user, :destroy_container_image, @project)
|
||||
cache_tags(tags_to_keep)
|
||||
end
|
||||
|
||||
def valid_regex?
|
||||
%w(name_regex_delete name_regex name_regex_keep).each do |param_name|
|
||||
regex = @params[param_name]
|
||||
::Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
|
||||
end
|
||||
true
|
||||
rescue RegexpError => e
|
||||
::Gitlab::ErrorTracking.log_exception(e, project_id: @project.id)
|
||||
false
|
||||
def pushed_at(tag)
|
||||
tag.created_at
|
||||
end
|
||||
|
||||
def truncate
|
||||
|
@ -153,40 +96,6 @@ module Projects
|
|||
def max_list_size
|
||||
::Gitlab::CurrentSettings.current_application_settings.container_registry_cleanup_tags_service_max_list_size.to_i
|
||||
end
|
||||
|
||||
def keep_n
|
||||
@params['keep_n']
|
||||
end
|
||||
|
||||
def keep_n_as_integer
|
||||
keep_n.to_i
|
||||
end
|
||||
|
||||
def older_than_in_seconds
|
||||
strong_memoize(:older_than_in_seconds) do
|
||||
ChronicDuration.parse(older_than).seconds
|
||||
end
|
||||
end
|
||||
|
||||
def older_than
|
||||
@params['older_than']
|
||||
end
|
||||
|
||||
def name_regex_delete
|
||||
@params['name_regex_delete']
|
||||
end
|
||||
|
||||
def name_regex
|
||||
@params['name_regex']
|
||||
end
|
||||
|
||||
def name_regex_keep
|
||||
@params['name_regex_keep']
|
||||
end
|
||||
|
||||
def container_expiration_policy
|
||||
@params['container_expiration_policy']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Projects
|
||||
module ContainerRepository
|
||||
module Gitlab
|
||||
class CleanupTagsService < CleanupTagsBaseService
|
||||
include ::Projects::ContainerRepository::Gitlab::Timeoutable
|
||||
|
||||
TAGS_PAGE_SIZE = 1000
|
||||
|
||||
def initialize(container_repository, user = nil, params = {})
|
||||
@container_repository = container_repository
|
||||
@current_user = user
|
||||
@params = params.dup
|
||||
|
||||
@project = container_repository.project
|
||||
end
|
||||
|
||||
def execute
|
||||
return error('access denied') unless can_destroy?
|
||||
return error('invalid regex') unless valid_regex?
|
||||
|
||||
with_timeout do |start_time, result|
|
||||
@container_repository.each_tags_page(page_size: TAGS_PAGE_SIZE) do |tags|
|
||||
execute_for_tags(tags, result)
|
||||
|
||||
raise TimeoutError if timeout?(start_time)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_for_tags(tags, overall_result)
|
||||
@tags = tags
|
||||
original_size = @tags.size
|
||||
|
||||
filter_out_latest
|
||||
filter_by_name
|
||||
|
||||
filter_by_keep_n
|
||||
filter_by_older_than
|
||||
|
||||
overall_result[:before_delete_size] += @tags.size
|
||||
overall_result[:original_size] += original_size
|
||||
|
||||
result = delete_tags
|
||||
|
||||
overall_result[:deleted_size] += result[:deleted]&.size
|
||||
overall_result[:deleted] += result[:deleted]
|
||||
overall_result[:status] = result[:status] unless overall_result[:status] == :error
|
||||
end
|
||||
|
||||
def with_timeout
|
||||
result = {
|
||||
original_size: 0,
|
||||
before_delete_size: 0,
|
||||
deleted_size: 0,
|
||||
deleted: []
|
||||
}
|
||||
|
||||
yield Time.zone.now, result
|
||||
|
||||
result
|
||||
rescue TimeoutError
|
||||
result[:status] = :error
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def filter_by_keep_n
|
||||
@tags, _ = partition_by_keep_n
|
||||
end
|
||||
|
||||
def filter_by_older_than
|
||||
@tags, _ = partition_by_older_than
|
||||
end
|
||||
|
||||
def pushed_at(tag)
|
||||
tag.updated_at || tag.created_at
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,10 +6,7 @@ module Projects
|
|||
class DeleteTagsService
|
||||
include BaseServiceUtility
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
DISABLED_TIMEOUTS = [nil, 0].freeze
|
||||
|
||||
TimeoutError = Class.new(StandardError)
|
||||
include ::Projects::ContainerRepository::Gitlab::Timeoutable
|
||||
|
||||
def initialize(container_repository, tag_names)
|
||||
@container_repository = container_repository
|
||||
|
@ -44,16 +41,6 @@ module Projects
|
|||
|
||||
@deleted_tags.any? ? success(deleted: @deleted_tags) : error('could not delete tags')
|
||||
end
|
||||
|
||||
def timeout?(start_time)
|
||||
return false if service_timeout.in?(DISABLED_TIMEOUTS)
|
||||
|
||||
(Time.zone.now - start_time) > service_timeout
|
||||
end
|
||||
|
||||
def service_timeout
|
||||
::Gitlab::CurrentSettings.current_application_settings.container_registry_delete_tags_service_timeout
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,16 +3,16 @@
|
|||
= form_errors(@group)
|
||||
= render 'shared/groups/group_name_and_path_fields', f: f, autofocus: true, new_subgroup: !!parent
|
||||
|
||||
- unless parent
|
||||
.row
|
||||
.form-group.gl-form-group.col-sm-12
|
||||
%label.label-bold
|
||||
= _('Visibility level')
|
||||
%p
|
||||
= _('Who will be able to see this group?')
|
||||
= link_to _('View the documentation'), help_page_path("user/public_access"), target: '_blank', rel: 'noopener noreferrer'
|
||||
= render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false
|
||||
.row
|
||||
.form-group.gl-form-group.col-sm-12
|
||||
%label.label-bold
|
||||
= _('Visibility level')
|
||||
%p
|
||||
= _('Who will be able to see this group?')
|
||||
= link_to _('View the documentation'), help_page_path("user/public_access"), target: '_blank', rel: 'noopener noreferrer'
|
||||
= render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false
|
||||
|
||||
- unless parent
|
||||
- if Gitlab.config.mattermost.enabled
|
||||
.row
|
||||
= render 'create_chat_team', f: f
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
- page_title _("Activity")
|
||||
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
|
||||
= render 'projects/last_push'
|
||||
= render 'projects/activity'
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
- expanded = expanded_by_default?
|
||||
- reduce_visibility_form_id = 'reduce-visibility-form'
|
||||
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
|
||||
%section.settings.general-settings.no-animate.expanded#js-general-settings
|
||||
.settings-header
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar')
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
- search = params[:search]
|
||||
- subscribed = params[:subscribed]
|
||||
- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present?
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
|
||||
- if labels_or_filters
|
||||
#js-promote-label-modal
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
- page_title _("Members")
|
||||
|
||||
= render_if_exists 'projects/free_user_cap_alert', project: @project
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-12
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
|
||||
|
||||
= render_if_exists 'projects/free_user_cap_alert', project: @project
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
= render partial: 'flash_messages', locals: { project: @project }
|
||||
= render 'clusters_deprecation_alert'
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
- page_title s_("UsageQuota|Usage")
|
||||
|
||||
= render_if_exists 'shared/ultimate_feature_removal_banner', project: @project
|
||||
|
||||
= render Pajamas::AlertComponent.new(title: _('Repository usage recalculation started'),
|
||||
variant: :info,
|
||||
alert_options: { class: 'js-recalculation-started-alert gl-mt-4 gl-mb-5 gl-display-none' }) do |c|
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ultimate_feature_removal_banner
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94271
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371690
|
||||
milestone: '15.4'
|
||||
type: development
|
||||
group: group::workspace
|
||||
default_enabled: false
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
table_name: packages_rpm_metadata
|
||||
classes:
|
||||
- Packages::Rpm::Metadatum
|
||||
feature_categories:
|
||||
- package_registry
|
||||
description: Rpm package metadata
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96019
|
||||
milestone: '15.4'
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddRpmMaxFileSizeToPlanLimits < Gitlab::Database::Migration[2.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :plan_limits, :rpm_max_file_size, :bigint, default: 5.gigabytes, null: false
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreatePackagesRpmMetadata < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
create_table :packages_rpm_metadata, id: false do |t|
|
||||
t.references :package,
|
||||
primary_key: true,
|
||||
default: nil,
|
||||
index: true,
|
||||
foreign_key: { to_table: :packages_packages, on_delete: :cascade },
|
||||
type: :bigint
|
||||
t.text :release, default: '1', null: false, limit: 128
|
||||
t.text :summary, default: '', null: false, limit: 1000
|
||||
t.text :description, default: '', null: false, limit: 5000
|
||||
t.text :arch, default: '', null: false, limit: 255
|
||||
t.text :license, null: true, limit: 1000
|
||||
t.text :url, null: true, limit: 1000
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
drop_table :packages_rpm_metadata
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
7373697e5064a5ecca5881e7b98a30deba033bf8d79d2121cd17200f72815252
|
|
@ -0,0 +1 @@
|
|||
d38668a9110a69f12c4d60886ace04da4f6dd7f250763a888d3c428a74032b7d
|
|
@ -18907,6 +18907,22 @@ CREATE TABLE packages_pypi_metadata (
|
|||
CONSTRAINT check_379019d5da CHECK ((char_length(required_python) <= 255))
|
||||
);
|
||||
|
||||
CREATE TABLE packages_rpm_metadata (
|
||||
package_id bigint NOT NULL,
|
||||
release text DEFAULT '1'::text NOT NULL,
|
||||
summary text DEFAULT ''::text NOT NULL,
|
||||
description text DEFAULT ''::text NOT NULL,
|
||||
arch text DEFAULT ''::text NOT NULL,
|
||||
license text,
|
||||
url text,
|
||||
CONSTRAINT check_3798bae3d6 CHECK ((char_length(arch) <= 255)),
|
||||
CONSTRAINT check_5d29ba59ac CHECK ((char_length(description) <= 5000)),
|
||||
CONSTRAINT check_6e8cbd536d CHECK ((char_length(url) <= 1000)),
|
||||
CONSTRAINT check_845ba4d7d0 CHECK ((char_length(license) <= 1000)),
|
||||
CONSTRAINT check_b010bf4870 CHECK ((char_length(summary) <= 1000)),
|
||||
CONSTRAINT check_c3e2fc2e89 CHECK ((char_length(release) <= 128))
|
||||
);
|
||||
|
||||
CREATE TABLE packages_rubygems_metadata (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
|
@ -19194,7 +19210,8 @@ CREATE TABLE plan_limits (
|
|||
web_hook_calls_low integer DEFAULT 0 NOT NULL,
|
||||
project_ci_variables integer DEFAULT 200 NOT NULL,
|
||||
group_ci_variables integer DEFAULT 200 NOT NULL,
|
||||
ci_max_artifact_size_cyclonedx integer DEFAULT 1 NOT NULL
|
||||
ci_max_artifact_size_cyclonedx integer DEFAULT 1 NOT NULL,
|
||||
rpm_max_file_size bigint DEFAULT '5368709120'::bigint NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE plan_limits_id_seq
|
||||
|
@ -25926,6 +25943,9 @@ ALTER TABLE ONLY packages_packages
|
|||
ALTER TABLE ONLY packages_pypi_metadata
|
||||
ADD CONSTRAINT packages_pypi_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
ALTER TABLE ONLY packages_rpm_metadata
|
||||
ADD CONSTRAINT packages_rpm_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
ALTER TABLE ONLY packages_rubygems_metadata
|
||||
ADD CONSTRAINT packages_rubygems_metadata_pkey PRIMARY KEY (package_id);
|
||||
|
||||
|
@ -29620,6 +29640,8 @@ CREATE INDEX index_packages_packages_on_project_id_and_version ON packages_packa
|
|||
|
||||
CREATE INDEX index_packages_project_id_name_partial_for_nuget ON packages_packages USING btree (project_id, name) WHERE (((name)::text <> 'NuGet.Temporary.Package'::text) AND (version IS NOT NULL) AND (package_type = 4));
|
||||
|
||||
CREATE INDEX index_packages_rpm_metadata_on_package_id ON packages_rpm_metadata USING btree (package_id);
|
||||
|
||||
CREATE INDEX index_packages_tags_on_package_id ON packages_tags USING btree (package_id);
|
||||
|
||||
CREATE INDEX index_packages_tags_on_package_id_and_updated_at ON packages_tags USING btree (package_id, updated_at DESC);
|
||||
|
@ -34491,6 +34513,9 @@ ALTER TABLE ONLY geo_hashed_storage_attachments_events
|
|||
ALTER TABLE ONLY ml_candidate_params
|
||||
ADD CONSTRAINT fk_rails_d4a51d1185 FOREIGN KEY (candidate_id) REFERENCES ml_candidates(id);
|
||||
|
||||
ALTER TABLE ONLY packages_rpm_metadata
|
||||
ADD CONSTRAINT fk_rails_d79f02264b FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY merge_request_reviewers
|
||||
ADD CONSTRAINT fk_rails_d9fec24b9d FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -4640,6 +4640,27 @@ Input type: `SecurityFindingCreateIssueInput`
|
|||
| <a id="mutationsecurityfindingcreateissueerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationsecurityfindingcreateissueissue"></a>`issue` | [`Issue`](#issue) | Issue created after mutation. |
|
||||
|
||||
### `Mutation.securityFindingDismiss`
|
||||
|
||||
Input type: `SecurityFindingDismissInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationsecurityfindingdismissclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationsecurityfindingdismisscomment"></a>`comment` | [`String`](#string) | Comment why finding should be dismissed. |
|
||||
| <a id="mutationsecurityfindingdismissdismissalreason"></a>`dismissalReason` | [`VulnerabilityDismissalReason`](#vulnerabilitydismissalreason) | Reason why finding should be dismissed. |
|
||||
| <a id="mutationsecurityfindingdismissuuid"></a>`uuid` | [`String!`](#string) | UUID of the finding to be dismissed. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationsecurityfindingdismissclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationsecurityfindingdismisserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
| <a id="mutationsecurityfindingdismissuuid"></a>`uuid` | [`String`](#string) | UUID of dismissed finding. |
|
||||
|
||||
### `Mutation.securityPolicyProjectAssign`
|
||||
|
||||
Assigns the specified project(`security_policy_project_id`) as security policy project for the given project(`full_path`). If the project already has a security policy project, this reassigns the project's security policy project with the given `security_policy_project_id`.
|
||||
|
@ -20605,6 +20626,7 @@ Values for sorting package.
|
|||
| <a id="packagetypeenumnpm"></a>`NPM` | Packages from the npm package manager. |
|
||||
| <a id="packagetypeenumnuget"></a>`NUGET` | Packages from the Nuget package manager. |
|
||||
| <a id="packagetypeenumpypi"></a>`PYPI` | Packages from the PyPI package manager. |
|
||||
| <a id="packagetypeenumrpm"></a>`RPM` | Packages from the Rpm package manager. |
|
||||
| <a id="packagetypeenumrubygems"></a>`RUBYGEMS` | Packages from the Rubygems package manager. |
|
||||
| <a id="packagetypeenumterraform_module"></a>`TERRAFORM_MODULE` | Packages from the Terraform Module package manager. |
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ module ContainerRegistry
|
|||
class Tag
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_reader :repository, :name
|
||||
attr_reader :repository, :name, :updated_at
|
||||
attr_writer :created_at
|
||||
|
||||
delegate :registry, :client, to: :repository
|
||||
|
@ -97,6 +97,17 @@ module ContainerRegistry
|
|||
instance_variable_set(ivar(:memoized_created_at), date)
|
||||
end
|
||||
|
||||
def updated_at=(string_value)
|
||||
return unless string_value
|
||||
|
||||
@updated_at =
|
||||
begin
|
||||
DateTime.iso8601(string_value)
|
||||
rescue ArgumentError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def layers
|
||||
return unless manifest
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.33.0'
|
||||
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.37.0'
|
||||
|
||||
.dast-auto-deploy:
|
||||
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.33.0'
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.37.0'
|
||||
|
||||
.auto-deploy:
|
||||
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.33.0'
|
||||
AUTO_DEPLOY_IMAGE_VERSION: 'v2.37.0'
|
||||
|
||||
.auto-deploy:
|
||||
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"
|
||||
|
|
|
@ -383,6 +383,7 @@ packages_events: :gitlab_main
|
|||
packages_helm_file_metadata: :gitlab_main
|
||||
packages_maven_metadata: :gitlab_main
|
||||
packages_npm_metadata: :gitlab_main
|
||||
packages_rpm_metadata: :gitlab_main
|
||||
packages_nuget_dependency_link_metadata: :gitlab_main
|
||||
packages_nuget_metadata: :gitlab_main
|
||||
packages_package_file_build_infos: :gitlab_main
|
||||
|
|
|
@ -10175,6 +10175,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Docker connection error"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Edit cleanup rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Enable expiration policy"
|
||||
msgstr ""
|
||||
|
||||
|
@ -10279,6 +10282,12 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Set cleanup rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Set rules to automatically remove unused packages to save storage space."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Set up cleanup"
|
||||
msgstr ""
|
||||
|
||||
|
@ -26407,6 +26416,9 @@ msgstr ""
|
|||
msgid "No credit card required."
|
||||
msgstr ""
|
||||
|
||||
msgid "No data available"
|
||||
msgstr ""
|
||||
|
||||
msgid "No data found"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27939,6 +27951,9 @@ msgstr ""
|
|||
msgid "Package type must be PyPi"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package type must be RPM"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package type must be RubyGems"
|
||||
msgstr ""
|
||||
|
||||
|
@ -45796,6 +45811,9 @@ msgstr ""
|
|||
msgid "Your profile"
|
||||
msgstr ""
|
||||
|
||||
msgid "Your project is no longer receiving GitLab Ultimate benefits as of 2022-07-01. As notified in-app previously, public open source projects on the Free tier can apply to the GitLab for Open Source Program to receive GitLab Ultimate benefits. Please refer to the %{faq_link_start}FAQ%{link_end} for more details."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -337,6 +337,14 @@ FactoryBot.define do
|
|||
size { 3989.bytes }
|
||||
end
|
||||
|
||||
trait(:rpm) do
|
||||
package
|
||||
file_fixture { 'spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm' }
|
||||
file_name { 'hello-0.0.1-1.fc29.x86_64.rpm' }
|
||||
file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
|
||||
size { 115.kilobytes }
|
||||
end
|
||||
|
||||
trait(:object_storage) do
|
||||
file_store { Packages::PackageFileUploader::Store::REMOTE }
|
||||
end
|
||||
|
|
|
@ -55,6 +55,12 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory :rpm_package do
|
||||
sequence(:name) { |n| "package-#{n}" }
|
||||
sequence(:version) { |n| "v1.0.#{n}" }
|
||||
package_type { :rpm }
|
||||
end
|
||||
|
||||
factory :debian_package do
|
||||
sequence(:name) { |n| "package-#{n}" }
|
||||
sequence(:version) { |n| "1.0-#{n}" }
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :rpm_metadatum, class: 'Packages::Rpm::Metadatum' do
|
||||
package { association(:rpm_package) }
|
||||
release { "#{rand(10)}.#{rand(10)}" }
|
||||
summary { FFaker::Lorem.sentences(2).join }
|
||||
description { FFaker::Lorem.sentences(4).join }
|
||||
arch { FFaker::Lorem.word }
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Project > Settings > Packages and registries > Container registry tag expiration policy', :js do
|
||||
RSpec.describe 'Project > Settings > Packages and registries > Container registry tag expiration policy' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, namespace: user.namespace) }
|
||||
|
||||
|
@ -19,7 +19,7 @@ RSpec.describe 'Project > Settings > Packages and registries > Container registr
|
|||
stub_container_registry_config(enabled: container_registry_enabled)
|
||||
end
|
||||
|
||||
context 'as owner' do
|
||||
context 'as owner', :js do
|
||||
it 'shows available section' do
|
||||
subject
|
||||
|
||||
|
@ -27,40 +27,14 @@ RSpec.describe 'Project > Settings > Packages and registries > Container registr
|
|||
expect(settings_block).to have_text 'Clean up image tags'
|
||||
end
|
||||
|
||||
it 'saves cleanup policy submit the form' do
|
||||
it 'contains link to clean up image tags page' do
|
||||
subject
|
||||
|
||||
within '[data-testid="container-expiration-policy-project-settings"]' do
|
||||
select('Every day', from: 'Run cleanup')
|
||||
select('50 tags per image name', from: 'Keep the most recent:')
|
||||
fill_in('Keep tags matching:', with: 'stable')
|
||||
select('7 days', from: 'Remove tags older than:')
|
||||
fill_in('Remove tags matching:', with: '.*-production')
|
||||
|
||||
submit_button = find('[data-testid="save-button"')
|
||||
expect(submit_button).not_to be_disabled
|
||||
submit_button.click
|
||||
end
|
||||
|
||||
expect(find('.gl-toast')).to have_content('Cleanup policy successfully saved.')
|
||||
end
|
||||
|
||||
it 'does not save cleanup policy submit form with invalid regex' do
|
||||
subject
|
||||
|
||||
within '[data-testid="container-expiration-policy-project-settings"]' do
|
||||
fill_in('Remove tags matching:', with: '*-production')
|
||||
|
||||
submit_button = find('[data-testid="save-button"')
|
||||
expect(submit_button).not_to be_disabled
|
||||
submit_button.click
|
||||
end
|
||||
|
||||
expect(find('.gl-toast')).to have_content('Something went wrong while updating the cleanup policy.')
|
||||
expect(page).to have_link('Edit cleanup rules', href: cleanup_image_tags_project_settings_packages_and_registries_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a project without expiration policy' do
|
||||
context 'with a project without expiration policy', :js do
|
||||
before do
|
||||
project.container_expiration_policy.destroy!
|
||||
end
|
||||
|
@ -74,7 +48,7 @@ RSpec.describe 'Project > Settings > Packages and registries > Container registr
|
|||
subject
|
||||
|
||||
within '[data-testid="container-expiration-policy-project-settings"]' do
|
||||
expect(find('[data-testid="enable-toggle"]')).to have_content('Disabled - Tags will not be automatically deleted.')
|
||||
expect(page).to have_link('Set cleanup rules', href: cleanup_image_tags_project_settings_packages_and_registries_path(project))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -288,6 +288,17 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'no Auto DevOps button if builds feature is disabled' do
|
||||
project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
|
||||
|
||||
visit project_path(project)
|
||||
|
||||
page.within('.project-buttons') do
|
||||
expect(page).not_to have_link('Enable Auto DevOps')
|
||||
expect(page).not_to have_link('Auto DevOps enabled')
|
||||
end
|
||||
end
|
||||
|
||||
it 'no "Enable Auto DevOps" button when .gitlab-ci.yml already exists' do
|
||||
Files::CreateService.new(
|
||||
project,
|
||||
|
|
Binary file not shown.
|
@ -1,12 +1,14 @@
|
|||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlAlert, GlSprintf, GlLink, GlCard } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import component from '~/packages_and_registries/settings/project/components/container_expiration_policy.vue';
|
||||
import ContainerExpirationPolicyForm from '~/packages_and_registries/settings/project/components/container_expiration_policy_form.vue';
|
||||
import {
|
||||
CONTAINER_CLEANUP_POLICY_EDIT_RULES,
|
||||
CONTAINER_CLEANUP_POLICY_SET_RULES,
|
||||
CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION,
|
||||
FETCH_SETTINGS_ERROR_MESSAGE,
|
||||
UNAVAILABLE_FEATURE_INTRO_TEXT,
|
||||
UNAVAILABLE_USER_FEATURE_TEXT,
|
||||
|
@ -14,11 +16,7 @@ import {
|
|||
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
|
||||
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
|
||||
|
||||
import {
|
||||
expirationPolicyPayload,
|
||||
emptyExpirationPolicyPayload,
|
||||
containerExpirationPolicyData,
|
||||
} from '../mock_data';
|
||||
import { expirationPolicyPayload, emptyExpirationPolicyPayload } from '../mock_data';
|
||||
|
||||
describe('Container expiration policy project settings', () => {
|
||||
let wrapper;
|
||||
|
@ -28,17 +26,19 @@ describe('Container expiration policy project settings', () => {
|
|||
projectPath: 'path',
|
||||
isAdmin: false,
|
||||
adminSettingsPath: 'settingsPath',
|
||||
cleanupSettingsPath: 'cleanupSettingsPath',
|
||||
enableHistoricEntries: false,
|
||||
helpPagePath: 'helpPagePath',
|
||||
showCleanupPolicyLink: false,
|
||||
};
|
||||
|
||||
const findFormComponent = () => wrapper.find(ContainerExpirationPolicyForm);
|
||||
const findAlert = () => wrapper.find(GlAlert);
|
||||
const findFormComponent = () => wrapper.findComponent(GlCard);
|
||||
const findDescription = () => wrapper.findByTestId('description');
|
||||
const findButton = () => wrapper.findByTestId('rules-button');
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findSettingsBlock = () => wrapper.find(SettingsBlock);
|
||||
|
||||
const mountComponent = (provide = defaultProvidedValues, config) => {
|
||||
wrapper = shallowMount(component, {
|
||||
wrapper = shallowMountExtended(component, {
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
SettingsBlock,
|
||||
|
@ -63,37 +63,19 @@ describe('Container expiration policy project settings', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('isEdited status', () => {
|
||||
it.each`
|
||||
description | apiResponse | workingCopy | result
|
||||
${'empty response and no changes from user'} | ${emptyExpirationPolicyPayload()} | ${{}} | ${false}
|
||||
${'empty response and changes from user'} | ${emptyExpirationPolicyPayload()} | ${{ enabled: true }} | ${true}
|
||||
${'response and no changes'} | ${expirationPolicyPayload()} | ${containerExpirationPolicyData()} | ${false}
|
||||
${'response and changes'} | ${expirationPolicyPayload()} | ${{ ...containerExpirationPolicyData(), nameRegex: '12345' }} | ${true}
|
||||
${'response and empty'} | ${expirationPolicyPayload()} | ${{}} | ${true}
|
||||
`('$description', async ({ apiResponse, workingCopy, result }) => {
|
||||
mountComponentWithApollo({
|
||||
provide: { ...defaultProvidedValues, enableHistoricEntries: true },
|
||||
resolver: jest.fn().mockResolvedValue(apiResponse),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
findFormComponent().vm.$emit('input', workingCopy);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findFormComponent().props('isEdited')).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the setting form', async () => {
|
||||
mountComponentWithApollo({
|
||||
resolver: jest.fn().mockResolvedValue(expirationPolicyPayload()),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(findFormComponent().exists()).toBe(true);
|
||||
expect(findSettingsBlock().exists()).toBe(true);
|
||||
expect(findFormComponent().exists()).toBe(true);
|
||||
expect(findDescription().text()).toMatchInterpolatedText(
|
||||
CONTAINER_CLEANUP_POLICY_RULES_DESCRIPTION,
|
||||
);
|
||||
expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_EDIT_RULES);
|
||||
expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath);
|
||||
});
|
||||
|
||||
describe('the form is disabled', () => {
|
||||
|
@ -157,6 +139,10 @@ describe('Container expiration policy project settings', () => {
|
|||
await waitForPromises();
|
||||
|
||||
expect(findFormComponent().exists()).toBe(isShown);
|
||||
if (isShown) {
|
||||
expect(findButton().text()).toMatchInterpolatedText(CONTAINER_CLEANUP_POLICY_SET_RULES);
|
||||
expect(findButton().attributes('href')).toBe(defaultProvidedValues.cleanupSettingsPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,6 +4,6 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
|
||||
it 'exposes all package types' do
|
||||
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE])
|
||||
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE RPM])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -240,6 +240,31 @@ RSpec.describe ContainerRegistry::Tag do
|
|||
it_behaves_like 'setting and caching the created_at value'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'updated_at=' do
|
||||
subject do
|
||||
tag.updated_at = input
|
||||
tag.updated_at
|
||||
end
|
||||
|
||||
context 'with a valid input' do
|
||||
let(:input) { 2.days.ago.iso8601 }
|
||||
|
||||
it { is_expected.to eq(DateTime.iso8601(input)) }
|
||||
end
|
||||
|
||||
context 'with a nil input' do
|
||||
let(:input) { nil }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
|
||||
context 'with an invalid input' do
|
||||
let(:input) { 'not a timestamp' }
|
||||
|
||||
it { is_expected.to eq(nil) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe IncidentManagement::TimelineEvent do
|
|||
it { is_expected.to validate_length_of(:action).is_at_most(128) }
|
||||
end
|
||||
|
||||
describe '.order_occurred_at_asc' do
|
||||
describe '.order_occurred_at_asc_id_asc' do
|
||||
let_it_be(:occurred_3mins_ago) do
|
||||
create(:incident_management_timeline_event, project: project, occurred_at: 3.minutes.ago)
|
||||
end
|
||||
|
@ -38,11 +38,23 @@ RSpec.describe IncidentManagement::TimelineEvent do
|
|||
create(:incident_management_timeline_event, project: project, occurred_at: 2.minutes.ago)
|
||||
end
|
||||
|
||||
subject(:order) { described_class.order_occurred_at_asc }
|
||||
subject(:order) { described_class.order_occurred_at_asc_id_asc }
|
||||
|
||||
it 'sorts timeline events by occurred_at' do
|
||||
is_expected.to eq([occurred_3mins_ago, occurred_2mins_ago, timeline_event])
|
||||
end
|
||||
|
||||
context 'when two events occured at the same time' do
|
||||
let_it_be(:also_occurred_2mins_ago) do
|
||||
create(:incident_management_timeline_event, project: project, occurred_at: occurred_2mins_ago.occurred_at)
|
||||
end
|
||||
|
||||
it 'sorts timeline events by occurred_at then sorts by id' do
|
||||
occurred_2mins_ago.touch # Interact with record of earlier id to switch default DB ordering
|
||||
|
||||
is_expected.to eq([occurred_3mins_ago, occurred_2mins_ago, also_occurred_2mins_ago, timeline_event])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#cache_markdown_field' do
|
||||
|
|
|
@ -21,6 +21,7 @@ RSpec.describe Packages::Package, type: :model do
|
|||
it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
|
||||
it { is_expected.to have_one(:rubygems_metadatum).inverse_of(:package) }
|
||||
it { is_expected.to have_one(:npm_metadatum).inverse_of(:package) }
|
||||
it { is_expected.to have_one(:rpm_metadatum).inverse_of(:package) }
|
||||
end
|
||||
|
||||
describe '.with_debian_codename' do
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::Metadatum, type: :model do
|
||||
describe 'relationships' do
|
||||
it { is_expected.to belong_to(:package) }
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:package) }
|
||||
it { is_expected.to validate_presence_of(:release) }
|
||||
it { is_expected.to validate_presence_of(:summary) }
|
||||
it { is_expected.to validate_presence_of(:description) }
|
||||
it { is_expected.to validate_presence_of(:arch) }
|
||||
it { is_expected.to validate_length_of(:release).is_at_most(128) }
|
||||
it { is_expected.to validate_length_of(:summary).is_at_most(1000) }
|
||||
it { is_expected.to validate_length_of(:description).is_at_most(5000) }
|
||||
it { is_expected.to validate_length_of(:arch).is_at_most(255) }
|
||||
it { is_expected.to validate_length_of(:license).is_at_most(1000) }
|
||||
it { is_expected.to validate_length_of(:url).is_at_most(1000) }
|
||||
|
||||
describe '#rpm_package_type' do
|
||||
it 'will not allow a package with a different package_type' do
|
||||
package = build('conan_package')
|
||||
rpm_metadatum = build('rpm_metadatum', package: package)
|
||||
|
||||
expect(rpm_metadatum).not_to be_valid
|
||||
expect(rpm_metadatum.errors.to_a).to include('Package type must be RPM')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -484,6 +484,12 @@ RSpec.describe ProjectPresenter do
|
|||
end
|
||||
|
||||
describe '#autodevops_anchor_data' do
|
||||
it 'returns nil if builds feature is not available' do
|
||||
allow(project).to receive(:feature_available?).with(:builds, user).and_return(false)
|
||||
|
||||
expect(presenter.autodevops_anchor_data).to be_nil
|
||||
end
|
||||
|
||||
context 'when Auto Devops is enabled' do
|
||||
it 'returns anchor data' do
|
||||
allow(project).to receive(:auto_devops_enabled?).and_return(true)
|
||||
|
|
|
@ -7,10 +7,11 @@ RSpec.describe API::Commits do
|
|||
include ProjectForksHelper
|
||||
include SessionHelpers
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:guest) { create(:user).tap { |u| project.add_guest(u) } }
|
||||
let(:developer) { create(:user).tap { |u| project.add_developer(u) } }
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } }
|
||||
let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
|
||||
|
||||
let(:branch_with_dot) { project.repository.find_branch('ends-with.json') }
|
||||
let(:branch_with_slash) { project.repository.find_branch('improve/awesome') }
|
||||
let(:project_id) { project.id }
|
||||
|
@ -46,7 +47,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'project commits'
|
||||
end
|
||||
|
@ -413,6 +414,9 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'create' do
|
||||
let_it_be(:sequencer) { FactoryBot::Sequence.new(:new_file_path) { |n| "files/test/#{n}.rb" } }
|
||||
|
||||
let(:new_file_path) { sequencer.next }
|
||||
let(:message) { 'Created a new file with a very very looooooooooooooooooooooooooooooooooooooooooooooong commit message' }
|
||||
let(:invalid_c_params) do
|
||||
{
|
||||
|
@ -435,7 +439,7 @@ RSpec.describe API::Commits do
|
|||
actions: [
|
||||
{
|
||||
action: 'create',
|
||||
file_path: 'foo/bar/baz.txt',
|
||||
file_path: new_file_path,
|
||||
content: 'puts 8'
|
||||
}
|
||||
]
|
||||
|
@ -449,7 +453,7 @@ RSpec.describe API::Commits do
|
|||
actions: [
|
||||
{
|
||||
action: 'create',
|
||||
file_path: 'foo/bar/baz.txt',
|
||||
file_path: new_file_path,
|
||||
content: 'puts 🦊'
|
||||
}
|
||||
]
|
||||
|
@ -959,6 +963,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'multiple operations' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let(:message) { 'Multiple actions' }
|
||||
let(:invalid_mo_params) do
|
||||
{
|
||||
|
@ -1028,17 +1033,11 @@ RSpec.describe API::Commits do
|
|||
}
|
||||
end
|
||||
|
||||
it 'are committed as one in project repo' do
|
||||
it 'is committed as one in project repo and includes stats' do
|
||||
post api(url, user), params: valid_mo_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response['title']).to eq(message)
|
||||
end
|
||||
|
||||
it 'includes the commit stats' do
|
||||
post api(url, user), params: valid_mo_params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(json_response).to include 'stats'
|
||||
end
|
||||
|
||||
|
@ -1124,7 +1123,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'GET /projects/:id/repository/commits/:sha/refs' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
let(:tag) { project.repository.find_tag('v1.1.0') }
|
||||
let(:commit_id) { tag.dereferenced_target.id }
|
||||
let(:route) { "/projects/#{project_id}/repository/commits/#{commit_id}/refs" }
|
||||
|
@ -1139,6 +1139,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
|
@ -1228,6 +1230,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
|
@ -1269,8 +1273,14 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
shared_examples_for 'ref with unaccessible pipeline' do
|
||||
let!(:pipeline) do
|
||||
create(:ci_empty_pipeline, project: project, status: :created, source: :push, ref: 'master', sha: commit.sha, protected: false)
|
||||
let(:pipeline) do
|
||||
create(:ci_empty_pipeline,
|
||||
project: project,
|
||||
status: :created,
|
||||
source: :push,
|
||||
ref: 'master',
|
||||
sha: commit.sha,
|
||||
protected: false)
|
||||
end
|
||||
|
||||
it 'does not include last_pipeline' do
|
||||
|
@ -1308,7 +1318,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be_with_reload(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'ref commit'
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
@ -1338,6 +1348,7 @@ RSpec.describe API::Commits do
|
|||
context 'when builds are disabled' do
|
||||
before do
|
||||
project
|
||||
.reload
|
||||
.project_feature
|
||||
.update!(builds_access_level: ProjectFeature::DISABLED)
|
||||
end
|
||||
|
@ -1389,7 +1400,7 @@ RSpec.describe API::Commits do
|
|||
|
||||
context 'with private builds' do
|
||||
before do
|
||||
project.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE)
|
||||
project.reload.project_feature.update!(builds_access_level: ProjectFeature::PRIVATE)
|
||||
end
|
||||
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
@ -1415,8 +1426,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when authenticated', 'as non_member and project is public' do
|
||||
let(:current_user) { create(:user) }
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be_with_reload(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'ref with pipeline'
|
||||
|
||||
|
@ -1469,6 +1480,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
|
@ -1478,7 +1491,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'ref diff'
|
||||
end
|
||||
|
@ -1568,6 +1581,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
|
@ -1577,7 +1592,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like 'ref comments'
|
||||
end
|
||||
|
@ -1666,6 +1681,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'POST :id/repository/commits/:sha/cherry_pick' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let(:commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') }
|
||||
let(:commit_id) { commit.id }
|
||||
let(:branch) { 'master' }
|
||||
|
@ -1703,6 +1719,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
|
@ -1712,7 +1730,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { post api(route), params: { branch: 'master' } }
|
||||
|
@ -1851,6 +1869,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'POST :id/repository/commits/:sha/revert' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
let(:commit_id) { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
|
||||
let(:commit) { project.commit(commit_id) }
|
||||
let(:branch) { 'master' }
|
||||
|
@ -1891,7 +1910,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like '403 response' do
|
||||
let(:request) { post api(route), params: { branch: branch } }
|
||||
|
@ -1998,6 +2017,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'POST /projects/:id/repository/commits/:sha/comments' do
|
||||
let(:project) { create(:project, :repository, :private) }
|
||||
let(:commit) { project.repository.commit }
|
||||
let(:commit_id) { commit.id }
|
||||
let(:note) { 'My comment' }
|
||||
|
@ -2018,6 +2038,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when repository is disabled' do
|
||||
let(:project) { create(:project, :repository, creator: user, path: 'my.project') }
|
||||
|
||||
include_context 'disabled repository'
|
||||
|
||||
it_behaves_like '404 response' do
|
||||
|
@ -2027,7 +2049,7 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'when unauthenticated', 'and project is public' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let_it_be(:project) { create(:project, :public, :repository) }
|
||||
|
||||
it_behaves_like '400 response' do
|
||||
let(:request) { post api(route), params: { note: 'My comment' } }
|
||||
|
@ -2047,12 +2069,13 @@ RSpec.describe API::Commits do
|
|||
it_behaves_like 'ref new comment'
|
||||
|
||||
it 'returns the inline comment' do
|
||||
post api(route, current_user), params: { note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 1, line_type: 'new' }
|
||||
path = project.repository.commit.raw_diffs.first.new_path
|
||||
post api(route, current_user), params: { note: 'My comment', path: path, line: 1, line_type: 'new' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
expect(response).to match_response_schema('public_api/v4/commit_note')
|
||||
expect(json_response['note']).to eq('My comment')
|
||||
expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path)
|
||||
expect(json_response['path']).to eq(path)
|
||||
expect(json_response['line']).to eq(1)
|
||||
expect(json_response['line_type']).to eq('new')
|
||||
end
|
||||
|
@ -2127,7 +2150,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do
|
||||
let(:project) { create(:project, :repository, :private) }
|
||||
let_it_be(:project) { create(:project, :repository, :private) }
|
||||
|
||||
let(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') }
|
||||
let(:commit) { merged_mr.merge_request_diff.commits.last }
|
||||
|
||||
|
@ -2159,7 +2183,8 @@ RSpec.describe API::Commits do
|
|||
end
|
||||
|
||||
context 'public project' do
|
||||
let(:project) { create(:project, :repository, :public, :merge_requests_private) }
|
||||
let_it_be(:project) { create(:project, :repository, :public, :merge_requests_private) }
|
||||
|
||||
let(:non_member) { create(:user) }
|
||||
|
||||
it 'responds 403 when only members are allowed to read merge requests' do
|
||||
|
|
|
@ -69,15 +69,6 @@ RSpec.describe 'Query.runners' do
|
|||
|
||||
it_behaves_like 'a working graphql query returning expected runner'
|
||||
end
|
||||
|
||||
context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do
|
||||
let(:runner_type) { 'PROJECT_TYPE' }
|
||||
let(:status) { 'NEVER_CONTACTED' }
|
||||
|
||||
let!(:expected_runner) { project_runner }
|
||||
|
||||
it_behaves_like 'a working graphql query returning expected runner'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
|
@ -221,6 +221,15 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
|
|||
end
|
||||
|
||||
context 'with update_stats: false' do
|
||||
let_it_be(:extra_artifact_with_file) do
|
||||
create(:ci_job_artifact, :zip, project: artifact_with_file.project)
|
||||
end
|
||||
|
||||
let(:artifacts) do
|
||||
Ci::JobArtifact.where(id: [artifact_with_file.id, extra_artifact_with_file.id,
|
||||
artifact_without_file.id, trace_artifact.id])
|
||||
end
|
||||
|
||||
it 'does not update project statistics' do
|
||||
expect(ProjectStatistics).not_to receive(:increment_statistic)
|
||||
|
||||
|
@ -230,7 +239,7 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
|
|||
it 'returns size statistics' do
|
||||
expected_updates = {
|
||||
statistics_updates: {
|
||||
artifact_with_file.project => -artifact_with_file.file.size,
|
||||
artifact_with_file.project => -(artifact_with_file.file.size + extra_artifact_with_file.file.size),
|
||||
artifact_without_file.project => 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ require 'spec_helper'
|
|||
RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_redis_cache do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
include_context 'for a cleanup tags service'
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :private) }
|
||||
|
||||
|
@ -39,268 +41,141 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
|
|||
describe '#execute' do
|
||||
subject { service.execute }
|
||||
|
||||
context 'when no params are specified' do
|
||||
let(:params) { {} }
|
||||
it_behaves_like 'handling invalid params',
|
||||
service_response_extra: {
|
||||
before_truncate_size: 0,
|
||||
after_truncate_size: 0,
|
||||
before_delete_size: 0,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
it 'does not remove anything' do
|
||||
expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService)
|
||||
.not_to receive(:execute)
|
||||
expect_no_caching
|
||||
it_behaves_like 'when regex matching everything is specified',
|
||||
delete_expectations: [%w(A Ba Bb C D E)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 6,
|
||||
after_truncate_size: 6,
|
||||
before_delete_size: 6,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
is_expected.to eq(expected_service_response(before_truncate_size: 0, after_truncate_size: 0, before_delete_size: 0))
|
||||
end
|
||||
end
|
||||
it_behaves_like 'when delete regex matching specific tags is used',
|
||||
service_response_extra: {
|
||||
before_truncate_size: 2,
|
||||
after_truncate_size: 2,
|
||||
before_delete_size: 2,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
context 'when regex matching everything is specified' do
|
||||
shared_examples 'removes all matches' do
|
||||
it 'does remove all tags except latest' do
|
||||
expect_no_caching
|
||||
it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex',
|
||||
service_response_extra: {
|
||||
before_truncate_size: 1,
|
||||
after_truncate_size: 1,
|
||||
before_delete_size: 1,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
expect_delete(%w(A Ba Bb C D E))
|
||||
it_behaves_like 'with allow regex value',
|
||||
delete_expectations: [%w(A C D E)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 4,
|
||||
after_truncate_size: 4,
|
||||
before_delete_size: 4,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(A Ba Bb C D E)))
|
||||
end
|
||||
end
|
||||
it_behaves_like 'when keeping only N tags',
|
||||
delete_expectations: [%w(Bb Ba C)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 4,
|
||||
after_truncate_size: 4,
|
||||
before_delete_size: 3,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*' }
|
||||
end
|
||||
it_behaves_like 'when not keeping N tags',
|
||||
delete_expectations: [%w(A Ba Bb C)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 4,
|
||||
after_truncate_size: 4,
|
||||
before_delete_size: 4,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
it_behaves_like 'removes all matches'
|
||||
it_behaves_like 'when removing keeping only 3',
|
||||
delete_expectations: [%w(Bb Ba C)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 6,
|
||||
after_truncate_size: 6,
|
||||
before_delete_size: 3,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
context 'with deprecated name_regex param' do
|
||||
let(:params) do
|
||||
{ 'name_regex' => '.*' }
|
||||
end
|
||||
it_behaves_like 'when removing older than 1 day',
|
||||
delete_expectations: [%w(Ba Bb C)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 6,
|
||||
after_truncate_size: 6,
|
||||
before_delete_size: 3,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
it_behaves_like 'removes all matches'
|
||||
end
|
||||
end
|
||||
it_behaves_like 'when combining all parameters',
|
||||
delete_expectations: [%w(Bb Ba C)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 6,
|
||||
after_truncate_size: 6,
|
||||
before_delete_size: 3,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
context 'with invalid regular expressions' do
|
||||
shared_examples 'handling an invalid regex' do
|
||||
it 'keeps all tags' do
|
||||
expect_no_caching
|
||||
it_behaves_like 'when running a container_expiration_policy',
|
||||
delete_expectations: [%w(Bb Ba C)],
|
||||
service_response_extra: {
|
||||
before_truncate_size: 6,
|
||||
after_truncate_size: 6,
|
||||
before_delete_size: 3,
|
||||
cached_tags_count: 0
|
||||
},
|
||||
supports_caching: true
|
||||
|
||||
expect(Projects::ContainerRepository::DeleteTagsService)
|
||||
.not_to receive(:new)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it { is_expected.to eq(status: :error, message: 'invalid regex') }
|
||||
|
||||
it 'calls error tracking service' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when name_regex_delete is invalid' do
|
||||
let(:params) { { 'name_regex_delete' => '*test*' } }
|
||||
|
||||
it_behaves_like 'handling an invalid regex'
|
||||
end
|
||||
|
||||
context 'when name_regex is invalid' do
|
||||
let(:params) { { 'name_regex' => '*test*' } }
|
||||
|
||||
it_behaves_like 'handling an invalid regex'
|
||||
end
|
||||
|
||||
context 'when name_regex_keep is invalid' do
|
||||
let(:params) { { 'name_regex_keep' => '*test*' } }
|
||||
|
||||
it_behaves_like 'handling an invalid regex'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when delete regex matching specific tags is used' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => 'C|D' }
|
||||
end
|
||||
|
||||
it 'does remove C and D' do
|
||||
expect_delete(%w(C D))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(C D), before_truncate_size: 2, after_truncate_size: 2, before_delete_size: 2))
|
||||
end
|
||||
|
||||
context 'with overriding allow regex' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => 'C|D',
|
||||
'name_regex_keep' => 'C' }
|
||||
end
|
||||
|
||||
it 'does not remove C' do
|
||||
expect_delete(%w(D))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(D), before_truncate_size: 1, after_truncate_size: 1, before_delete_size: 1))
|
||||
end
|
||||
end
|
||||
|
||||
context 'with name_regex_delete overriding deprecated name_regex' do
|
||||
let(:params) do
|
||||
{ 'name_regex' => 'C|D',
|
||||
'name_regex_delete' => 'D' }
|
||||
end
|
||||
|
||||
it 'does not remove C' do
|
||||
expect_delete(%w(D))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(D), before_truncate_size: 1, after_truncate_size: 1, before_delete_size: 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with allow regex value' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'name_regex_keep' => 'B.*' }
|
||||
end
|
||||
|
||||
it 'does not remove B*' do
|
||||
expect_delete(%w(A C D E))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(A C D E), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 4))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when keeping only N tags' do
|
||||
let(:params) do
|
||||
{ 'name_regex' => 'A|B.*|C',
|
||||
'keep_n' => 1 }
|
||||
end
|
||||
|
||||
it 'sorts tags by date' do
|
||||
expect_delete(%w(Bb Ba C))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
expect(service).to receive(:order_by_date).and_call_original
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 3))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not keeping N tags' do
|
||||
let(:params) do
|
||||
{ 'name_regex' => 'A|B.*|C' }
|
||||
end
|
||||
|
||||
it 'does not sort tags by date' do
|
||||
expect_delete(%w(A Ba Bb C))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
expect(service).not_to receive(:order_by_date)
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(A Ba Bb C), before_truncate_size: 4, after_truncate_size: 4, before_delete_size: 4))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when removing keeping only 3' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'keep_n' => 3 }
|
||||
end
|
||||
|
||||
it 'does remove B* and C as they are the oldest' do
|
||||
expect_delete(%w(Bb Ba C))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when removing older than 1 day' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'older_than' => '1 day' }
|
||||
end
|
||||
|
||||
it 'does remove B* and C as they are older than 1 day' do
|
||||
expect_delete(%w(Ba Bb C))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(Ba Bb C), before_delete_size: 3))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when combining all parameters' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day' }
|
||||
end
|
||||
|
||||
it 'does remove B* and C' do
|
||||
expect_delete(%w(Bb Ba C))
|
||||
|
||||
expect_no_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when running a container_expiration_policy' do
|
||||
context 'when running a container_expiration_policy with caching' do
|
||||
let(:user) { nil }
|
||||
|
||||
context 'with valid container_expiration_policy param' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day',
|
||||
'container_expiration_policy' => true }
|
||||
end
|
||||
|
||||
before do
|
||||
expect_delete(%w(Bb Ba C), container_expiration_policy: true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) }
|
||||
|
||||
context 'caching' do
|
||||
it 'expects caching to be used' do
|
||||
expect_caching
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when setting set to false' do
|
||||
before do
|
||||
stub_application_setting(container_registry_expiration_policies_caching: false)
|
||||
end
|
||||
|
||||
it 'does not use caching' do
|
||||
expect_no_caching
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day',
|
||||
'container_expiration_policy' => true
|
||||
}
|
||||
end
|
||||
|
||||
context 'without container_expiration_policy param' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day' }
|
||||
it 'expects caching to be used' do
|
||||
expect_delete(%w(Bb Ba C), container_expiration_policy: true)
|
||||
expect_caching
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
context 'when setting set to false' do
|
||||
before do
|
||||
stub_application_setting(container_registry_expiration_policies_caching: false)
|
||||
end
|
||||
|
||||
it 'fails' do
|
||||
is_expected.to eq(status: :error, message: 'access denied')
|
||||
it 'does not use caching' do
|
||||
expect_delete(%w(Bb Ba C), container_expiration_policy: true)
|
||||
expect_no_caching
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -322,10 +197,12 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
|
|||
service_response = expected_service_response(
|
||||
status: status,
|
||||
original_size: original_size,
|
||||
deleted: nil
|
||||
).merge(
|
||||
before_truncate_size: before_truncate_size,
|
||||
after_truncate_size: after_truncate_size,
|
||||
before_delete_size: before_delete_size,
|
||||
deleted: nil
|
||||
cached_tags_count: 0
|
||||
)
|
||||
|
||||
expect(result).to eq(service_response)
|
||||
|
@ -483,34 +360,6 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
|
|||
end
|
||||
end
|
||||
|
||||
def expect_delete(tags, container_expiration_policy: nil)
|
||||
expect(Projects::ContainerRepository::DeleteTagsService)
|
||||
.to receive(:new)
|
||||
.with(repository.project, user, tags: tags, container_expiration_policy: container_expiration_policy)
|
||||
.and_call_original
|
||||
|
||||
expect_any_instance_of(Projects::ContainerRepository::DeleteTagsService)
|
||||
.to receive(:execute)
|
||||
.with(repository) { { status: :success, deleted: tags } }
|
||||
end
|
||||
|
||||
# all those -1 because the default tags on L13 have a "latest" that will be filtered out
|
||||
def expected_service_response(status: :success, deleted: [], original_size: tags.size, before_truncate_size: tags.size - 1, after_truncate_size: tags.size - 1, before_delete_size: tags.size - 1)
|
||||
{
|
||||
status: status,
|
||||
deleted: deleted,
|
||||
original_size: original_size,
|
||||
before_truncate_size: before_truncate_size,
|
||||
after_truncate_size: after_truncate_size,
|
||||
before_delete_size: before_delete_size,
|
||||
cached_tags_count: 0
|
||||
}.compact.merge(deleted_size: deleted&.size)
|
||||
end
|
||||
|
||||
def expect_no_caching
|
||||
expect(::Gitlab::Redis::Cache).not_to receive(:with)
|
||||
end
|
||||
|
||||
def expect_caching
|
||||
::Gitlab::Redis::Cache.with do |redis|
|
||||
expect(redis).to receive(:mget).and_call_original
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::ContainerRepository::Gitlab::CleanupTagsService do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
include_context 'for a cleanup tags service'
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project, reload: true) { create(:project, :private) }
|
||||
|
||||
let(:repository) { create(:container_repository, :root, :import_done, project: project) }
|
||||
let(:service) { described_class.new(repository, user, params) }
|
||||
let(:tags) { %w[latest A Ba Bb C D E] }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user) if user
|
||||
|
||||
stub_container_registry_config(enabled: true)
|
||||
|
||||
stub_const("#{described_class}::TAGS_PAGE_SIZE", tags_page_size)
|
||||
|
||||
one_hour_ago = 1.hour.ago
|
||||
five_days_ago = 5.days.ago
|
||||
six_days_ago = 6.days.ago
|
||||
one_month_ago = 1.month.ago
|
||||
|
||||
stub_tags(
|
||||
{
|
||||
'latest' => one_hour_ago,
|
||||
'A' => one_hour_ago,
|
||||
'Ba' => five_days_ago,
|
||||
'Bb' => six_days_ago,
|
||||
'C' => one_month_ago,
|
||||
'D' => nil,
|
||||
'E' => nil
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
subject { service.execute }
|
||||
|
||||
context 'with several tags pages' do
|
||||
let(:tags_page_size) { 2 }
|
||||
|
||||
it_behaves_like 'handling invalid params'
|
||||
|
||||
it_behaves_like 'when regex matching everything is specified',
|
||||
delete_expectations: [%w[A], %w[Ba Bb], %w[C D], %w[E]]
|
||||
|
||||
it_behaves_like 'when delete regex matching specific tags is used'
|
||||
|
||||
it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex'
|
||||
|
||||
it_behaves_like 'with allow regex value',
|
||||
delete_expectations: [%w[A], %w[C D], %w[E]]
|
||||
|
||||
it_behaves_like 'when keeping only N tags',
|
||||
delete_expectations: [%w[Bb]]
|
||||
|
||||
it_behaves_like 'when not keeping N tags',
|
||||
delete_expectations: [%w[A], %w[Ba Bb], %w[C]]
|
||||
|
||||
context 'when removing keeping only 3' do
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'keep_n' => 3
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'not removing anything'
|
||||
end
|
||||
|
||||
it_behaves_like 'when removing older than 1 day',
|
||||
delete_expectations: [%w[Ba Bb], %w[C]]
|
||||
|
||||
it_behaves_like 'when combining all parameters',
|
||||
delete_expectations: [%w[Bb], %w[C]]
|
||||
|
||||
it_behaves_like 'when running a container_expiration_policy',
|
||||
delete_expectations: [%w[Bb], %w[C]]
|
||||
|
||||
context 'with a timeout' do
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*' }
|
||||
end
|
||||
|
||||
it 'removes the first few pages' do
|
||||
expect(service).to receive(:timeout?).and_return(false, true)
|
||||
|
||||
expect_delete(%w[A])
|
||||
expect_delete(%w[Ba Bb])
|
||||
|
||||
response = expected_service_response(status: :error, deleted: %w[A Ba Bb], original_size: 4)
|
||||
|
||||
is_expected.to eq(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a single tags page' do
|
||||
let(:tags_page_size) { 1000 }
|
||||
|
||||
it_behaves_like 'handling invalid params'
|
||||
|
||||
it_behaves_like 'when regex matching everything is specified',
|
||||
delete_expectations: [%w[A Ba Bb C D E]]
|
||||
|
||||
it_behaves_like 'when delete regex matching specific tags is used'
|
||||
|
||||
it_behaves_like 'when delete regex matching specific tags is used with overriding allow regex'
|
||||
|
||||
it_behaves_like 'with allow regex value',
|
||||
delete_expectations: [%w[A C D E]]
|
||||
|
||||
it_behaves_like 'when keeping only N tags',
|
||||
delete_expectations: [%w[Ba Bb C]]
|
||||
|
||||
it_behaves_like 'when not keeping N tags',
|
||||
delete_expectations: [%w[A Ba Bb C]]
|
||||
|
||||
it_behaves_like 'when removing keeping only 3',
|
||||
delete_expectations: [%w[Ba Bb C]]
|
||||
|
||||
it_behaves_like 'when removing older than 1 day',
|
||||
delete_expectations: [%w[Ba Bb C]]
|
||||
|
||||
it_behaves_like 'when combining all parameters',
|
||||
delete_expectations: [%w[Ba Bb C]]
|
||||
|
||||
it_behaves_like 'when running a container_expiration_policy',
|
||||
delete_expectations: [%w[Ba Bb C]]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stub_tags(tags)
|
||||
chunked = tags_page_size < tags.size
|
||||
previous_last = nil
|
||||
max_chunk_index = tags.size / tags_page_size
|
||||
|
||||
tags.keys.in_groups_of(tags_page_size, false).each_with_index do |chunked_tag_names, index|
|
||||
last = index == max_chunk_index
|
||||
pagination_needed = chunked && !last
|
||||
|
||||
response = {
|
||||
pagination: pagination_needed ? pagination_with(last: chunked_tag_names.last) : {},
|
||||
response_body: chunked_tag_names.map do |name|
|
||||
tag_raw_response(name, tags[name])
|
||||
end
|
||||
}
|
||||
|
||||
allow(repository.gitlab_api_client)
|
||||
.to receive(:tags)
|
||||
.with(repository.path, page_size: described_class::TAGS_PAGE_SIZE, last: previous_last)
|
||||
.and_return(response)
|
||||
previous_last = chunked_tag_names.last
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_with(last:)
|
||||
{
|
||||
next: {
|
||||
uri: URI("http://test.org?last=#{last}")
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def tag_raw_response(name, timestamp)
|
||||
timestamp_field = name.start_with?('B') ? 'updated_at' : 'created_at'
|
||||
{
|
||||
'name' => name,
|
||||
'digest' => 'sha256:1234567890',
|
||||
'media_type' => 'application/vnd.oci.image.manifest.v1+json',
|
||||
timestamp_field => timestamp&.iso8601
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_context 'for a cleanup tags service' do
|
||||
def expected_service_response(status: :success, deleted: [], original_size: tags.size)
|
||||
{
|
||||
status: status,
|
||||
deleted: deleted,
|
||||
original_size: original_size,
|
||||
before_delete_size: deleted&.size
|
||||
}.compact.merge(deleted_size: deleted&.size)
|
||||
end
|
||||
|
||||
def expect_delete(tags, container_expiration_policy: nil)
|
||||
service = instance_double('Projects::ContainerRepository::DeleteTagsService')
|
||||
|
||||
expect(Projects::ContainerRepository::DeleteTagsService)
|
||||
.to receive(:new)
|
||||
.with(repository.project, user, tags: tags, container_expiration_policy: container_expiration_policy)
|
||||
.and_return(service)
|
||||
|
||||
expect(service).to receive(:execute)
|
||||
.with(repository) { { status: :success, deleted: tags } }
|
||||
end
|
||||
|
||||
def expect_no_caching
|
||||
expect(::Gitlab::Redis::Cache).not_to receive(:with)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,263 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'handling invalid params' do |service_response_extra: {}, supports_caching: false|
|
||||
context 'when no params are specified' do
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'not removing anything',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching
|
||||
end
|
||||
|
||||
context 'with invalid regular expressions' do
|
||||
shared_examples 'handling an invalid regex' do
|
||||
it 'keeps all tags' do
|
||||
expect(Projects::ContainerRepository::DeleteTagsService)
|
||||
.not_to receive(:new)
|
||||
expect_no_caching unless supports_caching
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it { is_expected.to eq(status: :error, message: 'invalid regex') }
|
||||
|
||||
it 'calls error tracking service' do
|
||||
expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
|
||||
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'when name_regex_delete is invalid' do
|
||||
let(:params) { { 'name_regex_delete' => '*test*' } }
|
||||
|
||||
it_behaves_like 'handling an invalid regex'
|
||||
end
|
||||
|
||||
context 'when name_regex is invalid' do
|
||||
let(:params) { { 'name_regex' => '*test*' } }
|
||||
|
||||
it_behaves_like 'handling an invalid regex'
|
||||
end
|
||||
|
||||
context 'when name_regex_keep is invalid' do
|
||||
let(:params) { { 'name_regex_keep' => '*test*' } }
|
||||
|
||||
it_behaves_like 'handling an invalid regex'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when regex matching everything is specified' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*' }
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: delete_expectations
|
||||
|
||||
context 'with deprecated name_regex param' do
|
||||
let(:params) do
|
||||
{ 'name_regex' => '.*' }
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: delete_expectations
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when delete regex matching specific tags is used' do
|
||||
|service_response_extra: {}, supports_caching: false|
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => 'C|D' }
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: [%w[C D]]
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when delete regex matching specific tags is used with overriding allow regex' do
|
||||
|service_response_extra: {}, supports_caching: false|
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => 'C|D',
|
||||
'name_regex_keep' => 'C'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: [%w[D]]
|
||||
|
||||
context 'with name_regex_delete overriding deprecated name_regex' do
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex' => 'C|D',
|
||||
'name_regex_delete' => 'D'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: [%w[D]]
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'with allow regex value' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'name_regex_keep' => 'B.*'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: delete_expectations
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when keeping only N tags' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex' => 'A|B.*|C',
|
||||
'keep_n' => 1
|
||||
}
|
||||
end
|
||||
|
||||
it 'sorts tags by date' do
|
||||
delete_expectations.each { |expectation| expect_delete(expectation) }
|
||||
expect_no_caching unless supports_caching
|
||||
|
||||
expect(service).to receive(:order_by_date_desc).at_least(:once).and_call_original
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra))
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when not keeping N tags' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{ 'name_regex' => 'A|B.*|C' }
|
||||
end
|
||||
|
||||
it 'does not sort tags by date' do
|
||||
delete_expectations.each { |expectation| expect_delete(expectation) }
|
||||
expect_no_caching unless supports_caching
|
||||
|
||||
expect(service).not_to receive(:order_by_date_desc)
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra))
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when removing keeping only 3' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{ 'name_regex_delete' => '.*',
|
||||
'keep_n' => 3 }
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: delete_expectations
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when removing older than 1 day' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'older_than' => '1 day'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: delete_expectations
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when combining all parameters' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day'
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like 'removing the expected tags',
|
||||
service_response_extra: service_response_extra,
|
||||
supports_caching: supports_caching,
|
||||
delete_expectations: delete_expectations
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'when running a container_expiration_policy' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
let(:user) { nil }
|
||||
|
||||
context 'with valid container_expiration_policy param' do
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day',
|
||||
'container_expiration_policy' => true
|
||||
}
|
||||
end
|
||||
|
||||
it 'removes the expected tags' do
|
||||
delete_expectations.each { |expectation| expect_delete(expectation, container_expiration_policy: true) }
|
||||
expect_no_caching unless supports_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra))
|
||||
end
|
||||
end
|
||||
|
||||
context 'without container_expiration_policy param' do
|
||||
let(:params) do
|
||||
{
|
||||
'name_regex_delete' => '.*',
|
||||
'keep_n' => 1,
|
||||
'older_than' => '1 day'
|
||||
}
|
||||
end
|
||||
|
||||
it 'fails' do
|
||||
is_expected.to eq(status: :error, message: 'access denied')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'not removing anything' do |service_response_extra: {}, supports_caching: false|
|
||||
it 'does not remove anything' do
|
||||
expect(Projects::ContainerRepository::DeleteTagsService).not_to receive(:new)
|
||||
expect_no_caching unless supports_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: []).merge(service_response_extra))
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'removing the expected tags' do
|
||||
|service_response_extra: {}, supports_caching: false, delete_expectations:|
|
||||
it 'removes the expected tags' do
|
||||
delete_expectations.each { |expectation| expect_delete(expectation) }
|
||||
expect_no_caching unless supports_caching
|
||||
|
||||
is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra))
|
||||
end
|
||||
end
|
|
@ -227,6 +227,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
|
|||
let_it_be(:package10) { create(:rubygems_package, project: project) }
|
||||
let_it_be(:package11) { create(:helm_package, project: project) }
|
||||
let_it_be(:package12) { create(:terraform_module_package, project: project) }
|
||||
let_it_be(:package13) { create(:rpm_package, project: project) }
|
||||
|
||||
Packages::Package.package_types.keys.each do |package_type|
|
||||
context "for package type #{package_type}" do
|
||||
|
|
|
@ -25,4 +25,15 @@ RSpec.describe 'groups/new.html.haml' do
|
|||
expect(rendered).not_to have_checked_field('Just me')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a subgroup' do
|
||||
let_it_be(:group) { create(:group, :nested) }
|
||||
|
||||
it 'renders the visibility level section' do
|
||||
expect(rendered).to have_content('Visibility level')
|
||||
expect(rendered).to have_field('Private')
|
||||
expect(rendered).to have_field('Internal')
|
||||
expect(rendered).to have_field('Public')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue