diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index ae13e25..2068adf 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -47,13 +47,21 @@ jobs: # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Set image tags + id: set-tags + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:dev" >> $GITHUB_ENV + else + echo "tags=${{ steps.meta.outputs.tags }}" >> $GITHUB_ENV + fi - name: Build and push Docker image id: push uses: docker/build-push-action@v6.0.0 with: context: . push: true - tags: ${{ steps.meta.outputs.tags }} + tags: ${{ env.tags }} labels: ${{ steps.meta.outputs.labels }} # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." diff --git a/src/API/API_functions.tsx b/src/API/API_functions.tsx index 8672bff..13d5ae3 100644 --- a/src/API/API_functions.tsx +++ b/src/API/API_functions.tsx @@ -1,4 +1,5 @@ import { requestAPI } from './handler'; +import { UploadPayload } from '../components/type'; export async function getEnvVariable(varName: string) { try { @@ -17,7 +18,6 @@ export async function setEnvVariable(key: string, value: string) { method: 'POST', body: JSON.stringify({ key, value }) }); - //console.log(data); } catch (error) { console.error(`Error setting ${key}:`, error); } @@ -25,16 +25,31 @@ export async function setEnvVariable(key: string, value: string) { export async function testZenodoConnection() { try { - const data = await requestAPI('zenodo-jupyterlab/test-connection', { +/* const data = await requestAPI('zenodo-jupyterlab/test-connection', { method: 'GET' - }); - //console.log(data); + }); */ + const data = await requestAPI('zenodo-jupyterlab/zenodo-api', { + method: 'POST', + body: JSON.stringify({action: 'check-connection'}), + }) return data; } catch (error) { console.error(`Error testing connection:`, error); } } +export async function depositUpload(payload: UploadPayload) { + try { + const data = await requestAPI('zenodo-jupyterlab/zenodo-api', { + method: 'POST', + body: JSON.stringify(payload), + }); + return data; + } catch (error) { + console.error('Error uploading info:', error); + } +} + export async function searchRecords(search_field: string, page: number, kwargs: Record = {}) { try { let url = `zenodo-jupyterlab/search-records?search_field=${encodeURIComponent(search_field)}&page=${encodeURIComponent(page)}`; @@ -84,14 +99,18 @@ export async function getServerRootDir() { } } -/* export async function runPythonCode(code: string) { +export async function fetchSandboxStatus() { try { - const data = await requestAPI('zenodo-jupyterlab/code', { - method: 'POST', - body: JSON.stringify({ code: code }) - }); - console.log(data); + let response = await fetch('zenodo-jupyterlab/env?env_var=ZENODO_SANDBOX'); + if (response.ok) { + let data = await response.json(); + return data.ZENODO_SANDBOX; + } else { + console.error('Failed to fetch sandbox status'); + return null; + } } catch (error) { - console.error('Error running code:', error); + console.error('Error fetching sandbox status:', error); + return null; } -} */ \ No newline at end of file +} \ No newline at end of file diff --git a/src/components/FileBrowser.tsx b/src/components/FileBrowser.tsx index 0ae2bde..8351900 100644 --- a/src/components/FileBrowser.tsx +++ b/src/components/FileBrowser.tsx @@ -159,9 +159,7 @@ const FileBrowser: React.FC = ({ onSelectFile }) => { }; const handleBreadcrumbClick = (path: string) => { - console.log('Breadcrumb clicked, path:', path); if (path !== currentPath) { - //console.log('Updating currentPath from', currentPath, 'to', path); setCurrentPath(path); } }; diff --git a/src/components/login.tsx b/src/components/login.tsx index 55bd18f..d08475f 100644 --- a/src/components/login.tsx +++ b/src/components/login.tsx @@ -41,7 +41,21 @@ const useStyles = createUseStyles({ '&:hover': { backgroundColor: '#45a049', }, - } + }, + checkboxContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginBottom: '10px', + flexDirection: 'row', + }, + checkboxLabel: { + display: 'flex', + alignItems: 'center', + }, + checkboxInput: { + marginRight: '5px', + }, }); const Login: React.FC = () => { @@ -50,12 +64,13 @@ const Login: React.FC = () => { const[outputData, setOutputData] = useState(null); const [connectionStatus, setConnectionStatus] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [isSandbox, setIsSandbox] = useState(false); const handleLogin = useCallback(async () => { try { + await setEnvVariable('ZENODO_SANDBOX', String(isSandbox)); if (APIKey != '') { await setEnvVariable('ZENODO_API_KEY', APIKey); - //console.log(await getEnvVariable('ZENODO_API_KEY')); setOutputData("Zenodo Token Successfully Stored in Environment."); testAPIConnection(); } else { @@ -70,13 +85,16 @@ const Login: React.FC = () => { } catch (error) { console.error(error); } - }, [APIKey]); + }, [APIKey, isSandbox]); + + const handleCheckboxChange = (event: React.ChangeEvent) => { + setIsSandbox(event.target.checked); + }; const testAPIConnection = async () => { setIsLoading(true); try { var response = await testZenodoConnection(); - //console.log(response['status']); if (Number(response['status']) == 200) { setConnectionStatus("API Connection Successful") } else { @@ -89,25 +107,6 @@ const Login: React.FC = () => { } } -/* const handleLogin = () => { - try { - var code = ` -import os - `; - if (APIKey != '') { - code += ` -os.environ['TESTVAR'] = '${APIKey}' - `; - } - code += ` -os.environ['TESTVAR'] - `; - console.log(code); - } catch (error) { - alert('Invalid Zenodo API Token'); - } - } */ - return (
@@ -116,6 +115,17 @@ os.environ['TESTVAR'] setAPIKey(e.target.value)} required />
+
+ +
{outputData ? (
diff --git a/src/components/type.tsx b/src/components/type.tsx index f487ffa..1584bb9 100644 --- a/src/components/type.tsx +++ b/src/components/type.tsx @@ -8,4 +8,20 @@ export interface FileEntry { size?: number; // Size in bytes } -export type OnSelectFile = (filePath: string) => void; \ No newline at end of file +export type OnSelectFile = (filePath: string) => void; + +export interface Creator { + name: string; + affiliation?: string; +} + +export interface UploadPayload { + title: string; + resourceType: string; + creators: Creator[]; + doi: string; + description: string; + filePaths: string[]; + isSandbox: boolean; + action: string; +} \ No newline at end of file diff --git a/src/components/upload.tsx b/src/components/upload.tsx index eac3592..1deab36 100644 --- a/src/components/upload.tsx +++ b/src/components/upload.tsx @@ -1,7 +1,9 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { createUseStyles } from 'react-jss'; import FileBrowser from './FileBrowser'; import Confirmation from './confirmation'; +import { depositUpload } from '../API/API_functions'; +import { UploadPayload } from './type'; const useStyles = createUseStyles({ container: { @@ -235,15 +237,19 @@ const Upload: React.FC = () => { const [description, setDescription] = useState(''); const [expandedFile, setExpandedFile] = useState(null); - /* const handleFileChange = (event: React.ChangeEvent) => { - const fileList = event.target.files; - if (fileList) { - const newPaths = Array.from(fileList).map(file => file.name); - setSelectedFilePaths(prevPaths => [ - ...new Set([...prevPaths, ...newPaths]) // Add new paths, avoiding duplicates - ]); + useEffect(() => { + async function fetchSandboxStatus() { + try { + const response = await fetch('zenodo-jupyterlab/env?env_var=ZENODO_SANDBOX'); + const data = await response.json(); + setIsSandbox(data.ZENODO_SANDBOX === 'true'); + } catch (error) { + console.error('Error fetching sandbox status:', error); + } } - }; */ + + fetchSandboxStatus(); + }, []); const handleFileClick = (filePath: string) => { setExpandedFile(prev => (prev === filePath ? null : filePath)); @@ -259,16 +265,10 @@ const Upload: React.FC = () => { ]); }; - - const handleTitleChange = (event: React.ChangeEvent) => { setTitle(event.target.value); }; - const handleSandboxChange = (event: React.ChangeEvent) => { - setIsSandbox(event.target.checked); - }; - const handleResourceTypeChange = (event: React.ChangeEvent) => { setResourceType(event.target.value); }; @@ -295,6 +295,13 @@ const Upload: React.FC = () => { return; } setIsConfirmationVisible(true); + }; + + const handleEdit = () => { + setIsConfirmationVisible(false); + }; + + const handleConfirm = async () => { const formData = new FormData(); selectedFilePaths.forEach(filePath => formData.append('filePaths', filePath)); formData.append('title', title); @@ -302,38 +309,28 @@ const Upload: React.FC = () => { formData.append('creators', JSON.stringify(creators)); formData.append('doi', doi); formData.append('description', description); + formData.append('action', 'upload'); + + const payload: UploadPayload = { + title, + resourceType, + creators, + doi, + description, + filePaths: selectedFilePaths, + isSandbox, + action: 'upload' + }; - // Example: logging form data for (let [key, value] of formData.entries()) { console.log(`${key}: ${value}`); } - - /* // Make an API call here, e.g., using fetch - fetch('your-api-endpoint', { - method: 'POST', - body: formData, - }) - .then(response => response.json()) - .then(data => { - console.log('Success:', data); - }) - .catch((error) => { - console.error('Error:', error); - }); */ - }; - - const handleEdit = () => { - setIsConfirmationVisible(false); - }; - - const handleConfirm = () => { - // Make an API call or handle form submission here - console.log('Form submitted with:', { title, resourceType, creators, doi, selectedFilePaths }); - // Reset the form or navigate as needed + console.log(JSON.stringify(payload)); + const response = await depositUpload(payload); + console.log(response['status']); }; const fileName = (filePath: string) => { - console.log(filePath); const segments = filePath.split('/'); return segments.pop(); } @@ -368,7 +365,7 @@ const Upload: React.FC = () => { //onChange={() => handleCheckboxChange('communities')} className={classes.checkboxInput} checked={isSandbox} - onChange={handleSandboxChange} + readOnly /> Sandbox diff --git a/zenodo_jupyterlab/server/extension.py b/zenodo_jupyterlab/server/extension.py index 3dddd60..2af55cd 100644 --- a/zenodo_jupyterlab/server/extension.py +++ b/zenodo_jupyterlab/server/extension.py @@ -6,6 +6,6 @@ def _load_jupyter_server_extension(server_app): try: setup_handlers(web_app) server_app.log.info("Registered zenodo_jupyterlab server extension") - except Exception as e: - server_app.log.error(f"Failed to register zenodo_jupyterlab server extension: {e}") + except: + server_app.log.error(f"Failed to register zenodo_jupyterlab server extension") raise \ No newline at end of file diff --git a/zenodo_jupyterlab/server/handlers.py b/zenodo_jupyterlab/server/handlers.py index 01b2142..9ba4501 100644 --- a/zenodo_jupyterlab/server/handlers.py +++ b/zenodo_jupyterlab/server/handlers.py @@ -1,10 +1,14 @@ # handlers.py from datetime import timezone, datetime +import json from jupyter_server.base.handlers import APIHandler, JupyterHandler from jupyter_server.utils import url_path_join import os + +from .upload import upload from .testConnection import checkZenodoConnection from .search import searchRecords, searchCommunities, recordInformation +#from eossr.api.zenodo import ZenodoAPI class EnvHandler(APIHandler): @@ -27,10 +31,10 @@ async def post(self): exec(data['code'], globals()) self.finish({'status': 'success'}) -class ZenodoTestHandler(APIHandler): +""" class ZenodoTestHandler(APIHandler): async def get(self): - response = await checkZenodoConnection(sandbox = False) - self.finish({'status': response}) + response = await checkZenodoConnection() + self.finish({'status': response}) """ class XSRFTokenHandler(JupyterHandler): async def get(self): @@ -86,6 +90,41 @@ async def get(self): }) self.finish({"entries": entries}) + +class ZenodoAPIHandler(APIHandler): + zAPI = None + + async def post(self): + #data = self.get_json_body() + #action = data.get('action') + try: + form_data = json.loads(self.request.body) + except json.JSONDecodeError: + self.set_status(400) + self.finish(json.dumps({'status': 'Invalid JSON'})) + return + + action = form_data.get('action') + + if action == 'check-connection': + response, zAPI = await checkZenodoConnection() + if zAPI is not None: + ZenodoAPIHandler.zAPI = zAPI + self.finish({'status': response}) + elif action == 'upload': + if ZenodoAPIHandler.zAPI == None: + self.finish({'status': 'Please Log In before trying to '}) + else: + response = await upload(ZenodoAPIHandler.zAPI, form_data) + self.finish({'status': response}) + """ if response == None: + self.finish({'status': '0'}) + else: + self.finish({'status': 'Completed!!!'}) """ + else: + self.finish(json.dumps('null')) + + class ServerInfoHandler(APIHandler): async def get(self): @@ -102,12 +141,13 @@ def setup_handlers(web_app): (url_path_join(base_path, 'env'), EnvHandler), (url_path_join(base_path, 'code'), CodeHandler), (url_path_join(base_path, 'xsrf_token'), XSRFTokenHandler), - (url_path_join(base_path, 'test-connection'), ZenodoTestHandler), + #(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, 'record-info'), RecordInfoHandler), (url_path_join(base_path, 'files'), FileBrowserHandler), - (url_path_join(base_path, 'server-info'), ServerInfoHandler) + (url_path_join(base_path, 'server-info'), ServerInfoHandler), + (url_path_join(base_path, 'zenodo-api'), ZenodoAPIHandler) ] web_app.add_handlers(".*$", handlers) \ No newline at end of file diff --git a/zenodo_jupyterlab/server/testConnection.py b/zenodo_jupyterlab/server/testConnection.py index 7ed6dce..c321d3b 100644 --- a/zenodo_jupyterlab/server/testConnection.py +++ b/zenodo_jupyterlab/server/testConnection.py @@ -1,13 +1,16 @@ from eossr.api.zenodo import ZenodoAPI import os -#ZenodoHTTPStatus - -async def checkZenodoConnection(sandbox: bool): - access_token = os.environ['ZENODO_API_KEY'] - z = ZenodoAPI(access_token=access_token, sandbox = sandbox) +async def checkZenodoConnection(): try: - response = z.query_user_deposits() - return response.status_code + access_token = os.environ['ZENODO_API_KEY'] + env_sandbox = os.environ['ZENODO_SANDBOX'] + if (env_sandbox == 'true'): + sandbox = True + else: + sandbox = False + zAPI = ZenodoAPI(access_token=access_token, sandbox = sandbox) + response = zAPI.query_user_deposits() + return response.status_code, zAPI except: - return 0 \ No newline at end of file + return 0, None \ No newline at end of file diff --git a/zenodo_jupyterlab/server/tests/test_testConnection.py b/zenodo_jupyterlab/server/tests/test_testConnection.py index 5898e2f..9a9db4e 100644 --- a/zenodo_jupyterlab/server/tests/test_testConnection.py +++ b/zenodo_jupyterlab/server/tests/test_testConnection.py @@ -13,13 +13,15 @@ async def test_zenodo_connection_success(): # Mock the environment variable with patch.dict(os.environ, {'ZENODO_API_KEY': os.environ['CI_ZENODO_API_KEY']}): - # Call the function to test - status_code = await checkZenodoConnection(sandbox = True) + with patch.dict(os.environ, {'ZENODO_SANDBOX': "true"}): + # Call the function to test + status_code, zAPI = await checkZenodoConnection() - print(f"Returned status code: {status_code}") + print(f"Returned status code: {status_code}") - # Assert the expected status code - assert status_code == 200 + # Assert the expected status code + assert status_code == 200 + assert type(zAPI) == ZenodoAPI @pytest.mark.asyncio async def test_zenodo_connection_failure(): @@ -29,9 +31,11 @@ async def test_zenodo_connection_failure(): mock_instance.query_user_deposits.side_effect = Exception('Failed') """ # Mock the environment variable - with patch.dict('os.environ', {'ZENODO_API_KEY': 'fake_false_api_key'}): - # Call the function to test - status_code = await checkZenodoConnection(sandbox = True) + with patch.dict(os.environ, {'ZENODO_API_KEY': 'fake_false_api_key'}): + with patch.dict(os.environ, {"ZENODO_SANDBOX": 'true'}): + # Call the function to test + status_code, zAPI = await checkZenodoConnection() - # Assert the expected status code - assert status_code == 0 \ No newline at end of file + # Assert the expected status code + assert status_code == 0 + assert zAPI == None \ No newline at end of file diff --git a/zenodo_jupyterlab/server/upload.py b/zenodo_jupyterlab/server/upload.py index e69de29..4518e0f 100644 --- a/zenodo_jupyterlab/server/upload.py +++ b/zenodo_jupyterlab/server/upload.py @@ -0,0 +1,38 @@ + +async def createDeposit(zAPI): + response = zAPI.create_new_deposit() + return response.json()['id'] + +async def createMetadata(zAPI, recordID, form_data): + title = form_data.get('title') + #resource_type = form_data.get('resourceType') + #creators = form_data.get('creators') + #doi = form_data.get('doi') + #description = form_data.get('description') + #file_paths = form_data.get('filePaths') + #is_sandbox = form_data.get('isSandbox') + json_metadata = { + 'title': title, + #'resource_type': {'title': resource_type}, + #'creators': creators, + #'description': description, + } + """ if doi != '': + json_metadata['doi'] = doi """ + + response = zAPI.set_deposit_metadata(recordID, json_metadata) + return response + +async def upload(zAPI, form_data): + if zAPI == None: + return None + try: + recordID = await createDeposit(zAPI) + response = await createMetadata(zAPI, str(recordID), form_data) + if response != None: + return "Success" + else: + return "Adding the metadata returned a None response." + except: + return None +