diff --git a/src/components/Interactive.js b/src/components/Interactive.js new file mode 100644 index 0000000000000000000000000000000000000000..9ba219db463f3dc15c3ff14fea76442992a3a822 --- /dev/null +++ b/src/components/Interactive.js @@ -0,0 +1,6 @@ +import React from "react"; +import { Alert } from "react-bootstrap"; + +export default function Interactive() { + return <Alert variant="info">This service is under development!</Alert>; +} diff --git a/src/components/Paginate.js b/src/components/Paginate.js index 935de29fad040b5ff9d7812ba743ca37ab51bdf0..92fa6088915807973835757d2d20b7c4a7bd1f52 100644 --- a/src/components/Paginate.js +++ b/src/components/Paginate.js @@ -1,15 +1,33 @@ import React from "react"; import { Pagination } from "react-bootstrap"; -export default function Paginate() { +export default function Paginate(props) { + const { currentPage, getNewPage, numAdjacent, numPages } = props; + + console.log(props); + + const numBelow = Math.min(numAdjacent, currentPage-1); + const belowAdjacent = [...Array(numBelow).keys()].map((key) => { + const itemKey = `page_below_${key}`; + return (<Pagination.Item key={itemKey}>{currentPage - numBelow + key}</Pagination.Item>) + }); + const prefix = belowAdjacent.length >= numAdjacent ? (<><Pagination.Item>{1}</Pagination.Item><Pagination.Ellipsis disabled/>{belowAdjacent}</>) : (<>{belowAdjacent}</>); + + const numAbove = Math.min(numAdjacent, numPages-currentPage); + const aboveAdjacent = [...Array(numAbove).keys()].map((key) => { + const itemKey = `page_above_${key}`; + return (<Pagination.Item key={itemKey}>{currentPage + key + 1}</Pagination.Item>) + }); + const suffix = aboveAdjacent.length >= numAdjacent ? (<>{aboveAdjacent}<Pagination.Ellipsis disabled/><Pagination.Item>{numPages}</Pagination.Item></>) : (<>{aboveAdjacent}</>); + return ( - <Pagination> - <Pagination.First /> - <Pagination.Prev /> - <Pagination.Item active>{1}</Pagination.Item> - <Pagination.Ellipsis /> - <Pagination.Next /> - <Pagination.Last /> + <Pagination onClick={getNewPage} size="lg"> + {prefix} + <Pagination.Item active >{currentPage}</Pagination.Item> + {suffix} </Pagination> ); } + +const pagination_fields = ["requested_page", "pages"]; +export { pagination_fields }; diff --git a/src/components/Rucio.js b/src/components/Rucio.js new file mode 100644 index 0000000000000000000000000000000000000000..6dd2613edf49f20b5644027270190debe7f9ff91 --- /dev/null +++ b/src/components/Rucio.js @@ -0,0 +1,17 @@ +import React from "react"; +import { Alert } from "react-bootstrap"; + +export default function Rucio() { + return ( + <Alert variant="warning"> + <p>You will leave ESAP GUI and be redirected to</p> + <a + target="_blank" + rel="noopener noreferrer" + href="https://escape-dios-dl.cern.ch/ui/" + > + Rucio Web UI + </a> + </Alert> + ); +} diff --git a/src/components/archives/ArchiveDetails.js b/src/components/archives/ArchiveDetails.js index fa5e2759f7f3762805d243068fb24a3203407bfa..9ac3f575b9c86d70dadaf4a61da50d5b033ffa68 100644 --- a/src/components/archives/ArchiveDetails.js +++ b/src/components/archives/ArchiveDetails.js @@ -1,23 +1,23 @@ import React, { useContext } from "react"; import { useParams } from "react-router-dom"; import { GlobalContext } from "../../contexts/GlobalContext"; -import { Container, Row, Col, Card, Table, Alert, Image } from "react-bootstrap"; +import { + Container, + Row, + Col, + Card, + Table, + Alert, + Image, +} from "react-bootstrap"; import DataProductCategories from "./DataProductCategories"; export default function ArchiveDetails(props) { const { uri } = useParams(); console.log(uri); - const { archives, defaultConfigName, setConfigName } = useContext(GlobalContext); + const { archives } = useContext(GlobalContext); if (!archives) return null; - /* This is a nasty hack. There must be a better way! */ - if (uri.search("zooniverse") >= 0){ - setConfigName("zooniverse"); - } - else{ - setConfigName(defaultConfigName); - } - const archive = archives.find((archive) => archive.uri === uri); if (!archive) return <Alert variant="danger">No Archive found!</Alert>; console.log(archive); @@ -46,15 +46,19 @@ export default function ArchiveDetails(props) { </tbody> </Table> <Card> - <Image - className="mx-auto d-block" - src={archive.thumbnail} - alt="" - rounded - fluid - /> - <Card.Title className="text-center h4 pt-3">{archive.short_description}</Card.Title> - <Card.Text className="text-justify m-3">{archive.long_description}</Card.Text> + <Image + className="mx-auto d-block" + src={archive.thumbnail} + alt="" + rounded + fluid + /> + <Card.Title className="text-center h4 pt-3"> + {archive.short_description} + </Card.Title> + <Card.Text className="text-justify m-3"> + {archive.long_description} + </Card.Text> </Card> </Card.Body> </Card> diff --git a/src/components/archives/Archives.js b/src/components/archives/Archives.js index a53113a9c2846f1adcf2410c40ca3bf47dda8fa0..f2148c055ddd5cfab06cfe00ec15b63f65f5be7c 100644 --- a/src/components/archives/Archives.js +++ b/src/components/archives/Archives.js @@ -1,23 +1,19 @@ import React, { useContext } from "react"; -import { CardColumns} from "react-bootstrap"; +import { CardColumns } from "react-bootstrap"; import ArchiveCard from "./ArchiveCard"; import { GlobalContext } from "../../contexts/GlobalContext"; export function Archives() { - const { archives, defaultConfigName, setConfigName} = useContext(GlobalContext); + const { archives } = useContext(GlobalContext); if (!archives) return null; - setConfigName(defaultConfigName); - console.log("archives: ", { archives }); return ( <CardColumns className="row-2"> - {archives.map((archive) => { - let key = "archive-" + archive.id; - return ( - <ArchiveCard key={key} archive={archive} /> - ); - })} + {archives.map((archive) => { + let key = "archive-" + archive.id; + return <ArchiveCard key={key} archive={archive} />; + })} </CardColumns> ); } diff --git a/src/components/archives/DataProductCategories.js b/src/components/archives/DataProductCategories.js index 3846156353758d54215d96e93c8d37161a02f970..d7f4d71f5be66d7bcf2a123e35784e95ad746f56 100644 --- a/src/components/archives/DataProductCategories.js +++ b/src/components/archives/DataProductCategories.js @@ -1,12 +1,6 @@ import React, { useState, useContext, useEffect } from "react"; import { NavLink } from "react-router-dom"; -import { - ListGroup, - Card, - Button, - Row, - Col, -} from "react-bootstrap"; +import { ListGroup, Card, Button, Row, Col } from "react-bootstrap"; import { GlobalContext } from "../../contexts/GlobalContext"; import axios from "axios"; @@ -37,13 +31,15 @@ export default function DataProductCategories({ archive }) { {categories .filter((category) => archive.datasets.includes(category.uri)) .map((category) => { - console.log(category); + console.log("category:", category); let button; let extra_info; let documentation; if (category.service_connector) { + let query_url = `${category.archive_uri_derived}/query`; + console.log("query_url:", query_url); button = ( - <Button as={NavLink} variant="outline-info" to="/query"> + <Button as={NavLink} variant="outline-info" to={query_url}> Browse Catalog & Run Queries </Button> ); diff --git a/src/components/query/QueryCatalogs.js b/src/components/query/QueryCatalogs.js index c5709c393b687fb1cf5c41bd34fbbb0466296052..913d2aabf0450eb7cbfba1350a6b2bd375782360 100644 --- a/src/components/query/QueryCatalogs.js +++ b/src/components/query/QueryCatalogs.js @@ -1,4 +1,5 @@ import React, { useContext, useEffect } from "react"; +import { useParams } from "react-router-dom"; import axios from "axios"; import { Container } from "react-bootstrap"; import Form from "react-jsonschema-form"; @@ -13,17 +14,43 @@ export default function QueryCatalogs() { // "catalogquery": "querystring", // "status": "fetching|fechted", // "results": null} - const { queryMap, formData, setFormData } = useContext(QueryContext); - const { config, api_host } = useContext(GlobalContext); + const { queryMap, formData, setFormData, page } = useContext(QueryContext); + const { config, api_host, setConfigName } = useContext(GlobalContext); + const { uri } = useParams(); + console.log(uri); - console.log(config.query_schema); + // set ConfigName for archive useEffect(() => { + switch (uri) { + case "adex": + setConfigName("adex"); + break; + case "apertif": + setConfigName("apertif"); + break; + case "zooniverse": + setConfigName("zooniverse"); + break; + case "astron_vo": + setConfigName("astron_vo"); + break; + case "lofar": + setConfigName("lofar"); + break; + default: + setConfigName("esap_ivoa"); + } + return () => { + console.log("cleaned up"); + setConfigName("esap_ivoa"); + }; + }, [uri]); + + useEffect(() => { + console.log(config.query_schema); if (!formData) return; - let catalogs = config.query_schema.properties.catalog.enum.filter( - (name) => name !== "all" - ); - let gui = config.query_schema.name; - const queries = parseQueryForm(gui, formData, catalogs); + const gui = config.query_schema.name; + const queries = parseQueryForm(gui, formData, page); // Ideally query for each catalog is sent to ESAP API Gateway, and query results is returned // This is under development in the backend at the moment @@ -35,7 +62,7 @@ export default function QueryCatalogs() { status: "fetching", results: null, }); - let url = api_host + "query/query/?" + query.esapquery; + const url = api_host + "query/query/?" + query.esapquery; axios .get(url) .then((queryResponse) => { @@ -55,7 +82,7 @@ export default function QueryCatalogs() { }); }); }); - }, [formData]); + }, [formData, page]); function formTemplate({ TitleField, properties, title, description }) { return ( @@ -78,7 +105,7 @@ export default function QueryCatalogs() { console.log("queryMap", Array.from(queryMap.values())); - const uiSchemaProp = config.ui_schema ? { uiSchema : config.ui_schema } : {}; + const uiSchemaProp = config.ui_schema ? { uiSchema: config.ui_schema } : {}; return ( <Container fluid> <Form diff --git a/src/components/query/QueryIVOARegistry.js b/src/components/query/QueryIVOARegistry.js new file mode 100644 index 0000000000000000000000000000000000000000..c373f93c039dd2b6eeb538483efd98d140df9848 --- /dev/null +++ b/src/components/query/QueryIVOARegistry.js @@ -0,0 +1,145 @@ +import React, { useContext, useEffect } from "react"; +import { useParams } from "react-router-dom"; +import axios from "axios"; +import { Container, Button } from "react-bootstrap"; +import Form from "react-jsonschema-form"; +import { GlobalContext } from "../../contexts/GlobalContext"; +import { QueryContext } from "../../contexts/QueryContext"; +import QueryResults from "./QueryResults"; +import parseQueryForm from "../../utils/form/parseQueryForm"; + +export default function QueryIVOARegistry() { + // queryMap is a map of dictionaries, where each dictionary consists of + // {"catalog": "catalogname", + // "catalogquery": "querystring", + // "status": "fetching|fechted", + // "results": null} + const { queryMap, formData, setFormData } = useContext(QueryContext); + const { config, api_host, setConfigName } = useContext(GlobalContext); + const { uri } = useParams(); + console.log("uri:", uri); + + // set ConfigName for archive + useEffect(() => { + switch (uri) { + case "adex": + setConfigName("adex"); + break; + case "apertif": + setConfigName("apertif"); + break; + case "zooniverse": + setConfigName("zooniverse"); + break; + case "astron_vo": + setConfigName("astron_vo"); + break; + case "lofar": + setConfigName("lofar"); + break; + default: + setConfigName("esap_ivoa"); + } + return () => { + console.log("Set configuration back to default!"); + setConfigName("esap_ivoa"); + }; + }, [uri]); + + useEffect(() => { + console.log(config.query_schema); + if (!formData) return; + console.log("formData:", formData); + let gui = config.query_schema.name; + const queries = parseQueryForm(gui, formData); + + // Ideally query for each catalog is sent to ESAP API Gateway, and query results is returned + // This is under development in the backend at the moment + queryMap.clear(); + queries.forEach((query) => { + queryMap.set(query.catalog, { + catalog: query.catalog, + esapquery: query.esapquery, + status: "fetching", + results: null, + }); + let url = api_host + "query/" + query.esapquery; + axios + .get(url) + .then((queryResponse) => { + queryMap.set(query.catalog, { + catalog: query.catalog, + esapquery: query.esapquery, + status: "fetched", + results: queryResponse.data, + }); + }) + .catch(() => { + queryMap.set(query.catalog, { + catalog: query.catalog, + esapquery: query.esapquery, + status: "error", + results: null, + }); + }); + }); + }, [formData]); + + function formTemplate({ TitleField, properties, title, description }) { + return ( + <div> + <TitleField title={title} /> + <div className="row"> + {properties.map((prop) => ( + <div + className="col-lg-2 col-md-4 col-sm-6 col-xs-12" + key={prop.content.key} + > + {prop.content} + </div> + ))} + </div> + {description} + </div> + ); + } + + console.log("queryMap:", Array.from(queryMap.values())); + + const uiSchemaProp = config.ui_schema ? { uiSchema: config.ui_schema } : {}; + console.log("UI Schema props:", uiSchemaProp); + + return ( + <Container fluid> + <Form + schema={config.query_schema} + ObjectFieldTemplate={formTemplate} + formData={formData} + onSubmit={({ formData }) => setFormData(formData)} + {...uiSchemaProp} + > + <div> + <Button type="submit">Get Registry Services</Button> + </div> + </Form> + {Array.from(queryMap.keys()).map((catalog) => { + console.log("catalog:", catalog); + const details = queryMap.get(catalog); + console.log("Details:", details); + console.log("Results:", details.results); + let catalogName = + config.query_schema.properties.catalog.enumNames[ + config.query_schema.properties.catalog.enum.findIndex( + (name) => name === catalog + ) + ]; + return ( + <div key={catalog} className="mt-3"> + <h4>List of registries</h4> + <QueryResults catalog={catalog} /> + </div> + ); + })} + </Container> + ); +} diff --git a/src/components/query/QueryResults.js b/src/components/query/QueryResults.js index 9e0af9ea6da48474688580b5c83745b8f85668db..edc9542aed74a5a35a07649a4e78e932fe3c9c94 100644 --- a/src/components/query/QueryResults.js +++ b/src/components/query/QueryResults.js @@ -2,6 +2,7 @@ import React from "react"; import ApertifResults from "./ApertifResults"; import ASTRONVOResults from "./ASTRONVOResults"; import ZooniverseResults from "./ZooniverseResults"; +import VORegistryResults from "./VORegistryResults"; export default function QueryResults({ catalog }) { switch (catalog) { @@ -12,7 +13,9 @@ export default function QueryResults({ catalog }) { case "zooniverse_projects": return <ZooniverseResults catalog={catalog} />; case "zooniverse_workflows": - return <ZooniverseResults catalog={catalog} />; + return <ZooniverseResults catalog={catalog} />; + case "vo_reg": + return <VORegistryResults catalog={catalog} />; default: return null; } diff --git a/src/components/query/VORegistryResults.js b/src/components/query/VORegistryResults.js new file mode 100644 index 0000000000000000000000000000000000000000..828a9ae7670a24aa25c01f894bb8cc982cc28e84 --- /dev/null +++ b/src/components/query/VORegistryResults.js @@ -0,0 +1,96 @@ +import React, { useContext, useEffect, useState } from "react"; +import { Table, Alert, InputGroup } from "react-bootstrap"; +import { QueryContext } from "../../contexts/QueryContext"; +import LoadingSpinner from "../LoadingSpinner"; +import Paginate from "../Paginate"; +import { IVOAContext } from "../../contexts/IVOAContext"; + +export default function VORegistryResults({ catalog }) { + const { queryMap } = useContext(QueryContext); + const { registryList, add, remove } = useContext(IVOAContext); + // const [checkAll, setCheckAll] = useState(""); + + useEffect(() => { + console.log("RegistryList:", registryList); + }, [registryList]); + + // useEffect(() => { + // console.log("checkAll:", checkAll); + // }, [checkAll]); + + if (!queryMap) return null; + console.log("VOReg queryMap:", queryMap.get(catalog)); + + if (queryMap.get(catalog).status === "fetched") { + if (queryMap.get(catalog).results.results.length === 0) + return <Alert variant="warning">No matching results found!</Alert>; + console.log("VO Registry results:", queryMap.get(catalog).results.results); + + return ( + <> + <Table className="mt-3" responsive> + <thead> + <tr className="bg-light"> + <th> + <InputGroup> + <InputGroup.Checkbox + // onChange={(event) => { + // setCheckAll(event.target.checked); + // event.target.checked + // ? queryMap + // .get(catalog) + // .results.results.map((result) => { + // add(result.access_url); + // }) + // : queryMap + // .get(catalog) + // .results.results.map((result) => { + // remove(result.access_url); + // }); + // }} + /> + </InputGroup> + </th> + <th>Name</th> + <th>Access URL</th> + <th>Waveband</th> + <th>Title</th> + <th>Service Type</th> + <th>Content Types</th> + </tr> + </thead> + <tbody> + {queryMap.get(catalog).results.results.map((result) => { + return ( + <tr key={result.short_name}> + <th> + <InputGroup> + <InputGroup.Checkbox + // checked={checkAll} + onChange={(event) => { + console.log(event.target.checked); + event.target.checked + ? add(result.access_url) + : remove(result.access_url); + }} + /> + </InputGroup> + </th> + <td>{result.short_name}</td> + <td>{result.access_url}</td> + <td>{result.waveband}</td> + <td>{result.title}</td> + <td>{result.service_type}</td> + <td>{result.content_types}</td> + </tr> + ); + })} + </tbody> + </Table> + <Paginate /> + </> + ); + } else { + return <LoadingSpinner />; + } +} diff --git a/src/components/query/ZooniverseResults.js b/src/components/query/ZooniverseResults.js index baa1a98239cda58b005ae39c9e20bd703a08244d..f8814e4c5c3ab1cbc3c802750534604d9aa26591 100644 --- a/src/components/query/ZooniverseResults.js +++ b/src/components/query/ZooniverseResults.js @@ -1,8 +1,8 @@ -import React, { useContext } from "react"; +import React, { useContext, useState} from "react"; import { Table, Alert } from "react-bootstrap"; import { QueryContext } from "../../contexts/QueryContext"; import LoadingSpinner from "../LoadingSpinner"; -import Paginate from "../Paginate"; +import Paginate, { pagination_fields } from "../Paginate"; const DATETIME_OPTIONS = { year: 'numeric', @@ -16,14 +16,13 @@ const DATETIME_OPTIONS = { }; Object.isObject = function(obj) { - return obj && obj.constructor === this || false; + return (obj && obj.constructor === this) || false; }; function renderArray(array, currentReactKey=""){ return array.map((element, index) => { const updatedReactKey = `${currentReactKey}_${index}`; const separator = index < array.length - 1 ? ", " : ""; - // console.log([index, array.length, separator]); return renderIfCompound(element, updatedReactKey, separator); }); } @@ -50,10 +49,16 @@ function renderIfCompound(element, currentReactKey="", separatorForPod="", float return renderObject(element, currentReactKey, separatorForPod); } else if (typeof element === "boolean") { return JSON.stringify(element) + separatorForPod - } else if (typeof element === "float") { - return element.toFixed(floatPrecision) + separatorForPod + } else if (Number.isInteger(element)){ + return element.toString() + separatorForPod; + } else { + try{ + return element.toFixed(floatPrecision) + separatorForPod; + } + catch(err){ + return `${element}` + separatorForPod + } } - return `${element}` + separatorForPod } function titleCase(string) { @@ -64,17 +69,28 @@ function titleCase(string) { return sentence.join(" "); } -function ZooniverseProjectResults(queryMap){ - let date_formatter=new Intl.DateTimeFormat("default", DATETIME_OPTIONS); - let result = queryMap.get("zooniverse_projects").results.query_results[0]; - let mandatory_fields = ["launch_date", "created_at", "live", "updated_at", "project_id", "display_name", "slug"]; - let remaining_fields = Object.keys(result).filter(key => !mandatory_fields.includes(key)); - let remaining_headers = remaining_fields.map((field) => { - let title=titleCase(field.replace("_", " ")); +function newPageCallback(setPage){ + return (args) => { + if(args.target){ + setPage(parseFloat(args.target.text)); + } + }; +} + +function ZooniverseProjectResults(context){ + const { queryMap, page, setPage } = context; + const date_formatter=new Intl.DateTimeFormat("default", DATETIME_OPTIONS); + const result = queryMap.get("zooniverse_projects").results.query_results[0]; + const numPages = result.pages; + const mandatory_fields = ["launch_date", "created_at", "live", "updated_at", "project_id", "display_name", "slug"]; + const remaining_fields = Object.keys(result).filter(key => !(mandatory_fields.includes(key) || pagination_fields.includes(key))); + const remaining_headers = remaining_fields.map((field) => { + const title=titleCase(field.replace("_", " ")); return (<th key={`project_header_${field}`}>{title}</th>); }); return ( <> + <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/> <Table className="mt-3" responsive> <thead> <tr className="bg-light"> @@ -95,12 +111,12 @@ function ZooniverseProjectResults(queryMap){ </thead> <tbody> {queryMap.get("zooniverse_projects").results.query_results.map((result) => { - let launch_date = result.launch_date ? date_formatter.format(new Date(result.launch_date)) : "Not Launched"; - let created_at = date_formatter.format(new Date(result.created_at)); - let updated_at = date_formatter.format(new Date(result.updated_at)); - let live = result.live ? "Yes" : "No" - let remaining_cells = remaining_fields.map((field) => { - let reactKey = `project_${result.project_id}_${field}`; + const launch_date = result.launch_date ? date_formatter.format(new Date(result.launch_date)) : "Not Launched"; + const created_at = date_formatter.format(new Date(result.created_at)); + const updated_at = date_formatter.format(new Date(result.updated_at)); + const live = result.live ? "Yes" : "No" + const remaining_cells = remaining_fields.map((field) => { + const reactKey = `project_${result.project_id}_${field}`; return (<td key={reactKey}>{renderIfCompound(result[field], reactKey)}</td>); }); return ( @@ -123,24 +139,27 @@ function ZooniverseProjectResults(queryMap){ })} </tbody> </Table> - {/* <Paginate /> */} + <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/> </> ); } -function ZooniverseWorkflowResults(queryMap){ +function ZooniverseWorkflowResults(context){ + const { queryMap, page, setPage } = context; let date_formatter=new Intl.DateTimeFormat("default", DATETIME_OPTIONS); let result = queryMap.get("zooniverse_workflows").results.query_results[0]; - let result_workflow = result.workflows[0] + let result_workflow = result.workflows[0]; + const numPages = result.pages; let mandatory_fields = ["created_at", "updated_at", "workflow_id", "display_name"]; - let remaining_fields = Object.keys(result_workflow).filter(key => !mandatory_fields.includes(key)); + let remaining_fields = Object.keys(result_workflow).filter(key => !(mandatory_fields.includes(key) || pagination_fields.includes(key))); let remaining_headers = remaining_fields.map((field) => { let title=titleCase(field.replace("_", " ")); return (<th key={`project_header_${field}`}>{title}</th>); }); return ( <> + <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/> {queryMap.get("zooniverse_workflows").results.query_results.map((project) => { return (<div key={project.project_id}> <h4>{project.display_name}</h4> @@ -186,24 +205,24 @@ function ZooniverseWorkflowResults(queryMap){ })} </tbody> </Table> - {/* <Paginate /> */} </div>); })} + <Paginate getNewPage={newPageCallback(setPage)} currentPage={page} numAdjacent={3} numPages={numPages}/> </> ); } export default function ZooniverseResults({ catalog }) { - const { queryMap } = useContext(QueryContext); - if (!queryMap) return null; - if (queryMap.get(catalog).status === "fetched") { - if (queryMap.get(catalog).results.query_results.length === 0) + const context = useContext(QueryContext); + if (!context.queryMap) return null; + if (context.queryMap.get(catalog).status === "fetched") { + if (context.queryMap.get(catalog).results.query_results.length === 0) return <Alert variant="warning">No matching results found!</Alert>; else if (catalog === "zooniverse_projects"){ - return ZooniverseProjectResults(queryMap); + return ZooniverseProjectResults(context); } else if (catalog === "zooniverse_workflows"){ - return ZooniverseWorkflowResults(queryMap); + return ZooniverseWorkflowResults(context); } else { return <Alert variant="warning">Unrecognised Zooniverse Catalog!</Alert>; diff --git a/src/contexts/BasketContext.js b/src/contexts/BasketContext.js index 5f262b3f779ff45c30ff6574337af1408027d846..fd948e80ea70b9170e8d7bae68e5505de430d580 100644 --- a/src/contexts/BasketContext.js +++ b/src/contexts/BasketContext.js @@ -3,18 +3,18 @@ import Databasket from "../components/basket/databasket"; import Addtobasket from "../components/basket/addtobasket"; import BasketContext from "../contexts/BasketContext"; -const BasketContext = createContext(); -const initialDatasets = ["First Dataset 1", "Just another dataset"]; +export const BasketContext = createContext(); -function mydatasets() { - const [datasets, setDatasets] = useState(initialDatasets); +export function BasketContextProvider({ children }) { + const [datasets, setDatasets] = useState([]); - function handleAddDataset(Dataset) { - setDatasets([...datasets, Dataset]); + function handleAddDataset(dataset) { + setDatasets([...datasets, dataset]); } - function handleRemoveDataset(index) { + function handleRemoveDataset(dataset) { const copy = [...datasets]; + const index = copy.findIndex((ds) => ds === dataset); copy.splice(index, 1); setDatasets(copy); } @@ -22,15 +22,7 @@ function mydatasets() { <BasketContext.Provider value={{ datasets, add: handleAddDataset, remove: handleRemoveDataset }} > - <div className="App"> - <header className="App-header"> - <h2>DataBasket App</h2> - <Addtobasket /> - <Databasket /> - </header> - </div> + {children} </BasketContext.Provider> ); } - -export default BasketContext; diff --git a/src/contexts/GlobalContext.js b/src/contexts/GlobalContext.js index 0b828c476758051005a753ec2cca6c2594127260..e18b2eb607345a265f8060ccbf209a52e6d63753 100644 --- a/src/contexts/GlobalContext.js +++ b/src/contexts/GlobalContext.js @@ -8,17 +8,16 @@ export function GlobalContextProvider({ children }) { console.log("ASTRON ESAP version 14 aug 2020"); const api_host = process.env.NODE_ENV === "development" - ? "http://sdc.astron.nl:15671/esap-api/" + ? "http://localhost:15671/esap-api/" : "/esap-api/"; const [config, setConfig] = useState(); - const [configName, setConfigName] = useState("esap_config"); - const [defaultConfigName, setDefaultConfigName] = useState("esap_config"); + const [configName, setConfigName] = useState("esap_ivoa"); useEffect(() => { - let configNameString="" - if (configName){ - configNameString=`?name=${configName}` + let configNameString = ""; + if (configName) { + configNameString = `?name=${configName}`; } axios .get(api_host + "query/configuration" + configNameString) @@ -63,7 +62,7 @@ export function GlobalContextProvider({ children }) { archives, handleLogin, handleLogout, - setConfigName + setConfigName, }} > {children} diff --git a/src/contexts/IVOAContext.js b/src/contexts/IVOAContext.js new file mode 100644 index 0000000000000000000000000000000000000000..eba8707e13090945b095b033a21600474fd60d29 --- /dev/null +++ b/src/contexts/IVOAContext.js @@ -0,0 +1,28 @@ +import React, { createContext, useState } from "react"; + +export const IVOAContext = createContext(); + +export function IVOAContextProvider({ children }) { + const [registryList, setRegistryList] = useState([]); + function handleAddRegistry(access_url) { + setRegistryList([...registryList, access_url]); + } + + function handleRemoveRegistry(access_url) { + const copy = [...registryList]; + const index = copy.findIndex((url) => url === access_url); + copy.splice(index, 1); + setRegistryList(copy); + } + return ( + <IVOAContext.Provider + value={{ + registryList, + add: handleAddRegistry, + remove: handleRemoveRegistry, + }} + > + {children} + </IVOAContext.Provider> + ); +} diff --git a/src/contexts/QueryContext.js b/src/contexts/QueryContext.js index 91725aff18f08ab567f787c966367783cf1b6594..e0ad5419511ddddacc8a575a820f7ae199e5a56e 100644 --- a/src/contexts/QueryContext.js +++ b/src/contexts/QueryContext.js @@ -6,9 +6,12 @@ export const QueryContext = createContext(); export function QueryContextProvider({ children }) { const queryMap = useMap(); const [formData, setFormData] = useState(); + const [page, setPage] = useState(1); return ( <QueryContext.Provider value={{ + page, + setPage, queryMap, formData, setFormData, diff --git a/src/routes/Routes.js b/src/routes/Routes.js index e28032c493e06f280a9c7d9067f4091b3785911c..5cea8e2796c348ba82c3f4e7cccb50a87a2ced3d 100644 --- a/src/routes/Routes.js +++ b/src/routes/Routes.js @@ -4,9 +4,13 @@ import { Archives } from "../components/archives/Archives"; import ArchiveDetails from "../components/archives/ArchiveDetails"; import { GlobalContext } from "../contexts/GlobalContext"; import QueryCatalogs from "../components/query/QueryCatalogs"; +import QueryIVOARegistry from "../components/query/QueryIVOARegistry"; import { BrowserRouter as Router } from "react-router-dom"; import NavBar from "../components/NavBar"; import { QueryContextProvider } from "../contexts/QueryContext"; +import Rucio from "../components/Rucio"; +import Interactive from "../components/Interactive"; +import { IVOAContextProvider } from "../contexts/IVOAContext"; export default function Routes() { const { config, handleLogin, handleLogout } = useContext(GlobalContext); @@ -22,14 +26,27 @@ export default function Routes() { <Route exact path="/archives"> <Archives /> </Route> - <Route path="/query"> + <Route exact path="/query"> <QueryContextProvider> - <QueryCatalogs /> + <IVOAContextProvider> + <QueryIVOARegistry /> + </IVOAContextProvider> </QueryContextProvider> </Route> + <Route exact path="/rucio"> + <Rucio /> + </Route> + <Route exact path="/interactive"> + <Interactive /> + </Route> <Route exact path="/login" component={handleLogin} /> <Route exact path="/logout" component={handleLogout} /> - <Route path="/archives/:uri" component={ArchiveDetails} /> + <Route exact path="/archives/:uri" component={ArchiveDetails} /> + <Route exact path="/archives/:uri/query"> + <QueryContextProvider> + <QueryCatalogs /> + </QueryContextProvider> + </Route> </Switch> </Router> ); diff --git a/src/utils/form/parseADEXForm.js b/src/utils/form/parseADEXForm.js index c4f17bd8a42aa373f5cd0652ba8e2353c099f662..d2f9460dc23fc6ea74995f1f051a881a8590194f 100644 --- a/src/utils/form/parseADEXForm.js +++ b/src/utils/form/parseADEXForm.js @@ -1,4 +1,5 @@ -export default function ParseADEXForm(formData, catalogs) { +export default function ParseADEXForm(formData) { + let catalogs = ["apertif", "astron_vo"]; let queries = []; // queries is an array of dictionaries, where each dictionary consists of // {"catalog": "catalogname", diff --git a/src/utils/form/parseASTRONVOForm.js b/src/utils/form/parseASTRONVOForm.js new file mode 100644 index 0000000000000000000000000000000000000000..8724fc5aabc2c2c9d2a6cda65687d5f6d5b78b1d --- /dev/null +++ b/src/utils/form/parseASTRONVOForm.js @@ -0,0 +1,29 @@ +export default function ParseASTRONVOForm(formData) { + let queries = []; + // queries is an array of dictionaries, where each dictionary consists of + // {"catalog": "catalogname", + // "esapquery": "querystring"} + let query = ""; + let formInput = Object.entries(formData); + console.log(formInput); + + for (let [key, value] of formInput) { + console.log(`${key}: ${value}`); + if (value && value !== "all" && key !== "catalog") { + query += `${`${query}` ? "&" : ""}` + key + "=" + value; + } + } + console.log("Query:", query); + // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname", + // "catalogquery": "querystring", + // "status": "null|fetching|fetched", + // "results": null} + let catalog = formInput.find(([key]) => key === "catalog")[1]; + let esapquery = query + `${`${query}` ? "&" : ""}archive_uri=` + catalog; + queries.push({ + catalog: catalog, + esapquery: esapquery, + }); + console.log("Queries:", queries); + return queries; +} diff --git a/src/utils/form/parseApertifForm.js b/src/utils/form/parseApertifForm.js new file mode 100644 index 0000000000000000000000000000000000000000..4a764050acb24deea951cb6778e8e023e62364c6 --- /dev/null +++ b/src/utils/form/parseApertifForm.js @@ -0,0 +1,29 @@ +export default function ParseApertifForm(formData) { + let queries = []; + // queries is an array of dictionaries, where each dictionary consists of + // {"catalog": "catalogname", + // "esapquery": "querystring"} + let query = ""; + let formInput = Object.entries(formData); + console.log("formInput:", formInput); + + for (let [key, value] of formInput) { + console.log(`${key}: ${value}`); + if (value && value !== "all" && key !== "catalog") { + query += `${`${query}` ? "&" : ""}` + key + "=" + value; + } + } + console.log("Query:", query); + // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname", + // "catalogquery": "querystring", + // "status": "null|fetching|fetched", + // "results": null} + let catalog = formInput.find(([key]) => key === "catalog")[1]; + let esapquery = query + `${`${query}` ? "&" : ""}archive_uri=` + catalog; + queries.push({ + catalog: catalog, + esapquery: esapquery, + }); + console.log("Queries:", queries); + return queries; +} diff --git a/src/utils/form/parseIVOAForm.js b/src/utils/form/parseIVOAForm.js new file mode 100644 index 0000000000000000000000000000000000000000..640227870591c5f3dbbcb864355cc7240c5fb2df --- /dev/null +++ b/src/utils/form/parseIVOAForm.js @@ -0,0 +1,36 @@ +export default function ParseIVOAForm(formData) { + let queries = []; + // queries is an array of dictionaries, where each dictionary consists of + // {"catalog": "catalogname", + // "esapquery": "querystring"} + let query = ""; + let formInput = Object.entries(formData); + console.log(formInput); + + // IVOA query consists of multiple steps + // Step 1: get list of registry services + + for (let [key, value] of formInput) { + console.log(`${key}: ${value}`); + if (value && value !== "all" && key !== "catalog") { + query += `${`${query}` ? "&" : ""}` + key + "=" + value; + } + } + console.log("Query:", query); + // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname", + // "catalogquery": "querystring", + // "status": "null|fetching|fetched", + // "results": null} + let catalog = formInput.find(([key]) => key === "catalog")[1]; + + let esapquery = + "get-services/?" + query + `${`${query}` ? "&" : ""}dataset_uri=` + catalog; + + queries.push({ + catalog: catalog, + esapquery: esapquery, + }); + + console.log("Queries:", queries); + return queries; +} diff --git a/src/utils/form/parseLOFARForm.js b/src/utils/form/parseLOFARForm.js new file mode 100644 index 0000000000000000000000000000000000000000..badc2b8512cc5c64457384b6e23c3b435f4eb1d8 --- /dev/null +++ b/src/utils/form/parseLOFARForm.js @@ -0,0 +1,29 @@ +export default function ParseLOFARForm(formData) { + let queries = []; + // queries is an array of dictionaries, where each dictionary consists of + // {"catalog": "catalogname", + // "esapquery": "querystring"} + let query = ""; + let formInput = Object.entries(formData); + console.log(formInput); + + for (let [key, value] of formInput) { + console.log(`${key}: ${value}`); + if (value && value !== "all" && key !== "catalog") { + query += `${`${query}` ? "&" : ""}` + key + "=" + value; + } + } + console.log("Query:", query); + // If catalog is set to "all", query for each catalog needs to be generated {"catalog": "catalogname", + // "catalogquery": "querystring", + // "status": "null|fetching|fetched", + // "results": null} + let catalog = formInput.find(([key]) => key === "catalog")[1]; + let esapquery = query + `${`${query}` ? "&" : ""}archive_uri=` + catalog; + queries.push({ + catalog: catalog, + esapquery: esapquery, + }); + console.log("Queries:", queries); + return queries; +} diff --git a/src/utils/form/parseQueryForm.js b/src/utils/form/parseQueryForm.js index 21f9315428e7af25435b27b52d64b99f2cde7adb..ecf4e15093d67ab8c6142f82e434ee406d4128d9 100644 --- a/src/utils/form/parseQueryForm.js +++ b/src/utils/form/parseQueryForm.js @@ -1,12 +1,24 @@ import parseADEXForm from "./parseADEXForm"; import parseZooniverseForm from "./parseZooniverseForm"; +import parseLOFARForm from "./parseLOFARForm"; +import parseIVOAForm from "./parseIVOAForm"; +import parseApertifForm from "./parseApertifForm"; +import parseASTRONVOForm from "./parseASTRONVOForm"; -export default function parseQueryForm(gui, formData, catalogs) { +export default function parseQueryForm(gui, formData, page) { switch (gui) { case "adex": - return parseADEXForm(formData, catalogs); + return parseADEXForm(formData); case "zooniverse": - return parseZooniverseForm(formData, catalogs); + return parseZooniverseForm(formData, page); + case "lofar": + return parseLOFARForm(formData); + case "apertif": + return parseApertifForm(formData); + case "astron_vo": + return parseASTRONVOForm(formData); + case "ivoa": + return parseIVOAForm(formData); default: return null; } diff --git a/src/utils/form/parseZooniverseForm.js b/src/utils/form/parseZooniverseForm.js index c4fa8ceabc4b7fbbaab45686ea5328dad23fa783..50284bb4c8ab1e2db35e4cd2b3446662b477e284 100644 --- a/src/utils/form/parseZooniverseForm.js +++ b/src/utils/form/parseZooniverseForm.js @@ -1,4 +1,5 @@ -export default function parseZooniverseForm(formData, catalogs) { +export default function parseZooniverseForm(formData, page) { + let catalogs = ["zooniverse_projects", "zooniverse_workflows"]; let queries = []; // queries is an array of dictionaries, where each dictionary consists of // {"catalog": "catalogname", @@ -23,7 +24,11 @@ export default function parseZooniverseForm(formData, catalogs) { if (catalog === "all") { console.log(`Catalogs: ${catalogs}`); catalogs.map((catalog) => { - let esapquery = [query, "archive_uri=zooniverse", `catalog=${catalog}`].join("&"); + let esapquery = [ + query, + "archive_uri=zooniverse", + `catalog=${catalog}`, + ].join("&"); queries.push({ catalog: catalog, @@ -32,11 +37,15 @@ export default function parseZooniverseForm(formData, catalogs) { return null; }); } else { - let esapquery = [query, "archive_uri=zooniverse", `catalog=${catalog}`].join("&"); + let esapquery = [ + query, + "archive_uri=zooniverse", + `catalog=${catalog}`, + ].join("&"); queries.push({ catalog: catalog, - esapquery: esapquery, + esapquery: esapquery + `&page=${page}`, }); } console.log("Queries:", queries);