Skip to content

Commit

Permalink
pkp/pkp-lib#9658 user access table and table actions (#437)
Browse files Browse the repository at this point in the history
* user access manager

* Support user json object

* Support user json object

* Add Actions

* add user search function

* Rename variable

* add user table actions

* update after data changed

* fix disabled user enable

* hide actions that not allowed to do current user in the user list

* add phone and biography

* add user loginAs and mergeUser permission check

* remove localized user group name

* update user access mock and set  margin between tables

* improve flexibility on extensibility

* remove phone and biography meta data

* add separate columns components

* change user action functions variable

* add user get user permission for merge, loginAs user actions

---------

Co-authored-by: withanage <[email protected]>
  • Loading branch information
ipula and withanage authored Feb 4, 2025
1 parent 96aae59 commit e65555c
Show file tree
Hide file tree
Showing 13 changed files with 727 additions and 0 deletions.
7 changes: 7 additions & 0 deletions public/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ window.pkp = {
'email.cc': 'CC',
'email.confirmSwitchLocale':
'Are you sure you want to change to {$localeName} to compose this email? Any changes you have made to the subject and body of the email will be lost.',
'email.email': 'Email',
'email.subject': 'Subject',
'email.to': 'To',
'fileManager.copyeditedFiles': 'Copyedited Files',
Expand Down Expand Up @@ -439,18 +440,22 @@ window.pkp = {
'grid.action.deleteContributor': 'Delete Contributor',
'grid.action.deleteContributor.confirmationMessage':
'Are you sure you want to remove {$name} as a contributor? This action can not be undone.',
'grid.action.disable': 'Disable User',
'grid.action.edit': 'Edit',
'grid.action.editFile': 'Edit a file',
'grid.action.logInAs': 'Login As',
'grid.action.moreInformation': 'More Information',
'grid.action.order': 'Order',
'grid.action.remove': 'Remove',
'grid.action.saveOrdering': 'Save Order',
'grid.action.sort': 'Sort',
'grid.columns.actions': 'Actions',
'grid.libraryFiles.submission.title': 'Submission Library',
'grid.noItems': 'No Items',
'grid.user.confirmLogInAs':
'Log in as this user? All actions you perform will be attributed to this user.',
'grid.user.currentUsers':'Current Users',
'grid.action.mergeUser':'Merge User',
'help.help': 'Help',
'informationCenter.informationCenter': 'Information Center',
'invitation.cancelInvite.actionName': 'Cancel Invite',
Expand Down Expand Up @@ -770,6 +775,8 @@ window.pkp = {
'Are you sure want remove this role permanently?',
'user.role.reviewer': 'Reviewer',
'user.role.reviewers': 'Reviewers',
'user.roles': 'Roles',
'user.startDate': 'Start Date',
'user.username': 'Username',
'userInvitation.cancel.goBack': 'Go Back',
'userInvitation.cancel.message':
Expand Down
11 changes: 11 additions & 0 deletions src/managers/UserAccessManager/UserAccessManager.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Primary, Controls, Stories, Meta, ArgTypes} from '@storybook/blocks';

import * as UserAccessManager from './UserAccessManager.stories.js';

<Meta of={UserAccessManager} />

# User Access Manager

This table displays the current list of users ns and allows the user access manager to search users.

<ArgTypes />
39 changes: 39 additions & 0 deletions src/managers/UserAccessManager/UserAccessManager.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import UserAccessManager from './UserAccessManager.vue';
import {http, HttpResponse} from 'msw';
import userAccessMock from './mocks/UserAccessMock.js';

export default {
title: 'Managers/UserAccessManager',
component: UserAccessManager,
};

export const Init = {
render: (args) => ({
components: {UserAccessManager},
setup() {
return {args};
},
template: '<UserAccessManager v-bind="args"/>',
}),
parameters: {
msw: {
handlers: [
http.get(
'https://mock/index.php/publicknowledge/api/v1/users',
async ({request}) => {
const url = new URL(request.url);
const offset = parseInt(url.searchParams.get('offset') || 0);
const count = parseInt(url.searchParams.get('count'));
const users = userAccessMock.items.slice(offset, offset + count);

return HttpResponse.json({
itemsMax: userAccessMock.itemsMax,
items: users,
});
},
),
],
},
},
args: [],
};
71 changes: 71 additions & 0 deletions src/managers/UserAccessManager/UserAccessManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<PkpTable class="mt-2">
<template #label>
<h3 class="text-3xl-bold">
{{ t('grid.user.currentUsers') }}({{
store.userAccessPagination.itemCount
}})
</h3>
</template>
<template #top-controls>
<Search
:search-phrase="searchPhrase"
:search-label="t('userAccess.search')"
@search-phrase-changed="store.setSearchPhrase"
/>
</template>
<TableHeader>
<TableColumn v-for="(column, i) in store.getColumns()" :key="i">
<span :class="column.headerSrOnly ? 'sr-only' : ''">
{{ column.header }}
</span>
</TableColumn>
</TableHeader>
<TableBody>
<TableRow v-for="user in store.userList" :key="user.id">
<component
:is="Components[column.component] || column.component"
v-for="(column, i) in store.getColumns()"
:key="i"
:user="user"
></component>
</TableRow>
</TableBody>
</PkpTable>
<TablePagination
:pagination="store.userAccessPagination"
@set-page="store.setCurrentPage"
/>
</template>

<script setup>
import PkpTable from '@/components/Table/Table.vue';
import TableHeader from '@/components/Table/TableHeader.vue';
import TableColumn from '@/components/Table/TableColumn.vue';
import TableBody from '@/components/Table/TableBody.vue';
import TableRow from '@/components/Table/TableRow.vue';
import {useUserAccessManagerStore} from './UserAccessManagerStore.js';
import TablePagination from '@/components/Table/TablePagination.vue';
import {useTranslation} from '@/composables/useTranslation';
import Search from '@/components/Search/Search.vue';
import {ref} from 'vue';
import UserAccessManagerCellStartDate from './UserAccessManagerCellStartDate.vue';
import UserAccessManagerCellUserGroups from './UserAccessManagerCellUserGroups.vue';
import UserAccessManagerCellActions from './UserAccessManagerCellActions.vue';
import UserAccessManagerCellName from './UserAccessManagerCellName.vue';
import UserAccessManagerCellEmail from './UserAccessManagerCellEmail.vue';
import UserAccessManagerCellAffiliation from './UserAccessManagerCellAffiliation.vue';
const Components = {
UserAccessManagerCellStartDate,
UserAccessManagerCellUserGroups,
UserAccessManagerCellActions,
UserAccessManagerCellName,
UserAccessManagerCellEmail,
UserAccessManagerCellAffiliation,
};
const store = useUserAccessManagerStore();
const {t} = useTranslation();
const searchPhrase = ref('');
</script>
25 changes: 25 additions & 0 deletions src/managers/UserAccessManager/UserAccessManagerCellActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<TableCell>
<DropdownActions
:actions="store.getItemActions({user})"
:label="t('userAccess.management.options')"
button-variant="ellipsis"
direction="left"
@action="(actionName) => store[actionName]({user})"
/>
</TableCell>
</template>

<script setup>
import TableCell from '@/components/Table/TableCell.vue';
import DropdownActions from '@/components/DropdownActions/DropdownActions.vue';
import {useUserAccessManagerStore} from './UserAccessManagerStore.js';
import {useTranslation} from '@/composables/useTranslation';
defineProps({
user: {type: Object, required: true},
});
const store = useUserAccessManagerStore();
const {t} = useTranslation();
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<TableCell>
<span class="text-base-normal">
{{ localize(user.affiliation) }}
</span>
</TableCell>
</template>

<script setup>
import TableCell from '@/components/Table/TableCell.vue';
defineProps({
user: {type: Object, required: true},
});
</script>
15 changes: 15 additions & 0 deletions src/managers/UserAccessManager/UserAccessManagerCellEmail.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<template>
<TableCell>
<span class="text-base-normal">
{{ user.email }}
</span>
</TableCell>
</template>

<script setup>
import TableCell from '@/components/Table/TableCell.vue';
defineProps({
user: {type: Object, required: true},
});
</script>
18 changes: 18 additions & 0 deletions src/managers/UserAccessManager/UserAccessManagerCellName.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<TableCell>
<span class="text-base-normal">
{{ user.fullName }}
</span>

<Icon v-if="user.orcid" icon="Orcid" class="h-4 w-4" :inline="true" />
</TableCell>
</template>

<script setup>
import TableCell from '@/components/Table/TableCell.vue';
import Icon from '@/components/Icon/Icon.vue';
defineProps({
user: {type: Object, required: true},
});
</script>
19 changes: 19 additions & 0 deletions src/managers/UserAccessManager/UserAccessManagerCellStartDate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<TableCell>
<template v-for="(userGroups, i) in user.groups" :key="i">
<div class="flex flex-col">
{{ formatShortDate(userGroups?.startDate) }}
</div>
</template>
</TableCell>
</template>

<script setup>
import TableCell from '@/components/Table/TableCell.vue';
import {useDate} from '@/composables/useDate';
defineProps({
user: {type: Object, required: true},
});
const {formatShortDate} = useDate();
</script>
17 changes: 17 additions & 0 deletions src/managers/UserAccessManager/UserAccessManagerCellUserGroups.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<TableCell>
<template v-for="(userGroups, i) in user.groups" :key="i">
<div class="flex flex-col">
{{ userGroups.name }}
</div>
</template>
</TableCell>
</template>

<script setup>
import TableCell from '@/components/Table/TableCell.vue';
defineProps({
user: {type: Object, required: true},
});
</script>
Loading

0 comments on commit e65555c

Please sign in to comment.