Skip to content

Commit 7a7d225

Browse files
committed
Merge branch 'release-0.14.1'
2 parents 8cdd9ee + 18f610a commit 7a7d225

39 files changed

+71615
-343
lines changed

packages/pmc/CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,38 @@
99
- 535d5df: Aligning to latest `@curvenote/common`
1010
- 535d5df: Fixed lint command and applied it
1111

12+
## 0.1.16
13+
14+
### Patch Changes
15+
16+
- 7a83694: Limit the Request New Version transition from certain states
17+
- f42cdda: Fixes to workflow sync webhook and improvements in Workflow Sync UI to display more error info and indicate how a job was started
18+
19+
## 0.1.15
20+
21+
### Patch Changes
22+
23+
- 7368b9d: Adding a confirmation dialog for PMC Admin inbox actions
24+
- 7368b9d: Submitters will automatically receive an email when a PMC admin presses "Request New Version"
25+
26+
## 0.1.14
27+
28+
### Patch Changes
29+
30+
- 4c32dda: Made a fix to ensure that inbound submitters files requested emails always cause an auto transition to request new version. Added unit tests.
31+
- a9b438d: Auto transition from PENDING to send_to_pmc without user interaction
32+
- a9b438d: Send and email when a new PMC deposit is made
33+
- a7c978b: Changes to the language around Funding Ids, removing the "grants" from the UI
34+
- 4a1661e: Moved all emails to templates
35+
- 05119e8: Simplified breadcrumbs on deposit and preview pages, linking back to Home at the root link.
36+
- e9a8326: When a deposit is rejected with the message that it should be submited directly by the journal, it will transition to a "No Action Needed" end state
37+
38+
## 0.1.13
39+
40+
### Patch Changes
41+
42+
- 469d8da: Workflow sync will not update certain skipped statuses that are arraved at through workspace interactions e.g. Request New Version or No Action needed, preventing the data form PMC to override these statuses
43+
1244
## 0.1.0
1345

1446
### Minor Changes

packages/pmc/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
"dev": "tsc -w",
4949
"compile": "tsc --noEmit",
5050
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
51+
"test": "vitest run",
52+
"test:unit": "vitest run",
53+
"test:watch": "vitest",
54+
"test:ci": "vitest run --reporter=verbose",
5155
"changeset": "changeset",
5256
"version": "changeset version && npm install"
5357
},

packages/pmc/src/backend/email/handlers/bulk-submission-parser.server.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ export interface PackageResult {
1919
message?: string;
2020
}
2121

22+
/** Phrase that indicates the "direct submission only" error from PMC */
23+
const DIRECT_SUBMISSION_ONLY_PHRASE = 'should be submitted directly to PMC';
24+
25+
/**
26+
* Returns true if the error message indicates that the journal requires
27+
* direct submission to PMC (bulk submission not accepted).
28+
*/
29+
export function isDirectSubmissionOnlyError(message: string | undefined): boolean {
30+
if (!message || typeof message !== 'string') return false;
31+
return message.toLowerCase().includes(DIRECT_SUBMISSION_ONLY_PHRASE.toLowerCase());
32+
}
33+
2234
/**
2335
* Extracts package ID from text using regex patterns
2436
*/

packages/pmc/src/backend/email/handlers/bulk-submission.server.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import type {
55
ProcessingResult,
66
EmailProcessorConfig,
77
} from '../types.server.js';
8-
import { parseEmailContent, type ParsedEmailResult } from './bulk-submission-parser.server.js';
8+
import {
9+
parseEmailContent,
10+
isDirectSubmissionOnlyError,
11+
type ParsedEmailResult,
12+
} from './bulk-submission-parser.server.js';
913
import { updateSubmissionMetadataAndStatusIfChanged } from '../email-db.server.js';
1014

1115
/**
@@ -86,8 +90,9 @@ export const bulkSubmissionHandler: InboundEmailHandler = {
8690
// Process each package independently
8791
for (const packageResult of parsedResult.packages) {
8892
try {
89-
// Determine the target status based on result type
90-
let targetStatus: 'DEPOSIT_CONFIRMED_BY_PMC' | 'DEPOSIT_REJECTED_BY_PMC';
93+
// Determine the target status based on result type (errors always go to DEPOSIT_REJECTED_BY_PMC first)
94+
type BulkTargetStatus = 'DEPOSIT_CONFIRMED_BY_PMC' | 'DEPOSIT_REJECTED_BY_PMC';
95+
let targetStatus: BulkTargetStatus;
9196
if (packageResult.status === 'success' || packageResult.status === 'warning') {
9297
targetStatus = 'DEPOSIT_CONFIRMED_BY_PMC';
9398
} else if (packageResult.status === 'error') {
@@ -96,7 +101,7 @@ export const bulkSubmissionHandler: InboundEmailHandler = {
96101
throw new Error(`Unknown package status: ${packageResult.status}`);
97102
}
98103

99-
// Update metadata and status if changed (skips if already at target status)
104+
// First transition: update metadata and status (e.g. to DEPOSIT_REJECTED_BY_PMC for errors)
100105
await updateSubmissionMetadataAndStatusIfChanged(
101106
ctx,
102107
packageResult.packageId,
@@ -106,6 +111,22 @@ export const bulkSubmissionHandler: InboundEmailHandler = {
106111
'bulk-submission-initial-email',
107112
);
108113

114+
// For "direct submission only" errors, make a second transition to NO_ACTION_NEEDED
115+
// so we pass through DEPOSIT_REJECTED_BY_PMC and both status changes are recorded as activities
116+
if (
117+
packageResult.status === 'error' &&
118+
isDirectSubmissionOnlyError(packageResult.message)
119+
) {
120+
await updateSubmissionMetadataAndStatusIfChanged(
121+
ctx,
122+
packageResult.packageId,
123+
packageResult,
124+
messageId,
125+
'NO_ACTION_NEEDED',
126+
'bulk-submission-initial-email',
127+
);
128+
}
129+
109130
// TODO: if we are going to send an email to the submitter
110131
// we would prepare the content and queue it here
111132

packages/pmc/src/backend/email/handlers/compose-files-request-email.tsx

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import { describe, it, expect } from 'vitest';
3+
import { extractManuscriptId } from './email-parsing-utils.server.js';
4+
5+
describe('email-parsing-utils', () => {
6+
describe('extractManuscriptId', () => {
7+
it.each<{ input: string; expected: string | null; description?: string }>([
8+
// NIHMS pattern (checked first)
9+
{
10+
input: 'NIHMS12345',
11+
expected: '12345',
12+
description: 'NIHMS prefix with no space',
13+
},
14+
{
15+
input: 'NIHMS 2109555',
16+
expected: '2109555',
17+
description: 'NIHMS with space before digits',
18+
},
19+
{
20+
input: 'Your submission (NIHMS2109555) has been received',
21+
expected: '2109555',
22+
description: 'NIHMS in parentheses in sentence',
23+
},
24+
{
25+
input: 'nihms99999',
26+
expected: '99999',
27+
description: 'lowercase nihms',
28+
},
29+
// Manuscript ID patterns
30+
{
31+
input: 'Package ID=191e5fc4eea for Manuscript ID 1502493 was submitted',
32+
expected: '1502493',
33+
description: '"for Manuscript ID" with numeric ID',
34+
},
35+
{
36+
input: 'Manuscript ID 1502493',
37+
expected: '1502493',
38+
description: '"Manuscript ID" with numeric ID',
39+
},
40+
{
41+
input: 'Manuscript ID ABC123',
42+
expected: 'ABC123',
43+
description: '"Manuscript ID" with alphanumeric ID',
44+
},
45+
{
46+
input: 'for Manuscript ID XYZ789',
47+
expected: 'XYZ789',
48+
description: '"for Manuscript ID" with alphanumeric ID',
49+
},
50+
{
51+
input: 'Manuscript\tID\t12345',
52+
expected: '12345',
53+
description: 'tab-separated Manuscript ID',
54+
},
55+
{
56+
input: 'Manuscript\nID\n99999',
57+
expected: '99999',
58+
description: 'newline-separated Manuscript ID',
59+
},
60+
{
61+
input: ' against allergy." (NIHMS2146980) Dear Howard Hughes Medical Institute, The cap',
62+
expected: '2146980',
63+
description: 'Real-world example from NIHMS files request email',
64+
},
65+
// No match / edge cases
66+
{
67+
input: '',
68+
expected: null,
69+
description: 'empty string',
70+
},
71+
{
72+
input: 'No manuscript or NIHMS here',
73+
expected: null,
74+
description: 'no matching pattern',
75+
},
76+
{
77+
input: 'Manuscript ID ', // no ID after
78+
expected: null,
79+
description: 'Manuscript ID with no value',
80+
},
81+
])('$description', ({ input, expected }) => {
82+
expect(extractManuscriptId(input)).toBe(expected);
83+
});
84+
});
85+
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// eslint-disable-next-line import/no-extraneous-dependencies
2+
import { describe, it, expect } from 'vitest';
3+
import { nihmsFilesRequestHandler } from './nihms-files-request.server.js';
4+
5+
describe('nihms-files-request handler', () => {
6+
describe('identify', () => {
7+
it.each<{
8+
payload: { headers?: { subject?: string }; [key: string]: unknown };
9+
expected: boolean;
10+
description: string;
11+
}>([
12+
// Recognized: "please upload" before "to nihms"
13+
{
14+
payload: {
15+
headers: { subject: 'Please upload missing file(s) to NIHMS' },
16+
},
17+
expected: true,
18+
description: 'Real-world example from NIHMS files request email',
19+
},
20+
{
21+
payload: {
22+
headers: { subject: 'Please upload missing MDAR Reproducibility Checklist to NIHMS' },
23+
},
24+
expected: true,
25+
description: 'Real-world example from NIHMS files request email',
26+
},
27+
{
28+
payload: {
29+
headers: { subject: 'Please upload missing supplementary material to NIHMS' },
30+
},
31+
expected: true,
32+
description: 'Real-world example from NIHMS files request email',
33+
},
34+
{
35+
payload: {
36+
headers: { subject: 'Please upload missing figure to NIHMS' },
37+
},
38+
expected: true,
39+
description: 'Real-world example from NIHMS files request email',
40+
},
41+
{
42+
payload: {
43+
headers: { subject: 'Please upload missing Tables S1-S30 to NIHMS' },
44+
},
45+
expected: true,
46+
description: 'Real-world example from NIHMS files request email',
47+
},
48+
{
49+
payload: { headers: { subject: 'Please upload additional files to NIHMS' } },
50+
expected: true,
51+
description: 'subject with "Please upload ... to NIHMS"',
52+
},
53+
{
54+
payload: {
55+
headers: { subject: 'Please upload supplementary materials to NIHMS for review' },
56+
},
57+
expected: true,
58+
description: 'subject with extra text after "to NIHMS"',
59+
},
60+
{
61+
payload: {
62+
headers: { subject: 'PLEASE UPLOAD files TO NIHMS' },
63+
},
64+
expected: true,
65+
description: 'subject is case-insensitive',
66+
},
67+
{
68+
payload: {
69+
envelope: { from: 'other@example.com' },
70+
headers: { subject: 'Please upload additional files to NIHMS' },
71+
},
72+
expected: true,
73+
description: 'identifies regardless of envelope/sender',
74+
},
75+
{
76+
payload: {
77+
headers: { subject: 'Re: Please upload files to NIHMS' },
78+
},
79+
expected: true,
80+
description: 'subject with Re: prefix still matches',
81+
},
82+
// Rejected: missing or wrong structure
83+
{
84+
payload: {},
85+
expected: false,
86+
description: 'no headers',
87+
},
88+
{
89+
payload: { headers: {} },
90+
expected: false,
91+
description: 'headers but no subject',
92+
},
93+
{
94+
payload: { headers: { subject: '' } },
95+
expected: false,
96+
description: 'empty subject',
97+
},
98+
{
99+
payload: { headers: { subject: 'To NIHMS please upload files' } },
100+
expected: false,
101+
description: '"to nihms" before "please upload"',
102+
},
103+
{
104+
payload: { headers: { subject: 'Please upload something' } },
105+
expected: false,
106+
description: '"please upload" without "to nihms"',
107+
},
108+
{
109+
payload: { headers: { subject: 'To NIHMS only' } },
110+
expected: false,
111+
description: '"to nihms" without "please upload"',
112+
},
113+
{
114+
payload: { headers: { subject: 'Unrelated subject line' } },
115+
expected: false,
116+
description: 'subject with no matching phrases',
117+
},
118+
])('$description', ({ payload, expected }) => {
119+
expect(nihmsFilesRequestHandler.identify(payload)).toBe(expected);
120+
});
121+
});
122+
});

0 commit comments

Comments
 (0)