Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👯‍♀️ Add collaborations to Contributor type #1038

Merged
merged 4 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silent-pans-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'myst-frontmatter': patch
---

Add collaborations to Contributor type
5 changes: 5 additions & 0 deletions .changeset/swift-rocks-flash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'myst-frontmatter': patch
---

Always find a corresponding author unless (1) no email or (2) all corresponding:false
4 changes: 2 additions & 2 deletions packages/myst-frontmatter/src/affiliations/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { stashPlaceholder } from '../utils/referenceStash.js';
import { validateDoi } from '../utils/validators.js';
import type { Affiliation } from './types.js';

const AFFILIATION_KEYS = [
export const AFFILIATION_KEYS = [
'id',
'address',
'city',
Expand All @@ -34,7 +34,7 @@ const AFFILIATION_KEYS = [
'fax',
];

const AFFILIATION_ALIASES = {
export const AFFILIATION_ALIASES = {
ref: 'id', // Used in QMD to reference an affiliation
region: 'state',
province: 'state',
Expand Down
75 changes: 75 additions & 0 deletions packages/myst-frontmatter/src/contributors/authors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,42 @@ cases:
family: Name
email: example@example.com
corresponding: false
- title: Next email is corresponding if first is false
raw:
author:
- id: jn
name: Just A. Name
email: example@example.com
corresponding: false
- id: ne
name: No Email
- id: an
name: A. Nother Name
email: example@example.com
normalized:
authors:
- id: jn
name: Just A. Name
nameParsed:
literal: Just A. Name
given: Just A.
family: Name
email: example@example.com
corresponding: false
- id: ne
name: No Email
nameParsed:
literal: No Email
given: No
family: Email
- id: an
name: A. Nother Name
nameParsed:
literal: A. Nother Name
given: A. Nother
family: Name
email: example@example.com
corresponding: true
- title: Respect corresponding flag
raw:
authors:
Expand Down Expand Up @@ -161,6 +197,45 @@ cases:
family: Name
email: example@example.com
corresponding: true
- title: First email is corresponding (unless collaboration)
raw:
author:
id: my-group
name: Research Group
collaboration: true
email: example@example.com
normalized:
authors:
- id: my-group
name: Research Group
collaboration: true
email: example@example.com
- title: Collaboration cannot be corresponding (first Person gets corresponding flag)
raw:
authors:
- id: my-group
name: Research Group
collaboration: true
email: example@example.com
corresponding: true
- id: jn
name: Just A. Name
email: example@example.com
normalized:
authors:
- id: my-group
name: Research Group
collaboration: true
email: example@example.com
- id: jn
name: Just A. Name
nameParsed:
literal: Just A. Name
given: Just A.
family: Name
email: example@example.com
corresponding: true
warnings: 1
- title: Warn if author has no name
raw:
authors:
Expand Down
83 changes: 83 additions & 0 deletions packages/myst-frontmatter/src/contributors/contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,86 @@ cases:
literal: John Doe
family: Doe
given: John
- title: author as collaboration passes
raw:
authors:
- name: Research Group
collaboration: true
department: Example department
isni: '0000000000000000'
normalized:
authors:
- name: Research Group
collaboration: true
department: Example department
isni: '0000000000000000'
id: contributors-generated-uid-0
- title: author as non-collaboration affiliation fails and suggests collab:true
raw:
authors:
- name: Research Group
department: Example department
isni: '0000000000000000'
field_a: extra
normalized:
authors:
- name: Research Group
id: contributors-generated-uid-0
nameParsed:
literal: Research Group
given: Research
family: Group
warnings: 2
- title: author with totally unknown fields fails and does not suggest collab:true
raw:
authors:
- name: Research Group
field_a: Example department
field_b: '0000000000000000'
normalized:
authors:
- name: Research Group
id: contributors-generated-uid-0
nameParsed:
literal: Research Group
given: Research
family: Group
warnings: 1
- title: reviewer/editor/contributor as collaboration pass
raw:
reviewers:
- name: Research Group One
collaboration: true
department: Example department
isni: '0000000000000000'
editors:
- name: Research Group Two
collaboration: true
department: Example department
isni: '0000000000000000'
contributors:
- name: Research Group Three
collaboration: true
department: Example department
isni: '0000000000000000'
normalized:
reviewers:
- contributors-generated-uid-1
editors:
- contributors-generated-uid-2
contributors:
- name: Research Group Three
id: contributors-generated-uid-0
collaboration: true
department: Example department
isni: '0000000000000000'
- name: Research Group One
id: contributors-generated-uid-1
collaboration: true
department: Example department
isni: '0000000000000000'
- name: Research Group Two
id: contributors-generated-uid-2
collaboration: true
department: Example department
isni: '0000000000000000'
16 changes: 15 additions & 1 deletion packages/myst-frontmatter/src/contributors/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { CreditRole } from 'credit-roles';
import type { Affiliation } from '../affiliations/types.js';

export type ContributorRole = CreditRole | string;

Expand All @@ -11,7 +12,7 @@ export type Name = {
suffix?: string;
};

export interface Contributor {
interface Person {
id?: string;
name?: string; // may be set to Name object
userId?: string;
Expand All @@ -31,3 +32,16 @@ export interface Contributor {
// Computed property; only 'name' should be set in frontmatter as string or Name object
nameParsed?: Name;
}

/**
* Person or Collaboration contributor type
*
* After validation, objects of this type are better represented by:
*
* `Person | (Affiliation & { collaboration: true })`
*
* However, as all the fields are optional and the code must handle cases
* where everything may be undefined anyway, it's simpler to have a more
* permissive type.
*/
export type Contributor = Person & Affiliation;
42 changes: 32 additions & 10 deletions packages/myst-frontmatter/src/contributors/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,21 @@ import {
validationWarning,
} from 'simple-validators';
import { orcid } from 'orcid';
import { validateAffiliation } from '../affiliations/validators.js';
import {
AFFILIATION_ALIASES,
AFFILIATION_KEYS,
validateAffiliation,
} from '../affiliations/validators.js';
import { formatName, parseName } from '../utils/parseName.js';
import type { ReferenceStash } from '../utils/referenceStash.js';
import {
isStashPlaceholder,
stashPlaceholder,
validateAndStashObject,
} from '../utils/referenceStash.js';
import type { Contributor, Name } from './types.js';
import { formatName, parseName } from '../utils/parseName.js';

const CONTRIBUTOR_KEYS = [
const PERSON_KEYS = [
'id',
'userId',
'name',
Expand All @@ -43,7 +47,7 @@ const CONTRIBUTOR_KEYS = [
'phone',
'fax',
];
const CONTRIBUTOR_ALIASES = {
const PERSON_ALIASES = {
ref: 'id', // Used in QMD to reference a contributor
role: 'roles',
'equal-contributor': 'equal_contributor',
Expand Down Expand Up @@ -140,16 +144,34 @@ export function validateName(input: any, opts: ValidationOptions) {
/**
* Validate Contributor object against the schema
*/
export function validateContributor(input: any, stash: ReferenceStash, opts: ValidationOptions) {
export function validateContributor(
input: any,
stash: ReferenceStash,
opts: ValidationOptions,
): Contributor | undefined {
const inputAff = validateObjectKeys(
input,
{ optional: AFFILIATION_KEYS, alias: AFFILIATION_ALIASES },
{
...opts,
suppressErrors: true,
suppressWarnings: true,
},
);
if (inputAff?.collaboration === true) {
return validateAffiliation(input, opts);
}
if (typeof input === 'string') {
input = stashPlaceholder(input);
}
const value = validateObjectKeys(
input,
{ optional: CONTRIBUTOR_KEYS, alias: CONTRIBUTOR_ALIASES },
opts,
);
const value = validateObjectKeys(input, { optional: PERSON_KEYS, alias: PERSON_ALIASES }, opts);
if (value === undefined) return undefined;
if (inputAff && Object.keys(inputAff).length > Object.keys(value).length) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice little check that says "Your input looks more like an affiliation than a person."

validationWarning(
'contributor may be a collaboration, not a person - if so, add "collaboration: true"',
opts,
);
}
// If contributor only has an id, give it a matching name; this is equivalent to the case
// where a simple string is provided as a contributor.
if (Object.keys(value).length === 1 && value.id) {
Expand Down
10 changes: 6 additions & 4 deletions packages/myst-frontmatter/src/site/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,12 @@ export function validateSiteFrontmatterKeys(value: Record<string, any>, opts: Va
if (stashContribAuthors?.length) {
output.authors = stashContribAuthors;
// Ensure there is a corresponding author if an email is provided
const corresponding = output.authors?.find((a) => a.corresponding !== undefined);
const email = output.authors?.find((a) => a.email);
if (!corresponding && email) {
email.corresponding = true;
const correspondingAuthor = output.authors?.find((a) => a.corresponding);
const personWithEmail = output.authors?.find(
(a) => a.email && !a.collaboration && a.corresponding === undefined,
);
if (!correspondingAuthor && personWithEmail) {
personWithEmail.corresponding = true;
}
}
if (stashContribNonAuthors?.length) {
Expand Down
Loading