diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-10 15:09:45 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-10 15:09:45 +0300 |
commit | ec0ecba05cf7712bc8095af9363ee8ff8d999654 (patch) | |
tree | 703b6290381599c58b502e2b94b2d273cfcb00fe /app | |
parent | b6e10aaed70a798a57a40987b3aafcbb5b2a1f78 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
21 files changed, 506 insertions, 41 deletions
diff --git a/app/assets/javascripts/admin/users/components/actions/activate.vue b/app/assets/javascripts/admin/users/components/actions/activate.vue new file mode 100644 index 00000000000..99c260bf11e --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/activate.vue @@ -0,0 +1,44 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; + +export default { + components: { + GlDropdownItem, + }, + props: { + username: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + modalAttributes() { + return { + 'data-path': this.path, + 'data-method': 'put', + 'data-modal-attributes': JSON.stringify({ + title: sprintf(s__('AdminUsers|Activate user %{username}?'), { + username: this.username, + }), + message: s__('AdminUsers|You can always deactivate their account again if needed.'), + okVariant: 'confirm', + okTitle: s__('AdminUsers|Activate'), + }), + }; + }, + }, +}; +</script> + +<template> + <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item> + <slot></slot> + </gl-dropdown-item> + </div> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/approve.vue b/app/assets/javascripts/admin/users/components/actions/approve.vue new file mode 100644 index 00000000000..6fc43c246ea --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/approve.vue @@ -0,0 +1,21 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; + +export default { + components: { + GlDropdownItem, + }, + props: { + path: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <gl-dropdown-item :href="path" data-method="put"> + <slot></slot> + </gl-dropdown-item> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/block.vue b/app/assets/javascripts/admin/users/components/actions/block.vue new file mode 100644 index 00000000000..68dfefe14c2 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/block.vue @@ -0,0 +1,53 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; + +// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 +const messageHtml = ` + <p>${s__('AdminUsers|Blocking user has the following effects:')}</p> + <ul> + <li>${s__('AdminUsers|User will not be able to login')}</li> + <li>${s__('AdminUsers|User will not be able to access git repositories')}</li> + <li>${s__('AdminUsers|Personal projects will be left')}</li> + <li>${s__('AdminUsers|Owned groups will be left')}</li> + </ul> +`; + +export default { + components: { + GlDropdownItem, + }, + props: { + username: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + modalAttributes() { + return { + 'data-path': this.path, + 'data-method': 'put', + 'data-modal-attributes': JSON.stringify({ + title: sprintf(s__('AdminUsers|Block user %{username}?'), { username: this.username }), + okVariant: 'confirm', + okTitle: s__('AdminUsers|Block'), + messageHtml, + }), + }; + }, + }, +}; +</script> + +<template> + <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item> + <slot></slot> + </gl-dropdown-item> + </div> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/deactivate.vue b/app/assets/javascripts/admin/users/components/actions/deactivate.vue new file mode 100644 index 00000000000..7e0c17ba296 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/deactivate.vue @@ -0,0 +1,60 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; + +// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 +const messageHtml = ` + <p>${s__('AdminUsers|Deactivating a user has the following effects:')}</p> + <ul> + <li>${s__('AdminUsers|The user will be logged out')}</li> + <li>${s__('AdminUsers|The user will not be able to access git repositories')}</li> + <li>${s__('AdminUsers|The user will not be able to access the API')}</li> + <li>${s__('AdminUsers|The user will not receive any notifications')}</li> + <li>${s__('AdminUsers|The user will not be able to use slash commands')}</li> + <li>${s__( + 'AdminUsers|When the user logs back in, their account will reactivate as a fully active account', + )}</li> + <li>${s__('AdminUsers|Personal projects, group and user history will be left intact')}</li> + </ul> +`; + +export default { + components: { + GlDropdownItem, + }, + props: { + username: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + modalAttributes() { + return { + 'data-path': this.path, + 'data-method': 'put', + 'data-modal-attributes': JSON.stringify({ + title: sprintf(s__('AdminUsers|Deactivate user %{username}?'), { + username: this.username, + }), + okVariant: 'confirm', + okTitle: s__('AdminUsers|Deactivate'), + messageHtml, + }), + }; + }, + }, +}; +</script> + +<template> + <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item> + <slot></slot> + </gl-dropdown-item> + </div> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/delete.vue b/app/assets/javascripts/admin/users/components/actions/delete.vue new file mode 100644 index 00000000000..725d3dbf388 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/delete.vue @@ -0,0 +1,25 @@ +<script> +import SharedDeleteAction from './shared/shared_delete_action.vue'; + +export default { + components: { + SharedDeleteAction, + }, + props: { + username: { + type: String, + required: true, + }, + paths: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <shared-delete-action modal-type="delete" :username="username" :paths="paths"> + <slot></slot> + </shared-delete-action> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue b/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue new file mode 100644 index 00000000000..0ae15bfbebb --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/delete_with_contributions.vue @@ -0,0 +1,25 @@ +<script> +import SharedDeleteAction from './shared/shared_delete_action.vue'; + +export default { + components: { + SharedDeleteAction, + }, + props: { + username: { + type: String, + required: true, + }, + paths: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <shared-delete-action modal-type="delete-with-contributions" :username="username" :paths="paths"> + <slot></slot> + </shared-delete-action> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/index.js b/app/assets/javascripts/admin/users/components/actions/index.js new file mode 100644 index 00000000000..697bf284453 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/index.js @@ -0,0 +1,21 @@ +import Activate from './activate.vue'; +import Approve from './approve.vue'; +import Block from './block.vue'; +import Deactivate from './deactivate.vue'; +import Delete from './delete.vue'; +import DeleteWithContributions from './delete_with_contributions.vue'; +import Unblock from './unblock.vue'; +import Unlock from './unlock.vue'; +import Reject from './reject.vue'; + +export default { + Activate, + Approve, + Block, + Deactivate, + Delete, + DeleteWithContributions, + Unblock, + Unlock, + Reject, +}; diff --git a/app/assets/javascripts/admin/users/components/actions/reject.vue b/app/assets/javascripts/admin/users/components/actions/reject.vue new file mode 100644 index 00000000000..a80c1ff5458 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/reject.vue @@ -0,0 +1,21 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; + +export default { + components: { + GlDropdownItem, + }, + props: { + path: { + type: String, + required: true, + }, + }, +}; +</script> + +<template> + <gl-dropdown-item :href="path" data-method="delete"> + <slot></slot> + </gl-dropdown-item> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/shared/shared_delete_action.vue b/app/assets/javascripts/admin/users/components/actions/shared/shared_delete_action.vue new file mode 100644 index 00000000000..9107d9ccdd9 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/shared/shared_delete_action.vue @@ -0,0 +1,43 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; + +export default { + components: { + GlDropdownItem, + }, + props: { + username: { + type: String, + required: true, + }, + paths: { + type: Object, + required: true, + }, + modalType: { + type: String, + required: true, + }, + }, + computed: { + modalAttributes() { + return { + 'data-block-user-url': this.paths.block, + 'data-delete-user-url': this.paths.delete, + 'data-gl-modal-action': this.modalType, + 'data-username': this.username, + }; + }, + }, +}; +</script> + +<template> + <div class="js-delete-user-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item> + <span class="gl-text-red-500"> + <slot></slot> + </span> + </gl-dropdown-item> + </div> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/unblock.vue b/app/assets/javascripts/admin/users/components/actions/unblock.vue new file mode 100644 index 00000000000..f2b501caf09 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/unblock.vue @@ -0,0 +1,44 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; +import { sprintf, s__ } from '~/locale'; + +export default { + components: { + GlDropdownItem, + }, + props: { + username: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + modalAttributes() { + return { + 'data-path': this.path, + 'data-method': 'put', + 'data-modal-attributes': JSON.stringify({ + title: sprintf(s__('AdminUsers|Unblock user %{username}?'), { username: this.username }), + message: s__( + 'AdminUsers|You can always unblock their account, their data will remain intact.', + ), + okVariant: 'confirm', + okTitle: s__('AdminUsers|Unblock'), + }), + }; + }, + }, +}; +</script> + +<template> + <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item> + <slot></slot> + </gl-dropdown-item> + </div> +</template> diff --git a/app/assets/javascripts/admin/users/components/actions/unlock.vue b/app/assets/javascripts/admin/users/components/actions/unlock.vue new file mode 100644 index 00000000000..294aaade7c1 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/actions/unlock.vue @@ -0,0 +1,42 @@ +<script> +import { GlDropdownItem } from '@gitlab/ui'; +import { sprintf, s__, __ } from '~/locale'; + +export default { + components: { + GlDropdownItem, + }, + props: { + username: { + type: String, + required: true, + }, + path: { + type: String, + required: true, + }, + }, + computed: { + modalAttributes() { + return { + 'data-path': this.path, + 'data-method': 'put', + 'data-modal-attributes': JSON.stringify({ + title: sprintf(s__('AdminUsers|Unlock user %{username}?'), { username: this.username }), + message: __('Are you sure?'), + okVariant: 'confirm', + okTitle: s__('AdminUsers|Unlock'), + }), + }; + }, + }, +}; +</script> + +<template> + <div class="js-confirm-modal-button" v-bind="{ ...modalAttributes }"> + <gl-dropdown-item> + <slot></slot> + </gl-dropdown-item> + </div> +</template> diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue index 6c7c434cdf4..f6d2f6b7497 100644 --- a/app/assets/javascripts/admin/users/components/user_actions.vue +++ b/app/assets/javascripts/admin/users/components/user_actions.vue @@ -6,9 +6,11 @@ import { GlDropdownSectionHeader, GlDropdownDivider, } from '@gitlab/ui'; -import { s__, __ } from '~/locale'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { convertArrayToCamelCase } from '~/lib/utils/common_utils'; import { generateUserPaths } from '../utils'; +import { I18N_USER_ACTIONS } from '../constants'; +import Actions from './actions'; export default { components: { @@ -17,6 +19,7 @@ export default { GlDropdownItem, GlDropdownSectionHeader, GlDropdownDivider, + ...Actions, }, props: { user: { @@ -58,21 +61,11 @@ export default { isLdapAction(action) { return action === 'ldapBlocked'; }, + getActionComponent(action) { + return Actions[capitalizeFirstCharacter(action)]; + }, }, - i18n: { - edit: __('Edit'), - settings: __('Settings'), - unlock: __('Unlock'), - block: s__('AdminUsers|Block'), - unblock: s__('AdminUsers|Unblock'), - approve: s__('AdminUsers|Approve'), - reject: s__('AdminUsers|Reject'), - deactivate: s__('AdminUsers|Deactivate'), - activate: s__('AdminUsers|Activate'), - ldapBlocked: s__('AdminUsers|Cannot unblock LDAP blocked users'), - delete: s__('AdminUsers|Delete user'), - deleteWithContributions: s__('AdminUsers|Delete user and contributions'), - }, + i18n: I18N_USER_ACTIONS, }; </script> @@ -92,24 +85,35 @@ export default { <gl-dropdown-section-header>{{ $options.i18n.settings }}</gl-dropdown-section-header> <template v-for="action in dropdownSafeActions"> - <gl-dropdown-item v-if="isLdapAction(action)" :key="action" :data-testid="action"> - {{ $options.i18n.ldap }} - </gl-dropdown-item> - <gl-dropdown-item v-else :key="action" :href="userPaths[action]" :data-testid="action"> + <component + :is="getActionComponent(action)" + v-if="getActionComponent(action)" + :key="action" + :path="userPaths[action]" + :username="user.name" + :data-testid="action" + > + {{ $options.i18n[action] }} + </component> + <gl-dropdown-item v-else-if="isLdapAction(action)" :key="action" :data-testid="action"> {{ $options.i18n[action] }} </gl-dropdown-item> </template> <gl-dropdown-divider v-if="hasDeleteActions" /> - <gl-dropdown-item - v-for="action in dropdownDeleteActions" - :key="action" - :href="userPaths[action]" - :data-testid="`delete-${action}`" - > - <span class="gl-text-red-500">{{ $options.i18n[action] }}</span> - </gl-dropdown-item> + <template v-for="action in dropdownDeleteActions"> + <component + :is="getActionComponent(action)" + v-if="getActionComponent(action)" + :key="action" + :paths="userPaths" + :username="user.name" + :data-testid="`delete-${action}`" + > + {{ $options.i18n[action] }} + </component> + </template> </gl-dropdown> </div> </template> diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js index e26643cad60..8ea1bd3ca7a 100644 --- a/app/assets/javascripts/admin/users/constants.js +++ b/app/assets/javascripts/admin/users/constants.js @@ -1,5 +1,22 @@ +import { s__, __ } from '~/locale'; + export const USER_AVATAR_SIZE = 32; export const SHORT_DATE_FORMAT = 'd mmm, yyyy'; export const LENGTH_OF_USER_NOTE_TOOLTIP = 100; + +export const I18N_USER_ACTIONS = { + edit: __('Edit'), + settings: __('Settings'), + unlock: __('Unlock'), + block: s__('AdminUsers|Block'), + unblock: s__('AdminUsers|Unblock'), + approve: s__('AdminUsers|Approve'), + reject: s__('AdminUsers|Reject'), + deactivate: s__('AdminUsers|Deactivate'), + activate: s__('AdminUsers|Activate'), + ldapBlocked: s__('AdminUsers|Cannot unblock LDAP blocked users'), + delete: s__('AdminUsers|Delete user'), + deleteWithContributions: s__('AdminUsers|Delete user and contributions'), +}; diff --git a/app/assets/javascripts/issuable_show/components/issuable_header.vue b/app/assets/javascripts/issuable_show/components/issuable_header.vue index 4c6df31a0f3..9cfbc2d9bbc 100644 --- a/app/assets/javascripts/issuable_show/components/issuable_header.vue +++ b/app/assets/javascripts/issuable_show/components/issuable_header.vue @@ -3,6 +3,7 @@ import { GlIcon, GlButton, GlTooltipDirective, GlAvatarLink, GlAvatarLabeled } f import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { isExternal } from '~/lib/utils/url_utility'; export default { components: { @@ -49,6 +50,9 @@ export default { authorId() { return getIdFromGraphQLId(`${this.author.id}`); }, + isAuthorExternal() { + return isExternal(this.author.webUrl); + }, }, mounted() { this.toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button'); @@ -98,7 +102,11 @@ export default { :src="author.avatarUrl" :label="author.name" class="d-none d-sm-inline-flex gl-ml-1" - /> + > + <template #meta> + <gl-icon v-if="isAuthorExternal" name="external-link" /> + </template> + </gl-avatar-labeled> <strong class="author d-sm-none d-inline">@{{ author.username }}</strong> </gl-avatar-link> </div> diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue index 6b61dc5902b..6a803c06df2 100644 --- a/app/assets/javascripts/jobs/components/job_container_item.vue +++ b/app/assets/javascripts/jobs/components/job_container_item.vue @@ -51,7 +51,7 @@ export default { v-gl-tooltip :href="job.status.details_path" :title="tooltipText" - class="js-job-link d-flex" + class="js-job-link gl-display-flex gl-align-items-center" > <gl-icon v-if="isActive" diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 44d3e78b334..0b920ba8e7a 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -310,6 +310,20 @@ export function isAbsoluteOrRootRelative(url) { } /** + * Returns true if url is an external URL + * + * @param {String} url + * @returns {Boolean} + */ +export function isExternal(url) { + if (isRootRelative(url)) { + return false; + } + + return !url.includes(gon.gitlab_url); +} + +/** * Converts a relative path to an absolute or a root relative path depending * on what is passed as a basePath. * diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js index ae5db5f5bdc..42afcf434f6 100644 --- a/app/assets/javascripts/pages/admin/users/index.js +++ b/app/assets/javascripts/pages/admin/users/index.js @@ -34,6 +34,8 @@ function loadModalsConfigurationFromHtml(modalsElement) { document.addEventListener('DOMContentLoaded', () => { Vue.use(Translate); + initAdminUsersApp(); + const modalConfiguration = loadModalsConfigurationFromHtml( document.querySelector(MODAL_TEXTS_CONTAINER_SELECTOR), ); @@ -60,7 +62,6 @@ document.addEventListener('DOMContentLoaded', () => { }); initConfirmModal(); - initAdminUsersApp(); initCohortsEmptyState(); initTabs(); }); diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 9654b7cd9a9..1a5b578cc75 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -122,15 +122,24 @@ module SearchHelper end def search_sort_options - options = [] - options << { - title: _('Created date'), - sortable: true, - sortParam: { - asc: 'created_asc', - desc: 'created_desc' + [ + { + title: _('Created date'), + sortable: true, + sortParam: { + asc: 'created_asc', + desc: 'created_desc' + } + }, + { + title: _('Last updated'), + sortable: true, + sortParam: { + asc: 'updated_asc', + desc: 'updated_desc' + } } - } + ] end private diff --git a/app/models/concerns/repository_storage_movable.rb b/app/models/concerns/repository_storage_movable.rb index e584922025a..8607f0d94f4 100644 --- a/app/models/concerns/repository_storage_movable.rb +++ b/app/models/concerns/repository_storage_movable.rb @@ -68,6 +68,18 @@ module RepositoryStorageMovable storage_move.update_repository_storage(storage_move.destination_storage_name) end + after_transition started: :replicated do |storage_move| + # We have several scripts in place that replicate some statistics information + # to other databases. Some of them depend on the updated_at column + # to identify the models they need to extract. + # + # If we don't update the `updated_at` of the container after a repository storage move, + # the scripts won't know that they need to sync them. + # + # See https://gitlab.com/gitlab-data/analytics/-/issues/7868 + storage_move.container.touch + end + before_transition started: :failed do |storage_move| storage_move.container.set_repository_writable! end diff --git a/app/models/pages_deployment.rb b/app/models/pages_deployment.rb index 95949eeed9a..d67a92af6af 100644 --- a/app/models/pages_deployment.rb +++ b/app/models/pages_deployment.rb @@ -2,6 +2,7 @@ # PagesDeployment stores a zip archive containing GitLab Pages web-site class PagesDeployment < ApplicationRecord + include EachBatch include FileStoreMounter MIGRATED_FILE_NAME = "_migrated.zip" diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml index ea14e2d6ca5..3c7afff57f6 100644 --- a/app/views/projects/no_repo.html.haml +++ b/app/views/projects/no_repo.html.haml @@ -15,14 +15,14 @@ = render 'projects/invite_members_modal', project: @project .no-repo-actions - = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do + = link_to project_repository_path(@project), method: :post, class: 'btn gl-button btn-confirm' do #{ _('Create empty repository') } %strong.gl-ml-3.gl-mr-3 or - = link_to new_project_import_path(@project), class: 'btn' do + = link_to new_project_import_path(@project), class: 'btn gl-button btn-default' do #{ _('Import repository') } - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to _('Delete project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "gl-button btn btn-danger btn-danger-secondary float-right" + = link_to _('Delete project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn gl-button btn-danger float-right" |