Commit 56dca689 authored by Vasilii Iakliushin's avatar Vasilii Iakliushin

Merge branch '342640-add-context-to-project-deletion-modal' into 'master'

Add additional project details to the user before deleting

See merge request gitlab-org/gitlab!71961
parents 704701cb 10541d3b
......@@ -18,12 +18,36 @@ export default {
type: String,
required: true,
},
isFork: {
type: Boolean,
required: true,
},
issuesCount: {
type: Number,
required: true,
},
mergeRequestsCount: {
type: Number,
required: true,
},
forksCount: {
type: Number,
required: true,
},
starsCount: {
type: Number,
required: true,
},
},
strings: {
alertTitle: __('You are about to permanently delete this project'),
alertBody: __(
'Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc.',
),
isNotForkMessage: __(
'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
),
isForkMessage: __('This forked project has the following:'),
},
};
</script>
......@@ -37,6 +61,38 @@ export default {
:title="$options.strings.alertTitle"
:dismissible="false"
>
<p>
<gl-sprintf v-if="isFork" :message="$options.strings.isForkMessage" />
<gl-sprintf v-else :message="$options.strings.isNotForkMessage">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
</p>
<ul>
<li>
<gl-sprintf :message="n__('%d issue', '%d issues', issuesCount)">
<template #issuesCount>{{ issuesCount }}</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf
:message="n__('%d merge requests', '%d merge requests', mergeRequestsCount)"
>
<template #mergeRequestsCount>{{ mergeRequestsCount }}</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf :message="n__('%d fork', '%d forks', forksCount)">
<template #forksCount>{{ forksCount }}</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf :message="n__('%d star', '%d stars', starsCount)">
<template #starsCount>{{ starsCount }}</template>
</gl-sprintf>
</li>
</ul>
<gl-sprintf :message="$options.strings.alertBody">
<template #strong="{ content }">
<strong>{{ content }}</strong>
......
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import ProjectDeleteButton from './components/project_delete_button.vue';
export default (selector = '#js-project-delete-button') => {
......@@ -6,7 +7,15 @@ export default (selector = '#js-project-delete-button') => {
if (!el) return;
const { confirmPhrase, formPath } = el.dataset;
const {
confirmPhrase,
formPath,
isFork,
issuesCount,
mergeRequestsCount,
forksCount,
starsCount,
} = el.dataset;
// eslint-disable-next-line no-new
new Vue({
......@@ -16,6 +25,11 @@ export default (selector = '#js-project-delete-button') => {
props: {
confirmPhrase,
formPath,
isFork: parseBoolean(isFork),
issuesCount: parseInt(issuesCount, 10),
mergeRequestsCount: parseInt(mergeRequestsCount, 10),
forksCount: parseInt(forksCount, 10),
starsCount: parseInt(starsCount, 10),
},
});
},
......
# frozen_string_literal: true
module Projects
# Service class for counting and caching the number of all issues of a
# project.
class AllIssuesCountService < Projects::CountService
def relation_for_count
@project.issues
end
def cache_key_name
'all_issues_count'
end
end
end
# frozen_string_literal: true
module Projects
# Service class for counting and caching the number of all merge requests of
# a project.
class AllMergeRequestsCountService < Projects::CountService
def relation_for_count
@project.merge_requests
end
def cache_key_name
'all_merge_requests_count'
end
end
end
- return unless can?(current_user, :remove_project, project)
- merge_requests_count = Projects::AllMergeRequestsCountService.new(project).count
- issues_count = Projects::AllIssuesCountService.new(project).count
.sub-section
%h4.danger-title= _('Delete project')
......@@ -7,4 +9,4 @@
= link_to _('Learn more.'), help_page_path('user/project/settings/index', anchor: 'removing-a-fork-relationship'), target: '_blank', rel: 'noopener noreferrer'
%p
%strong= _('Deleted projects cannot be restored!')
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project) } }
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project), is_fork: project.forked?.to_s, issues_count: number_with_delimiter(issues_count), merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(project.forks_count), stars_count: number_with_delimiter(project.star_count) } }
<script>
import { GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
import { GlAlert, GlLink, GlIcon, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
export default {
components: {
GlAlert,
GlSprintf,
GlIcon,
GlLink,
......@@ -27,13 +28,38 @@ export default {
type: String,
required: true,
},
isFork: {
type: Boolean,
required: true,
},
issuesCount: {
type: Number,
required: true,
},
mergeRequestsCount: {
type: Number,
required: true,
},
forksCount: {
type: Number,
required: true,
},
starsCount: {
type: Number,
required: true,
},
},
strings: {
modalBody: __(
"Once a project is permanently deleted, it cannot be recovered. You will lose this project's repository and all related resources, including issues and merge requests.",
alertTitle: __('You are about to permanently delete this project'),
alertBody: __(
"Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. You will lose this project's repository and %{strongStart}all related resources%{strongEnd}, including issues and merge requests.",
),
helpLabel: __('Recovering projects'),
recoveryMessage: __('You can recover this project until %{date}'),
isNotForkMessage: __(
'This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:',
),
isForkMessage: __('This forked project has the following:'),
},
};
</script>
......@@ -41,7 +67,50 @@ export default {
<template>
<shared-delete-button v-bind="{ confirmPhrase, formPath }">
<template #modal-body>
<p>{{ $options.strings.modalBody }}</p>
<gl-alert
class="gl-mb-5"
variant="danger"
:title="$options.strings.alertTitle"
:dismissible="false"
>
<p>
<gl-sprintf v-if="isFork" :message="$options.strings.isForkMessage" />
<gl-sprintf v-else :message="$options.strings.isNotForkMessage">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
</p>
<ul>
<li>
<gl-sprintf :message="n__('%d issue', '%d issues', issuesCount)">
<template #issuesCount>{{ issuesCount }}</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf
:message="n__('%d merge requests', '%d merge requests', mergeRequestsCount)"
>
<template #mergeRequestsCount>{{ mergeRequestsCount }}</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf :message="n__('%d fork', '%d forks', forksCount)">
<template #forksCount>{{ forksCount }}</template>
</gl-sprintf>
</li>
<li>
<gl-sprintf :message="n__('%d star', '%d stars', starsCount)">
<template #starsCount>{{ starsCount }}</template>
</gl-sprintf>
</li>
</ul>
<gl-sprintf :message="$options.strings.alertBody">
<template #strong="{ content }">
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
</gl-alert>
</template>
<template #modal-footer>
<p
......
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import ProjectAdjournedDeleteButton from './components/project_adjourned_delete_button.vue';
export default (selector = '#js-project-adjourned-delete-button') => {
......@@ -6,7 +7,17 @@ export default (selector = '#js-project-adjourned-delete-button') => {
if (!el) return;
const { adjournedRemovalDate, confirmPhrase, formPath, recoveryHelpPath } = el.dataset;
const {
adjournedRemovalDate,
confirmPhrase,
formPath,
recoveryHelpPath,
isFork,
issuesCount,
mergeRequestsCount,
forksCount,
starsCount,
} = el.dataset;
// eslint-disable-next-line no-new
new Vue({
......@@ -18,6 +29,11 @@ export default (selector = '#js-project-adjourned-delete-button') => {
confirmPhrase,
formPath,
recoveryHelpPath,
isFork: parseBoolean(isFork),
issuesCount: parseInt(issuesCount, 10),
mergeRequestsCount: parseInt(mergeRequestsCount, 10),
forksCount: parseInt(forksCount, 10),
starsCount: parseInt(starsCount, 10),
},
});
},
......
......@@ -3,6 +3,8 @@
- adjourned_date = adjourned_deletion ? permanent_deletion_date(Time.now.utc).to_s : nil
- admin_help_path = help_page_path('user/admin_area/settings/visibility_and_access_controls', anchor: 'default-deletion-delay')
- recovery_help_path = help_page_path('user/project/settings/index', anchor: 'delete-a-project')
- merge_requests_count = Projects::AllMergeRequestsCountService.new(project).count
- issues_count = Projects::AllIssuesCountService.new(project).count
- unless project.marked_for_deletion?
.sub-section
......@@ -11,7 +13,7 @@
%strong= s_('Delayed Project Deletion (%{adjourned_deletion})') % { adjourned_deletion: adjourned_deletion ? 'Enabled' : 'Disabled' }
- if adjourned_deletion
= render 'projects/settings/marked_for_removal'
#js-project-adjourned-delete-button{ data: { recovery_help_path: recovery_help_path, adjourned_removal_date: adjourned_date, form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project) } }
#js-project-adjourned-delete-button{ data: { recovery_help_path: recovery_help_path, adjourned_removal_date: adjourned_date, form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project), is_fork: project.forked?.to_s, issues_count: number_with_delimiter(issues_count), merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(project.forks_count), stars_count: number_with_delimiter(project.star_count) } }
- else
%p
%span.gl-text-gray-500= _('Projects will be permanently deleted immediately.')
......@@ -19,8 +21,7 @@
%p= permanent_delete_message(project)
%p
%strong= _('Are you ABSOLUTELY SURE you wish to delete this project?')
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project) } }
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project), is_fork: project.forked?.to_s, issues_count: number_with_delimiter(issues_count), merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(project.forks_count), stars_count: number_with_delimiter(project.star_count) } }
- else
= render 'projects/settings/restore', project: project
= render 'projects/settings/permanently_delete', project: project
- merge_requests_count = Projects::AllMergeRequestsCountService.new(project).count
- issues_count = Projects::AllIssuesCountService.new(project).count
.sub-section
%h4.danger-title= _('Permanently delete project')
%p
......@@ -5,4 +8,4 @@
%p= permanent_delete_message(project)
%p
%strong= _('Are you ABSOLUTELY SURE you wish to delete this project?')
#js-project-delete-button{ data: { form_path: project_path(project, permanently_delete: true), confirm_phrase: delete_confirm_phrase(project) } }
#js-project-delete-button{ data: { form_path: project_path(project, permanently_delete: true), confirm_phrase: delete_confirm_phrase(project), is_fork: project.forked?.to_s, issues_count: number_with_delimiter(issues_count), merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(project.forks_count), stars_count: number_with_delimiter(project.star_count) } }
......@@ -42,10 +42,52 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
>
<div>
<gl-alert-stub
class="gl-mb-5"
dismisslabel="Dismiss"
primarybuttonlink=""
primarybuttontext=""
secondarybuttonlink=""
secondarybuttontext=""
title="You are about to permanently delete this project"
variant="danger"
>
<p>
Once a project is permanently deleted, it cannot be recovered. You will lose this project's repository and all related resources, including issues and merge requests.
This project is
<strong>
NOT
</strong>
a fork, and has the following:
</p>
<ul>
<li>
1 issue
</li>
<li>
2 merge requests
</li>
<li>
3 forks
</li>
<li>
4 stars
</li>
</ul>
Once a project is permanently deleted, it
<strong>
cannot be recovered
</strong>
. You will lose this project's repository and
<strong>
all related resources
</strong>
, including issues and merge requests.
</gl-alert-stub>
<p
class="gl-mb-1"
>
......@@ -69,9 +111,8 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
<p
class="gl-display-flex gl-display-flex gl-align-items-center gl-mt-3 gl-mb-0 gl-text-gray-500"
>
<gl-sprintf-stub
message="You can recover this project until %{date}"
/>
You can recover this project until
2020-12-12
<gl-link-stub
aria-label="Recovering projects"
......
import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import ProjectAdjournedDeleteButton from 'ee/projects/components/project_adjourned_delete_button.vue';
import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
......@@ -14,6 +15,11 @@ describe('Project remove modal', () => {
confirmPhrase: 'foo',
formPath: 'some/path',
recoveryHelpPath: 'recovery/help/path',
isFork: false,
issuesCount: 1,
mergeRequestsCount: 2,
forksCount: 3,
starsCount: 4,
};
const createComponent = (props = {}) => {
......@@ -23,6 +29,7 @@ describe('Project remove modal', () => {
...props,
},
stubs: {
GlSprintf,
SharedDeleteButton,
},
});
......
......@@ -236,6 +236,11 @@ msgid_plural "%d fixed test results"
msgstr[0] ""
msgstr[1] ""
msgid "%d fork"
msgid_plural "%d forks"
msgstr[0] ""
msgstr[1] ""
msgid "%d group"
msgid_plural "%d groups"
msgstr[0] ""
......@@ -286,6 +291,11 @@ msgid_plural "%d merge requests that you don't have access to."
msgstr[0] ""
msgstr[1] ""
msgid "%d merge requests"
msgid_plural "%d merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "%d metric"
msgid_plural "%d metrics"
msgstr[0] ""
......@@ -351,6 +361,11 @@ msgid_plural "%d shards selected"
msgstr[0] ""
msgstr[1] ""
msgid "%d star"
msgid_plural "%d stars"
msgstr[0] ""
msgstr[1] ""
msgid "%d tag"
msgid_plural "%d tags"
msgstr[0] ""
......@@ -24173,7 +24188,7 @@ msgstr ""
msgid "Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc."
msgstr ""
msgid "Once a project is permanently deleted, it cannot be recovered. You will lose this project's repository and all related resources, including issues and merge requests."
msgid "Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. You will lose this project's repository and %{strongStart}all related resources%{strongEnd}, including issues and merge requests."
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
......@@ -35292,6 +35307,9 @@ msgstr ""
msgid "This file was modified for readability, and can't accept suggestions. Edit it directly."
msgstr ""
msgid "This forked project has the following:"
msgstr ""
msgid "This form is disabled in preview"
msgstr ""
......@@ -35544,6 +35562,9 @@ msgstr ""
msgid "This project has no active access tokens."
msgstr ""
msgid "This project is %{strongStart}NOT%{strongEnd} a fork, and has the following:"
msgstr ""
msgid "This project is archived and cannot be commented on."
msgstr ""
......
......@@ -52,9 +52,44 @@ exports[`Project remove modal initialized matches the snapshot 1`] = `
title="You are about to permanently delete this project"
variant="danger"
>
<gl-sprintf-stub
message="Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc."
/>
<p>
This project is
<strong>
NOT
</strong>
a fork, and has the following:
</p>
<ul>
<li>
1 issue
</li>
<li>
2 merge requests
</li>
<li>
3 forks
</li>
<li>
4 stars
</li>
</ul>
Once a project is permanently deleted, it
<strong>
cannot be recovered
</strong>
. Permanently deleting this project will
<strong>
immediately delete
</strong>
its repositories and
<strong>
all related resources
</strong>
, including issues, merge requests etc.
</gl-alert-stub>
<p
......
import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import ProjectDeleteButton from '~/projects/components/project_delete_button.vue';
import SharedDeleteButton from '~/projects/components/shared/delete_button.vue';
......@@ -12,6 +13,11 @@ describe('Project remove modal', () => {
const defaultProps = {
confirmPhrase: 'foo',
formPath: 'some/path',
isFork: false,
issuesCount: 1,
mergeRequestsCount: 2,
forksCount: 3,
starsCount: 4,
};
const createComponent = (props = {}) => {
......@@ -21,6 +27,7 @@ describe('Project remove modal', () => {
...props,
},
stubs: {
GlSprintf,
SharedDeleteButton,
},
});
......@@ -41,7 +48,10 @@ describe('Project remove modal', () => {
});
it('passes confirmPhrase and formPath props to the shared delete button', () => {
expect(findSharedDeleteButton().props()).toEqual(defaultProps);
expect(findSharedDeleteButton().props()).toEqual({
confirmPhrase: defaultProps.confirmPhrase,
formPath: defaultProps.formPath,
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::AllIssuesCountService, :use_clean_rails_memory_store_caching do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:banned_user) { create(:user, :banned) }
subject { described_class.new(project) }
it_behaves_like 'a counter caching service'
describe '#count' do
it 'returns the number of all issues' do
create(:issue, :opened, project: project)
create(:issue, :opened, confidential: true, project: project)
create(:issue, :opened, author: banned_user, project: project)
create(:issue, :closed, project: project)
expect(subject.count).to eq(4)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::AllMergeRequestsCountService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
subject { described_class.new(project) }
it_behaves_like 'a counter caching service'
describe '#count' do
it 'returns the number of all merge requests' do
create(:merge_request,
:opened,
source_project: project,
target_project: project)
create(:merge_request,
:closed,
source_project: project,
target_project: project)
create(:merge_request,
:merged,
source_project: project,
target_project: project)
expect(subject.count).to eq(3)
end
end
end
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