{loadingDoiDelete ? (
diff --git a/src/components/FormComponents/MetadataRecordListItem.jsx b/src/components/FormComponents/MetadataRecordListItem.jsx
index 3b4e0d76..7405a8d3 100644
--- a/src/components/FormComponents/MetadataRecordListItem.jsx
+++ b/src/components/FormComponents/MetadataRecordListItem.jsx
@@ -28,13 +28,14 @@ import { useParams } from "react-router-dom";
import { getRecordFilename } from "../../utils/misc";
import recordToEML from "../../utils/recordToEML";
import recordToERDDAP from "../../utils/recordToERDDAP";
-import recordToDataCite from "../../utils/recordToDataCite";
import { recordIsValid, percentValid } from "../../utils/validate";
+import recordToDataCite from "../../utils/recordToDataCite";
import { I18n, En, Fr } from "../I18n";
import LastEdited from "./LastEdited";
import RecordStatusIcon from "./RecordStatusIcon";
import { UserContext } from "../../providers/UserProvider";
import regions from "../../regions";
+import licenses from "../../utils/licenses"
const MetadataRecordListItem = ({
record,
@@ -102,7 +103,7 @@ const MetadataRecordListItem = ({
} else if (fileType === "erddap") {
data = [recordToERDDAP(record)];
} else if (fileType === "json") {
- data = [JSON.stringify(recordToDataCite(record, language, region), null, 2)];
+ data = await [JSON.stringify(recordToDataCite({metadata: record, language, regions, region, licenses}), null, 2)];
} else {
const res = await downloadRecord({ record, fileType });
data = Object.values(res.data.message);
diff --git a/src/components/FormComponents/SharedUsersList.jsx b/src/components/FormComponents/SharedUsersList.jsx
new file mode 100644
index 00000000..35a4a338
--- /dev/null
+++ b/src/components/FormComponents/SharedUsersList.jsx
@@ -0,0 +1,240 @@
+import React, { useEffect, useState } from "react";
+import { Add, Delete } from "@material-ui/icons";
+import {
+ Typography,
+ Paper,
+ Grid,
+ TextField,
+ Button,
+ List,
+ ListItem,
+ ListItemText,
+ IconButton,
+ ListItemSecondaryAction,
+ Box,
+} from "@material-ui/core";
+import Autocomplete from "@material-ui/lab/Autocomplete";
+
+import { paperClass, SupplementalText } from "./QuestionStyles";
+import { En, Fr, I18n } from "../I18n";
+import {
+ loadRegionUsers,
+ updateSharedRecord,
+} from "../../utils/firebaseRecordFunctions";
+
+const SharedUsersList = ({ record, updateRecord, region }) => {
+ const [users, setUsers] = useState({});
+ const [currentUser, setCurrentUser] = useState(null);
+ const [sharedWithUsers, setSharedWithUsers] = useState({});
+ const [shareRecordDisabled, setShareRecordDisabled] = useState(true);
+ const authorID = record.userID
+
+ // fetching users based on region
+ useEffect(() => {
+ let isMounted = true;
+
+ if (record.recordID) {
+ setShareRecordDisabled(false)
+ }
+
+ const fetchRegionUsers = async () => {
+ try {
+ const regionUsers = await loadRegionUsers(region);
+
+ if (isMounted) {
+ setUsers(regionUsers);
+ }
+ } catch (error) {
+ throw new Error(`Error loading region users: ${error}`);
+ }
+ };
+
+ fetchRegionUsers();
+
+ return () => {
+ isMounted = false;
+ };
+ }, [region, record.recordID]);
+
+ // Updates state with user emails for each userID in record.sharedWith, to track which users a record is shared with.
+ useEffect(() => {
+ const sharedWithDetails = {};
+ Object.keys(record.sharedWith || {}).forEach((userID) => {
+ const name = users[userID]?.userinfo?.displayName;
+ if (name) {
+ sharedWithDetails[userID] = { name };
+ }
+ });
+
+ setSharedWithUsers(sharedWithDetails);
+ }, [record.sharedWith, users]);
+
+ // Function to add an email to the sharedWith list
+ const addUserToSharedWith = (userID) => {
+ const updatedSharedWith = {
+ ...record.sharedWith,
+ [userID]: true,
+ };
+
+ setSharedWithUsers(updatedSharedWith);
+
+ updateRecord("sharedWith")(updatedSharedWith);
+
+ const shareRecordAsync = async () => {
+ try {
+ await updateSharedRecord(userID, record.recordID, authorID, region, true);
+ } catch (error) {
+ throw new Error(`Failed to update shared record: ${error}`);
+ }
+ };
+
+ shareRecordAsync();
+ };
+
+ // Function to remove an email from the sharedWith list
+ const removeUserFromSharedWith = (userID) => {
+ if (record.sharedWith && record.sharedWith[userID]) {
+ const updatedSharedWith = { ...record.sharedWith };
+ delete updatedSharedWith[userID];
+ updateRecord("sharedWith")(updatedSharedWith);
+
+ const unshareRecordAsync = async () => {
+ try {
+ await updateSharedRecord(userID, record.recordID, authorID, region, false);
+ } catch (error) {
+ throw new Error(`Failed to unshare the record: ${error}`);
+ }
+ };
+
+ unshareRecordAsync();
+ }
+ };
+
+ const shareWithOptions = Object.entries(users)
+ .map(([userID, userInfo]) => ({
+ label: userInfo.userinfo?.displayName,
+ userID,
+ }))
+ .filter((x) => x.label)
+ .sort((a, b) => a.label.localeCompare(b.label));
+
+ return (
+
+
+
+
+
+
+ To share editing access with another user, start typing their
+ name and select from the suggestions.
+
+
+ Pour partager l'accès en modification avec un autre utilisateur,
+ commencez à saisir son nom et sélectionnez parmi les
+ suggestions.
+
+
+
+
+
+
+ Please save the form before sharing access.
+
+
+
+ Veuillez enregistrer le formulaire avant de partager l'accès.
+
+
+
+
+
+
+
+
+ option.label}
+ getOptionSelected={(option, value) =>
+ option.userID === value.userID
+ }
+ value={currentUser}
+ onChange={(event, newValue) => setCurrentUser(newValue)}
+ fullWidth
+ filterSelectedOptions
+ renderInput={(params) => (
+ }
+ variant="outlined"
+ style={{ marginTop: "16px" }}
+ />
+ )}
+ />
+ }
+ onClick={() => {
+ if (currentUser) {
+ addUserToSharedWith(currentUser.userID);
+ setCurrentUser(null);
+ }
+ }}
+ style={{
+ height: "46px",
+ justifyContent: "center",
+ marginTop: "15px",
+ }}
+ >
+
+
+ Share Record
+ Partager l'enregistrement
+
+
+
+
+
+
+
+ {Object.keys(sharedWithUsers).length > 0 && (
+
+ Users this record is shared with:
+
+ Utilisateurs avec lesquels cet enregistrement est
+ partagé :
+
+
+ )}
+
+
+ {Object.entries(sharedWithUsers).map(
+ ([userID, userDetails], index) => (
+
+ {userDetails.name}}
+ />
+
+ removeUserFromSharedWith(userID)}
+ >
+
+
+
+
+ )
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default SharedUsersList;
diff --git a/src/components/NavDrawer.jsx b/src/components/NavDrawer.jsx
index ac8c8c2c..ed962f7d 100644
--- a/src/components/NavDrawer.jsx
+++ b/src/components/NavDrawer.jsx
@@ -15,6 +15,7 @@ import {
SupervisorAccount,
Menu,
AssignmentTurnedIn,
+ FolderShared,
} from "@material-ui/icons";
import {
@@ -128,6 +129,7 @@ export default function MiniDrawer({ children }) {
isReviewer: userIsReviewer,
isAdmin: userIsAdmin,
authIsLoading,
+ hasSharedRecords,
} = useContext(UserContext);
let { language = "en", region = "region-select" } = useParams();
@@ -168,6 +170,7 @@ export default function MiniDrawer({ children }) {
admin:
,
signIn:
,
logout:
,
+ sharedWithMe:
,
};
const topBarBackgroundColor = region
? regions[region].colors.primary
@@ -351,6 +354,24 @@ export default function MiniDrawer({ children }) {
+ {hasSharedRecords && (
+
+ history.push(`${baseURL}/shared`)}
+ >
+
+
+
+
+
+
+ )}
+
{userIsReviewer && (
{
+ this.unsubscribe = onAuthStateChanged(getAuth(firebase), async (user) => {
if (user) {
// Reference to the regionAdmin in the database
const adminRef = ref(database, "admin");
@@ -66,8 +73,16 @@ class Admin extends FormClassTemplate {
const permissionsRef = child(regionAdminRef, "permissions");
const projects = await getRegionProjects(region);
- const datacitePrefix = await getDatacitePrefix(region);
- const credentialsStored = await getCredentialsStored(region);
+ const datacitePrefix = await getDatacitePrefix(region).then(
+ (response) => {
+ return response.data;
+ }
+ );
+ const credentialsStored = await getCredentialsStored(region).then(
+ (response) => {
+ return response.data;
+ }
+ );
onValue(permissionsRef, (permissionsFirebase) => {
const permissions = permissionsFirebase.toJSON();
@@ -125,7 +140,7 @@ class Admin extends FormClassTemplate {
handleDisableDoiCreation = async () => {
const { region } = this.props.match.params;
-
+
try {
await deleteAllDataciteCredentials(region);
this.setState({
@@ -145,22 +160,33 @@ class Admin extends FormClassTemplate {
const { match } = this.props;
const { region } = match.params;
+ const { reviewers, admins, projects } = this.state;
+ const database = getDatabase(firebase);
+
+ if (auth.currentUser) {
+ const regionAdminRef = ref(database, `admin/${region}`);
+ const permissionsRef = child(regionAdminRef, "permissions");
+ const projectsRef = child(regionAdminRef, "projects");
+
+ set(child(permissionsRef, "admins"), cleanArr(admins).join());
+
+ set(projectsRef, cleanArr(projects));
+ set(child(permissionsRef, "reviewers"), cleanArr(reviewers).join());
+ }
+ }
+
+ saveDoiCredentials() {
+ const { match } = this.props;
+ const { region } = match.params;
+
const {
- reviewers,
- admins,
- projects,
datacitePrefix,
dataciteAccountId,
datacitePass,
isDoiCreationEnabled,
} = this.state;
- const database = getDatabase(firebase);
- // Check if DOI creation is enabled but credentials are not stored
- if (isDoiCreationEnabled && (!datacitePrefix || !dataciteAccountId || !datacitePass)) {
- this.setState({ showCredentialsMissingDialog: true });
- return;
- }
+ const database = getDatabase(firebase);
const bufferObj = Buffer.from(
`${dataciteAccountId}:${datacitePass}`,
@@ -168,16 +194,19 @@ class Admin extends FormClassTemplate {
);
const base64String = bufferObj.toString("base64");
+ // Check if DOI creation is enabled but credentials are not stored
+ if (
+ isDoiCreationEnabled &&
+ (!datacitePrefix || !dataciteAccountId || !datacitePass)
+ ) {
+ this.setState({ showCredentialsMissingDialog: true });
+ return;
+ }
+
if (auth.currentUser) {
const regionAdminRef = ref(database, `admin/${region}`);
- const permissionsRef = child(regionAdminRef, "permissions");
- const projectsRef = child(regionAdminRef, "projects");
const dataciteRef = child(regionAdminRef, "dataciteCredentials");
- set(child(permissionsRef,"admins"), cleanArr(admins).join());
-
- set(projectsRef, cleanArr(projects));
- set(child(permissionsRef, "reviewers"), cleanArr(reviewers).join());
set(child(dataciteRef, "prefix"), datacitePrefix);
set(child(dataciteRef, "dataciteHash"), base64String);
@@ -196,17 +225,27 @@ class Admin extends FormClassTemplate {
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
- Delete Datacite Credentials?
+
+ Delete Datacite Credentials?
+
- Disabling DOI creation will delete the stored credentials. Are you sure you want to proceed?
+ Disabling DOI creation will delete the stored credentials. Are you
+ sure you want to proceed?
-
@@ -222,14 +261,22 @@ class Admin extends FormClassTemplate {
aria-labelledby="credentials-missing-dialog-title"
aria-describedby="credentials-=missing-dialog-description"
>
- Missing DataCite Credentials
+
+ Missing DataCite Credentials
+
Please add DataCite credentials before saving.
- this.setState({ showCredentialsMissingDialog: false })} color="primary" autoFocus>
+
+ this.setState({ showCredentialsMissingDialog: false })
+ }
+ color="primary"
+ autoFocus
+ >
OK
@@ -247,9 +294,9 @@ class Admin extends FormClassTemplate {
};
validateDatacitePrefix = (prefix) => {
- const isValid = /^10\.\d+/.test(prefix);
- this.setState({ datacitePrefixValid: isValid });
-};
+ const isValid = /^10\.\d+/.test(prefix);
+ this.setState({ datacitePrefixValid: isValid });
+ };
render() {
const {
@@ -289,68 +336,82 @@ class Admin extends FormClassTemplate {
) : (
<>
-
-
-
-
- Projects
- Projets
-
-
-
-
-
- this.setState({ projects: e.target.value.split("\n") })
- }
- />
-
+
+
+
+
+ Projects
+ Projets
+
+
+
+
+
+ this.setState({ projects: e.target.value.split("\n") })
+ }
+ />
+
-
-
-
- Admins
- Administrateurs
-
-
-
-
-
- this.setState({ admins: e.target.value.split("\n") })
- }
- />
-
+
+
+
+ Admins
+ Administrateurs
+
+
+
+
+
+ this.setState({ admins: e.target.value.split("\n") })
+ }
+ />
+
+
+
+
+ Reviewers
+ Réviseurs
+
+
+
+
+
+ this.setState({
+ reviewers: e.target.value.split("\n"),
+ })
+ }
+ />
+
+
-
+ }
+ variant="contained"
+ color="primary"
+ style={{ margin: 10 }}
+ onClick={() => this.save()}
+ >
- Reviewers
- Réviseurs
+ Save Admin Settings
+ Enregistrer
-
-
-
-
- this.setState({
- reviewers: e.target.value.split("\n"),
- })
- }
- />
+
-
@@ -388,7 +449,13 @@ class Admin extends FormClassTemplate {
-
+
Credentials Stored
Identifiants Enregistrés
@@ -401,7 +468,13 @@ class Admin extends FormClassTemplate {
-
+
Please Add DataCite Credentials
Identifiants Enregistrés
@@ -427,7 +500,10 @@ class Admin extends FormClassTemplate {
onChange={this.handleChange}
fullWidth
error={!this.state.datacitePrefixValid}
- helperText={!this.state.datacitePrefixValid && "Prefix must start with '10.' followed by numbers."}
+ helperText={
+ !this.state.datacitePrefixValid &&
+ "Prefix must start with '10.' followed by numbers."
+ }
/>
@@ -476,28 +552,30 @@ class Admin extends FormClassTemplate {
>
)}
+ {this.state.isDoiCreationEnabled && (
+ }
+ variant="contained"
+ color="primary"
+ onClick={() => this.saveDoiCredentials()}
+ style={{ margin: 10 }}
+ >
+
+ Save DOI Settings
+ Enregistrer les paramètres DOI
+
+
+ )}
-
- }
- variant="contained"
- color="secondary"
- onClick={() => this.save()}
- >
-
- Save
- Enregistrer
-
-
-
>
)}
- {this.renderDeletionDialog()}
+ {this.renderDeletionDialog()}
{this.renderCredentialsMissingDialog()}
);
}
}
+Admin.contextType = UserContext;
export default Admin;
diff --git a/src/components/Pages/MetadataForm.jsx b/src/components/Pages/MetadataForm.jsx
index ee822ce6..e0aaba3e 100644
--- a/src/components/Pages/MetadataForm.jsx
+++ b/src/components/Pages/MetadataForm.jsx
@@ -144,7 +144,7 @@ class MetadataForm extends FormClassTemplate {
const loggedInUserOwnsRecord = loggedInUserID === recordUserID;
const { isReviewer } = this.context;
- this.setState({ projects: await getRegionProjects(region) });
+ this.setState({ projects: await getRegionProjects(region), loggedInUserID: user.uid });
let editorInfo;
// get info of the person openeing the record
const editorDataRef = child(ref(database, `${region}/users`), loggedInUserID);
@@ -187,8 +187,10 @@ class MetadataForm extends FormClassTemplate {
}
const record = firebaseToJSObject(recordFireBaseObj);
+ const loggedInUserIsSharedWith = record.sharedWith && record.sharedWith[loggedInUserID] === true;
+
const loggedInUserCanEditRecord =
- isReviewer || loggedInUserOwnsRecord;
+ isReviewer || loggedInUserOwnsRecord || loggedInUserIsSharedWith;
this.setState({
record: standardizeRecord(record, null, null, recordID),
@@ -254,11 +256,11 @@ class MetadataForm extends FormClassTemplate {
const { match } = this.props;
const { region, language } = match.params;
const { record} = this.state;
- const { datacitePrefix, dataciteAuthHash } = this.context;
+ const { datacitePrefix } = this.context;
try {
- if (datacitePrefix && dataciteAuthHash){
- const statusCode = await performUpdateDraftDoi(record, region, language, datacitePrefix, dataciteAuthHash);
+ if (datacitePrefix){
+ const statusCode = await performUpdateDraftDoi(record, region, language, datacitePrefix);
if (statusCode === 200) {
this.state.doiUpdated = true
@@ -382,6 +384,7 @@ class MetadataForm extends FormClassTemplate {
loggedInUserCanEditRecord,
saveIncompleteRecordModalOpen,
projects,
+ loggedInUserID,
} = this.state;
if (!record) {
@@ -397,8 +400,10 @@ class MetadataForm extends FormClassTemplate {
record,
handleUpdateRecord: this.handleUpdateRecord,
updateRecord: this.updateRecord,
+ userID:loggedInUserID,
};
const percentValidInt = Math.round(percentValid(record) * 100);
+
return loading ? (
) : (
diff --git a/src/components/Pages/Shared.jsx b/src/components/Pages/Shared.jsx
new file mode 100644
index 00000000..f048b788
--- /dev/null
+++ b/src/components/Pages/Shared.jsx
@@ -0,0 +1,202 @@
+import React from "react";
+
+import { Typography, CircularProgress, List } from "@material-ui/core";
+import { getDatabase, ref, onValue, get, off } from "firebase/database";
+import { I18n, En, Fr } from "../I18n";
+import FormClassTemplate from "./FormClassTemplate";
+import firebase from "../../firebase";
+import {
+ multipleFirebaseToJSObject,
+ cloneRecord,
+} from "../../utils/firebaseRecordFunctions";
+
+import { getAuth, onAuthStateChanged } from "../../auth";
+
+import MetadataRecordListItem from "../FormComponents/MetadataRecordListItem";
+
+class Shared extends FormClassTemplate {
+ constructor(props) {
+ super(props);
+ this.state = {
+ sharedRecords: {},
+ loading: false,
+ };
+ this.unsubscribe = null;
+ this.listenerRefs = [];
+ this.handleCloneRecord = this.handleCloneRecord.bind(this);
+ }
+
+ async loadSharedRecords() {
+ this.setState({ loading: true });
+ const { match } = this.props;
+ const { region } = match.params;
+ const database = getDatabase();
+
+ // Set up a listener for changes in authentication state
+ this.unsubscribe = onAuthStateChanged(getAuth(firebase), async (user) => {
+ if (user) {
+ // Reference to the 'shares' node for the current user in the specified region
+ const sharesRef = ref(database, `${region}/shares/${user.uid}`);
+ this.listenerRefs.push(sharesRef);
+
+ // Listen for changes in the shares data
+ onValue(sharesRef, async (snapshot) => {
+ // Snapshot of the user's shares
+ const sharesSnapshot = snapshot.val();
+
+ // Initialize an array to hold promises for fetching each shared record
+ const recordsPromises = [];
+
+ // Iterate over each shared record, organized by authorID under the user's ID
+ Object.entries(sharesSnapshot || {}).forEach(([authorID, recordsByAuthor]) => {
+ // Iterate over each recordID shared by this author
+ Object.keys(recordsByAuthor || {}).forEach((recordID) => {
+ // Construct the path to the actual record data based on its authorID and recordID
+ const recordPath = `${region}/users/${authorID}/records/${recordID}`;
+ const recordRef = ref(database, recordPath);
+ // Fetch the record's details and store the promise in the array
+ const recordPromise = get(recordRef).then((recordSnapshot) => {
+ const recordDetails = recordSnapshot.val();
+ if (recordDetails) {
+ // Return the complete record details, ensuring all data fields are included
+ return {
+ ...recordDetails,
+ recordID,
+ };
+ }
+ throw new Error(`No details found for record ${recordID} by author ${authorID}`);
+ });
+ recordsPromises.push(recordPromise); // Collect the promise
+ });
+ });
+
+ // Await the resolution of all record detail promises
+ const records = await Promise.all(recordsPromises);
+ // Accumulate the records into an object mapping record IDs to record details
+ const sharedRecords = records.reduce((acc, record) => {
+ acc[record.recordID] = record; // Map each record by its ID
+ return acc;
+ }, {});
+
+ // Update the component state with the fetched shared records and set loading to false
+ this.setState({
+ sharedRecords: multipleFirebaseToJSObject(sharedRecords),
+ loading: false,
+ });
+ });
+
+ // Keep a reference to the listener to remove it when the component unmounts
+ this.listenerRefs.push(sharesRef);
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ // fixes error Can't perform a React state update on an unmounted component
+ this.unsubscribeAndCloseListeners();
+ }
+
+ unsubscribeAndCloseListeners() {
+ if (this.unsubscribe) this.unsubscribe();
+ if (this.listenerRefs.length) {
+ this.listenerRefs.forEach((refListener) => off(refListener));
+ }
+ }
+
+ async componentDidMount() {
+ this.loadSharedRecords();
+ }
+
+ editRecord(key, authorID) {
+ const { match, history } = this.props;
+ const { language, region } = match.params;
+ history.push(`/${language}/${region}/${authorID}/${key}`);
+ }
+
+ handleCloneRecord(recordID, authorID) {
+ const { region } = this.props.match.params;
+
+ const { currentUser } = getAuth(firebase);
+ if (currentUser) {
+ cloneRecord(recordID, authorID, currentUser.uid, region)
+ }
+ }
+
+ render() {
+ const { sharedRecords, loading } = this.state;
+
+ const recordDateSort = (a, b) =>
+ new Date(b[1].created) - new Date(a[1].created);
+
+ return (
+
+
+
+ Shared with me
+ Partagé avec moi
+
+
+
+ {loading ? (
+
+ ) : (
+
+
+
+
+
+ The following records have been shared with you for editing.
+
+
+ Les enregistrements suivants ont été partagés avec vous pour
+ modification.
+
+
+
+
+
+ You can edit them, but you cannot submit or delete.
+
+ Vous pouvez les modifier, mais vous ne pouvez pas les
+ soumettre ou les supprimer.
+
+
+
+
+ {Object.entries(sharedRecords || {})
+ .sort(recordDateSort)
+ .map(([key, record]) => {
+ const { title } = record;
+
+ if (!(title?.en || !title?.fr)) return null;
+
+ return (
+ this.handleCloneRecord(key, record.userID)}
+ showEditAction
+ showPercentComplete
+ onViewEditClick={() => this.editRecord(key, record.userID)}
+ />
+ );
+ })}
+
+
+ {!sharedRecords && (
+
+
+ You don't have any records shared with you.
+ Vous n'avez aucun enregistrement partagé avec vous.
+
+
+ )}
+
+ )}
+
+ );
+ }
+}
+
+export default Shared;
diff --git a/src/components/Tabs/StartTab.jsx b/src/components/Tabs/StartTab.jsx
index 5f759dc8..e1de8d4f 100644
--- a/src/components/Tabs/StartTab.jsx
+++ b/src/components/Tabs/StartTab.jsx
@@ -1,4 +1,4 @@
-import React, { useRef, useEffect } from "react";
+import React, { useRef, useEffect, useState } from "react";
import { Save } from "@material-ui/icons";
import {
@@ -21,14 +21,16 @@ import { paperClass, QuestionText, SupplementalText } from "../FormComponents/Qu
import { validateField } from "../../utils/validate";
import { metadataScopeCodes } from "../../isoCodeLists";
import CheckBoxList from "../FormComponents/CheckBoxList";
+import SharedUsersList from "../FormComponents/SharedUsersList";
import SelectInput from "../FormComponents/SelectInput";
const {DataCollectionSampling, ...filtereMetadataScopeCodes} = metadataScopeCodes;
-const StartTab = ({ disabled, record, updateRecord, handleUpdateRecord }) => {
+const StartTab = ({ disabled, record, updateRecord, handleUpdateRecord, userID }) => {
const { language, region } = useParams();
const regionInfo = regions[region];
+ const [showShareRecord, setShowShareRecord] = useState(false)
const mounted = useRef(false);
const updateResourceType = (value) => {
@@ -70,11 +72,20 @@ const StartTab = ({ disabled, record, updateRecord, handleUpdateRecord }) => {
};
}, [language]);
+ useEffect(() => {
+
+ const isNewRecord = !record.recordID;
+
+ if (userID === record.userID || isNewRecord) {
+ setShowShareRecord(true);
+ }
+ }, [userID, record.userID, record.recordID]);
+
return (
{disabled && (
-
+
@@ -89,7 +100,7 @@ const StartTab = ({ disabled, record, updateRecord, handleUpdateRecord }) => {
-
+
)}
@@ -181,7 +192,7 @@ const StartTab = ({ disabled, record, updateRecord, handleUpdateRecord }) => {
-
+
@@ -299,6 +310,13 @@ const StartTab = ({ disabled, record, updateRecord, handleUpdateRecord }) => {
disabled={disabled}
/>
+ {showShareRecord && (
+
+ )}
);
};
diff --git a/src/components/Tabs/SubmitTab.jsx b/src/components/Tabs/SubmitTab.jsx
index 6269ac48..e931e527 100644
--- a/src/components/Tabs/SubmitTab.jsx
+++ b/src/components/Tabs/SubmitTab.jsx
@@ -26,10 +26,11 @@ import tabs from "../../utils/tabs";
import GetRegionInfo from "../FormComponents/Regions";
-const SubmitTab = ({ record, submitRecord, doiUpdated, doiError }) => {
+const SubmitTab = ({ record, submitRecord, userID, doiUpdated, doiError }) => {
const mounted = useRef(false);
const [isSubmitting, setSubmitting] = useState(false);
const [validationWarnings, setValidationWarnings] = useState(false);
+ const [showSubmitButton, setShowSubmitButton] = useState(false);
const { language } = useParams();
@@ -38,8 +39,11 @@ const SubmitTab = ({ record, submitRecord, doiUpdated, doiError }) => {
const regionInfo = GetRegionInfo();
useEffect(() => {
+ mounted.current = true;
- mounted.current = true
+ if (userID === record.userID) {
+ setShowSubmitButton(true);
+ }
const getUrlWarningsByTab = async (recordObj) => {
const fields = Object.keys(warnings);
@@ -71,8 +75,7 @@ const SubmitTab = ({ record, submitRecord, doiUpdated, doiError }) => {
},
{}
);
- if (mounted.current)
- setValidationWarnings(fieldWarningInfoReduced);
+ if (mounted.current) setValidationWarnings(fieldWarningInfoReduced);
};
getUrlWarningsByTab(record);
@@ -80,8 +83,7 @@ const SubmitTab = ({ record, submitRecord, doiUpdated, doiError }) => {
return () => {
mounted.current = false;
};
-
- }, [record]);
+ }, [record, userID]);
return (
@@ -177,9 +179,8 @@ const SubmitTab = ({ record, submitRecord, doiUpdated, doiError }) => {
- {isSubmitting ? (
-
- ) : (
+ {isSubmitting && }
+ {!isSubmitting && showSubmitButton && (
{
setSubmitting(true);
diff --git a/src/firebase.js b/src/firebase.js
index b219ba79..0ad66a32 100644
--- a/src/firebase.js
+++ b/src/firebase.js
@@ -7,10 +7,7 @@ import { initializeApp } from 'firebase/app'
const deployedOnTestServer = process.env.REACT_APP_DEV_DEPLOYMENT;
-const productionDB = "https://cioos-metadata-form.firebaseio.com";
-const devDB = "https://cioos-metadata-form-dev.firebaseio.com";
-
-const config = {
+const prodConfig = {
// see https://console.cloud.google.com/apis/credentials?project=cioos-metadata-form
// and https://console.cloud.google.com/apis/credentials?project=cioos-metadata-form-dev
// for api key location which is then stored in a github secret and added to several
@@ -20,16 +17,28 @@ const config = {
// To prevent the future foot gun, we are restricting the key now.
apiKey: process.env.REACT_APP_GOOGLE_CLOUD_API_KEY,
authDomain: "cioos-metadata-form.firebaseapp.com",
- databaseURL:
- process.env.NODE_ENV === "production" && !deployedOnTestServer
- ? productionDB
- : devDB,
+ databaseURL: "https://cioos-metadata-form.firebaseio.com",
projectId: "cioos-metadata-form",
storageBucket: "cioos-metadata-form.appspot.com",
messagingSenderId: "646114203434",
appId: "1:646114203434:web:bccceadc5144270f98f053",
};
+const devConfig = {
+ apiKey: process.env.REACT_APP_GOOGLE_CLOUD_API_KEY_DEV,
+ authDomain: "cioos-metadata-form-dev.firebaseapp.com",
+ databaseURL: "https://cioos-metadata-form-dev-default-rtdb.firebaseio.com/",
+ projectId: "cioos-metadata-form-dev",
+ storageBucket: "cioos-metadata-form-dev.appspot.com",
+ messagingSenderId: "392401521083",
+ appId: "1:392401521083:web:45d1539f9d284f446d5c9e",
+};
+
+
+const config = process.env.NODE_ENV === "production" && !deployedOnTestServer
+ ? prodConfig
+ : devConfig
+
if (window.location.hostname === "localhost" && deployedOnTestServer) {
config.databaseURL = "http://localhost:9001?ns=cioos-metadata-form"
}
diff --git a/src/providers/UserProvider.jsx b/src/providers/UserProvider.jsx
index 1898f3f7..1539b1f6 100644
--- a/src/providers/UserProvider.jsx
+++ b/src/providers/UserProvider.jsx
@@ -1,3 +1,4 @@
+/* eslint-disable camelcase */
import React, { createContext } from "react";
import { withRouter } from "react-router-dom";
import * as Sentry from "@sentry/react";
@@ -20,6 +21,7 @@ class UserProvider extends FormClassTemplate {
reviewers: [],
isReviewer: false,
loggedIn: false,
+ hasSharedRecords: false,
};
}
@@ -40,24 +42,18 @@ class UserProvider extends FormClassTemplate {
});
});
- const database = getDatabase(firebase);
- // should be replaced with getDatacitePrefix but can't as this function is not async
- const prefixRef = ref(database, `admin/${region}/dataciteCredentials/prefix`);
- onValue(prefixRef, (prefix) => {
- this.setState({
- datacitePrefix: prefix.val(),
- });
- });
- // should be replaced with getDataciteAuthHash but can't as this function is not async
- const authHashRef = ref(database, `admin/${region}/dataciteCredentials/dataciteHash`);
- onValue(authHashRef, (authHash) => {
- this.setState({
- dataciteAuthHash: authHash.val(),
+ // should be replaced with getDatacitePrefix but can't as this function is not async
+ const functions = getFunctions();
+ const getDatacitePrefix = httpsCallable(functions, "getDatacitePrefix");
+ getDatacitePrefix(region)
+ .then((prefix) => {
+ this.setState({
+ datacitePrefix: prefix?.data,
+ });
});
- });
-
+ const database = getDatabase(firebase);
update( ref(database, `${region}/users/${uid}/userinfo`), { displayName, email });
const permissionsRef = ref(database, `admin/${region}/permissions`)
@@ -78,7 +74,19 @@ class UserProvider extends FormClassTemplate {
isReviewer,
});
});
+
this.listenerRefs.push(permissionsRef);
+
+ // real-time listener for shared records
+ const sharesRef = ref(database, `${region}/shares/${uid}`);
+
+ onValue(sharesRef, (snapshot) => {
+ const hasSharedRecords = snapshot.exists();
+ this.setState({ hasSharedRecords, authIsLoading: false });
+ });
+
+ this.listenerRefs.push(sharesRef);
+
} else {
this.setState({
loggedIn: false,
@@ -100,6 +108,8 @@ class UserProvider extends FormClassTemplate {
const deleteDraftDoi = httpsCallable(functions, "deleteDraftDoi");
const getDoiStatus = httpsCallable(functions, "getDoiStatus");
const checkURLActive = httpsCallable(functions, "checkURLActive");
+ const getCredentialsStored = httpsCallable(functions, "getCredentialsStored");
+ const getDatacitePrefix = httpsCallable(functions, "getDatacitePrefix");
return (
{children}
diff --git a/src/utils/doiUpdate.js b/src/utils/doiUpdate.js
index e332a5d3..fe00b4a8 100644
--- a/src/utils/doiUpdate.js
+++ b/src/utils/doiUpdate.js
@@ -1,7 +1,7 @@
import { getFunctions, httpsCallable } from "firebase/functions";
import recordToDataCite from "./recordToDataCite";
-async function performUpdateDraftDoi(record, region, language, datacitePrefix, dataciteAuthHash) {
+async function performUpdateDraftDoi(record, region, language, datacitePrefix) {
const functions = getFunctions();
const updateDraftDoi = httpsCallable(functions, "updateDraftDoi");
@@ -13,9 +13,9 @@ async function performUpdateDraftDoi(record, region, language, datacitePrefix, d
const doi = record.datasetIdentifier.replace('https://doi.org/', '');
const dataObject = {
- doi,
+ doi,
+ region,
data: mappedDataCiteObject,
- dataciteAuthHash,
}
const response = await updateDraftDoi(dataObject);
diff --git a/src/utils/firebaseEnableDoiCreation.js b/src/utils/firebaseEnableDoiCreation.js
index cf2fc147..ee4745e7 100644
--- a/src/utils/firebaseEnableDoiCreation.js
+++ b/src/utils/firebaseEnableDoiCreation.js
@@ -1,4 +1,4 @@
-import { getDatabase, ref, child, get, set, remove } from "firebase/database";
+import { getDatabase, ref, set, remove } from "firebase/database";
import firebase from "../firebase";
export async function newDataciteAccount(region, prefix, authHash) {
@@ -25,41 +25,4 @@ export async function deleteAllDataciteCredentials(region) {
} catch (error) {
throw new Error(`Failed to delete Datacite credentials.: ${error}`);
}
-}
-
-export async function getDatacitePrefix(region) {
- try {
- const database = getDatabase(firebase);
- const prefix = (await get(ref(database, `admin/${region}/dataciteCredentials/prefix`), "value")).val();
- return prefix;
- } catch (error) {
- throw new Error(`Error fetching Datacite Prefix for region ${region}: ${error}`);
- }
-}
-
-export async function getAuthHash(region) {
- try {
- const database = getDatabase(firebase);
- const authHash = (await get(ref(database, `admin/${region}/dataciteCredentials/dataciteHash`), "value")).val();
- return authHash;
- } catch (error) {
- throw new Error(`Error fetching Datacite Auth Hash for region ${region}: ${error}`);
- }
-}
-
-export async function getCredentialsStored(region) {
- try {
- const database = getDatabase(firebase);
- const credentialsRef = ref(database, `admin/${region}/dataciteCredentials`);
- const authHashSnapshot = await get(child(credentialsRef, "dataciteHash"), "value");
- const prefixSnapshot = await get(child(credentialsRef, "prefix"), "value");
-
- const authHash = authHashSnapshot.val();
- const prefix = prefixSnapshot.val();
-
- // Check for non-null and non-empty
- return authHash && authHash !== "" && prefix && prefix !== "";
- } catch (error) {
- throw new Error(`Error checking Datacite credentials: ${error}`);
- }
-}
+}
\ No newline at end of file
diff --git a/src/utils/firebaseRecordFunctions.js b/src/utils/firebaseRecordFunctions.js
index 2aa3fba1..3797fffb 100644
--- a/src/utils/firebaseRecordFunctions.js
+++ b/src/utils/firebaseRecordFunctions.js
@@ -157,3 +157,42 @@ export const multipleFirebaseToJSObject = (multiple) => {
return acc;
}, {});
};
+
+// fetches a region's users
+export async function loadRegionUsers(region) {
+ const database = getDatabase(firebase);
+ try {
+ const regionUsersRef = ref(database, `${region}/users`);
+ const regionUsers = (await get(regionUsersRef, "value")).val();
+
+ return regionUsers
+
+ } catch (error) {
+ throw new Error(`Error fetching user emails for region ${region}: ${error}`);
+ }
+}
+
+/**
+ * Asynchronously shares or unshares a record with a single user by updating the 'shares' node in Firebase.
+ * This function directly uses the userID to share or unshare the record.
+ *
+ * @param {string} userID
+ * @param {string} recordID
+ * @param {string} authorID - The ID of the author of the record, included when the record is shared.
+ * @param {string} region
+ * @param {boolean} share
+ */
+export async function updateSharedRecord(userID, recordID, authorID, region, share) {
+ const database = getDatabase(firebase);
+ const sharesRef = ref(database, `${region}/shares/${userID}/${authorID}/${recordID}`);
+
+ if (share) {
+ // Share the record with the user by setting it directly under the authorID node
+ await set(sharesRef, { shared: true })
+ .catch(error => {throw new Error(`Error sharing record by author ${authorID} with user ${userID}: ${error}`)});
+ } else {
+ // Unshare the record from the user
+ await remove(sharesRef)
+ .catch(error => { throw new Error(`Error unsharing record by author ${authorID} with user ${userID}: ${error}`) });
+ }
+}
\ No newline at end of file
diff --git a/src/utils/recordToDataCite.js b/src/utils/recordToDataCite.js
index e1dc55b1..0efe1ebc 100644
--- a/src/utils/recordToDataCite.js
+++ b/src/utils/recordToDataCite.js
@@ -6,218 +6,218 @@ function recordToDataCite(metadata, language, region, datacitePrefix) {
// Reduce contacts to a list of creators
const creators = metadata.contacts ? metadata.contacts.reduce((creatorList, contact) => {
- let creator;
-
- if (contact.inCitation && !contact.role.includes("publisher")) {
- const {
- givenNames,
- lastName,
- orgName,
- indOrcid,
- orgRor,
- } = contact;
-
- // Create an individual creator object with names
- if (givenNames) {
- creator = {
- name: `${lastName}, ${givenNames}`,
- nameType: "Personal",
- givenName: givenNames,
- familyName: lastName,
- // Add affiliation for individual if organization details are provided
- affiliation: orgName ? [{
- name: orgName,
- schemeUri: "https://ror.org",
- affiliationIdentifier: orgRor,
- affiliationIdentifierScheme: "ROR",
- }] : [],
- };
-
- // Add nameIdentifiers for individual with an ORCID
- if (indOrcid) {
- creator.nameIdentifiers = [
- {
- schemeUri: "https://orcid.org",
- nameIdentifier: indOrcid,
- nameIdentifierScheme: "ORCID",
- },
- ];
+ let creator;
+
+ if (contact.inCitation && !contact.role.includes("publisher")) {
+ const {
+ givenNames,
+ lastName,
+ orgName,
+ indOrcid,
+ orgRor,
+ } = contact;
+
+ // Create an individual creator object with names
+ if (givenNames) {
+ creator = {
+ name: `${lastName}, ${givenNames}`,
+ nameType: "Personal",
+ givenName: givenNames,
+ familyName: lastName,
+ // Add affiliation for individual if organization details are provided
+ affiliation: orgName ? [{
+ name: orgName,
+ schemeUri: "https://ror.org",
+ affiliationIdentifier: orgRor,
+ affiliationIdentifierScheme: "ROR",
+ }] : [],
+ };
+
+ // Add nameIdentifiers for individual with an ORCID
+ if (indOrcid) {
+ creator.nameIdentifiers = [
+ {
+ schemeUri: "https://orcid.org",
+ nameIdentifier: indOrcid,
+ nameIdentifierScheme: "ORCID",
+ },
+ ];
+ }
}
- }
}
-
- // Add the creator to the list if it exists
- if (creator) {
- creatorList.push(creator);
- }
-
- return creatorList;
+
+ // Add the creator to the list if it exists
+ if (creator) {
+ creatorList.push(creator);
+ }
+
+ return creatorList;
}, []) : [];
-
+
// Find the publisher contact
const publisher = metadata.contacts.find((contact) =>
- contact.role.includes("publisher")
+ contact.role.includes("publisher")
);
// Filter all contacts with the role of 'funder'
- const funders = metadata.contacts.filter((contact) =>
- contact.role.includes("funder")
+ const funders = metadata.contacts.filter((contact) =>
+ contact.role.includes("funder")
);
-
+
// Get the publication year from the datePublished field, if dateRevised contains value use dateRevised as publication year
let publicationYear;
if (metadata.dateRevised) {
- const revisedYear = parseInt(metadata.dateRevised.slice(0, 4), 10);
- publicationYear = Number.isNaN(revisedYear) ? undefined : revisedYear;
+ const revisedYear = parseInt(metadata.dateRevised.slice(0, 4), 10);
+ publicationYear = Number.isNaN(revisedYear) ? undefined : revisedYear;
} else if (metadata.datePublished) {
- const publishedYear = parseInt(metadata.datePublished.slice(0, 4), 10)
- publicationYear = Number.isNaN(publishedYear) ? undefined : publishedYear;
+ const publishedYear = parseInt(metadata.datePublished.slice(0, 4), 10)
+ publicationYear = Number.isNaN(publishedYear) ? undefined : publishedYear;
} else {
- publicationYear = undefined;
+ publicationYear = undefined;
}
-
+
// Create the DataCite subjects from the keywords field
const subjects = metadata.keywords
- ? Object.entries(metadata.keywords).flatMap(([lang, keywords]) =>
- keywords.map((keyword) => ({
- lang,
- subject: keyword,
- }))
+ ? Object.entries(metadata.keywords).flatMap(([lang, keywords]) =>
+ keywords.map((keyword) => ({
+ lang,
+ subject: keyword,
+ }))
)
- : undefined;
-
+ : undefined;
+
// Create the DataCite dates from the date fields
const dates = [];
-
+
if (metadata.dateStart) {
- dates.push({
- date: metadata.dateStart,
- dateType: "Collected",
- dateInformation: "Start date when data was first collected",
- });
+ dates.push({
+ date: metadata.dateStart,
+ dateType: "Collected",
+ dateInformation: "Start date when data was first collected",
+ });
}
-
+
if (metadata.dateEnd) {
- dates.push({
- date: metadata.dateEnd,
- dateType: "Collected",
- dateInformation: "End date when data was last collected",
- });
+ dates.push({
+ date: metadata.dateEnd,
+ dateType: "Collected",
+ dateInformation: "End date when data was last collected",
+ });
}
-
+
if (metadata.dateRevised) {
- dates.push({
- date: metadata.dateRevised,
- dateType: "Updated",
- dateInformation: "Date when the data was last revised",
- });
+ dates.push({
+ date: metadata.dateRevised,
+ dateType: "Updated",
+ dateInformation: "Date when the data was last revised",
+ });
}
// Look up the license information
const licenseInfo = licenses[metadata.license];
-
+
// Create the DataCite rightsList object
const rightsList = licenseInfo && licenseInfo.title ? [
- {
- rights: licenseInfo.title.en,
- rightsUri: licenseInfo.url,
- schemeUri: "https://spdx.org/licenses/",
- rightsIdentifier: licenseInfo.code,
- rightsIdentifierScheme: "SPDX",
- },
+ {
+ rights: licenseInfo.title.en,
+ rightsUri: licenseInfo.url,
+ schemeUri: "https://spdx.org/licenses/",
+ rightsIdentifier: licenseInfo.code,
+ rightsIdentifierScheme: "SPDX",
+ },
] : [];
-
+
// Extract the values from the map field
let { east, north, south, west } = metadata.map ? metadata.map : {};
east = Number.isNaN(east) ? undefined : east;
north = Number.isNaN(north) ? undefined : north;
south = Number.isNaN(south) ? undefined : south;
- west = Number.isNaN(west) ? undefined : west;
-
+ west = Number.isNaN(west) ? undefined : west;
+
// Create the DataCite geoLocations object
const geoLocations = metadata.map && east && north && south && west ? [
- {
- geoLocationBox: {
- eastBoundLongitude: parseFloat(east),
- northBoundLatitude: parseFloat(north),
- southBoundLatitude: parseFloat(south),
- westBoundLongitude: parseFloat(west),
+ {
+ geoLocationBox: {
+ eastBoundLongitude: parseFloat(east),
+ northBoundLatitude: parseFloat(north),
+ southBoundLatitude: parseFloat(south),
+ westBoundLongitude: parseFloat(west),
+ },
},
- },
] : [];
-
+
// Create the mapped DataCite object with the extracted information
const mappedDataCiteObject = {
- data: {
- type: "dois",
- attributes: {
- prefix: datacitePrefix,
- creators,
- // Initialize an empty array for titles
- titles: [],
+ data: {
+ type: "dois",
+ attributes: {
+ prefix: datacitePrefix,
+ creators,
+ // Initialize an empty array for titles
+ titles: [],
+ },
},
- },
};
// Add English title if it exists
if (metadata.title.en) {
- mappedDataCiteObject.data.attributes.titles.push({
- lang: "en",
- title: metadata.title.en,
- });
+ mappedDataCiteObject.data.attributes.titles.push({
+ lang: "en",
+ title: metadata.title.en,
+ });
}
// Add French title if it exists
if (metadata.title.fr) {
- mappedDataCiteObject.data.attributes.titles.push({
- lang: "fr",
- title: metadata.title.fr,
- });
+ mappedDataCiteObject.data.attributes.titles.push({
+ lang: "fr",
+ title: metadata.title.fr,
+ });
}
// Add publisher if it exists
if (publisher) {
- mappedDataCiteObject.data.attributes.publisher =
- publisher.orgName || publisher.indName;
+ mappedDataCiteObject.data.attributes.publisher =
+ publisher.orgName || publisher.indName;
}
// Add funders list if it exists
if (funders && funders.length > 0) {
- mappedDataCiteObject.data.attributes.fundingReferences = funders.map(funder => {
- const fundingReference = {
- funderName: funder.orgName,
- };
-
- // Add ROR information if available
- if (funder.orgRor) {
- fundingReference.funderIdentifier = funder.orgRor;
- fundingReference.funderIdentifierType = "ROR";
- }
+ mappedDataCiteObject.data.attributes.fundingReferences = funders.map(funder => {
+ const fundingReference = {
+ funderName: funder.orgName,
+ };
+
+ // Add ROR information if available
+ if (funder.orgRor) {
+ fundingReference.funderIdentifier = funder.orgRor;
+ fundingReference.funderIdentifierType = "ROR";
+ }
- return fundingReference;
- });
- }
+ return fundingReference;
+ });
+ }
// Add publication year if it exists
if (metadata.datePublished) {
- mappedDataCiteObject.data.attributes.publicationYear = publicationYear;
+ mappedDataCiteObject.data.attributes.publicationYear = publicationYear;
}
// Add subjects if they exist
if (metadata.keywords) {
- mappedDataCiteObject.data.attributes.subjects = subjects;
+ mappedDataCiteObject.data.attributes.subjects = subjects;
}
// add Version if it exists
if (metadata.edition) {
- mappedDataCiteObject.data.attributes.version = metadata.edition;
+ mappedDataCiteObject.data.attributes.version = metadata.edition;
}
-
+
// Add dates if they exist
if (dates.length > 0) {
- mappedDataCiteObject.data.attributes.dates = dates;
+ mappedDataCiteObject.data.attributes.dates = dates;
}
// Add rightsList
@@ -227,44 +227,44 @@ function recordToDataCite(metadata, language, region, datacitePrefix) {
// eslint-disable-next-line no-param-reassign
delete metadata.abstract.translations
mappedDataCiteObject.data.attributes.descriptions = Object.entries(
- metadata.abstract
+ metadata.abstract
).map(([lang, description]) => ({
- lang,
- description,
- descriptionType: "Abstract",
+ lang,
+ description,
+ descriptionType: "Abstract",
}));
// Add geolocations if they exist
if (metadata.map) {
- mappedDataCiteObject.data.attributes.geoLocations = geoLocations;
+ mappedDataCiteObject.data.attributes.geoLocations = geoLocations;
}
// Auto-populate Datacite Resource type general as 'dataset'
mappedDataCiteObject.data.attributes.types = {
- resourceTypeGeneral: "Dataset", // TODO: change this to reflect resource type in form
+ resourceTypeGeneral: "Dataset", // TODO: change this to reflect resource type in form
};
// link data download resources to this record via relatedIdentifiers datacite field
- if (metadata.distribution){
- mappedDataCiteObject.data.attributes.relatedIdentifiers =
- metadata.distribution.map(({ url }) => ({
- relatedIdentifier: url,
- relatedIdentifierType: 'URL',
- relationType: 'IsMetadataFor',
- }))
+ if (metadata.distribution) {
+ mappedDataCiteObject.data.attributes.relatedIdentifiers =
+ metadata.distribution.map(({ url }) => ({
+ relatedIdentifier: url,
+ relatedIdentifierType: 'URL',
+ relationType: 'IsMetadataFor',
+ }))
}
// Link related works to this record via relatedIdentifiers datacite field
if (metadata.associated_resources) {
- mappedDataCiteObject.data.attributes.relatedIdentifiers =
- [
- ...mappedDataCiteObject.data.attributes.relatedIdentifiers,
- ...metadata.associated_resources.map(({ authority, code, association_type: associationType }) => ({
- relatedIdentifier: code,
- relatedIdentifierType: authority,
- relationType: associationType,
- })
- )];
+ mappedDataCiteObject.data.attributes.relatedIdentifiers =
+ [
+ ...mappedDataCiteObject.data.attributes.relatedIdentifiers,
+ ...metadata.associated_resources.map(({ authority, code, association_type: associationType }) => ({
+ relatedIdentifier: code,
+ relatedIdentifierType: authority,
+ relationType: associationType,
+ })
+ )];
}
// link lineage source, processing step documents, and additional documentation to this record
@@ -295,15 +295,15 @@ function recordToDataCite(metadata, language, region, datacitePrefix) {
}
// filter out any entries that do not have code and authority set
// this can happen if only the description is populated for a processing step, for example
- if (mappedDataCiteObject.data.attributes.relatedIdentifiers){
- mappedDataCiteObject.data.attributes.relatedIdentifiers =
- mappedDataCiteObject.data.attributes.relatedIdentifiers.filter((ident) => (ident.relatedIdentifier && ident.relatedIdentifierType))
+ if (mappedDataCiteObject.data.attributes.relatedIdentifiers) {
+ mappedDataCiteObject.data.attributes.relatedIdentifiers =
+ mappedDataCiteObject.data.attributes.relatedIdentifiers.filter((ident) => (ident.relatedIdentifier && ident.relatedIdentifierType))
}
-
+
// Generate URL element
mappedDataCiteObject.data.attributes.url = `${regions[region].catalogueURL[language]}dataset/ca-cioos_${metadata.identifier}`;
return mappedDataCiteObject;
- }
+}
- export default recordToDataCite;
\ No newline at end of file
+export default recordToDataCite;
\ No newline at end of file