Skip to content

Commit

Permalink
I7495 new workflow page (#418)
Browse files Browse the repository at this point in the history
* ParticipantManager using new api structure, ReviewerManager displaying detailed status, various bug fixes

* Integrate views to the SideNav

* Display recommendations both for decising editor and recommendOnly editor

* Add message that deciding editors needs to be assigned before making decision

* pkp/pkp-lib#7495 Improvements&Fixes to pass end2tests for new workflow

* pkp/pkp-lib#7495 Refine permission logic

* pkp/pkp-lib#7495 clean up debug statements
  • Loading branch information
jardakotesovec authored Oct 3, 2024
1 parent 3ef7800 commit 619f90a
Show file tree
Hide file tree
Showing 46 changed files with 1,418 additions and 507 deletions.
28 changes: 19 additions & 9 deletions src/components/Form/fields/FieldSelectIssue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
v-if="button"
v-bind="button"
class="pkpFormField--selectIssue__button"
@click="emitGlobal(button.event)"
@click="selectIssue"
>
{{ button.label }}
</PkpButton>
Expand Down Expand Up @@ -111,15 +111,8 @@ export default {
*/
button() {
let button = null;
if (this.publicationStatus === pkp.const.STATUS_SCHEDULED) {
if (this.publicationStatus !== pkp.const.STATUS_PUBLISHED) {
button = {
event: 'unpublish:publication',
isWarnable: true,
label: this.unscheduleLabel,
};
} else if (this.publicationStatus !== pkp.const.STATUS_PUBLISHED) {
button = {
event: 'schedule:publication',
label: this.value ? this.changeIssueLabel : this.assignLabel,
};
}
Expand Down Expand Up @@ -162,6 +155,23 @@ export default {
},
},
methods: {
async selectIssue() {
// workaround to avoid circular dependencies in storybook
// There is chain if imports, and some of them imported form
// which seems to be causing circular dependency
const {useSubmissionSummaryStore} = await import(
'@/pages/dashboard/SubmissionSummaryModal/submissionSummaryStore.js'
);
const summaryStore = useSubmissionSummaryStore();
summaryStore.workflowAssignToIssue({}, (finishedData) => {
if (finishedData.data.issueId) {
this.currentValue = finishedData.data.issueId;
}
});
},
/**
* Emit a global event
*
Expand Down
1 change: 1 addition & 0 deletions src/components/Modal/Dialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<DialogPanel
data-cy="dialog"
class="modal__panel modal__panel--dialog relative mx-3 w-10/12 max-w-3xl transform overflow-hidden rounded bg-secondary text-start shadow transition-all sm:my-8"
>
<div class="flex min-h-12 items-center">
Expand Down
22 changes: 22 additions & 0 deletions src/components/Modal/ModalManager.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<template>
<SideModal
:key="sideModal1?.modalId"
:data-cy="
activeModalId === sideModal1?.modalId ? 'active-modal' : undefined
"
close-label="Close"
:open="sideModal1?.opened || false"
:modal-level="1"
Expand All @@ -15,7 +18,11 @@
></PkpDialog>
<SideModal
:key="sideModal2?.modalId"
close-label="Close"
:data-cy="
activeModalId === sideModal2?.modalId ? 'active-modal' : undefined
"
:modal-level="2"
:open="sideModal2?.opened || false"
@close="(returnData) => close(sideModal2?.modalId, returnData)"
Expand All @@ -32,6 +39,9 @@
@close="closeDialog"
></PkpDialog>
<SideModal
:data-cy="
activeModalId === sideModal3?.modalId ? 'active-modal' : undefined
"
close-label="Close"
:modal-level="3"
:open="sideModal3?.opened || false"
Expand Down Expand Up @@ -74,6 +84,18 @@ const {
dialogLevel,
} = storeToRefs(useModalStore());
const activeModalId = computed(() => {
if (sideModal3.value?.opened) {
return sideModal3.value.modalId;
} else if (sideModal2.value?.opened) {
return sideModal2.value.modalId;
} else if (sideModal1.value?.opened) {
return sideModal1.value.modalId;
}
return null;
});
// Component can be either string or vue component
const component1 = computed(() => {
if (!sideModal1.value?.component) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Modal/SideModalBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/>
</button>
</div>
<div class="ml-8 mr-8 flex-grow">
<div class="ml-8 mr-8 flex-grow" data-cy="sidemodal-header">
<div class="flex">
<div class="flex-grow">
<!-- @slot Small text above title, might be useful for example to display submission Id-->
Expand Down
3 changes: 2 additions & 1 deletion src/components/SideMenu/SideMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
v-bind="itemProps.action"
:href="item.link || '#'"
tabindex="-1"
@click.prevent="() => {}"
>
<Badge
v-if="item.badge?.slot"
v-if="item.badge?.slot != null"
:color-variant="item.badge.colorVariant || 'primary'"
v-bind="item.badge"
class="me-1"
Expand Down
91 changes: 83 additions & 8 deletions src/components/SideNav/SideNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
</template>

<script setup>
import {reactive, toRef, watchEffect} from 'vue';
import {ref, watch, computed} from 'vue';
import {useSideMenu} from '@/composables/useSideMenu.js';
import SideMenu from '../SideMenu/SideMenu.vue';
import {useUrl} from '@/composables/useUrl';
import {useFetch} from '@/composables/useFetch';
const props = defineProps({
/**
Expand All @@ -40,8 +42,78 @@ const props = defineProps({
});
let currentActiveKey = '';
const linksRef = toRef(props, 'links');
const items = reactive(convertLinksToArray(linksRef.value));
const menuItems = ref(convertLinksToArray(props.links));
/**
* Dashboards count
* */
const {apiUrl: dashboardCountUrl} = useUrl('_submissions/viewsCount');
const {data: dashboardCount, fetch: fetchDashboardCount} =
useFetch(dashboardCountUrl);
const dashboardsMenuItem = menuItems.value.find(
(item) => item.key === 'dashboards',
);
if (dashboardsMenuItem) {
fetchDashboardCount();
}
/**
* mySubmissions count
*/
const {apiUrl: mySubmissionsCountUrl} = useUrl('_submissions/viewsCount');
const {data: mySubmissionsCount, fetch: fetchMySubmissionsCount} = useFetch(
mySubmissionsCountUrl,
);
const mySubmissionsMenuItem = menuItems.value.find(
(item) => item.key === 'mySubmissions',
);
if (mySubmissionsMenuItem) {
fetchMySubmissionsCount();
}
/**
* reviewAssignments count
*/
const {apiUrl: reviewAssignmentCountUrl} = useUrl('_submissions/viewsCount');
const {data: reviewAssignmentCount, fetch: fetchReviewAssignmentCount} =
useFetch(reviewAssignmentCountUrl);
const reviewAssignmentMenuItem = menuItems.value.find(
(item) => item.key === 'reviewAssignments',
);
if (reviewAssignmentMenuItem) {
fetchReviewAssignmentCount();
}
// helper to attach count to the menu item
function enrichMenuItemWithCounts(page, itemsCount) {
if (itemsCount.value) {
const menuItem = menuItems.value.find((item) => item.key === page);
if (menuItem) {
const menuItemsEnriched = menuItem.items.map((item) => ({
...item,
badge: {
slot: itemsCount.value[item.id],
},
}));
menuItem.items = menuItemsEnriched;
}
}
}
const menuItemsEnriched = computed(() => {
enrichMenuItemWithCounts('dashboards', dashboardCount);
enrichMenuItemWithCounts('mySubmissions', mySubmissionsCount);
enrichMenuItemWithCounts('reviewAssignments', reviewAssignmentCount);
return menuItems.value;
});
function convertLinksToArray(links, level = 1, parentKey = '') {
const result = [];
Expand Down Expand Up @@ -99,12 +171,15 @@ function getExpandedKeys(items) {
return _expandedKeys;
}
const {sideMenuProps} = useSideMenu(items, {
const {sideMenuProps} = useSideMenu(menuItemsEnriched, {
activeItemKey: currentActiveKey,
expandedKeys: getExpandedKeys(items),
expandedKeys: getExpandedKeys(menuItems.value),
});
watchEffect(() => {
Object.assign(items, convertLinksToArray(linksRef.value));
});
watch(
() => props.links,
(newLinks) => {
menuItems.value = convertLinksToArray(newLinks);
},
);
</script>
26 changes: 11 additions & 15 deletions src/composables/useCurrentUser.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import {computed} from 'vue';
export const EditorialRoles = [
pkp.const.ROLE_ID_SITE_ADMIN,
pkp.const.ROLE_ID_MANAGER,
pkp.const.ROLE_ID_SUB_EDITOR,
pkp.const.ROLE_ID_ASSISTANT,
];

export function useCurrentUser() {
const isSiteAdmin = computed(
() =>
!!pkp.currentUser.roles.find(
(role) => role === pkp.const.ROLE_ID_SITE_ADMIN,
),
);
const isManager = computed(
() =>
!!pkp.currentUser.roles.find(
(role) => role === pkp.const.ROLE_ID_MANAGER,
),
);

function hasCurrentUserAtLeastOneRole(roles = []) {
return roles.some((role) => pkp.currentUser.roles.includes(role));
}

return {isSiteAdmin, isManager, hasCurrentUserAtLeastOneRole};
function getCurrentUserId() {
return pkp.currentUser.id;
}

return {hasCurrentUserAtLeastOneRole, getCurrentUserId};
}
9 changes: 0 additions & 9 deletions src/composables/useParticipant.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ export function useParticipant() {
pkp.const.ROLE_ID_ASSISTANT,
];
}
function hasParticipantAtLeastOneRole(participant, roleIds = []) {
return participant.groups.some((group) => roleIds.includes(group.roleId));
}

function getFirstGroupWithFollowingRoles(participant, roleIds = []) {
return participant.groups.find((group) => roleIds.includes(group.roleId));
}

function getUserAvatarInitialsFromName(fullName) {
const fullNameParts = fullName.split(' ');
Expand All @@ -31,7 +24,5 @@ export function useParticipant() {
return {
getUserAvatarInitialsFromName,
getEditorRoleIds,
hasParticipantAtLeastOneRole,
getFirstGroupWithFollowingRoles,
};
}
7 changes: 7 additions & 0 deletions src/composables/useQueryParams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {useUrlSearchParams} from '@vueuse/core';

const queryParams = useUrlSearchParams();

export function useQueryParams() {
return queryParams;
}
34 changes: 28 additions & 6 deletions src/composables/useSideMenu.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import {toRef, ref, computed} from 'vue';
import {useQueryParams} from '@/composables/useQueryParams';

export function useSideMenu(_items, opts = {}) {
const queryParams = useQueryParams();

const _activeItemKey = opts.activeItemKey || '';
const _expandedKeys = opts.expandedKeys || {};
const onActionFn = opts.onActionFn || (() => {});

const itemsRef = toRef(_items);

if (typeof itemsRef.value === 'undefined') {
throw new Error('items must be provided to use this api');
}

const items = computed(() => mapItems(itemsRef.value));
const items = computed(() => {
return mapItems(itemsRef.value);
});
const expandedKeys = ref(_expandedKeys);
const activeItemKey = ref(_activeItemKey);

Expand Down Expand Up @@ -53,11 +57,20 @@ export function useSideMenu(_items, opts = {}) {
activeItemKey.value = key;
}

function compareUrlPaths(url1, url2) {
const parsedUrl1 = new URL(url1);
const parsedUrl2 = new URL(url2);
return (
parsedUrl1.pathname === parsedUrl2.pathname &&
parsedUrl1.hostname === parsedUrl2.hostname
);
}

// Maps the level attributes which are necessary to render the nested menu
function mapItems(_items, level = 1) {
function mapItems(__items, level = 1) {
const result = [];

_items.forEach((_item, index) => {
__items.forEach((_item, index) => {
const item = {
..._item,
level,
Expand All @@ -70,7 +83,16 @@ export function useSideMenu(_items, opts = {}) {

if (item.link) {
item.command = () => {
window.location.href = item.link;
if (compareUrlPaths(window.location.href, item.link)) {
// only update query params, without reloading page, important for dashboards
const parsedUrl = new URL(item.link);
const params = new URLSearchParams(parsedUrl.search);
for (const [key, value] of params) {
queryParams[key] = value;
}
} else {
window.location.href = item.link;
}
setActiveItemKey(item.key);
};
}
Expand Down
Loading

0 comments on commit 619f90a

Please sign in to comment.