Skip to content

Commit

Permalink
Added Expandable Records (not communities) and pagination on both rec…
Browse files Browse the repository at this point in the history
…ords and communities (#39)

* developed clickable records that displays authors and files

* added styling for hovering over rows

* shrunk data size to 25

* added pagination

* pulled old yarn.lock

* attempted fix at adding package

* attempted fix

* added end case to search
  • Loading branch information
mrzengel authored Jul 22, 2024
1 parent 56c34f7 commit 96ec6e0
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 28 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@jupyterlab/application": "^4.2.2",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3",
"clsx": "^2.1.1",
"dotenv": "^16.4.5",
"eslint": "8.0.0",
"node-notifier": "^10.0.1",
Expand Down
15 changes: 13 additions & 2 deletions src/API/API_functions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export async function searchRecords(search_field: string, page: number) {
}
}

export async function searchCommunities(search_field: string) {
export async function searchCommunities(search_field: string, page:number) {
try{
const data = await requestAPI(`zenodo-jupyterlab/search-communities?search_field=${encodeURIComponent(search_field)}`, {
const data = await requestAPI(`zenodo-jupyterlab/search-communities?search_field=${encodeURIComponent(search_field)}&page=${encodeURIComponent(page)}`, {
method: 'GET'
});
return data;
Expand All @@ -57,6 +57,17 @@ export async function searchCommunities(search_field: string) {
}
}

export async function recordInformation(recordID: number) {
try{
const data = await requestAPI(`zenodo-jupyterlab/record-info?record-id=${encodeURIComponent(recordID)}`, {
method: 'GET'
});
return data;
} catch (error) {
console.error('Error retriving record information: ', error);
}
}

/* export async function runPythonCode(code: string) {
try {
const data = await requestAPI('zenodo-jupyterlab/code', {
Expand Down
157 changes: 139 additions & 18 deletions src/components/SearchPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import React, { useState } from 'react';
import { searchRecords, searchCommunities } from '../API/API_functions';
import { searchRecords, searchCommunities, recordInformation } from '../API/API_functions';
import { createUseStyles } from 'react-jss';

// interface SearchResult {
// id: number;
// title: string;
// date: string;
// }
//title, version, type, date
import clsx from 'clsx';

const useStyles = createUseStyles({
searchWidget: {
Expand Down Expand Up @@ -47,11 +41,15 @@ const useStyles = createUseStyles({
marginTop: '20px',
},
table: {
minWidth: '400px',
width: '100%',
borderCollapse: 'collapse',
marginTop: '20px',
},
tableBody: {
borderLeft: '2px solid black',
borderRight: '2px solid black',
borderBottom: '2px solid black',
},
headerRow: {
backgroundColor: '#007bff',
color: 'white',
Expand All @@ -63,6 +61,19 @@ const useStyles = createUseStyles({
},
row: {
borderBottom: '1px solid #ccc',
cursor: 'pointer',
backgroundColor: '#e6f7ff',
'&:hover': {
backgroundColor: '#b3e0ff',
},
},
alternateRow: {
borderBottom: '1px solid #ccc',
cursor: 'pointer',
backgroundColor: '#cceeff',
'&:hover': {
backgroundColor: '#99d6ff',
},
},
cell: {
padding: '10px',
Expand All @@ -81,6 +92,15 @@ const useStyles = createUseStyles({
},
checkboxInput: {
marginRight: '5px',
},
buttonContainer: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginTop: '5px',
},
spacer: {
flexGrow: '1',
}
});

Expand All @@ -92,18 +112,23 @@ const SearchWidget: React.FC = () => {
const [selectedType, setSelectedType] = useState('records');
const [lastSearchType, setLastSearchType] = useState('records');
const [hasSearched, setHasSearched] = useState(false);
const [selectedRecordID, setSelectedRecordID] = useState<number | null>(null);
const [recordInfo, setRecordInfo] = useState<any>({});
const [recordLoading, setRecordLoading] = useState(false);
const [resultsPage, setResultsPage] = useState(1);
const [endPage, setEndPage] = useState(false);

const handleSearch = async () => {
const handleSearch = async (page: number) => {
setIsLoading(true);
setHasSearched(true);
try {
//const response = await searchRecords(searchTerm);
const response = selectedType === 'records'
? await searchRecords(searchTerm, 1)
: await searchCommunities(searchTerm);
? await searchRecords(searchTerm, page)
: await searchCommunities(searchTerm, page);
//const data: SearchResult[] = await response;
setResults(response[selectedType]);
//console.log(response['records']);
setSelectedRecordID(null);
} catch (error) {
console.error('Error during search: ', error);
} finally {
Expand All @@ -114,6 +139,49 @@ const SearchWidget: React.FC = () => {
const handleCheckboxChange = (type: string) => {
setSelectedType(type);
};

const handleRowClick = async (recordID: number) => {
if (selectedRecordID === recordID) {
setSelectedRecordID(null);
setRecordInfo({});
setRecordLoading(false);
} else {
setSelectedRecordID(recordID);
setRecordLoading(true);
try {
var response = await recordInformation(recordID);
setRecordInfo(response['data']);
//console.log(recordInfo);
} catch (error) {
console.error('Error fetching record information: ', error);
} finally {
setRecordLoading(false);
}
}
}

const handleNextPageClick = () => {
const nextPage = resultsPage + 1;
setResultsPage(nextPage);
handleSearch(nextPage);
if (results.length !> 0) {
setEndPage(true);
}
}

const handleLastPageClick = () => {
setEndPage(false);
const prevPage = resultsPage - 1;
setResultsPage(prevPage);
handleSearch(prevPage);
}

const handleSearchClick = () => {
setEndPage(false);
setResultsPage(1);
handleSearch(1);
}

return (
<div className={classes.searchWidget}>
<div className={classes.container}>
Expand Down Expand Up @@ -144,7 +212,7 @@ const SearchWidget: React.FC = () => {
Communities
</label>
</div>
<button onClick={handleSearch} className={classes.button}>Search</button>
<button onClick={handleSearchClick} className={classes.button}>Search</button>
</div>
{isLoading && <p>Loading Search Results...</p>}
{hasSearched && !isLoading && (
Expand All @@ -159,16 +227,55 @@ const SearchWidget: React.FC = () => {
<th className={classes.headerCell}>Date Published</th>
</tr>
</thead>
<tbody>
<tbody className={classes.tableBody}>
{results.map((result, index) => (
<tr key={result.id} className={classes.row} style={{ backgroundColor: index % 2 === 0 ? '#e6f7ff' : '#cceeff' }}>
<React.Fragment key={result.id}>
<tr className={clsx(classes.row, { [classes.alternateRow]: index % 2 !== 0 })} onClick={() => handleRowClick(result.id)}>
<td className={classes.cell}>{result.title}</td>
<td className={classes.cell}>{result.resource_type}</td>
<td className={classes.cell}>{result.date}</td>
</tr>
{selectedRecordID === result.id && !recordLoading &&(
<tr>
<td colSpan={3} className={classes.cell}>
<div>
<p><strong>Additional information for {result.title}:</strong></p>
{recordInfo.authors && (
<div>
<p><strong>Authors:</strong></p>
<ul>
{recordInfo.authors.map((author: {'name': string, 'affiliation': string}, index: number) => (
<li key={index}>{author.name}, Affiliation: {author.affiliation}</li>
))}
</ul>
</div>
)}
{recordInfo.filelist && (
<div>
<p><strong>Files:</strong></p>
<ul>
{recordInfo.filelist.map((file: string, index: number) => (
<li key={index}>{file}</li>
))}
</ul>
</div>
)}
</div>
</td>
</tr>
)}
</React.Fragment>
))}
</tbody>
</table>
<br></br>
<div className={classes.buttonContainer}>
{resultsPage > 1 && (
<button className={classes.button} onClick={handleLastPageClick}>Last Page</button>
)}
<div className={classes.spacer}></div>
<button className={classes.button} onClick={handleNextPageClick}>Next Page</button>
</div>
</div>
) : (
<div className={classes.tableContainer}>
Expand All @@ -179,7 +286,7 @@ const SearchWidget: React.FC = () => {
<th className={classes.headerCell}>Date Published</th>
</tr>
</thead>
<tbody>
<tbody className={classes.tableBody}>
{results.map((result, index) => (
<tr key={result.id} className={classes.row} style={{ backgroundColor: index % 2 === 0 ? '#e6f7ff' : '#cceeff' }}>
<td className={classes.cell}>{result.title}</td>
Expand All @@ -188,10 +295,24 @@ const SearchWidget: React.FC = () => {
))}
</tbody>
</table>
<div className={classes.buttonContainer}>
{resultsPage > 1 && (
<button className={classes.button} onClick={handleLastPageClick}>Last Page</button>
)}
<div className={classes.spacer}></div>
<button className={classes.button} onClick={handleNextPageClick}>Next Page</button>
</div>
</div>
)
) : (
<p>No results found.</p>
!endPage ? (
<p>No results found.</p>
) : (
<div>
<p>No further results found. Please return to the previous page.</p>
<button className={classes.button} onClick={handleLastPageClick}>Last Page</button>
</div>
)
)
)}

Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4545,6 +4545,12 @@ __metadata:
languageName: node
linkType: hard

"clsx@npm:^2.1.1":
version: 2.1.1
resolution: "clsx@npm:2.1.1"
languageName: node
linkType: hard

"co@npm:^4.6.0":
version: 4.6.0
resolution: "co@npm:4.6.0"
Expand Down Expand Up @@ -11226,6 +11232,7 @@ __metadata:
"@typescript-eslint/eslint-plugin": ^6.21.0
"@typescript-eslint/parser": ^6.21.0
bootstrap: ^5.3.3
clsx: ^2.1.1
css-loader: ^7.1.2
dotenv: ^16.4.5
eslint: 8.0.0
Expand Down
14 changes: 11 additions & 3 deletions zenodo_jupyterlab/server/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from jupyter_server.utils import url_path_join
import os
from .testConnection import testZenodoConnection
from .search import searchRecords, searchCommunities
from .search import searchRecords, searchCommunities, recordInformation


class EnvHandler(APIHandler):
Expand Down Expand Up @@ -43,9 +43,16 @@ async def get(self):
class SearchCommunityHandler(APIHandler):
async def get(self):
search_field = self.get_query_argument('search_field', default="")
response = await searchCommunities(search_field=search_field)
page = self.get_query_argument('page', default=1)
response = await searchCommunities(search_field=search_field, page = page)
self.finish({'communities': response})

class RecordInfoHandler(APIHandler):
async def get(self):
recordID = int(self.get_query_argument('record-id'))
response = await recordInformation(recordID)
self.finish({'data': response})


def setup_handlers(web_app):
base_path = web_app.settings['base_url']
Expand All @@ -57,7 +64,8 @@ def setup_handlers(web_app):
(url_path_join(base_path, 'xsrf_token'), XSRFTokenHandler),
(url_path_join(base_path, 'test-connection'), ZenodoTestHandler),
(url_path_join(base_path, 'search-records'), SearchRecordHandler),
(url_path_join(base_path, 'search-communities'), SearchCommunityHandler)
(url_path_join(base_path, 'search-communities'), SearchCommunityHandler),
(url_path_join(base_path, 'record-info'), RecordInfoHandler)
]

web_app.add_handlers(".*$", handlers)
18 changes: 13 additions & 5 deletions zenodo_jupyterlab/server/search.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
from eossr.api.zenodo import search_records, search_communities
from eossr.api.zenodo import search_records, search_communities, get_record

async def searchRecords(search_field, page):
try:
records = search_records(search=search_field, size= 50, page = page)
records = search_records(search=search_field, size= 25, page = page)
response = []
for record in records:
response.append({'id': record.id, 'title': record.title, 'date': record.metadata['publication_date'], 'resource_type': record.metadata['resource_type']['title']})
return response
except:
return ["failed"]

async def searchCommunities(search_field):
async def searchCommunities(search_field, page):
try:
communities = search_communities(search=search_field, size= 50)
communities = search_communities(search=search_field, size= 25, page=page)
response = []
for community in communities:
response.append({'id': community['id'], 'title': community['metadata']['title'], 'date': community['created'].split('T')[0]})
return response
except:
return ["failed"]
return ["failed"]

async def recordInformation(recordID):
try:
response = get_record(recordID)
record = {'authors': response.metadata['creators'], 'filelist': response.filelist}
return record
except:
return {'status': 'failed'}

0 comments on commit 96ec6e0

Please sign in to comment.