diff --git a/app/assets/javascripts/api/groups_api.js b/app/assets/javascripts/api/groups_api.js index 01f86fb4dae0c45470fb129f44a28bf1842a2732..e15bcca0c45bd953ecdb66187696d0860e6136fa 100644 --- a/app/assets/javascripts/api/groups_api.js +++ b/app/assets/javascripts/api/groups_api.js @@ -4,6 +4,8 @@ import { buildApiUrl } from './api_utils'; const GROUP_PATH = '/api/:version/groups/:id'; const GROUP_RESTORE_PATH = '/api/:version/groups/:id/restore'; +const GROUP_ARCHIVE_PATH = '/api/:version/groups/:id/archive'; +const GROUP_UNARCHIVE_PATH = '/api/:version/groups/:id/unarchive'; const GROUPS_PATH = '/api/:version/groups.json'; const GROUP_MEMBERS_PATH = '/api/:version/groups/:id/members'; const GROUP_MEMBER_PATH = '/api/:version/groups/:id/members/:user_id'; @@ -66,6 +68,18 @@ export function restoreGroup(groupId) { return axios.post(url); } +export function archiveGroup(groupId) { + const url = buildApiUrl(GROUP_ARCHIVE_PATH).replace(':id', groupId); + + return axios.post(url); +} + +export function unarchiveGroup(groupId) { + const url = buildApiUrl(GROUP_UNARCHIVE_PATH).replace(':id', groupId); + + return axios.post(url); +} + export const getGroupTransferLocations = (groupId, params = {}) => { const url = buildApiUrl(GROUP_TRANSFER_LOCATIONS_PATH).replace(':id', groupId); const defaultParams = { per_page: DEFAULT_PER_PAGE }; diff --git a/app/assets/javascripts/api/projects_api.js b/app/assets/javascripts/api/projects_api.js index aa3e2f7f59a8d5d565d1020797f86613bf23d316..bebf4bfc2f61e0418f0cc7269b06f7b5a15a7b36 100644 --- a/app/assets/javascripts/api/projects_api.js +++ b/app/assets/javascripts/api/projects_api.js @@ -12,6 +12,8 @@ const PROJECT_TRANSFER_LOCATIONS_PATH = 'api/:version/projects/:id/transfer_loca const PROJECT_SHARE_LOCATIONS_PATH = 'api/:version/projects/:id/share_locations'; const PROJECT_UPLOADS_PATH = '/api/:version/projects/:id/uploads'; const PROJECT_RESTORE_PATH = '/api/:version/projects/:id/restore'; +const PROJECT_ARCHIVE_PATH = '/api/:version/projects/:id/archive'; +const PROJECT_UNARCHIVE_PATH = '/api/:version/projects/:id/unarchive'; export function getProjects(query, options, callback = () => {}) { const url = buildApiUrl(PROJECTS_PATH); @@ -64,6 +66,18 @@ export function restoreProject(projectId) { return axios.post(url); } +export function archiveProject(projectId) { + const url = buildApiUrl(PROJECT_ARCHIVE_PATH).replace(':id', projectId); + + return axios.post(url); +} + +export function unarchiveProject(projectId) { + const url = buildApiUrl(PROJECT_UNARCHIVE_PATH).replace(':id', projectId); + + return axios.post(url); +} + export function importProjectMembers(sourceId, targetId) { const url = buildApiUrl(PROJECT_IMPORT_MEMBERS_PATH) .replace(':id', sourceId) diff --git a/app/assets/javascripts/groups_projects/archive/components/archive_settings.vue b/app/assets/javascripts/groups_projects/archive/components/archive_settings.vue new file mode 100644 index 0000000000000000000000000000000000000000..0723babd3cd98792db683e3e3b891f689506da75 --- /dev/null +++ b/app/assets/javascripts/groups_projects/archive/components/archive_settings.vue @@ -0,0 +1,97 @@ + + + + + + {{ headerText }} + + + + {{ bodyText }} + + {{ helpLinkText }} + + + + {{ __('Archive') }} + + + + + diff --git a/app/assets/javascripts/groups_projects/archive/index.js b/app/assets/javascripts/groups_projects/archive/index.js new file mode 100644 index 0000000000000000000000000000000000000000..c71fa033be2922aea2b29cd2dce605553d78eb5b --- /dev/null +++ b/app/assets/javascripts/groups_projects/archive/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue'; +import ArchiveSettings from '~/groups_projects/archive/components/archive_settings.vue'; + +export default function initArchiveSettings() { + const el = document.getElementById('js-archive-settings'); + if (!el) return null; + + return new Vue({ + el, + name: 'ArchiveSettingsRoot', + render(createElement) { + return createElement(ArchiveSettings, { props: el.dataset }); + }, + }); +} diff --git a/app/assets/javascripts/groups_projects/components/archive_modal.vue b/app/assets/javascripts/groups_projects/components/archive_modal.vue new file mode 100644 index 0000000000000000000000000000000000000000..bc2eba301091ea411999aa73b32cfa8636f5a86b --- /dev/null +++ b/app/assets/javascripts/groups_projects/components/archive_modal.vue @@ -0,0 +1,90 @@ + + + + + {{ body }} + + diff --git a/app/assets/javascripts/groups_projects/components/unarchive_modal.vue b/app/assets/javascripts/groups_projects/components/unarchive_modal.vue new file mode 100644 index 0000000000000000000000000000000000000000..d45d70d0b9b8cda10cb300f1f2c64724eb2b408b --- /dev/null +++ b/app/assets/javascripts/groups_projects/components/unarchive_modal.vue @@ -0,0 +1,90 @@ + + + + + {{ body }} + + diff --git a/app/assets/javascripts/groups_projects/constants.js b/app/assets/javascripts/groups_projects/constants.js index faad11f8691735a7cb24ea14e548b696f906fc7b..eb6bd0893629ea6ccf677d97fb910578551a6abd 100644 --- a/app/assets/javascripts/groups_projects/constants.js +++ b/app/assets/javascripts/groups_projects/constants.js @@ -1,5 +1,7 @@ import { __ } from '~/locale'; +export const RESOURCE_TYPES = ['group', 'project']; + export const SORT_LABEL_NAME = __('Name'); export const SORT_LABEL_CREATED = __('Created date'); export const SORT_LABEL_UPDATED = __('Updated date'); diff --git a/app/assets/javascripts/groups_projects/unarchive/components/unarchive_settings.vue b/app/assets/javascripts/groups_projects/unarchive/components/unarchive_settings.vue new file mode 100644 index 0000000000000000000000000000000000000000..635ba08ee1589fe72cc9bb77c9f83bb30ef7c893 --- /dev/null +++ b/app/assets/javascripts/groups_projects/unarchive/components/unarchive_settings.vue @@ -0,0 +1,132 @@ + + + + + + + {{ headerText }} + + + + + + {{ bodyText }} + + {{ helpLinkText }} + + + + + {{ __('Unarchive') }} + + + + + + diff --git a/app/assets/javascripts/groups_projects/unarchive/index.js b/app/assets/javascripts/groups_projects/unarchive/index.js new file mode 100644 index 0000000000000000000000000000000000000000..681cb0cbbb4dbfcb276285116f1a5260cb5acd9e --- /dev/null +++ b/app/assets/javascripts/groups_projects/unarchive/index.js @@ -0,0 +1,15 @@ +import Vue from 'vue'; +import UnarchiveSettings from '~/groups_projects/unarchive/components/unarchive_settings.vue'; + +export default function initUnarchiveSettings() { + const el = document.getElementById('js-unarchive-settings'); + if (!el) return null; + + return new Vue({ + el, + name: 'UnarchiveSettingsRoot', + render(createElement) { + return createElement(UnarchiveSettings, { props: el.dataset }); + }, + }); +} diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index 47d1741cf2d6f1febb38daaaf76f74741844da38..7f22f7f7f0bb755bb3ca93d028c46d8fe7123706 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -10,6 +10,8 @@ import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; import initConfirmDanger from '~/init_confirm_danger'; import { initGroupSettingsReadme } from '~/groups/settings/init_group_settings_readme'; +import initArchiveSettings from '~/groups_projects/archive'; +import initUnarchiveSettings from '~/groups_projects/unarchive'; initFilePickers(); initConfirmDanger(); @@ -28,3 +30,5 @@ initSearchSettings(); initCascadingSettingsLockTooltips(); initGroupSettingsReadme(); +initArchiveSettings(); +initUnarchiveSettings(); diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index 235ed07ba53686dbe83e27517c4fa2eef2cd43bc..eff5716e097719502c969a8e5e1bafed1e542b33 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -13,6 +13,8 @@ import UserCallout from '~/user_callout'; import initTopicsTokenSelector from '~/projects/settings/topics'; import { initProjectSelects } from '~/vue_shared/components/entity_select/init_project_selects'; import initPruneObjectsButton from '~/projects/prune_objects_button'; +import initArchiveSettings from '~/groups_projects/archive'; +import initUnarchiveSettings from '~/groups_projects/unarchive'; import initProjectPermissionsSettings from '../shared/permissions'; import initGitlabDuoSettings from '../shared/permissions/gitlab_duo_settings'; import initProjectLoadingSpinner from '../shared/save_project_loader'; @@ -38,3 +40,6 @@ dirtySubmitFactory(document.querySelectorAll('.js-general-settings-form, .js-mr- initSearchSettings(); initTopicsTokenSelector(); initProjectSelects(); + +initArchiveSettings(); +initUnarchiveSettings(); diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index a96690afa6fa717bcc422ce5e84664a9349dbe51..ecd070b068eec86fee5083beca248cf03f940201 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -170,6 +170,23 @@ def group_settings_readme_app_data(group) } end + def group_archive_settings_app_data(group) + { + resource_type: 'group', + resource_id: group.id, + resource_path: group_path(group) + } + end + + def group_unarchive_settings_app_data(group) + { + resource_type: 'group', + resource_id: group.id, + resource_path: group_path(group), + ancestors_archived: group.ancestors_archived? + } + end + def enabled_git_access_protocol_options_for_group case ::Gitlab::CurrentSettings.enabled_git_access_protocol when nil, "" diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a47081a6a8608b34b657d173165a9b4340f82e39..ac8710132c825b8ac3a2e4319504a1fab6c06353 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -701,6 +701,25 @@ def issue_manual_ordering_class 'manual-ordering' end + def project_archive_settings_app_data(project) + { + resource_type: 'project', + resource_id: project.id, + resource_path: project_path(project), + help_path: help_page_path('user/project/working_with_projects.md', anchor: 'archive-a-project') + } + end + + def project_unarchive_settings_app_data(project) + { + resource_type: 'project', + resource_id: project.id, + resource_path: project_path(project), + ancestors_archived: project.ancestors_archived?, + help_path: help_page_path('user/project/working_with_projects.md', anchor: 'unarchive-a-project') + } + end + def projects_filtered_search_and_sort_app_data { initial_sort: project_list_sort_by, diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml index efbeaf9ea5b947a4ef1cebee75a0637c84080c00..7e6d58cc851b3dc6208f102fe9c1b5f87cf7037b 100644 --- a/app/views/groups/settings/_advanced.html.haml +++ b/app/views/groups/settings/_advanced.html.haml @@ -2,6 +2,7 @@ .gl-flex.gl-gap-5.gl-flex-col = render 'groups/settings/export', group: @group + = render 'groups/settings/archive', group: @group = render Pajamas::CardComponent.new(header_options: { class: 'gl-px-5 gl-py-4 gl-border-b-1 gl-border-b-solid gl-border-default' }, body_options: { class: 'gl-px-5 gl-py-4' }) do |c| - c.with_header do diff --git a/app/views/groups/settings/_archive.html.haml b/app/views/groups/settings/_archive.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..0126f99ea70aa322db6a664f20bb1888cf43b66c --- /dev/null +++ b/app/views/groups/settings/_archive.html.haml @@ -0,0 +1,6 @@ +- return unless Feature.enabled?(:archive_group, group) + +- if group.self_or_ancestors_archived? + #js-unarchive-settings{ data: group_unarchive_settings_app_data(group) } +- else + #js-archive-settings{ data: group_archive_settings_app_data(group) } diff --git a/app/views/projects/settings/_archive.html.haml b/app/views/projects/settings/_archive.html.haml index cde601533a999e5dbeb9aec3f49a22547305053a..fea8664ecb41771aa0bd827c35adab1c5b4cb292 100644 --- a/app/views/projects/settings/_archive.html.haml +++ b/app/views/projects/settings/_archive.html.haml @@ -1,22 +1,6 @@ - return unless archiving_available?(@project) -= render Pajamas::CardComponent.new do |c| - - c.with_header do - .gl-flex.gl-grow - %h4.gl-text-base.gl-leading-24.gl-m-0 - - if @project.archived? - = _('Unarchive project') - - else - = _('Archive project') - - - c.with_body do - - if @project.archived? - - link_start = ''.html_safe % { url: help_page_path('user/project/working_with_projects.md', anchor: 'unarchive-a-project') } - %p= _("Unarchiving the project restores its members' ability to make commits, and create issues, comments, and other entities. %{strong_start}After you unarchive the project, it displays in the search and on the dashboard.%{strong_end} %{link_start}Learn more.%{link_end}").html_safe % { strong_start: ''.html_safe, strong_end: ''.html_safe, link_start: link_start, link_end: ''.html_safe } - = render Pajamas::ButtonComponent.new(method: :post, href: unarchive_project_path(@project), variant: :confirm, button_options: { aria: { label: _('Unarchive project') }, data: { confirm: _("Are you sure that you want to unarchive this project?"), testid: 'unarchive-project-link' } }) do - = _('Unarchive project') - - else - - link_start = ''.html_safe % { url: help_page_path('user/project/working_with_projects.md', anchor: 'archive-a-project') } - %p= _("Archiving the project makes it entirely read-only. It is hidden from the dashboard and doesn't display in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end} %{link_start}Learn more.%{link_end}").html_safe % { strong_start: ''.html_safe, strong_end: ''.html_safe, link_start: link_start, link_end: ''.html_safe } - = render Pajamas::ButtonComponent.new(method: :post, href: archive_project_path(@project), variant: :confirm, button_options: { aria: { label: _('Archive project') }, data: { confirm: _("Are you sure that you want to archive this project?"), testid: 'archive-project-link', 'confirm-btn-variant': 'confirm' } }) do - = _('Archive project') +- if @project.self_or_ancestors_archived? + #js-unarchive-settings{ data: project_unarchive_settings_app_data(@project) } +- else + #js-archive-settings{ data: project_archive_settings_app_data(@project) } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 00202bd4f49e8be534eaf5f8eb0c10f9b04bf1dd..9c90f09dd1c2675f1da24c2e55545bee98aecc59 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1339,6 +1339,9 @@ msgid_plural "%{resolvedDiscussionsCount} of %{resolvableDiscussionsCount} threa msgstr[0] "" msgstr[1] "" +msgid "%{resourceType} has successfully been restored to an active state. You'll are now able to modify its contents and settings again." +msgstr "" + msgid "%{retryButtonStart}Try again%{retryButtonEnd} or %{newFileButtonStart}attach a new file%{newFileButtonEnd}." msgstr "" @@ -7213,6 +7216,9 @@ msgstr "" msgid "An error occurred while adding formatted title for epic" msgstr "" +msgid "An error occurred while archiving the %{resourceType}. Please refresh the page an try again." +msgstr "" + msgid "An error occurred while checking group path. Please refresh and try again." msgstr "" @@ -7501,6 +7507,9 @@ msgstr "" msgid "An error occurred while trying to update the registry: '%{error_message}'." msgstr "" +msgid "An error occurred while unarchiving the %{resourceType}. Please refresh the page an try again." +msgstr "" + msgid "An error occurred while updating approvers" msgstr "" @@ -8634,10 +8643,10 @@ msgstr "" msgid "Archive" msgstr "" -msgid "Archive pipelines" +msgid "Archive %{resourceType}" msgstr "" -msgid "Archive project" +msgid "Archive pipelines" msgstr "" msgid "Archived" @@ -8652,27 +8661,21 @@ msgstr "" msgid "Archiving projects" msgstr "" -msgid "Archiving the project makes it entirely read-only. It is hidden from the dashboard and doesn't display in searches. %{strong_start}The repository cannot be committed to, and no issues, comments, or other entities can be created.%{strong_end} %{link_start}Learn more.%{link_end}" -msgstr "" - msgid "Are you absolutely sure?" msgstr "" -msgid "Are you sure that you want to archive this project?" -msgstr "" - msgid "Are you sure that you want to destroy %{application}" msgstr "" -msgid "Are you sure that you want to unarchive this project?" -msgstr "" - msgid "Are you sure you want to %{action} %{name}?" msgstr "" msgid "Are you sure you want to approve %{user}?" msgstr "" +msgid "Are you sure you want to archive this %{resourceType}" +msgstr "" + msgid "Are you sure you want to attempt to merge?" msgstr "" @@ -8831,6 +8834,9 @@ msgstr "" msgid "Are you sure you want to stop this environment?" msgstr "" +msgid "Are you sure you want to unarchive this %{resourceType}" +msgstr "" + msgid "Are you sure you want to unlock %{path_lock_path}?" msgstr "" @@ -31920,6 +31926,9 @@ msgstr "" msgid "How can I make my variables more secure?" msgstr "" +msgid "How do I archive a %{resourceType}?" +msgstr "" + msgid "How do I configure Akismet?" msgstr "" @@ -31950,6 +31959,9 @@ msgstr "" msgid "How do I set up this service?" msgstr "" +msgid "How do I unarchive a %{resourceType}?" +msgstr "" + msgid "How do I use a web terminal?" msgstr "" @@ -37656,6 +37668,9 @@ msgstr "" msgid "Make sure you trust %{strong_start}%{client_name}%{strong_end} before authorizing." msgstr "" +msgid "Make your %{resourceType} read-only while preserving all data and access to its repository, issues, and merge requests." +msgstr "" + msgid "Makes this %{type} confidential." msgstr "" @@ -52765,6 +52780,9 @@ msgstr "" msgid "Restore project" msgstr "" +msgid "Restore your %{resourceType} to an active state. You'll be able to modify its content and settings again." +msgstr "" + msgid "Restoring projects" msgstr "" @@ -65130,6 +65148,9 @@ msgstr "" msgid "To submit your changes in a merge request, switch to one of these forks or create a new fork." msgstr "" +msgid "To unarchive this %{resourceType}, you must unarchive its parent group." +msgstr "" + msgid "To unsubscribe from this issue, please paste the following link into your browser:" msgstr "" @@ -66450,10 +66471,10 @@ msgstr "" msgid "Unapproved the current merge request." msgstr "" -msgid "Unarchive project" +msgid "Unarchive" msgstr "" -msgid "Unarchiving the project restores its members' ability to make commits, and create issues, comments, and other entities. %{strong_start}After you unarchive the project, it displays in the search and on the dashboard.%{strong_end} %{link_start}Learn more.%{link_end}" +msgid "Unarchive %{resourceType}" msgstr "" msgid "Unassign all"
+ {{ bodyText }} + + {{ helpLinkText }} + +