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

Prefill annotation classes with values #140

Merged
merged 15 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion src/app/Boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Callback = (bootState: {

/**
* Function that runs the application's necessary processes before it can be booted
* @returns True or false depening on if is has booted
* @returns True or false depening on if it has booted
*/
const Boot = (callback: Callback) => {
/* Initiate keycloak which will render the root after finishing setting up */
Expand Down
47 changes: 39 additions & 8 deletions src/app/utilities/AnnotateUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import jp from 'jsonpath';
import { cloneDeep, isEmpty, toLower } from 'lodash';

/* Import Utilities */
import { CheckForClassDefaultValues } from './ClassAnnotationUtilities';
import { ExtractLowestLevelSchema, ExtractClassesAndTermsFromSchema, MakeJsonPathReadableString } from 'app/utilities/SchemaUtilities';

/* Import Types */
Expand Down Expand Up @@ -182,7 +183,7 @@ const FormatJsonPathFromFieldName = (fieldName: string): string => {
* @param schemaName The name of the base schema
* @returns The annotation form field properties and their associated form values
*/
const GenerateAnnotationFormFieldProperties = async (jsonPath: string, superClass: SuperClass, schemaName: string) => {
const GenerateAnnotationFormFieldProperties = async (jsonPath: string, superClass: SuperClass, schemaName: string, motivation: string) => {
const annotationFormFieldProperties: {
[propertyName: string]: AnnotationFormProperty
} = {};
Expand All @@ -208,14 +209,33 @@ const GenerateAnnotationFormFieldProperties = async (jsonPath: string, superClas

/**
* Function to check for a default value for a term
*
* @param key The term key to check
*/
const CheckForTermDefaultValue = (key: string) => {
if (toLower(key).includes('date')) {
return format(new Date, 'yyyy-MM-dd');
}
};

/**
* Function to depict the values to be set for the class to be annotated
* @param fieldName The field name of the class (parsed from JSON path)
* @param classValues Possible existing class values from the super class
* @param isArray Boolean indicating if the class represents an array
* @param localClassValues Possible existing local class values of an annotation
*/
const DepictClassValues = (fieldName: string, classValues: Dict | Dict[] | undefined, isArray?: boolean, localClassValues?: Dict[]) => {
if (classValues) {
return classValues;
} else if (!isEmpty(localClassValues)) {
return localClassValues;
} else if (motivation !== 'oa:editing' && isArray) {
TomDijkema marked this conversation as resolved.
Show resolved Hide resolved
return [CheckForClassDefaultValues(fieldName)];
} else if (motivation !== 'oa:editing') {
return CheckForClassDefaultValues(fieldName) ?? {};
}
};

/* For each class, add it as a key property to the annotation form fields dictionary */
classesList.forEach(classProperty => {
if (!termValue) {
Expand All @@ -240,21 +260,32 @@ const GenerateAnnotationFormFieldProperties = async (jsonPath: string, superClas
}
});

formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = classFormValues ?? {};
formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = {};

/* Check if (default) values are present, if so, set form values property with them */
formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = DepictClassValues(
classProperty.value.replace(`$`, jsonPath), classFormValues
);

} else if (classProperty.value.includes('has') && classProperty.value.at(-3) === 's') {
const localClassValues: Dict[] | undefined = classValues ?? [];
let localClassValues: Dict[] = classValues ?? [];

if (!classValues) {
const parentFieldName: string = classProperty.value.replace(`$`, jsonPath).split('[').slice(0, -1).join('[');
const parentValues: Dict | undefined = jp.value(superClass, parentFieldName);
const childFieldName: string = classProperty.value.split('[').pop()?.replace(']', '').replaceAll("'", '') ?? '';

PushToLocalClassValues(parentValues, childFieldName, (value: Dict) => localClassValues.push(value));
PushToLocalClassValues(parentValues, childFieldName, (value: Dict) => localClassValues?.push(value));
}

formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = classValues ?? localClassValues ?? [];
formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = [];

/* Check if (default) values are present, if so, set form values property with them */
formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = DepictClassValues(
classProperty.value.replace(`$`, jsonPath), classValues, true, localClassValues
);
} else {
formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = {};
formValues[FormatFieldNameFromJsonPath(classProperty.value.replace(`$`, jsonPath))] = CheckForClassDefaultValues(classProperty.value.replace(`$`, jsonPath)) ?? {};
}

/* For each term of class, add it to the properties array of the corresponding class in the annotation form fields dictionary */
Expand All @@ -271,7 +302,7 @@ const GenerateAnnotationFormFieldProperties = async (jsonPath: string, superClas
});

/* Check if the term field needs to be filled with a default value */
if (CheckForTermDefaultValue(termOption.key)) {
if (CheckForTermDefaultValue(termOption.key) && !(termOption.key in classFormValues)) {
classFormValues[termOption.key] = CheckForTermDefaultValue(termOption.key);
}
});
Expand Down
101 changes: 101 additions & 0 deletions src/app/utilities/ClassAnnotationUtilities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* Import Dependencies */
import KeycloakService from "app/Keycloak";

/* Import Utilities */
import { FormatFieldNameFromJsonPath } from "./AnnotateUtilities";


/* Utilities associated with a class annotation */


/**
* Function to check for class default values and if present, return them
* @param jsonPath The JSON path of the class to check
* @returns Prefilled data array or object depending on provided class
*/
const CheckForClassDefaultValues = (jsonPath: string) => {
const className: string = FormatFieldNameFromJsonPath(jsonPath.replaceAll(/\[(\d+)\]/g, '')).split('_').slice(-1)[0].replace('$', '').replaceAll("'", '');

switch (className) {
case 'ods:hasAgents': {
return ClassAgents();
}
case 'ods:hasGeoreference': {
return ClassGeoreference();
}
case 'ods:hasIdentifiers': {
return ClassIdentifiers(jsonPath);
}
case 'ods:hasRoles': {
return ClassRoles();
}
};
};

/**
* Function to create a prefilled agent values object
* @returns Prefilled agents values array
*/
const ClassAgents = () => {
/* Construct agent values object */
const classValues = {
"@id": KeycloakService.GetParsedToken()?.orcid,
"@type": 'schema:Person',
"schema:identifier": KeycloakService.GetParsedToken()?.orcid,
"schema:name": `${KeycloakService.GetParsedToken()?.given_name ?? ''} ${KeycloakService.GetParsedToken()?.family_name ?? ''}`
};

return classValues;
};

/**
* Function to create a prefilled role values object
* @returns Prefilled georeferene values object
*/
const ClassGeoreference = () => {
/* Construct role values object */
const classValues = {
"@type": 'ods:Georeference'
};

return classValues;
};

/**
* Function to create a prefilled role values object
* @returns Prefilled identifiers values array
*/
const ClassIdentifiers = (jsonPath: string) => {
/* Extract parent class name from JSON path */
const parentClassName: string = FormatFieldNameFromJsonPath(jsonPath.replaceAll(/\[(\d+)\]/g, '')).split('_').slice(-2)[0].replace('$', '').replaceAll("'", '');

/* Set parent specific values */
const dctermsIdentifier: string = parentClassName === 'ods:hasAgents' ? KeycloakService.GetParsedToken()?.orcid : '';

/* Construct identifier values object */
const classValues = {
"@type": '',
"dcterms:title": '',
"dcterms:identifier": dctermsIdentifier
};

return classValues;
};

/**
* Function to create a prefilled role values object
* @returns Prefilled roles values array
*/
const ClassRoles = () => {
/* Construct role values object */
const classValues = {
"@type": '',
"schema:roleName": ''
};

return classValues;
};

export {
CheckForClassDefaultValues
};
11 changes: 11 additions & 0 deletions src/app/utilities/NomenclaturalUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ const GetSpecimenNameHTMLLabel = (digitalSpecimen: DigitalSpecimen): string => {
}
};

/**
* Function to get the scientific name identifier (that leads to COL) of the first accepted identification
* @param digitalSpecimen The provided digital specimen
*/
const GetSpecimenNameIdentifier = (digitalSpecimen: DigitalSpecimen) => {
const acceptedIdentification: Identification | undefined = digitalSpecimen['ods:hasIdentifications']?.find(identification => identification['ods:isVerifiedIdentification']);

return acceptedIdentification?.['ods:hasTaxonIdentifications']?.[0]["dwc:acceptedNameUsageID"];
};

/**
* Function to get the genus HTML label of the first accepted identification within the digital specimen, if not present, provide generic genus string
* @param digitalSpecimen The provided digital specimen
Expand All @@ -74,5 +84,6 @@ export {
DetermineScientificName,
DetermineTopicDisciplineIcon,
GetSpecimenNameHTMLLabel,
GetSpecimenNameIdentifier,
GetSpecimenGenusLabel
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState } from 'react';
import { Row, Col, Card } from 'react-bootstrap';

/* Import Utiltiies */
import { GetSpecimenNameHTMLLabel } from 'app/utilities/NomenclaturalUtilities';
import { GetSpecimenNameHTMLLabel, GetSpecimenNameIdentifier } from 'app/utilities/NomenclaturalUtilities';

/* Import Types */
import { DigitalSpecimen } from 'app/types/DigitalSpecimen';
Expand Down Expand Up @@ -74,16 +74,68 @@ const DigitalSpecimenOverview = (props: Props) => {
* @returns Citation string
*/
const CraftCitation = (label?: boolean) => {
/**
* Function to determine the organisation name for the citation string
* @returns Organisation name with link
*/
const OrganisationName = () => {
return <a href={digitalSpecimen['ods:organisationID']}
target="_blank"
rel="noreferer"
className="tc-accent"
>
{digitalSpecimen['ods:organisationName'] ?? digitalSpecimen['ods:organisationID']}
</a>
};

/**
* Function to determine the scientific name for the citation string
* @returns Scientific name, with or without link/label
*/
const ScientificName = () => {
const scientificNameIdentifier = GetSpecimenNameIdentifier(digitalSpecimen);

if (scientificNameIdentifier) {
return (
<a href={scientificNameIdentifier}
target="_blank"
rel="noreferer"
className="tc-accent"
>
<span dangerouslySetInnerHTML={{ __html: GetSpecimenNameHTMLLabel(digitalSpecimen) }} />
</a>
);
} else {
return <span dangerouslySetInnerHTML={{ __html: (GetSpecimenNameHTMLLabel(digitalSpecimen) ?? digitalSpecimen['ods:specimenName']) }} />;
}
};

if (label) {
return (
<>
{`Distributed System of Scientific Collections (${new Date().getFullYear()}). `}
<span dangerouslySetInnerHTML={{ __html: GetSpecimenNameHTMLLabel(digitalSpecimen) }} />
{`. ${digitalSpecimen['ods:organisationName'] ? digitalSpecimen['ods:organisationName'] + '.' : ''} [Dataset]. ${digitalSpecimen['@id']}`}
{OrganisationName()}
{` (${new Date().getFullYear()}). `}
{ScientificName()}
{'. '}
<a href="https://ror.org/02wddde16"
target="_blank"
rel="noreferer"
className="tc-accent"
>
Distributed System of Scientific Collections
</a>
{`. [Dataset]. `}
<a href={digitalSpecimen['@id']}
target="_blank"
rel="noreferer"
className="tc-accent"
>
{digitalSpecimen['@id']}
</a>
</>
);
} else {
return `Distributed System of Scientific Collections (${new Date().getFullYear()}). ${digitalSpecimen['ods:specimenName']}. ${digitalSpecimen['ods:organisationName'] ? digitalSpecimen['ods:organisationName'] + '.' : ''} [Dataset]. ${digitalSpecimen['@id']}`;
return `${digitalSpecimen['ods:organisationName'] ? digitalSpecimen['ods:organisationName'] + '.' : digitalSpecimen['ods:organisationID'] + '.'} (${new Date().getFullYear()}). ${digitalSpecimen['ods:specimenName']}. Distributed System of Scientific Collections. [Dataset]. ${digitalSpecimen['@id']}`;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const AnnotationFormStep = (props: Props) => {
}

/* For selected class, get annotation form field properties and their values */
GenerateAnnotationFormFieldProperties(jsonPath, localSuperClass, schemaName).then(({ annotationFormFieldProperties, newFormValues }) => {
GenerateAnnotationFormFieldProperties(jsonPath, localSuperClass, schemaName, formValues?.motivation).then(({ annotationFormFieldProperties, newFormValues }) => {
let parentJsonPath: string = '$';

if (annotationTarget?.jsonPath && FormatFieldNameFromJsonPath(annotationTarget.jsonPath).split('_').length > 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const SelectField = (props: Props) => {
<Field name={`annotationValues.${fieldName}`}
as="select"
value={fieldValue ?? ''}
className="w-100 b-grey br-corner mt-1 py-1 px-2"
className="w-100 b-grey br-corner mt-1 py-2 px-2"
>
<option value=""
disabled
Expand Down
14 changes: 11 additions & 3 deletions src/components/elements/footer/FooterPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const FooterPlugin = () => {
/* Base variables */
const [bootPlugin, setBootPlugin] = useState<boolean>(false);
const [backgroundColor, setBackgroundColor] = useState<string | undefined>();
const [explanationInterval, setExplanationInterval] = useState<any>();
const codeCheck: number[] = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 13];
let pluginCode: number[] = [];

Expand All @@ -34,6 +35,11 @@ const FooterPlugin = () => {
const audioExplanation = new Audio(ExplanationAudio);

audioExplanation.play();

audioExplanation.onended = () => {
setBootPlugin(false);
setBackgroundColor(undefined);
};
}
} else {
pluginCode = [];
Expand All @@ -48,9 +54,11 @@ const FooterPlugin = () => {
useEffect(() => {
/* If boot plugin is loaded, start explanation process */
if (bootPlugin) {
setInterval(() => {
setBackgroundColor(Math.floor(Math.random()*16777215).toString(16));
}, 1250);
setExplanationInterval(setInterval(() => {
setBackgroundColor(Math.floor(Math.random() * 16777215).toString(16));
}, 1250));
} else {
clearInterval(explanationInterval);
}
}, [bootPlugin]);

Expand Down
Loading