diff --git a/src/app/Boot.ts b/src/app/Boot.ts index bde58abe..daf6b41a 100644 --- a/src/app/Boot.ts +++ b/src/app/Boot.ts @@ -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 */ diff --git a/src/app/Hooks.ts b/src/app/Hooks.ts index a80f924e..2ed6fbd3 100644 --- a/src/app/Hooks.ts +++ b/src/app/Hooks.ts @@ -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, diff --git a/src/app/utilities/AnnotateUtilities.ts b/src/app/utilities/AnnotateUtilities.ts index 2f63697a..f68e426a 100644 --- a/src/app/utilities/AnnotateUtilities.ts +++ b/src/app/utilities/AnnotateUtilities.ts @@ -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 */ @@ -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') { @@ -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 } = {}; @@ -208,7 +209,7 @@ 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')) { @@ -216,6 +217,25 @@ const GenerateAnnotationFormFieldProperties = async (jsonPath: string, superClas } }; + /** + * 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) { @@ -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 */ @@ -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); } }); diff --git a/src/app/utilities/ClassAnnotationUtilities.ts b/src/app/utilities/ClassAnnotationUtilities.ts new file mode 100644 index 00000000..2aa81ba9 --- /dev/null +++ b/src/app/utilities/ClassAnnotationUtilities.ts @@ -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 +}; \ No newline at end of file diff --git a/src/app/utilities/NomenclaturalUtilities.ts b/src/app/utilities/NomenclaturalUtilities.ts index aab0aae3..a1d25a83 100644 --- a/src/app/utilities/NomenclaturalUtilities.ts +++ b/src/app/utilities/NomenclaturalUtilities.ts @@ -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 @@ -74,5 +84,6 @@ export { DetermineScientificName, DetermineTopicDisciplineIcon, GetSpecimenNameHTMLLabel, + GetSpecimenNameIdentifier, GetSpecimenGenusLabel }; \ No newline at end of file diff --git a/src/components/digitalSpecimen/components/contentBlock/components/DigitalSpecimenOverview.tsx b/src/components/digitalSpecimen/components/contentBlock/components/DigitalSpecimenOverview.tsx index 32fa8711..a3cbc0fe 100644 --- a/src/components/digitalSpecimen/components/contentBlock/components/DigitalSpecimenOverview.tsx +++ b/src/components/digitalSpecimen/components/contentBlock/components/DigitalSpecimenOverview.tsx @@ -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'; @@ -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 + {digitalSpecimen['ods:organisationName'] ?? digitalSpecimen['ods:organisationID']} + + }; + + /** + * 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 ( + + + + ); + } else { + return ; + } + }; + if (label) { return ( <> - {`Distributed System of Scientific Collections (${new Date().getFullYear()}). `} - - {`. ${digitalSpecimen['ods:organisationName'] ? digitalSpecimen['ods:organisationName'] + '.' : ''} [Dataset]. ${digitalSpecimen['@id']}`} + {OrganisationName()} + {` (${new Date().getFullYear()}). `} + {ScientificName()} + {'. '} + + Distributed System of Scientific Collections + + {`. [Dataset]. `} + + {digitalSpecimen['@id']} + ); } 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']}`; } }; diff --git a/src/components/elements/annotationSidePanel/components/AnnotationsOverview.tsx b/src/components/elements/annotationSidePanel/components/AnnotationsOverview.tsx index e3251cce..c5846fde 100644 --- a/src/components/elements/annotationSidePanel/components/AnnotationsOverview.tsx +++ b/src/components/elements/annotationSidePanel/components/AnnotationsOverview.tsx @@ -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("'", '"'); } }); } diff --git a/src/components/elements/annotationSidePanel/components/annotationWizard/AnnotationWizard.tsx b/src/components/elements/annotationSidePanel/components/annotationWizard/AnnotationWizard.tsx index f0553109..f4f50dc8 100644 --- a/src/components/elements/annotationSidePanel/components/annotationWizard/AnnotationWizard.tsx +++ b/src/components/elements/annotationSidePanel/components/annotationWizard/AnnotationWizard.tsx @@ -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) */ diff --git a/src/components/elements/annotationSidePanel/components/annotationWizard/steps/AnnotationFormStep.tsx b/src/components/elements/annotationSidePanel/components/annotationWizard/steps/AnnotationFormStep.tsx index e3f8fd97..cb05ec57 100644 --- a/src/components/elements/annotationSidePanel/components/annotationWizard/steps/AnnotationFormStep.tsx +++ b/src/components/elements/annotationSidePanel/components/annotationWizard/steps/AnnotationFormStep.tsx @@ -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) { @@ -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 && { diff --git a/src/components/elements/annotationSidePanel/components/annotationWizard/steps/formFields/SelectField.tsx b/src/components/elements/annotationSidePanel/components/annotationWizard/steps/formFields/SelectField.tsx index 77e63913..21611aaf 100644 --- a/src/components/elements/annotationSidePanel/components/annotationWizard/steps/formFields/SelectField.tsx +++ b/src/components/elements/annotationSidePanel/components/annotationWizard/steps/formFields/SelectField.tsx @@ -31,7 +31,7 @@ const SelectField = (props: Props) => {