diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 13:34:06 +0300 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/admin | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/admin')
23 files changed, 666 insertions, 15 deletions
diff --git a/app/assets/javascripts/admin/statistics_panel/components/app.vue b/app/assets/javascripts/admin/statistics_panel/components/app.vue index 29077d926cf..1f0db422807 100644 --- a/app/assets/javascripts/admin/statistics_panel/components/app.vue +++ b/app/assets/javascripts/admin/statistics_panel/components/app.vue @@ -1,6 +1,6 @@ <script> -import { mapState, mapGetters, mapActions } from 'vuex'; import { GlLoadingIcon } from '@gitlab/ui'; +import { mapState, mapGetters, mapActions } from 'vuex'; import statisticsLabels from '../constants'; export default { @@ -26,8 +26,8 @@ export default { </script> <template> - <div class="info-well"> - <div class="well-segment admin-well admin-well-statistics"> + <div class="gl-card"> + <div class="gl-card-body"> <h4>{{ __('Statistics') }}</h4> <gl-loading-icon v-if="isLoading" size="md" class="my-3" /> <template v-else> diff --git a/app/assets/javascripts/admin/statistics_panel/index.js b/app/assets/javascripts/admin/statistics_panel/index.js index 8c49fffe630..2f8c3d2e167 100644 --- a/app/assets/javascripts/admin/statistics_panel/index.js +++ b/app/assets/javascripts/admin/statistics_panel/index.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import StatisticsPanelApp from './components/app.vue'; import createStore from './store'; -export default function (el) { +export default function initStatisticsPanel(el) { if (!el) { return false; } diff --git a/app/assets/javascripts/admin/statistics_panel/store/actions.js b/app/assets/javascripts/admin/statistics_panel/store/actions.js index 149540c4222..459f11c02f1 100644 --- a/app/assets/javascripts/admin/statistics_panel/store/actions.js +++ b/app/assets/javascripts/admin/statistics_panel/store/actions.js @@ -1,7 +1,7 @@ import Api from '~/api'; -import { s__ } from '~/locale'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { s__ } from '~/locale'; import * as types from './mutation_types'; export const requestStatistics = ({ commit }) => commit(types.REQUEST_STATISTICS); 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..e34b01346b9 --- /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 Reject from './reject.vue'; +import Unblock from './unblock.vue'; +import Unlock from './unlock.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/cohorts/components/usage_ping_disabled.vue b/app/assets/javascripts/admin/users/components/usage_ping_disabled.vue index 5da38495010..5da38495010 100644 --- a/app/assets/javascripts/admin/cohorts/components/usage_ping_disabled.vue +++ b/app/assets/javascripts/admin/users/components/usage_ping_disabled.vue diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue new file mode 100644 index 00000000000..e92c97b54a3 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/user_actions.vue @@ -0,0 +1,119 @@ +<script> +import { + GlButton, + GlDropdown, + GlDropdownItem, + GlDropdownSectionHeader, + GlDropdownDivider, +} from '@gitlab/ui'; +import { convertArrayToCamelCase } from '~/lib/utils/common_utils'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; +import { I18N_USER_ACTIONS } from '../constants'; +import { generateUserPaths } from '../utils'; +import Actions from './actions'; + +export default { + components: { + GlButton, + GlDropdown, + GlDropdownItem, + GlDropdownSectionHeader, + GlDropdownDivider, + ...Actions, + }, + props: { + user: { + type: Object, + required: true, + }, + paths: { + type: Object, + required: true, + }, + }, + computed: { + userActions() { + return convertArrayToCamelCase(this.user.actions); + }, + dropdownActions() { + return this.userActions.filter((a) => a !== 'edit'); + }, + dropdownDeleteActions() { + return this.dropdownActions.filter((a) => a.includes('delete')); + }, + dropdownSafeActions() { + return this.dropdownActions.filter((a) => !this.dropdownDeleteActions.includes(a)); + }, + hasDropdownActions() { + return this.dropdownActions.length > 0; + }, + hasDeleteActions() { + return this.dropdownDeleteActions.length > 0; + }, + hasEditAction() { + return this.userActions.includes('edit'); + }, + userPaths() { + return generateUserPaths(this.paths, this.user.username); + }, + }, + methods: { + isLdapAction(action) { + return action === 'ldapBlocked'; + }, + getActionComponent(action) { + return Actions[capitalizeFirstCharacter(action)]; + }, + }, + i18n: I18N_USER_ACTIONS, +}; +</script> + +<template> + <div class="gl-display-flex gl-justify-content-end"> + <gl-button v-if="hasEditAction" data-testid="edit" :href="userPaths.edit">{{ + $options.i18n.edit + }}</gl-button> + + <gl-dropdown + v-if="hasDropdownActions" + data-testid="actions" + right + class="gl-ml-2" + icon="settings" + > + <gl-dropdown-section-header>{{ $options.i18n.settings }}</gl-dropdown-section-header> + + <template v-for="action in dropdownSafeActions"> + <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" /> + + <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/components/user_avatar.vue b/app/assets/javascripts/admin/users/components/user_avatar.vue index 4f79c4fd451..ce22595609d 100644 --- a/app/assets/javascripts/admin/users/components/user_avatar.vue +++ b/app/assets/javascripts/admin/users/components/user_avatar.vue @@ -1,12 +1,16 @@ <script> -import { GlAvatarLink, GlAvatarLabeled, GlBadge } from '@gitlab/ui'; -import { USER_AVATAR_SIZE } from '../constants'; +import { GlAvatarLabeled, GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { truncate } from '~/lib/utils/text_utility'; +import { USER_AVATAR_SIZE, LENGTH_OF_USER_NOTE_TOOLTIP } from '../constants'; export default { + directives: { + GlTooltip: GlTooltipDirective, + }, components: { - GlAvatarLink, GlAvatarLabeled, GlBadge, + GlIcon, }, props: { user: { @@ -22,16 +26,23 @@ export default { adminUserHref() { return this.adminUserPath.replace('id', this.user.username); }, + adminUserMailto() { + // NOTE: 'mailto:' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives + // eslint-disable-next-line @gitlab/require-i18n-strings + return `mailto:${this.user.email}`; + }, + userNoteShort() { + return truncate(this.user.note, LENGTH_OF_USER_NOTE_TOOLTIP); + }, }, USER_AVATAR_SIZE, }; </script> <template> - <gl-avatar-link + <div v-if="user" - class="js-user-link" - :href="adminUserHref" + class="js-user-link gl-display-inline-block" :data-user-id="user.id" :data-username="user.username" > @@ -40,8 +51,13 @@ export default { :src="user.avatarUrl" :label="user.name" :sub-label="user.email" + :label-link="adminUserHref" + :sub-label-link="adminUserMailto" > <template #meta> + <div v-if="user.note" class="gl-text-gray-500 gl-p-1"> + <gl-icon v-gl-tooltip="userNoteShort" name="document" /> + </div> <div v-for="(badge, idx) in user.badges" :key="idx" class="gl-p-1"> <gl-badge class="gl-display-flex!" size="sm" :variant="badge.variant">{{ badge.text @@ -49,5 +65,5 @@ export default { </div> </template> </gl-avatar-labeled> - </gl-avatar-link> + </div> </template> diff --git a/app/assets/javascripts/admin/users/components/user_date.vue b/app/assets/javascripts/admin/users/components/user_date.vue new file mode 100644 index 00000000000..38dddbf72c2 --- /dev/null +++ b/app/assets/javascripts/admin/users/components/user_date.vue @@ -0,0 +1,29 @@ +<script> +import { formatDate } from '~/lib/utils/datetime_utility'; +import { __ } from '~/locale'; +import { SHORT_DATE_FORMAT } from '../constants'; + +export default { + props: { + date: { + type: String, + required: false, + default: null, + }, + }, + computed: { + formattedDate() { + const { date } = this; + if (date === null) { + return __('Never'); + } + return formatDate(new Date(date), SHORT_DATE_FORMAT); + }, + }, +}; +</script> +<template> + <span> + {{ formattedDate }} + </span> +</template> diff --git a/app/assets/javascripts/admin/users/components/users_table.vue b/app/assets/javascripts/admin/users/components/users_table.vue index 15e31935a4c..8962068601c 100644 --- a/app/assets/javascripts/admin/users/components/users_table.vue +++ b/app/assets/javascripts/admin/users/components/users_table.vue @@ -1,7 +1,9 @@ <script> import { GlTable } from '@gitlab/ui'; import { __ } from '~/locale'; +import UserActions from './user_actions.vue'; import UserAvatar from './user_avatar.vue'; +import UserDate from './user_date.vue'; const DEFAULT_TH_CLASSES = 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; @@ -11,6 +13,8 @@ export default { components: { GlTable, UserAvatar, + UserActions, + UserDate, }, props: { users: { @@ -62,7 +66,19 @@ export default { stacked="md" > <template #cell(name)="{ item: user }"> - <UserAvatar :user="user" :admin-user-path="paths.adminUser" /> + <user-avatar :user="user" :admin-user-path="paths.adminUser" /> + </template> + + <template #cell(createdAt)="{ item: { createdAt } }"> + <user-date :date="createdAt" /> + </template> + + <template #cell(lastActivityOn)="{ item: { lastActivityOn } }"> + <user-date :date="lastActivityOn" show-never /> + </template> + + <template #cell(settings)="{ item: user }"> + <user-actions :user="user" :paths="paths" /> </template> </gl-table> </div> diff --git a/app/assets/javascripts/admin/users/constants.js b/app/assets/javascripts/admin/users/constants.js index 675fcf00c39..8ea1bd3ca7a 100644 --- a/app/assets/javascripts/admin/users/constants.js +++ b/app/assets/javascripts/admin/users/constants.js @@ -1 +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/admin/users/index.js b/app/assets/javascripts/admin/users/index.js index f35b57c4e1a..0365d054fc9 100644 --- a/app/assets/javascripts/admin/users/index.js +++ b/app/assets/javascripts/admin/users/index.js @@ -1,8 +1,9 @@ import Vue from 'vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import AdminUsersApp from './components/app.vue'; +import UsagePingDisabled from './components/usage_ping_disabled.vue'; -export default function (el = document.querySelector('#js-admin-users-app')) { +export const initAdminUsersApp = (el = document.querySelector('#js-admin-users-app')) => { if (!el) { return false; } @@ -19,4 +20,24 @@ export default function (el = document.querySelector('#js-admin-users-app')) { }, }), }); -} +}; + +export const initCohortsEmptyState = (el = document.querySelector('#js-cohorts-empty-state')) => { + if (!el) { + return false; + } + + const { emptyStateSvgPath, enableUsagePingLink, docsLink } = el.dataset; + + return new Vue({ + el, + provide: { + svgPath: emptyStateSvgPath, + primaryButtonPath: enableUsagePingLink, + docsLink, + }, + render(h) { + return h(UsagePingDisabled); + }, + }); +}; diff --git a/app/assets/javascripts/admin/users/tabs.js b/app/assets/javascripts/admin/users/tabs.js new file mode 100644 index 00000000000..9ada77396c7 --- /dev/null +++ b/app/assets/javascripts/admin/users/tabs.js @@ -0,0 +1,23 @@ +import { historyPushState } from '~/lib/utils/common_utils'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; + +const COHORTS_PANE = 'cohorts'; + +const tabClickHandler = (e) => { + const { hash } = e.currentTarget; + const tab = hash === `#${COHORTS_PANE}` ? COHORTS_PANE : null; + const newUrl = mergeUrlParams({ tab }, window.location.href); + historyPushState(newUrl); +}; + +const initTabs = () => { + const tabLinks = document.querySelectorAll('.js-users-tab-item a'); + + if (tabLinks.length) { + tabLinks.forEach((tabLink) => { + tabLink.addEventListener('click', (e) => tabClickHandler(e)); + }); + } +}; + +export default initTabs; diff --git a/app/assets/javascripts/admin/users/utils.js b/app/assets/javascripts/admin/users/utils.js new file mode 100644 index 00000000000..f6c1091ba27 --- /dev/null +++ b/app/assets/javascripts/admin/users/utils.js @@ -0,0 +1,7 @@ +export const generateUserPaths = (paths, id) => { + return Object.fromEntries( + Object.entries(paths).map(([action, genericPath]) => { + return [action, genericPath.replace('id', id)]; + }), + ); +}; |