Commit 9ba1176e authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents e4515e07 263a9980
<script>
import {
GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownItem,
GlSearchBoxByType,
GlButton,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownForm,
GlDropdownItem,
GlSearchBoxByType,
GlButton,
},
directives: {
GlTooltip,
},
props: {
projectsFetchPath: {
type: String,
required: true,
},
dropdownButtonTitle: {
type: String,
required: true,
},
dropdownHeaderTitle: {
type: String,
required: true,
},
moveInProgress: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
projectsListLoading: false,
projectsListLoadFailed: false,
searchKey: '',
projects: [],
selectedProject: null,
projectItemClick: false,
};
},
computed: {
hasNoSearchResults() {
return Boolean(
!this.projectsListLoading &&
!this.projectsListLoadFailed &&
this.searchKey &&
!this.projects.length,
);
},
failedToLoadResults() {
return !this.projectsListLoading && this.projectsListLoadFailed;
},
},
watch: {
searchKey(value = '') {
this.fetchProjects(value);
},
},
methods: {
fetchProjects(search = '') {
this.projectsListLoading = true;
this.projectsListLoadFailed = false;
return axios
.get(this.projectsFetchPath, {
params: {
search,
},
})
.then(({ data }) => {
this.projects = data;
this.$refs.searchInput.focusInput();
})
.catch(() => {
this.projectsListLoadFailed = true;
})
.finally(() => {
this.projectsListLoading = false;
});
},
isSelectedProject(project) {
if (this.selectedProject) {
return this.selectedProject.id === project.id;
}
return false;
},
/**
* This handler is to prevent dropdown
* from closing when an item is selected
* and emit an event only when dropdown closes.
*/
handleDropdownHide(e) {
if (this.projectItemClick) {
e.preventDefault();
this.projectItemClick = false;
} else {
this.$emit('dropdown-close');
}
},
handleDropdownCloseClick() {
this.$refs.dropdown.hide();
},
handleProjectSelect(project) {
this.selectedProject = project.id === this.selectedProject?.id ? null : project;
this.projectItemClick = true;
},
handleMoveClick() {
this.$refs.dropdown.hide();
this.$emit('move-issuable', this.selectedProject);
},
},
};
</script>
<template>
<div class="block js-issuable-move-block issuable-move-dropdown sidebar-move-issue-dropdown">
<div
v-gl-tooltip.left.viewport
data-testid="move-collapsed"
:title="dropdownButtonTitle"
class="sidebar-collapsed-icon"
@click="$emit('toggle-collapse')"
>
<gl-icon name="arrow-right" />
</div>
<gl-dropdown
ref="dropdown"
:block="true"
:disabled="moveInProgress"
class="hide-collapsed"
toggle-class="js-sidebar-dropdown-toggle"
@shown="fetchProjects"
@hide="handleDropdownHide"
>
<template #button-content
><gl-loading-icon v-if="moveInProgress" class="gl-mr-3" />{{
dropdownButtonTitle
}}</template
>
<gl-dropdown-form class="gl-pt-0">
<div
data-testid="header"
class="gl-display-flex gl-pb-3 gl-border-1 gl-border-b-solid gl-border-gray-100"
>
<span class="gl-flex-grow-1 gl-text-center gl-font-weight-bold gl-py-1">{{
dropdownHeaderTitle
}}</span>
<gl-button
variant="link"
icon="close"
class="gl-mr-2 gl-w-auto! gl-p-2!"
@click.prevent="handleDropdownCloseClick"
/>
</div>
<gl-search-box-by-type
ref="searchInput"
v-model.trim="searchKey"
:placeholder="__('Search project')"
:debounce="300"
/>
<div data-testid="content" class="dropdown-content">
<gl-loading-icon v-if="projectsListLoading" size="md" class="gl-p-5" />
<ul v-else>
<gl-dropdown-item
v-for="project in projects"
:key="project.id"
:is-check-item="true"
:is-checked="isSelectedProject(project)"
@click.stop.prevent="handleProjectSelect(project)"
>{{ project.name_with_namespace }}</gl-dropdown-item
>
</ul>
<div v-if="hasNoSearchResults" class="gl-text-center gl-p-3">
{{ __('No matching results') }}
</div>
<div v-if="failedToLoadResults" class="gl-text-center gl-p-3">
{{ __('Failed to load projects') }}
</div>
</div>
<div
data-testid="footer"
class="gl-pt-3 gl-px-3 gl-border-1 gl-border-t-solid gl-border-gray-100"
>
<gl-button
category="primary"
variant="success"
:disabled="!Boolean(selectedProject)"
class="gl-text-center! issuable-move-button"
@click="handleMoveClick"
>{{ __('Move') }}</gl-button
>
</div>
</gl-dropdown-form>
</gl-dropdown>
</div>
</template>
...@@ -921,6 +921,25 @@ ...@@ -921,6 +921,25 @@
} }
} }
/*
* Following overrides are done to prevent
* legacy dropdown styles from influencing
* GitLab UI components used within GlDropdown
*/
.issuable-move-dropdown {
.b-dropdown-form {
@include gl-p-0;
}
.gl-search-box-by-type button.gl-clear-icon-button:hover {
@include gl-bg-transparent;
}
.issuable-move-button:not(.disabled):hover {
@include gl-text-white;
}
}
.right-sidebar-collapsed { .right-sidebar-collapsed {
.sidebar-grouped-item { .sidebar-grouped-item {
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
......
...@@ -36,11 +36,11 @@ module Projects ...@@ -36,11 +36,11 @@ module Projects
def log_response(response) def log_response(response)
log_data = LOG_DATA_BASE.merge( log_data = LOG_DATA_BASE.merge(
container_repository_id: @container_repository.id, container_repository_id: @container_repository.id,
message: 'deleted tags' message: 'deleted tags',
) deleted_tags_count: response[:deleted]&.size
).compact
if response[:status] == :success if response[:status] == :success
log_data[:deleted_tags_count] = response[:deleted].size
log_info(log_data) log_info(log_data)
else else
log_data[:message] = response[:message] log_data[:message] = response[:message]
......
...@@ -14,6 +14,7 @@ module Projects ...@@ -14,6 +14,7 @@ module Projects
def initialize(container_repository, tag_names) def initialize(container_repository, tag_names)
@container_repository = container_repository @container_repository = container_repository
@tag_names = tag_names @tag_names = tag_names
@deleted_tags = []
end end
# Delete tags by name with a single DELETE request. This is only supported # Delete tags by name with a single DELETE request. This is only supported
...@@ -25,7 +26,7 @@ module Projects ...@@ -25,7 +26,7 @@ module Projects
delete_tags delete_tags
rescue TimeoutError => e rescue TimeoutError => e
::Gitlab::ErrorTracking.track_exception(e, tags_count: @tag_names&.size, container_repository_id: @container_repository&.id) ::Gitlab::ErrorTracking.track_exception(e, tags_count: @tag_names&.size, container_repository_id: @container_repository&.id)
error('timeout while deleting tags') error('timeout while deleting tags', nil, pass_back: { deleted: @deleted_tags })
end end
private private
...@@ -33,13 +34,15 @@ module Projects ...@@ -33,13 +34,15 @@ module Projects
def delete_tags def delete_tags
start_time = Time.zone.now start_time = Time.zone.now
deleted_tags = @tag_names.select do |name| @tag_names.each do |name|
raise TimeoutError if timeout?(start_time) raise TimeoutError if timeout?(start_time)
@container_repository.delete_tag_by_name(name) if @container_repository.delete_tag_by_name(name)
@deleted_tags.append(name)
end
end end
deleted_tags.any? ? success(deleted: deleted_tags) : error('could not delete tags') @deleted_tags.any? ? success(deleted: @deleted_tags) : error('could not delete tags')
end end
def timeout?(start_time) def timeout?(start_time)
......
---
title: Improving Container Registry Delete Tags Service to log number of successfully
deleted tags even if deletion process was interrupted by a timeout
merge_request: 46079
author: Maksim Stankevic, @maksimstankevic
type: changed
...@@ -11139,6 +11139,9 @@ msgstr "" ...@@ -11139,6 +11139,9 @@ msgstr ""
msgid "Failed to load milestones. Please try again." msgid "Failed to load milestones. Please try again."
msgstr "" msgstr ""
msgid "Failed to load projects"
msgstr ""
msgid "Failed to load related branches" msgid "Failed to load related branches"
msgstr "" msgstr ""
......
...@@ -27,13 +27,17 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do ...@@ -27,13 +27,17 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
end end
end end
RSpec.shared_examples 'logging an error response' do |message: 'could not delete tags'| RSpec.shared_examples 'logging an error response' do |message: 'could not delete tags', extra_log: {}|
it 'logs an error message' do it 'logs an error message' do
expect(service).to receive(:log_error).with( log_data = {
service_class: 'Projects::ContainerRepository::DeleteTagsService', service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: message, message: message,
container_repository_id: repository.id container_repository_id: repository.id
) }
log_data.merge!(extra_log) if extra_log.any?
expect(service).to receive(:log_error).with(log_data)
subject subject
end end
...@@ -115,7 +119,7 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do ...@@ -115,7 +119,7 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
it { is_expected.to include(status: :error, message: 'timeout while deleting tags') } it { is_expected.to include(status: :error, message: 'timeout while deleting tags') }
it_behaves_like 'logging an error response', message: 'timeout while deleting tags' it_behaves_like 'logging an error response', message: 'timeout while deleting tags', extra_log: { deleted_tags_count: 0 }
end end
end end
end end
......
...@@ -67,7 +67,7 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do ...@@ -67,7 +67,7 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do
stub_delete_reference_requests('A' => 200) stub_delete_reference_requests('A' => 200)
end end
it { is_expected.to include(status: :error, message: 'timeout while deleting tags') } it { is_expected.to eq(status: :error, message: 'timeout while deleting tags', deleted: ['A']) }
it 'tracks the exception' do it 'tracks the exception' do
expect(::Gitlab::ErrorTracking) expect(::Gitlab::ErrorTracking)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment