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 @@
}
}
/*
* 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 {
.sidebar-grouped-item {
.sidebar-collapsed-icon {
......
......@@ -36,11 +36,11 @@ module Projects
def log_response(response)
log_data = LOG_DATA_BASE.merge(
container_repository_id: @container_repository.id,
message: 'deleted tags'
)
message: 'deleted tags',
deleted_tags_count: response[:deleted]&.size
).compact
if response[:status] == :success
log_data[:deleted_tags_count] = response[:deleted].size
log_info(log_data)
else
log_data[:message] = response[:message]
......
......@@ -14,6 +14,7 @@ module Projects
def initialize(container_repository, tag_names)
@container_repository = container_repository
@tag_names = tag_names
@deleted_tags = []
end
# Delete tags by name with a single DELETE request. This is only supported
......@@ -25,7 +26,7 @@ module Projects
delete_tags
rescue TimeoutError => e
::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
private
......@@ -33,13 +34,15 @@ module Projects
def delete_tags
start_time = Time.zone.now
deleted_tags = @tag_names.select do |name|
@tag_names.each do |name|
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
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
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 ""
msgid "Failed to load milestones. Please try again."
msgstr ""
msgid "Failed to load projects"
msgstr ""
msgid "Failed to load related branches"
msgstr ""
......
......@@ -27,13 +27,17 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
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
expect(service).to receive(:log_error).with(
service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: message,
container_repository_id: repository.id
)
log_data = {
service_class: 'Projects::ContainerRepository::DeleteTagsService',
message: message,
container_repository_id: repository.id
}
log_data.merge!(extra_log) if extra_log.any?
expect(service).to receive(:log_error).with(log_data)
subject
end
......@@ -115,7 +119,7 @@ RSpec.describe Projects::ContainerRepository::DeleteTagsService do
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
......
......@@ -67,7 +67,7 @@ RSpec.describe Projects::ContainerRepository::Gitlab::DeleteTagsService do
stub_delete_reference_requests('A' => 200)
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
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