Skip to content

Commit

Permalink
Merge pull request #616 from SynBioHub/removecollection
Browse files Browse the repository at this point in the history
Removecollection
  • Loading branch information
danielfang97 authored Mar 13, 2024
2 parents f27ae7c + ab95079 commit e29b11b
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 101 deletions.
8 changes: 8 additions & 0 deletions frontend/CommitHash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const commitHash = execSync('git rev-parse HEAD').toString().trim();
const outputPath = path.join(__dirname, 'public', 'commitHash.txt');

fs.writeFileSync(outputPath, commitHash);
7 changes: 7 additions & 0 deletions frontend/CustomDev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { execSync } = require('child_process');

// Run setCommitHash.js script
execSync('node CommitHash.js', { stdio: 'inherit' });

// Start the Next.js development server
execSync('next dev -p 3333', { stdio: 'inherit' });
35 changes: 29 additions & 6 deletions frontend/components/Admin/Registries.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,37 @@ const fetcher = (url, token, dispatch) =>
export async function processUrl(inputUrl, token, dispatch) {

const data = await fetcher(`${publicRuntimeConfig.backend}/admin/registries`, token, dispatch);
const registries = data.registries;

for (const registry of registries) {
if (inputUrl.startsWith(registry.uri)) {
const urlRemovedForLink = inputUrl.replace(registry.uri, "");
const urlReplacedForBackend = inputUrl.replace(registry.uri, registry.url);
return { urlRemovedForLink, urlReplacedForBackend };
if (data) {
const registries = data.registries;

for (const registry of registries) {
if (inputUrl.startsWith(registry.uri)) {
const urlRemovedForLink = inputUrl.replace(registry.uri, "");
const urlReplacedForBackend = inputUrl.replace(registry.uri, registry.url);
return { urlRemovedForLink, urlReplacedForBackend };
}
}
return { original: inputUrl }; // if you don't match any uri
}
return { original: inputUrl }; // if you don't match any uri
}

export async function processUrlReverse(inputUrl, token, dispatch) {

const data = await fetcher(`${publicRuntimeConfig.backend}/admin/registries`, token, dispatch);

if (data) {
const registries = data.registries;

for (const registry of registries) {
if (inputUrl.startsWith(registry.url)) {
const uriRemovedForLink = inputUrl.replace(registry.url, "");
const uriReplacedForBackend = inputUrl.replace(registry.url, registry.uri);
return { uriRemovedForLink, uriReplacedForBackend };
}
}
return { original: inputUrl }; // if you don't match any uri
}
return { original: inputUrl }; // if you don't match any uri
}
14 changes: 11 additions & 3 deletions frontend/components/Admin/Theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,15 @@ const fetcher = (url, dispatch) =>
})
.then(response => response.data)
.catch(error => {
error.customMessage = 'Request failed for GET /admin/theme';
error.fullUrl = url;
dispatch(addError(error));
if (error.message === 'Network Error') {
// Change the error message or handle it differently
console.error('Unable to connect to the server. Please check your network connection.');
dispatch(addError(error));
} else {
// Handle other errors
error.customMessage = 'Request failed for GET /admin/theme31278931739137131';
error.fullUrl = url;
dispatch(addError(error));
}

});
35 changes: 33 additions & 2 deletions frontend/components/Error/ErrorClearer.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
import { useDispatch } from 'react-redux';
import styles from '../../styles/error.module.css';
import { clearErrors, removeErrorByIndex } from '../../redux/actions';
import { useRouter } from 'next/router';
import { clearErrors, removeErrorByIndex, logoutUser } from '../../redux/actions';

export default function ErrorClearer({ index, setIndex }) {
export default function ErrorClearer({ index, setIndex, size }) {
const dispatch = useDispatch();
const router = useRouter();
if (size === 'relog') {
return (
<div
className={styles.clearButton}
onClick={() => {
dispatch(clearErrors());
dispatch(logoutUser());
router.push('/');
}}
>
Return to Login
</div>
);
}
if (size === 'small') {
return (
<div
className={styles.clearButton}
onClick={() => {
dispatch(removeErrorByIndex(index));
if (index > 0) {
setIndex(index - 1);
}
}}
>
Dismiss
</div>
);
}
return (
<div className={styles.sideBySide}>
<div
Expand Down
79 changes: 51 additions & 28 deletions frontend/components/Error/Errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ import { useSelector } from 'react-redux';
import { useEffect, useState } from 'react';
import styles from '../../styles/error.module.css';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { faExclamationCircle, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import Error from './Error';
import ErrorNavigation from './ErrorNavigation';
import ErrorClearer from './ErrorClearer';

/**
* This component renders all errors in the store.
* @returns
*/
export default function Errors() {
const [viewIndex, setViewIndex] = useState(0);
const [isExpanded, setIsExpanded] = useState(false);
const errors = useSelector(state => state.errors.errors);

useEffect(() => {
Expand All @@ -23,36 +20,62 @@ export default function Errors() {
if (errors.length === 0) return null;

const error = errors[viewIndex];
const toggleExpand = () => setIsExpanded(!isExpanded);

console.log(error.response.data);

if (error.response.data === "Error: Cannot access other users' graphs.") {
return (
<div className={styles.smallErrorContainer}>
<div className={styles.errorHeaderContainer}>
<div className={styles.errorTitle}>
<FontAwesomeIcon icon={faExclamationCircle} color="#f00" size="1x" />
<h1 className={styles.header}>{error.message = "User token is expired. Please relog."}</h1>
</div>
<div>
<ErrorClearer index={viewIndex} setIndex={setViewIndex} size={'relog'} />
</div>
</div>
</div>
);
}

return (
<div className={styles.errorContainer}>
<div className={isExpanded ? styles.errorContainer : styles.smallErrorContainer}>
<div className={styles.errorHeaderContainer}>
<div className={styles.errorTitle}>
<FontAwesomeIcon icon={faExclamationCircle} color="#f00" size="1x" />
<h1 className={styles.header}>Something went wrong</h1>
<h1 className={styles.header}>{error.message === "Network Error" ? "Backend Server Not Responding" : "Something went wrong"}</h1>
</div>
<a
href="https://github.com/SynBioHub/synbiohub3/issues/new"
target="_blank"
rel="noreferrer"
className={styles.reportIssue}
>
Report Issue
</a>
</div>
<Error error={error} />
<div className={styles.errorFooterContainer}>
<div className={styles.sideBySide}>
<ErrorNavigation
index={viewIndex}
length={errors.length}
setIndex={setViewIndex}
/>
<div className={styles.errorIndex}>
Error {viewIndex + 1} of {errors.length}
</div>
<div>
{!isExpanded && (
<>
<ErrorClearer index={viewIndex} setIndex={setViewIndex} size={'small'} />
</>
)}
</div>
<ErrorClearer index={viewIndex} setIndex={setViewIndex} />
<button onClick={toggleExpand} className={styles.expandButton}>
{isExpanded ? <FontAwesomeIcon icon={faChevronUp} /> : <FontAwesomeIcon icon={faChevronDown} />}
</button>
</div>
{isExpanded && (
<>
<Error error={error} />
<div className={styles.errorFooterContainer}>
<div className={styles.sideBySide}>
<ErrorNavigation
index={viewIndex}
length={errors.length}
setIndex={setViewIndex}
/>
<div className={styles.errorIndex}>
Error {viewIndex + 1} of {errors.length}
</div>
</div>
<ErrorClearer index={viewIndex} setIndex={setViewIndex} />
</div>
</>
)}
</div>
);
}
14 changes: 12 additions & 2 deletions frontend/components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ import Image from 'next/image';

import styles from '../styles/footer.module.css';

import { useState, useEffect } from 'react';

/**
* This component renders the footer in sbh
*/
export default function Footer() {
const [commitHash, setCommitHash] = useState('');

useEffect(() => {
fetch('/commitHash.txt')
.then(response => response.text())
.then(hash => setCommitHash(hash.slice(0, 8)));
}, []);

return (
<footer className={styles.footer}>
<div className={styles.copyrightcontainer}>
Expand Down Expand Up @@ -50,11 +60,11 @@ export default function Footer() {
API
</a>
<a
href="https://github.com/SynBioHub/synbiohub3"
href={`https://github.com/SynBioHub/synbiohub3/commit/${commitHash}`} // Incorporate the commit hash into the link
target="_blank"
rel="noreferrer"
>
Github Repo
Github Repo ({commitHash}) {/* Display the trimmed commit hash */}
</a>
<a
href="https://github.com/SynBioHub/synbiohub3/issues"
Expand Down
79 changes: 51 additions & 28 deletions frontend/components/Viewing/Collection/Members.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { shortName } from '../../../namespace/namespace';
import lookupRole from '../../../namespace/lookupRole';
import Link from 'next/link';
import { addError } from '../../../redux/actions';
import { processUrl } from '../../Admin/Registries';
import { processUrl, processUrlReverse } from '../../Admin/Registries';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrash, faUnlink } from '@fortawesome/free-solid-svg-icons';
import { getAfterThirdSlash } from '../ViewHeader';
Expand Down Expand Up @@ -50,6 +50,7 @@ export default function Members(properties) {
const [customBounds, setCustomBounds] = useState([0, 10000]);
const [typeFilter, setTypeFilter] = useState('Show Only Root Objects');
const dispatch = useDispatch();
const [processedUri, setProcessedUri] = useState(publicRuntimeConfig.backend);

let preparedSearch =
search !== ''
Expand Down Expand Up @@ -128,6 +129,19 @@ export default function Members(properties) {
setOffset(newBounds[0]);
};

useEffect(() => {
let isMounted = true;
async function processUri() {
const result = await processUrlReverse(publicRuntimeConfig.backend, token, dispatch);
if (isMounted) {
setProcessedUri(result.uriReplacedForBackend);
}
}

processUri();
return () => { isMounted = false };
}, [token, dispatch]);

return (
<React.Fragment>
<FilterHeader filters={filters} setTypeFilter={setTypeFilter} />
Expand All @@ -147,6 +161,8 @@ export default function Members(properties) {
defaultSortOption={defaultSortOption}
setDefaultSortOption={setDefaultSortOption}
uri={properties.uri}
processedUri={processedUri}
mutate={mutate}
/>
</React.Fragment>
);
Expand Down Expand Up @@ -299,43 +315,47 @@ function MemberTable(properties) {
if (icon && icon === faTrash) {
handleDelete(member);
} else if (icon && icon === faUnlink) {
handleUnlink(member);
handleUnlink(member, properties.processedUri); // Use processedUri from props
}
};

const handleDelete = (member) => {


const handleDelete = async (member) => {
if (member.uri && window.confirm("Would you like to remove this item from the collection?")) {
axios.get(`${publicRuntimeConfig.backend}${member.uri}/remove`, {
headers: {
"Accept": "text/plain; charset=UTF-8",
"X-authorization": token
}
})
.then(response => {
window.location.reload();
})
.catch(error => {
console.error('Error removing item:', error);
try {
await axios.get(`${publicRuntimeConfig.backend}${member.uri}/remove`, {
headers: {
"Accept": "text/plain; charset=UTF-8",
"X-authorization": token
}
});
// After successful deletion, update the state
properties.mutate(); // This will re-fetch the members
} catch (error) {
console.error('Error removing item:', error);
// Handle error appropriately
}
}
};

const handleUnlink = (member) => {
const handleUnlink = async (member, processedUri) => {
if (member.uri && window.confirm("Would you like to unlink this item from the collection?")) {
axios.post(`${objectUri}/removeMembership`, {
"member": `${publicRuntimeConfig.backend}${member.uri}`
}, {
headers: {
"Accept": "text/plain; charset=UTF-8",
"X-authorization": token
}
})
.then(response => {
window.location.reload();
})
.catch(error => {
console.error('Error removing item:', error);
try {
await axios.post(`${objectUri}/removeMembership`, {
"member": `${processedUri}${member.uri}`
}, {
headers: {
"Accept": "text/plain; charset=UTF-8",
"X-authorization": token
}
});
// After successful unlinking, update the state
properties.mutate(); // This will re-fetch the members
} catch (error) {
console.error('Error unlinking item:', error);
// Handle error appropriately
}
}
};

Expand Down Expand Up @@ -459,7 +479,10 @@ const fetcher = (url, token, dispatch) =>
// Check if the error is 401 Unauthorized or 400 Bad Request
if (error.response && (error.response.status === 401 || error.response.status === 400)) {
// Check if the user is logged in by looking for 'userToken' in local storage
console.log(localStorage);
console.log(error.response);
if (!localStorage.getItem('userToken')) {
console.log('Missing user token');
// User is not logged in, redirect to the login page
// window.location.href = '/login';
}
Expand Down
Loading

0 comments on commit e29b11b

Please sign in to comment.