diff --git a/config/default.yaml b/config/default.yaml
index 73e50e5ba..259e78fdc 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -78,6 +78,8 @@ services:
base: https://www.ebi.ac.uk/ols/api/select
unified_astronomy_thesaurus:
base: https://vocabs.ands.org.au/repository/api/lda/aas/the-unified-astronomy-thesaurus/current/concept.json
+ local_contexts:
+ base: https://sandbox.localcontextshub.org/api/v1
grouper:
allUsers: GrouperAll
diff --git a/public/static/locales/en/localcontexts.json b/public/static/locales/en/localcontexts.json
new file mode 100644
index 000000000..71fee72bd
--- /dev/null
+++ b/public/static/locales/en/localcontexts.json
@@ -0,0 +1,7 @@
+{
+ "dataHasLocalContextsLabelsNotices": "This data has associated Local Contexts labels or notices.",
+ "localContextsAttrDefaultValue": "The \"{{projectTitle}}\" project has Labels and/or Notices applied through the Local Contexts Hub. For more information, refer to the project page: {{projectPage}}",
+ "localContextsHubError": "Error fetching Local Contexts Hub project information.",
+ "localContextsHubProjectURI": "Local Contexts Hub Project URI",
+ "projectMoreInfoWithLink": "For more information on the Local Contexts project for this data, please visit the project's page: {{projectTitle}}"
+}
diff --git a/src/components/data/listing/Listing.js b/src/components/data/listing/Listing.js
index 646a5be7e..d32adeaa8 100644
--- a/src/components/data/listing/Listing.js
+++ b/src/components/data/listing/Listing.js
@@ -55,6 +55,13 @@ import {
DEFAULTS_MAPPING_QUERY_KEY,
} from "serviceFacades/instantlaunches";
+import {
+ getFilesystemMetadata,
+ FILESYSTEM_METADATA_QUERY_KEY,
+ LOCAL_CONTEXTS_QUERY_KEY,
+ getLocalContextsProject,
+} from "serviceFacades/metadata";
+
import { announce } from "components/announcer/CyVerseAnnouncer";
import { ERROR, INFO } from "components/announcer/AnnouncerConstants";
import buildID from "components/utils/DebugIDUtil";
@@ -64,7 +71,7 @@ import { useBagAddItems } from "serviceFacades/bags";
import { useQueryClient, useMutation, useQuery } from "react-query";
-import { Button, Typography, useTheme } from "@mui/material";
+import { Button, Stack, Typography, useTheme } from "@mui/material";
import DEDialog from "components/utils/DEDialog";
import PublicLinks from "../PublicLinks";
import constants from "../../../constants";
@@ -82,6 +89,12 @@ import {
} from "serviceFacades/dashboard";
import { getUserQuota } from "common/resourceUsage";
+import LocalContextsLabelDisplay from "components/metadata/LocalContextsLabelDisplay";
+import {
+ LocalContextsAttrs,
+ parseProjectID,
+} from "components/models/metadata/LocalContexts";
+
function Listing(props) {
const {
baseId,
@@ -102,7 +115,7 @@ function Listing(props) {
} = props;
const [config] = useConfig();
- const { t } = useTranslation(["data", "common"]);
+ const { t } = useTranslation(["data", "common", "localcontexts"]);
const [userProfile] = useUserProfile();
const uploadTracker = useUploadTrackingState();
@@ -112,6 +125,7 @@ function Listing(props) {
const [selected, setSelected] = useState([]);
const [lastSelectIndex, setLastSelectIndex] = useState(-1);
const [data, setData] = useState({ total: 0, listing: [] });
+ const [localContextsProjectURI, setLocalContextsProjectURI] = useState();
const [detailsEnabled, setDetailsEnabled] = useState(false);
const [detailsOpen, setDetailsOpen] = useState(false);
const [advancedSearchOpen, setAdvancedSearchOpen] = useState(false);
@@ -204,6 +218,8 @@ function Listing(props) {
path,
});
setData({
+ id: respData?.id,
+ label: respData?.label,
total: respData?.total,
permission: respData?.permission,
listing: [
@@ -217,6 +233,8 @@ function Listing(props) {
})),
].map((i) => camelcaseit(i)), // camelcase the fields for each object, for consistency.
});
+
+ setLocalContextsProjectURI(null);
},
});
@@ -234,6 +252,42 @@ function Listing(props) {
{ exact: true }
);
+ useQuery({
+ queryKey: [FILESYSTEM_METADATA_QUERY_KEY, { dataId: data?.id }],
+ queryFn: () => getFilesystemMetadata({ dataId: data?.id }),
+ enabled: !!data?.id,
+ onSuccess: (metadata) => {
+ const { avus } = metadata;
+
+ const rightsURI = avus
+ ?.find((avu) => avu.attr === LocalContextsAttrs.LOCAL_CONTEXTS)
+ ?.avus?.find(
+ (childAVU) =>
+ childAVU.attr === LocalContextsAttrs.RIGHTS_URI
+ )?.value;
+
+ setLocalContextsProjectURI(rightsURI);
+ },
+ onError: (error) =>
+ console.log(
+ "Unable to fetch metadata for folder " + data?.label,
+ error
+ ), // fail silently.
+ });
+
+ const projectID = parseProjectID(localContextsProjectURI);
+
+ const { data: localContextsProject } = useQuery({
+ queryKey: [LOCAL_CONTEXTS_QUERY_KEY, projectID],
+ queryFn: () => getLocalContextsProject({ projectID }),
+ enabled: !!localContextsProjectURI,
+ onError: (error) =>
+ console.log("Error fetching Local Contexts project.", {
+ localContextsProjectURI,
+ error,
+ }), // fail silently.
+ });
+
const { isFetching: isFetchingDefaultsMapping } = useQuery({
queryKey: [DEFAULTS_MAPPING_QUERY_KEY],
queryFn: getDefaultsMapping,
@@ -324,6 +378,7 @@ function Listing(props) {
useEffect(() => {
setSelected([]);
+ setLocalContextsProjectURI(null);
}, [path, rowsPerPage, orderBy, order, page, uploadsCompleted]);
const viewUploadQueue = useCallback(() => {
@@ -677,6 +732,21 @@ function Listing(props) {
onMoveSelected={onMoveSelected}
uploadsEnabled={uploadsEnabled}
/>
+ {localContextsProjectURI && localContextsProject && (
+
+
+ }}
+ />
+
+
+
+ )}
{!isGridView && (
getFilesystemMetadata({ dataId: resourceId }),
+ enabled: resourceId && isFolder,
+ onSuccess: (metadata) => {
+ const { avus } = metadata;
+
+ const rightsURI = avus
+ ?.find((avu) => avu.attr === LocalContextsAttrs.LOCAL_CONTEXTS)
+ ?.avus?.find(
+ (childAVU) =>
+ childAVU.attr === LocalContextsAttrs.RIGHTS_URI
+ )?.value;
+
+ if (rightsURI) {
+ setLocalContextsProjectURI(rightsURI);
+ }
+ },
+ onError: (error) =>
+ console.log(
+ "Unable to fetch metadata for folder " + resource.label,
+ error
+ ), // fail silently.
+ });
+
+ const projectID = parseProjectID(localContextsProjectURI);
+
+ const { data: project } = useQuery({
+ queryKey: [LOCAL_CONTEXTS_QUERY_KEY, projectID],
+ queryFn: () => getLocalContextsProject({ projectID }),
+ enabled: !!localContextsProjectURI,
+ onError: (error) =>
+ console.log("Error fetching Local Contexts project.", {
+ localContextsProjectURI,
+ error,
+ }), // fail silently.
+ });
+
+ return (
+
+
+ {
+ handlePathChange(
+ resource.path,
+ resource.type,
+ resourceId
+ );
+ }}
+ >
+ {resource.label}
+
+ {localContextsProjectURI && project && (
+
+ )}
+
+ {instantLaunch && (
+
+ )}
+
+
+ );
+}
+
function SizeCell({ resource }) {
return {formatFileSize(resource.fileSize)};
}
@@ -161,7 +271,6 @@ function TableView(props) {
const dataRecordFields = dataFields(t);
const tableId = buildID(baseId, ids.LISTING_TABLE);
const trashPath = useBaseTrashPath();
- const theme = useTheme();
const [displayColumns, setDisplayColumns] = useState(
getLocalStorageCols(rowDotMenuVisibility, dataRecordFields) ||
@@ -310,7 +419,8 @@ function TableView(props) {
listing.map((resource, index) => {
const resourceName = resource.label;
const resourceId = resource.id;
- const resourcePath = resource.path;
+ const rowId = buildID(tableId, resourceName);
+
const isSelected =
selected.indexOf(resourceId) !== -1;
const isInvalid =
@@ -337,7 +447,7 @@ function TableView(props) {
role="checkbox"
tabIndex={0}
hover
- id={buildID(tableId, resourceName)}
+ id={rowId}
key={resourceId}
selected={isSelected}
aria-checked={isSelected}
@@ -354,8 +464,7 @@ function TableView(props) {
checked={isSelected}
tabIndex={0}
id={buildID(
- tableId,
- resourceName,
+ rowId,
ids.checkbox
)}
onChange={(event) =>
@@ -380,42 +489,15 @@ function TableView(props) {
type={resource.type}
/>
-
- {
- handlePathChange(
- resourcePath,
- resource.type,
- resource.id
- );
- }}
- >
- {resource.label}
-
-
- {instantLaunch && (
-
- )}
-
+
{getColumnDetails(displayColumns).map(
(column, index) => (
@@ -431,10 +513,7 @@ function TableView(props) {
{rowDotMenuVisibility && (
{
const [searchValue, setSearchValue] = React.useState(value);
@@ -48,6 +49,7 @@ const FormSearchField = ({
return (
option[labelKey] === value[labelKey]
}
diff --git a/src/components/metadata/LocalContextsLabelDisplay.js b/src/components/metadata/LocalContextsLabelDisplay.js
new file mode 100644
index 000000000..11a9daa4a
--- /dev/null
+++ b/src/components/metadata/LocalContextsLabelDisplay.js
@@ -0,0 +1,124 @@
+import React from "react";
+
+import {
+ Card,
+ CardContent,
+ CardMedia,
+ IconButton,
+ Stack,
+ Tooltip,
+ Typography,
+ useTheme,
+} from "@mui/material";
+
+import { Trans, useTranslation } from "i18n";
+
+import DEDialog from "components/utils/DEDialog";
+import ExternalLink from "components/utils/ExternalLink";
+
+const sizeToSpacing = (size, theme) =>
+ size === "large"
+ ? theme.spacing(8)
+ : size === "small"
+ ? theme.spacing(3)
+ : theme.spacing(5);
+
+const LocalContextsLabel = ({ baseId, label, project, size = "medium" }) => {
+ const { t } = useTranslation("localcontexts");
+
+ const [dialogOpen, setDialogOpen] = React.useState(false);
+ const theme = useTheme();
+ const labelURI = label.svg_url || label.img_url;
+
+ return (
+ <>
+
+ setDialogOpen(true)}>
+
+
+
+ setDialogOpen(false)}
+ >
+
+
+
+
+
+ {label.label_text || label.default_text}
+
+
+
+
+
+
+ ,
+ ProjectLink: (
+
+ ),
+ }}
+ />
+
+
+
+
+ >
+ );
+};
+
+const LocalContextsLabelDisplay = ({ project, size = "medium" }) => {
+ const labels = [
+ ...(project?.notice || []),
+ ...(project?.bc_labels || []),
+ ...(project?.tk_labels || []),
+ ].filter((label) => label);
+
+ return (
+
+ {labels.map((label) => (
+
+ ))}
+
+ );
+};
+
+export default LocalContextsLabelDisplay;
diff --git a/src/components/metadata/form/MetadataFormToolbar.js b/src/components/metadata/form/MetadataFormToolbar.js
index bfacf66f9..a7614c470 100644
--- a/src/components/metadata/form/MetadataFormToolbar.js
+++ b/src/components/metadata/form/MetadataFormToolbar.js
@@ -38,6 +38,7 @@ const MetadataFormToolbar = (props) => {
const {
baseId,
title,
+ dirty,
saveDisabled,
showSave,
onSave,
@@ -59,7 +60,7 @@ const MetadataFormToolbar = (props) => {
return (
-
+
{
setShowImportConfirmationDialog(true)
}
targetResource={targetResource}
+ dirty={dirty}
/>
{irodsAVUs?.length > 0 && (
diff --git a/src/components/metadata/templates/LocalContextsField.js b/src/components/metadata/templates/LocalContextsField.js
new file mode 100644
index 000000000..c5ddb4462
--- /dev/null
+++ b/src/components/metadata/templates/LocalContextsField.js
@@ -0,0 +1,241 @@
+/**
+ * This custom metadata template field displays only a Local Contexts Hub
+ * project URL field, and auto-populates DataCite metadata and child AVUs using
+ * the project ID parsed from the URL and the Local Contexts Hub API response.
+ *
+ * @author psarando
+ */
+import React from "react";
+
+import { useQuery } from "react-query";
+
+import { useTranslation } from "i18n";
+
+import LocalContextsLabelDisplay from "../LocalContextsLabelDisplay";
+
+import ErrorTypographyWithDialog from "components/error/ErrorTypographyWithDialog";
+import getFormError from "components/forms/getFormError";
+import {
+ LocalContextsAttrs,
+ parseProjectID,
+} from "components/models/metadata/LocalContexts";
+
+import {
+ LOCAL_CONTEXTS_QUERY_KEY,
+ getLocalContextsProject,
+} from "serviceFacades/metadata";
+
+import { Skeleton, TextField } from "@mui/material";
+
+const findAVU = (avus, attr) => avus?.find((avu) => avu.attr === attr);
+
+const LocalContextsField = ({
+ avu,
+ onUpdate,
+ helperText,
+ form: { setFieldValue, ...form },
+ field: { value, onChange, ...field },
+ ...props
+}) => {
+ const { t } = useTranslation("localcontexts");
+
+ const [rightsURIAVU, setRightsURIAVU] = React.useState(
+ () =>
+ findAVU(avu.avus, LocalContextsAttrs.RIGHTS_URI) || {
+ attr: LocalContextsAttrs.RIGHTS_URI,
+ value: "",
+ unit: "",
+ }
+ );
+
+ const [projectHubURI, setProjectHubURI] = React.useState(
+ rightsURIAVU.value
+ );
+
+ const [projectHubError, setProjectHubError] = React.useState();
+
+ const [rightsIDSchemeURIAVU] = React.useState(() => {
+ let rightsIDScheme = findAVU(
+ avu.avus,
+ LocalContextsAttrs.RIGHTS_ID_SCHEME
+ );
+
+ if (
+ rightsIDScheme?.value !== LocalContextsAttrs.RIGHTS_ID_SCHEME_VALUE
+ ) {
+ rightsIDScheme = {
+ attr: LocalContextsAttrs.RIGHTS_ID_SCHEME,
+ value: LocalContextsAttrs.RIGHTS_ID_SCHEME_VALUE,
+ unit: "",
+ };
+ }
+
+ return rightsIDScheme;
+ });
+
+ const [schemeURIAVU] = React.useState(() => {
+ let schemeURI = findAVU(avu.avus, LocalContextsAttrs.SCHEME_URI);
+
+ if (schemeURI?.value !== LocalContextsAttrs.SCHEME_URI_VALUE) {
+ schemeURI = {
+ attr: LocalContextsAttrs.SCHEME_URI,
+ value: LocalContextsAttrs.SCHEME_URI_VALUE,
+ unit: "",
+ };
+ }
+
+ return schemeURI;
+ });
+
+ const { touched, errors } = form;
+ const fieldError = getFormError(field.name, touched, errors);
+ const projectID = parseProjectID(projectHubURI);
+
+ const { data: project, isFetching } = useQuery({
+ queryKey: [LOCAL_CONTEXTS_QUERY_KEY, projectID],
+ queryFn: () =>
+ getLocalContextsProject({
+ projectID,
+ }),
+ enabled: projectHubURI && !fieldError,
+ onSuccess: (project) => {
+ let newValue = avu.value || "";
+
+ const projectLabels = [
+ ...(project?.notice?.filter((label) => label?.name) || []),
+ ...(project?.bc_labels?.filter((label) => label?.name) || []),
+ ...(project?.tk_labels?.filter((label) => label?.name) || []),
+ ];
+
+ if (projectLabels.length === 1) {
+ newValue =
+ projectLabels[0].label_text ||
+ projectLabels[0].default_text;
+ } else {
+ newValue = t("localContextsAttrDefaultValue", {
+ projectTitle: project?.title,
+ projectPage: project?.project_page,
+ });
+ }
+
+ const newLabels = projectLabels.map((label) => label.name);
+
+ let newAVUs = avu.avus || [];
+
+ const currentLabels = newAVUs
+ .filter(
+ (childAVU) => childAVU.attr === LocalContextsAttrs.RIGHTS_ID
+ )
+ ?.map((childAVU) => childAVU.value);
+
+ const missingLabels = newLabels.filter(
+ (label) => !currentLabels.includes(label)
+ );
+
+ const extraLabels = currentLabels.filter(
+ (label) => !newLabels.includes(label)
+ );
+
+ const requiresUpdate =
+ avu.value !== newValue ||
+ missingLabels.length > 0 ||
+ extraLabels.length > 0;
+
+ if (requiresUpdate) {
+ newAVUs = newAVUs.filter(
+ (childAVU) =>
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_URI &&
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_ID_SCHEME &&
+ childAVU.attr !== LocalContextsAttrs.SCHEME_URI &&
+ (childAVU.attr !== LocalContextsAttrs.RIGHTS_ID ||
+ newLabels.includes(childAVU.value))
+ );
+
+ newAVUs = [
+ ...newAVUs,
+ rightsURIAVU,
+ schemeURIAVU,
+ rightsIDSchemeURIAVU,
+ ...missingLabels.map((label) => ({
+ attr: LocalContextsAttrs.RIGHTS_ID,
+ value: label,
+ unit: "",
+ })),
+ ];
+
+ onUpdate({ ...avu, value: newValue, avus: newAVUs });
+ }
+ },
+ onError: (error) => {
+ setProjectHubError(
+
+ );
+ },
+ });
+
+ const updateProjectHubURI = (uri) => {
+ setProjectHubURI(uri);
+ setProjectHubError(null);
+
+ let newAVUs = avu.avus || [];
+
+ if (rightsURIAVU.value !== uri) {
+ const newRightsURIAVU = {
+ attr: LocalContextsAttrs.RIGHTS_URI,
+ value: uri,
+ unit: "",
+ };
+ setRightsURIAVU(newRightsURIAVU);
+
+ newAVUs = newAVUs.filter(
+ (childAVU) =>
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_URI &&
+ childAVU.attr !== LocalContextsAttrs.RIGHTS_ID_SCHEME &&
+ childAVU.attr !== LocalContextsAttrs.SCHEME_URI
+ );
+
+ newAVUs = [
+ ...newAVUs,
+ newRightsURIAVU,
+ schemeURIAVU,
+ rightsIDSchemeURIAVU,
+ ];
+
+ onUpdate({ ...avu, avus: newAVUs });
+ }
+ };
+
+ const errorMsg = fieldError || projectHubError;
+
+ return (
+ <>
+ updateProjectHubURI(event?.target?.value)}
+ />
+ {isFetching ? (
+
+
+
+ ) : (
+
+ )}
+ >
+ );
+};
+
+export default LocalContextsField;
diff --git a/src/components/metadata/templates/index.js b/src/components/metadata/templates/index.js
index 8f9ca08a6..34e8174de 100644
--- a/src/components/metadata/templates/index.js
+++ b/src/components/metadata/templates/index.js
@@ -43,6 +43,8 @@ import FormCheckboxStringValue from "components/forms/FormCheckboxStringValue";
import AstroThesaurusSearchField from "./AstroThesaurusSearchField";
import OntologyLookupServiceSearchField from "./OntologyLookupServiceSearchField";
+import LocalContextsField from "./LocalContextsField";
+
import SlideUpTransition from "../SlideUpTransition";
import {
@@ -66,6 +68,7 @@ import {
} from "@mui/icons-material";
import { Skeleton } from "@mui/material";
+import { LocalContextsAttrs } from "components/models/metadata/LocalContexts";
const useStyles = makeStyles(styles);
@@ -238,7 +241,7 @@ const MetadataTemplateAttributeForm = (props) => {
fieldProps = {
...fieldProps,
searchAstroThesaurusTerms,
- isDisabled: !writable,
+ readOnly: !writable,
};
break;
@@ -248,7 +251,7 @@ const MetadataTemplateAttributeForm = (props) => {
...fieldProps,
searchOLSTerms,
attribute,
- isDisabled: !writable,
+ readOnly: !writable,
};
break;
@@ -270,6 +273,17 @@ const MetadataTemplateAttributeForm = (props) => {
return null;
}
+ const isLocalContextsAttr =
+ attribute.name ===
+ LocalContextsAttrs.LOCAL_CONTEXTS;
+ if (isLocalContextsAttr) {
+ FieldComponent = LocalContextsField;
+ fieldProps.avu = avu;
+ fieldProps.onUpdate = (avu) => {
+ arrayHelpers.replace(index, avu);
+ };
+ }
+
const avuFieldName = `${field}.avus[${index}]`;
const avuError = getFormError(
avuFieldName,
@@ -298,7 +312,8 @@ const MetadataTemplateAttributeForm = (props) => {
);
- const childAVUs = attribute.attributes &&
+ const childAVUs = !isLocalContextsAttr &&
+ attribute.attributes &&
attribute.attributes.length > 0 && (
{
attributes
.filter((attribute) => attribute.required)
.forEach((attribute) => {
- if (
- avus.filter((avu) => avu.attr === attribute.name)
- .length < 1
- ) {
+ if (!avus.find((avu) => avu.attr === attribute.name)) {
avus.push(newAVU(attribute));
}
@@ -701,19 +713,34 @@ const MetadataTemplateView = (props) => {
const avuArrayErrors = [];
avus.forEach((avu, avuIndex) => {
const avuErrors = {};
- const value = avu.value;
const attrTemplate = attributeMap[avu.attr];
if (!attrTemplate) {
return;
}
- if (attrTemplate.required && value === "") {
+ let attrType = attrTemplate.type;
+ let value = avu.value;
+
+ const isLocalContexts =
+ avu.attr === LocalContextsAttrs.LOCAL_CONTEXTS;
+ if (isLocalContexts) {
+ const rightsURIAVU = avu.avus?.find(
+ (childAVU) =>
+ childAVU.attr === LocalContextsAttrs.RIGHTS_URI
+ );
+
+ attrType = AttributeTypes.URL;
+ value = rightsURIAVU?.value;
+ }
+
+ const isRequired = isLocalContexts || attrTemplate.required;
+ if (isRequired && value === "") {
avuErrors.value = t("required");
avuErrors.error = true;
avuArrayErrors[avuIndex] = avuErrors;
} else if (value) {
- switch (attrTemplate.type) {
+ switch (attrType) {
case AttributeTypes.NUMBER:
case AttributeTypes.INTEGER:
const numVal = Number(value);
@@ -795,12 +822,11 @@ const MetadataTemplateView = (props) => {
const attrTemplate = attributeMap[avu.attr];
const isNumberAttr =
- attrTemplate &&
- (attrTemplate.type === AttributeTypes.NUMBER ||
- attrTemplate.type === AttributeTypes.INTEGER);
+ attrTemplate?.type === AttributeTypes.NUMBER ||
+ attrTemplate?.type === AttributeTypes.INTEGER;
const isGroupingAttr =
- attrTemplate && attrTemplate.type === AttributeTypes.GROUPING;
+ attrTemplate?.type === AttributeTypes.GROUPING;
const hasChildAVUs = avu.avus && avu.avus.length > 0;
diff --git a/src/components/models/metadata/LocalContexts.js b/src/components/models/metadata/LocalContexts.js
new file mode 100644
index 000000000..487ea6192
--- /dev/null
+++ b/src/components/models/metadata/LocalContexts.js
@@ -0,0 +1,14 @@
+// Remove any trailing slash from the rightsURI
+// and take the final part of the path as the project ID.
+export const parseProjectID = (projectHubURI) =>
+ projectHubURI?.replace(/\/$/, "").split("/").at(-1);
+
+export const LocalContextsAttrs = {
+ LOCAL_CONTEXTS: "LocalContexts",
+ RIGHTS_URI: "rightsURI",
+ RIGHTS_ID: "rightsIdentifier",
+ RIGHTS_ID_SCHEME: "rightsIdentifierScheme",
+ RIGHTS_ID_SCHEME_VALUE: "Local Contexts",
+ SCHEME_URI: "schemeURI",
+ SCHEME_URI_VALUE: "https://localcontexts.org",
+};
diff --git a/src/pages/data/ds/[...pathItems].js b/src/pages/data/ds/[...pathItems].js
index 3c220d55a..7f02db92a 100644
--- a/src/pages/data/ds/[...pathItems].js
+++ b/src/pages/data/ds/[...pathItems].js
@@ -205,6 +205,7 @@ export async function getServerSideProps(context) {
title,
...(await serverSideTranslations(locale, [
"data",
+ "localcontexts",
"metadata",
"upload",
"urlImport",
diff --git a/src/server/api/metadata.js b/src/server/api/metadata.js
index 955ce3984..a46d0516f 100644
--- a/src/server/api/metadata.js
+++ b/src/server/api/metadata.js
@@ -7,7 +7,7 @@
import express from "express";
import * as auth from "../auth";
-import { olsURL, uatURL } from "../configuration";
+import { localContextsURL, olsURL, uatURL } from "../configuration";
import logger from "../logging";
import { handler as externalHandler } from "./external";
@@ -119,5 +119,15 @@ export default function metadataRouter() {
})
);
+ logger.info("adding the GET /api/local-contexts/projects/:id handler");
+ api.get("/local-contexts/projects/:id", async (req, res) => {
+ const localContextsHandler = externalHandler({
+ method: "GET",
+ url: `${localContextsURL}/projects/${req.params.id}`,
+ });
+
+ return localContextsHandler(req, res);
+ });
+
return api;
}
diff --git a/src/server/configuration.js b/src/server/configuration.js
index 00ee1d6af..f372200f4 100644
--- a/src/server/configuration.js
+++ b/src/server/configuration.js
@@ -289,6 +289,14 @@ export const olsURL = config.get("services.ontology_lookup_service.base");
*/
export const uatURL = config.get("services.unified_astronomy_thesaurus.base");
+/**
+ * The Local Contexts Hub API.
+ * https://github.com/localcontexts/localcontextshub/wiki/API-Documentation
+ *
+ * @type {string}
+ */
+export const localContextsURL = config.get("services.local_contexts.base");
+
/**
* The base URL for the User Portal's API
*/
diff --git a/src/serviceFacades/metadata.js b/src/serviceFacades/metadata.js
index 27942a863..a3c8a0e7e 100644
--- a/src/serviceFacades/metadata.js
+++ b/src/serviceFacades/metadata.js
@@ -12,6 +12,7 @@ const FILESYSTEM_METADATA_TEMPLATE_LISTING_QUERY_KEY =
"fetchFilesystemMetadataTemplateListingKey";
const SEARCH_OLS_QUERY_KEY = "searchOntologyLookupServiceKey";
const SEARCH_UAT_QUERY_KEY = "searchUnifiedAstronomyThesaurusKey";
+const LOCAL_CONTEXTS_QUERY_KEY = "localContextsKey";
function getFilesystemMetadataTemplateListing() {
return callApi({
@@ -138,10 +139,20 @@ function searchUnifiedAstronomyThesaurus({ searchTerm, orderBy }) {
.then((apiResponse) => apiResponse.data);
}
+function getLocalContextsProject({ projectID }) {
+ return axiosInstance
+ .request({
+ url: `/api/local-contexts/projects/${projectID}`,
+ method: "GET",
+ })
+ .then((apiResponse) => apiResponse.data);
+}
+
export {
FILESYSTEM_METADATA_QUERY_KEY,
FILESYSTEM_METADATA_TEMPLATE_QUERY_KEY,
FILESYSTEM_METADATA_TEMPLATE_LISTING_QUERY_KEY,
+ LOCAL_CONTEXTS_QUERY_KEY,
SEARCH_OLS_QUERY_KEY,
SEARCH_UAT_QUERY_KEY,
getFilesystemMetadata,
@@ -150,6 +161,7 @@ export {
saveFilesystemMetadata,
setFilesystemMetadata,
applyBulkMetadataFromFile,
+ getLocalContextsProject,
searchOntologyLookupService,
searchUnifiedAstronomyThesaurus,
};