Skip to content

Commit

Permalink
I10618 (#451)
Browse files Browse the repository at this point in the history
* pkp/pkp-lib#10618 Initial display of notifications and emails on workflow page. Removed some code duplications in workflow configs.

* pkp/pkp-lib#10618 Add OMP notifications for productiong stage

* pkp/pkp-lib#10618 Remove some redundant logic from omp, which can be inherited from OJS

* pkp/pkp-lib#10618 Fix config for Jats to control correctly whether its editable or not

* pkp/pkp-lib#10618 Styling of email listing and individual notifications

* pkp/pkp-lib#10618 fix typo

* pkp/pkp-lib#10618 Refine submission status, clean up inherited configurations, various fixes

* pkp/pkp-lib#10599 Add createnew  internal round action
  • Loading branch information
jardakotesovec authored Nov 28, 2024
1 parent 145ce66 commit 14c7d53
Show file tree
Hide file tree
Showing 24 changed files with 593 additions and 1,207 deletions.
13 changes: 13 additions & 0 deletions src/composables/useApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export function useApp() {
function isOJS() {
return pkp.context.app === 'ojs2';
}
function isOMP() {
return pkp.context.app === 'omp';
}
function isOPS() {
return pkp.context.app === 'ops';
}

return {isOJS, isOMP, isOPS};
}
6 changes: 5 additions & 1 deletion src/composables/useDate.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ export function useDate() {
return moment(dateString).format('DD-MM-YYYY');
}

return {calculateDaysBetweenDates, formatShortDate};
function formatDateAndTime(dateString) {
return moment(dateString).format('YYYY-MM-DD hh:mm A');
}

return {calculateDaysBetweenDates, formatShortDate, formatDateAndTime};
}
10 changes: 8 additions & 2 deletions src/composables/useFetch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ref, unref} from 'vue';
import {ofetch, createFetch} from 'ofetch';
import {useModalStore} from '@/stores/modalStore';
import {useDebounceFn} from '@vueuse/core';

let ofetchInstance = ofetch;

Expand All @@ -26,7 +27,8 @@ export function getCSRFToken() {
* @param {Object} [options.body] - The request payload, typically used with 'POST', 'PUT', or 'DELETE' requests.
* @param {Object} [options.headers] - Additional HTTP headers to be sent with the request.
* @param {string} [options.method] - The HTTP method to be used for the request (e.g., 'GET', 'POST', etc.).
*
* @param {number} options.debouncedMs - When the fetch should be debounce, this defines the delay
* @returns {Object} An object containing several reactive properties and a method for performing the fetch operation:
* @returns {Ref<Object|null>} return.data - A ref object containing the response data from the fetch operation.
* @returns {Ref<Object|null>} return.validationError - A ref object containing validation error data, relevant when `expectValidationError` is true.
Expand Down Expand Up @@ -62,7 +64,7 @@ export function useFetch(url, options = {}) {

let lastRequestController = null;

async function fetch() {
async function _fetch() {
if (lastRequestController) {
// abort in-flight request
lastRequestController.abort();
Expand Down Expand Up @@ -123,6 +125,10 @@ export function useFetch(url, options = {}) {
}
}

let fetch = _fetch;
if (options.debouncedMs) {
fetch = useDebounceFn(_fetch);
}
return {
data,
isSuccess,
Expand Down
21 changes: 15 additions & 6 deletions src/managers/FileManager/fileManagerStore.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {defineComponentStore} from '@/utils/defineComponentStore';

import {ref, computed} from 'vue';
import {ref, computed, watch} from 'vue';
import {useFetch} from '@/composables/useFetch';
import {useUrl} from '@/composables/useUrl';
import {useFileManagerActions} from './useFileManagerActions';
Expand All @@ -26,16 +26,25 @@ export const useFileManagerStore = defineComponentStore(
`submissions/${submissionId.value}/files`,
);

const queryParams = computed(() => ({
fileStages: managerConfig.value.fileStage,
reviewRoundIds: props.reviewRoundId ? props.reviewRoundId : undefined,
}));

const {data, fetch: fetchFiles} = useFetch(filesApiUrl, {
query: {
fileStages: managerConfig.value.fileStage,
reviewRoundIds: props.reviewRoundId ? props.reviewRoundId : undefined,
},
query: queryParams,
});

const files = computed(() => data.value?.items || []);

fetchFiles();
watch(
[filesApiUrl, queryParams],
() => {
files.value = null;
fetchFiles();
},
{immediate: true},
);

/** Reload files when data on screen changes */
const {triggerDataChange} = useDataChanged(() => fetchFiles());
Expand Down
5 changes: 3 additions & 2 deletions src/managers/GalleyManager/useGalleyManagerConfiguration.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {useLocalize} from '@/composables/useLocalize';
import {useApp} from '@/composables/useApp';

export function useGalleyManagerConfiguration() {
const {t} = useLocalize();

const {isOPS} = useApp();
function getGalleyGridComponent() {
if (pkp.context.app === 'ops') {
if (isOPS()) {
return 'grid.preprintGalleys.PreprintGalleyGridHandler';
} else {
return 'grid.articleGalleys.ArticleGalleyGridHandler';
Expand Down
2 changes: 2 additions & 0 deletions src/pages/dashboard/dashboardPageStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ export const useDashboardPageStore = defineComponentStore(
currentPage,
pageSize: countPerPage,
query: submissionsQuery,
// to avoid multiple fetch calls while view changing watchers triggers query params recalculation
debouncedMs: 2,
});
watch(
[submissionsUrl, submissionsQuery, currentPage],
Expand Down
90 changes: 90 additions & 0 deletions src/pages/workflow/components/primary/WorkflowListingEmails.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<template>
<div v-if="emails?.length" class="border border-light">
<h3 class="lg-bold m-3 text-heading">
{{ t('notification.notifications') }}
</h3>
<ul>
<li
v-for="email in emails"
:key="email.id"
class="flex items-center border-t border-light px-3 py-1"
>
<span class="flex-1 truncate">
<a
class="text cursor-pointer text-base-normal hover:underline"
@click.prevent="openEmail(email.id)"
>
{{ email.subject }}
</a>
</span>
<span class="ms-4 shrink-0 text-base-normal text-secondary">
{{ formatDateAndTime(email.dateSent) }}
</span>
</li>
</ul>
</div>
</template>

<script setup>
import {computed, watch} from 'vue';
import {useUrl} from '@/composables/useUrl';
import {useFetch} from '@/composables/useFetch';
import {useDate} from '@/composables/useDate';
import {useModal} from '@/composables/useModal';
import {useLocalize} from '@/composables/useLocalize';
const props = defineProps({
submission: {type: Object, required: true},
selectedStageId: {type: Number, required: true},
});
const {t} = useLocalize();
const {formatDateAndTime} = useDate();
const {apiUrl} = useUrl('emails/authorEmails');
const requestQuery = computed(() => {
if (props.selectedStageId === pkp.const.WORKFLOW_STAGE_ID_EXTERNAL_REVIEW) {
return {
submissionId: props.submission.id,
eventType: pkp.const.EMAIL_LOG_EVENT_TYPE_EDITOR_NOTIFY_AUTHOR,
};
}
return null;
});
const {data: emails, fetch: fetchEmails} = useFetch(apiUrl, {
// currently only used in review stage, this can be extended if used across multiple stages
query: {
submissionId: props.submission.id,
eventType: pkp.const.EMAIL_LOG_EVENT_TYPE_EDITOR_NOTIFY_AUTHOR,
},
});
watch(
requestQuery,
(newRequestQuery) => {
if (newRequestQuery) {
fetchEmails();
}
},
{immediate: true},
);
function openEmail(emailId) {
const {openSideModal} = useModal();
//en/authorDashboard/readSubmissionEmail?submissionId=19&stageId=3&reviewRoundId=13&submissionEmailId=158
//en/authorDashboard/readSubmissionEmail?submissionId19&submissionEmailId=158
const {pageUrl} = useUrl(
`authorDashboard/readSubmissionEmail?submissionId=${props.submission.id}&submissionEmailId=${emailId}`,
);
openSideModal('LegacyAjax', {
legacyOptions: {
title: t('notification.notifications'),
url: pageUrl,
},
});
}
</script>
120 changes: 80 additions & 40 deletions src/pages/workflow/components/primary/WorkflowNotificationDisplay.vue
Original file line number Diff line number Diff line change
@@ -1,52 +1,77 @@
<template>
<div v-for="(notification, i) in notificationsToDisplay" :key="i">
{{ notification.text }}
<div v-if="notificationsToDisplay?.length" class="flex flex-row space-y-3">
<div
v-for="(notification, i) in notificationsToDisplay"
:key="i"
class="w-full border border-light p-3"
>
<h3 class="lg-bold text-heading">{{ notification.title }}</h3>
<p class="pt-2 text-base-normal">{{ notification.text }}</p>
</div>
</div>
</template>
<script setup>
import {computed} from 'vue';
import {computed, watch} from 'vue';
import {useUrl} from '@/composables/useUrl';
import {useFetch} from '@/composables/useFetch';
import {useApp} from '@/composables/useApp';
const props = defineProps({submission: {type: Object, required: true}});
const {pageUrl} = useUrl(`notification/fetchNotification`);
const {isOJS, isOMP} = useApp();
/**
*
*
return [
Notification::NOTIFICATION_LEVEL_NORMAL => [
Notification::NOTIFICATION_TYPE_VISIT_CATALOG => [Application::ASSOC_TYPE_SUBMISSION, $submissionId],
Notification::NOTIFICATION_TYPE_ASSIGN_PRODUCTIONUSER => [Application::ASSOC_TYPE_SUBMISSION, $submissionId],
Notification::NOTIFICATION_TYPE_AWAITING_REPRESENTATIONS => [Application::ASSOC_TYPE_SUBMISSION, $submissionId],
],
Notification::NOTIFICATION_LEVEL_TRIVIAL => []
];
*
*
*/
const requestData = {
requestOptions: {
[pkp.const.NOTIFICATION_LEVEL_NORMAL]: {
[pkp.const.NOTIFICATION_TYPE_VISIT_CATALOG]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
[pkp.const.NOTIFICATION_TYPE_ASSIGN_PRODUCTIONUSER]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
[pkp.const.NOTIFICATION_TYPE_AWAITING_REPRESENTATIONS]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
},
[pkp.const.NOTIFICATION_LEVEL_TRIVIAL]: 0,
},
};
function getRequestOptionsPerStage(stageId) {
switch (stageId) {
case pkp.const.WORKFLOW_STAGE_ID_EDITING:
return {
[pkp.const.NOTIFICATION_LEVEL_NORMAL]: {
[pkp.const.NOTIFICATION_TYPE_ASSIGN_COPYEDITOR]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
[pkp.const.NOTIFICATION_TYPE_AWAITING_COPYEDITS]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
},
[pkp.const.NOTIFICATION_LEVEL_TRIVIAL]: 0,
};
case pkp.const.WORKFLOW_STAGE_ID_PRODUCTION:
if (isOJS()) {
return {
[pkp.const.NOTIFICATION_LEVEL_NORMAL]: {
[pkp.const.NOTIFICATION_TYPE_ASSIGN_PRODUCTIONUSER]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
[pkp.const.NOTIFICATION_TYPE_AWAITING_REPRESENTATIONS]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
},
[pkp.const.NOTIFICATION_LEVEL_TRIVIAL]: 0,
};
} else if (isOMP()) {
return {
[pkp.const.NOTIFICATION_LEVEL_NORMAL]: {
[pkp.const.NOTIFICATION_TYPE_VISIT_CATALOG]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
[pkp.const.NOTIFICATION_TYPE_FORMAT_NEEDS_APPROVED_SUBMISSION]: {
assocType: pkp.const.ASSOC_TYPE_SUBMISSION,
assocId: props.submission.id,
},
},
[pkp.const.NOTIFICATION_LEVEL_TRIVIAL]: 0,
};
}
return null;
default:
return null;
}
}
function objectToFormData(obj, formData = new FormData(), parentKey = '') {
for (const key in obj) {
Expand All @@ -64,12 +89,27 @@ function objectToFormData(obj, formData = new FormData(), parentKey = '') {
return formData;
}
const requestBody = computed(() => {
const requestOptions = getRequestOptionsPerStage(props.submission.stageId);
if (requestOptions) {
return objectToFormData({requestOptions});
}
return null;
});
const {data, fetch} = useFetch(pageUrl, {
method: 'POST',
body: objectToFormData(requestData),
body: requestBody,
});
fetch();
watch(
requestBody,
(requestBodyNew) => {
if (requestBodyNew) {
fetch();
}
},
{immediate: true},
);
const notificationsToDisplay = computed(() => {
const notifications = [];
Expand Down

This file was deleted.

Loading

0 comments on commit 14c7d53

Please sign in to comment.