hotfix (dataset): implement reject to reviewer functionality for editors
- Added "rejected_to_reviewer" state to the `ServerStates` enum. - Implemented routes and controller actions (`rejectToReviewer`, `rejectToReviewerUpdate`) for editors to reject datasets back to reviewers with a rejection note. - Added UI elements (button) in the editor dataset index and publish views to trigger the "reject to reviewer" action. - Updated the reviewer dataset index view to display datasets in the "rejected_to_reviewer" state and show the editor's rejection note. - Modified the reviewer dataset review page to allow reviewers to view and accept datasets that have been rejected back to them by editors. - Updated the database migration to include the "rejected_to_reviewer" state in the `documents_server_state_check` constraint. - Updated dependencies (pinia, redis).
This commit is contained in:
parent
c245c8e97d
commit
be6b38d0a3
15 changed files with 1647 additions and 404 deletions
|
@ -248,6 +248,10 @@ export default class DatasetsController {
|
|||
if (dataset.reject_reviewer_note != null) {
|
||||
dataset.reject_reviewer_note = null;
|
||||
}
|
||||
if (dataset.reject_editor_note != null) {
|
||||
dataset.reject_editor_note = null;
|
||||
}
|
||||
|
||||
|
||||
//save main and additional titles
|
||||
const reviewer_id = request.input('reviewer_id', null);
|
||||
|
@ -286,6 +290,8 @@ export default class DatasetsController {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async rejectUpdate({ request, response, auth }: HttpContext) {
|
||||
const authUser = auth.user!;
|
||||
|
||||
|
@ -372,7 +378,7 @@ export default class DatasetsController {
|
|||
.toRoute('editor.dataset.list');
|
||||
}
|
||||
|
||||
public async publish({ request, inertia, response }: HttpContext) {
|
||||
public async publish({ request, inertia, response, auth }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
|
||||
const dataset = await Dataset.query()
|
||||
|
@ -396,8 +402,14 @@ export default class DatasetsController {
|
|||
.back();
|
||||
}
|
||||
|
||||
|
||||
|
||||
return inertia.render('Editor/Dataset/Publish', {
|
||||
dataset,
|
||||
can: {
|
||||
reject: await auth.user?.can(['dataset-editor-reject']),
|
||||
publish: await auth.user?.can(['dataset-publish']),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -439,6 +451,119 @@ export default class DatasetsController {
|
|||
}
|
||||
}
|
||||
|
||||
public async rejectToReviewer({ request, inertia, response }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const dataset = await Dataset.query()
|
||||
.where('id', id)
|
||||
.preload('reviewer', (builder) => {
|
||||
builder.select('id', 'login', 'email');
|
||||
})
|
||||
.firstOrFail();
|
||||
|
||||
const validStates = ['reviewed'];
|
||||
if (!validStates.includes(dataset.server_state)) {
|
||||
// session.flash('errors', 'Invalid server state!');
|
||||
return response
|
||||
.flash(
|
||||
'warning',
|
||||
`Invalid server state. Dataset with id ${id} cannot be rejected to the reviewer. Datset has server state ${dataset.server_state}.`,
|
||||
)
|
||||
.redirect()
|
||||
.toRoute('editor.dataset.list');
|
||||
}
|
||||
|
||||
return inertia.render('Editor/Dataset/RejectToReviewer', {
|
||||
dataset,
|
||||
});
|
||||
}
|
||||
|
||||
public async rejectToReviewerUpdate({ request, response, auth }: HttpContext) {
|
||||
const authUser = auth.user!;
|
||||
|
||||
const id = request.param('id');
|
||||
const dataset = await Dataset.query()
|
||||
.where('id', id)
|
||||
.preload('reviewer', (builder) => {
|
||||
builder.select('id', 'login', 'email');
|
||||
})
|
||||
.firstOrFail();
|
||||
|
||||
const newSchema = vine.object({
|
||||
server_state: vine.string().trim(),
|
||||
reject_editor_note: vine.string().trim().minLength(10).maxLength(500),
|
||||
send_mail: vine.boolean().optional(),
|
||||
});
|
||||
|
||||
try {
|
||||
// await request.validate({ schema: newSchema });
|
||||
const validator = vine.compile(newSchema);
|
||||
await request.validateUsing(validator);
|
||||
} catch (error) {
|
||||
// return response.badRequest(error.messages);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const validStates = ['reviewed'];
|
||||
if (!validStates.includes(dataset.server_state)) {
|
||||
// throw new Error('Invalid server state!');
|
||||
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||
return response
|
||||
.flash(
|
||||
`Invalid server state. Dataset with id ${id} cannot be rejected to reviewer. Datset has server state ${dataset.server_state}.`,
|
||||
'warning',
|
||||
)
|
||||
.redirect()
|
||||
.toRoute('editor.dataset.list');
|
||||
}
|
||||
|
||||
dataset.server_state = 'rejected_to_reviewer';
|
||||
const rejectEditorNote = request.input('reject_editor_note', '');
|
||||
dataset.reject_editor_note = rejectEditorNote;
|
||||
|
||||
// add logic for sending reject message
|
||||
const sendMail = request.input('send_email', false);
|
||||
// const validRecipientEmail = await this.checkEmailDomain('arno.kaimbacher@outlook.at');
|
||||
const validationResult = await validate({
|
||||
email: dataset.reviewer.email,
|
||||
validateSMTP: false,
|
||||
});
|
||||
const validRecipientEmail: boolean = validationResult.valid;
|
||||
|
||||
await dataset.save();
|
||||
|
||||
let emailStatusMessage = '';
|
||||
if (sendMail == true) {
|
||||
if (dataset.reviewer.email && validRecipientEmail) {
|
||||
try {
|
||||
await mail.send((message) => {
|
||||
message.to(dataset.reviewer.email).subject('Dataset Rejection Notification').html(`
|
||||
<p>Dear ${dataset.reviewer.login},</p>
|
||||
<p>Your dataset with ID ${dataset.id} has been rejected.</p>
|
||||
<p>Reason for rejection: ${rejectEditorNote}</p>
|
||||
<p>Best regards,<br>Your Tethys editor: ${authUser.login}</p>
|
||||
`);
|
||||
});
|
||||
emailStatusMessage = ` A rejection email was successfully sent to ${dataset.reviewer.email}.`;
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return response
|
||||
.flash('Dataset has not been rejected due to an email error: ' + error.message, 'error')
|
||||
.toRoute('editor.dataset.list');
|
||||
}
|
||||
} else {
|
||||
emailStatusMessage = ` However, the email could not be sent because the submitter's email address (${dataset.reviewer.email}) is not valid.`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return response
|
||||
.flash(
|
||||
`You have successfully rejected dataset ${dataset.id} reviewed by ${dataset.reviewer.login}.${emailStatusMessage}`,
|
||||
'message',
|
||||
)
|
||||
.toRoute('editor.dataset.list');
|
||||
}
|
||||
|
||||
public async doiCreate({ request, inertia }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const dataset = await Dataset.query()
|
||||
|
|
|
@ -9,6 +9,7 @@ import vine from '@vinejs/vine';
|
|||
import mail from '@adonisjs/mail/services/main';
|
||||
import logger from '@adonisjs/core/services/logger';
|
||||
import { validate } from 'deep-email-validator';
|
||||
import File from '#models/file';
|
||||
|
||||
interface Dictionary {
|
||||
[index: string]: string;
|
||||
|
@ -38,13 +39,21 @@ export default class DatasetsController {
|
|||
}
|
||||
datasets.orderBy(attribute, sortOrder);
|
||||
} else {
|
||||
// users.orderBy('created_at', 'desc');
|
||||
datasets.orderBy('id', 'asc');
|
||||
// datasets.orderBy('id', 'asc');
|
||||
// Custom ordering to prioritize rejected_editor state
|
||||
datasets.orderByRaw(`
|
||||
CASE
|
||||
WHEN server_state = 'rejected_to_reviewer' THEN 0
|
||||
ELSE 1
|
||||
END ASC,
|
||||
id ASC
|
||||
`);
|
||||
}
|
||||
|
||||
// const users = await User.query().orderBy('login').paginate(page, limit);
|
||||
const myDatasets = await datasets
|
||||
.where('server_state', 'approved')
|
||||
// .where('server_state', 'approved')
|
||||
.whereIn('server_state', ['approved', 'rejected_to_reviewer'])
|
||||
.where('reviewer_id', user.id)
|
||||
|
||||
.preload('titles')
|
||||
|
@ -63,6 +72,51 @@ export default class DatasetsController {
|
|||
}
|
||||
|
||||
public async review({ request, inertia, response, auth }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const datasetQuery = Dataset.query().where('id', id);
|
||||
|
||||
datasetQuery
|
||||
.preload('titles', (query) => query.orderBy('id', 'asc'))
|
||||
.preload('descriptions', (query) => query.orderBy('id', 'asc'))
|
||||
.preload('coverage')
|
||||
.preload('licenses')
|
||||
.preload('authors', (query) => query.orderBy('pivot_sort_order', 'asc'))
|
||||
.preload('contributors', (query) => query.orderBy('pivot_sort_order', 'asc'))
|
||||
// .preload('subjects')
|
||||
.preload('subjects', (builder) => {
|
||||
builder.orderBy('id', 'asc').withCount('datasets');
|
||||
})
|
||||
.preload('references')
|
||||
.preload('project')
|
||||
.preload('files', (query) => {
|
||||
query.orderBy('sort_order', 'asc'); // Sort by sort_order column
|
||||
});
|
||||
|
||||
const dataset = await datasetQuery.firstOrFail();
|
||||
|
||||
const validStates = ['approved', 'rejected_to_reviewer'];
|
||||
if (!validStates.includes(dataset.server_state)) {
|
||||
// session.flash('errors', 'Invalid server state!');
|
||||
return response
|
||||
.flash(
|
||||
'warning',
|
||||
`Invalid server state. Dataset with id ${id} cannot be reviewed. Datset has server state ${dataset.server_state}.`,
|
||||
)
|
||||
.redirect()
|
||||
.toRoute('reviewer.dataset.list');
|
||||
}
|
||||
|
||||
return inertia.render('Reviewer/Dataset/Review', {
|
||||
dataset,
|
||||
can: {
|
||||
review: await auth.user?.can(['dataset-review']),
|
||||
reject: await auth.user?.can(['dataset-review-reject']),
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public async review_old({ request, inertia, response, auth }: HttpContext) {
|
||||
const id = request.param('id');
|
||||
const dataset = await Dataset.query()
|
||||
.where('id', id)
|
||||
|
@ -170,7 +224,7 @@ export default class DatasetsController {
|
|||
// const { id } = params;
|
||||
const dataset = await Dataset.findOrFail(id);
|
||||
|
||||
const validStates = ['approved'];
|
||||
const validStates = ['approved', 'rejected_to_reviewer'];
|
||||
if (!validStates.includes(dataset.server_state)) {
|
||||
// throw new Error('Invalid server state!');
|
||||
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||
|
@ -184,6 +238,10 @@ export default class DatasetsController {
|
|||
}
|
||||
|
||||
dataset.server_state = 'reviewed';
|
||||
// if editor has rejected to reviewer:
|
||||
if (dataset.reject_editor_note != null) {
|
||||
dataset.reject_editor_note = null;
|
||||
}
|
||||
|
||||
try {
|
||||
// await dataset.related('editor').associate(user); // speichert schon ab
|
||||
|
@ -207,7 +265,7 @@ export default class DatasetsController {
|
|||
})
|
||||
.firstOrFail();
|
||||
|
||||
const validStates = ['approved'];
|
||||
const validStates = ['approved', 'rejected_to_reviewer'];
|
||||
if (!validStates.includes(dataset.server_state)) {
|
||||
// session.flash('errors', 'Invalid server state!');
|
||||
return response
|
||||
|
@ -254,12 +312,12 @@ export default class DatasetsController {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const validStates = ['approved'];
|
||||
const validStates = ['approved', 'rejected_to_reviewer'];
|
||||
if (!validStates.includes(dataset.server_state)) {
|
||||
// throw new Error('Invalid server state!');
|
||||
// return response.flash('warning', 'Invalid server state. Dataset cannot be released to editor').redirect().back();
|
||||
return response
|
||||
.flash(
|
||||
.flash(
|
||||
`Invalid server state. Dataset with id ${id} cannot be rejected. Datset has server state ${dataset.server_state}.`,
|
||||
'warning',
|
||||
)
|
||||
|
@ -311,4 +369,17 @@ export default class DatasetsController {
|
|||
.toRoute('reviewer.dataset.list')
|
||||
.flash(`You have rejected dataset ${dataset.id}! to editor ${dataset.editor.login}`, 'message');
|
||||
}
|
||||
|
||||
public async download({ params, response }: HttpContext) {
|
||||
const id = params.id;
|
||||
// Find the file by ID
|
||||
const file = await File.findOrFail(id);
|
||||
// const filePath = await drive.use('local').getUrl('/'+ file.filePath)
|
||||
const filePath = file.filePath;
|
||||
const fileExt = file.filePath.split('.').pop() || '';
|
||||
// Set the response headers and download the file
|
||||
response.header('Content-Type', file.mime_type || 'application/octet-stream');
|
||||
response.attachment(`${file.label}.${fileExt}`);
|
||||
return response.download(filePath);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue