Skip to content

Commit

Permalink
Merge pull request #140 from DiSSCo/PrefillAnnotationClassesWithValues
Browse files Browse the repository at this point in the history
Prefill annotation classes with values
  • Loading branch information
TomDijkema authored Dec 11, 2024
2 parents 6e88e7f + 6ec74bb commit b63679d
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 26 deletions.
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
2 changes: 1 addition & 1 deletion src/app/Hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ const usePagination = ({ pageSize, resultKey, params, allowSearchParams = false,
const result = await Method({ ...params, pageNumber: pageNumber, pageSize, ...(allowSearchParams && { searchFilters: searchFilters.GetSearchFilters() }) });

/* Set return data */
const records = resultKey ? result[resultKey] : result[Object.keys(result)[0]];
const records = resultKey ? result[resultKey] : result;

setReturnData({
records,
Expand Down
49 changes: 40 additions & 9 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 @@ -42,7 +43,7 @@ const ConstructAnnotationObject = (params: {
}): AnnotationTemplate => {
const { digitalObjectId, digitalObjectType, motivation, annotationTargetType, jsonPath, annotationValues, fragments } = params;

let localJsonPath: string = jsonPath ?? '';
let localJsonPath: string = jsonPath?.replaceAll("'", '"') ?? '';

/* If motivation is adding, check for new index at end of JSON path and remove if it is there */
if (jsonPath && typeof (jp.parse(jsonPath).at(-1).expression.value) === 'number') {
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 === 'ods:adding' && isArray) {
return [CheckForClassDefaultValues(fieldName)];
} else if (motivation === 'ods:adding') {
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 @@ -92,9 +92,9 @@ const AnnotationsOverview = (props: Props) => {
if (jsonPath) {
filteredSortedAnnotations = filteredSortedAnnotations.filter(annotation => {
if (annotation["oa:hasTarget"]["oa:hasSelector"]?.["@type"] === 'ods:ClassSelector') {
return annotation["oa:hasTarget"]["oa:hasSelector"]["ods:class"] === jsonPath;
return annotation["oa:hasTarget"]["oa:hasSelector"]["ods:class"] === jsonPath.replaceAll("'", '"');
} else if (annotation["oa:hasTarget"]["oa:hasSelector"]?.["@type"] === 'ods:TermSelector') {
return annotation["oa:hasTarget"]["oa:hasSelector"]["ods:term"] === jsonPath;
return annotation["oa:hasTarget"]["oa:hasSelector"]["ods:term"] === jsonPath.replaceAll("'", '"');
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ const AnnotationWizard = (props: Props) => {
});
});

console.log(annotationTarget);

/**
* Function to set the annotation target based on the user's selection (wizard step one)
*/
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 All @@ -107,7 +107,9 @@ const AnnotationFormStep = (props: Props) => {
annotationValues: {
...newFormValues,
...formValues?.annotationValues,
value: (localAnnotationTarget?.jsonPath !== annotationTarget?.jsonPath) ? newFormValues.value : formValues?.annotationValues.value ?? newFormValues.value
...(annotationTarget?.type === 'term' && {
value: (localAnnotationTarget?.jsonPath !== annotationTarget?.jsonPath) ? newFormValues.value : formValues?.annotationValues.value ?? newFormValues.value
})
},
/* Set JSON path and motivation from this checkpoint if editing an annotation */
...(annotationTarget?.annotation && {
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
Loading

0 comments on commit b63679d

Please sign in to comment.