diff --git a/src/components/query/QueryIVOARegistry.js b/src/components/query/QueryIVOARegistry.js index aefc08cd8da6c455752413265ff55161b912a11f..1cff633ed033e663714c73d36d84723c04accddb 100644 --- a/src/components/query/QueryIVOARegistry.js +++ b/src/components/query/QueryIVOARegistry.js @@ -1,13 +1,15 @@ import React, { useContext, useEffect } from "react"; import { useParams } from "react-router-dom"; import axios from "axios"; -import { Container, Button, Row, Col } from "react-bootstrap"; +import { Container, Button, Row, Col, Form as RBForm } 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"; import { IVOAContext } from "../../contexts/IVOAContext"; +import parseVOServiceForm from "../../utils/form/parseVOServiceForm"; +import VOServiceResults from "./VOServiceResults"; export default function QueryIVOARegistry() { // queryMap is a map of dictionaries, where each dictionary consists of @@ -17,7 +19,7 @@ export default function QueryIVOARegistry() { // "results": null} const { queryMap, formData, setFormData } = useContext(QueryContext); const { config, api_host, setConfigName } = useContext(GlobalContext); - const { selectedRegistry } = useContext(IVOAContext); + const { selectedRegistry, queryStep, setQueryStep } = useContext(IVOAContext); const { uri } = useParams(); console.log("uri:", uri); @@ -49,11 +51,21 @@ export default function QueryIVOARegistry() { }, [uri]); useEffect(() => { - console.log(config.query_schema); + console.log("query schema:", config.query_schema); if (!formData) return; console.log("formData:", formData); - let gui = config.query_schema.name; - const queries = parseQueryForm(gui, formData); + const gui = config.query_schema.name; + let queries = []; + + if (queryStep === "run-query") { + selectedRegistry.forEach((access_url) => { + queries = [...queries, ...parseVOServiceForm(formData, access_url)]; + }); + } else { + queries = parseQueryForm(gui, formData); + } + + console.log("queries:", queries); // 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 @@ -113,49 +125,89 @@ export default function QueryIVOARegistry() { const uiSchemaProp = config.ui_schema ? { uiSchema: config.ui_schema } : {}; console.log("UI Schema props:", uiSchemaProp); + console.log("Form Data:", formData); - 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"> - <Row> - <Col> - <h4>List of registries</h4> - </Col> - <Col> - {selectedRegistry.length === 0 ? ( - <></> - ) : ( - <Button type="submit">Query selected registry</Button> - )} - </Col> - </Row> - <QueryResults catalog={catalog} /> + if (queryStep === "run-query") { + uiSchemaProp.uiSchema = { query: { "ui:widget": "textarea" } }; + console.log("new ui schema:", uiSchemaProp); + return ( + <Container fluid> + <Form + schema={config.query_schema} + ObjectFieldTemplate={formTemplate} + formData={formData} + onSubmit={({ formData }) => setFormData(formData)} + {...uiSchemaProp} + > + <RBForm.Control as="select" multiple> + {selectedRegistry.map((registry) => { + return <option>{registry}</option>; + })} + </RBForm.Control> + <div> + <Button className="mt-3" type="submit"> + Query Registry + </Button> </div> - ); - })} - </Container> - ); + </Form> + {selectedRegistry.map((registry) => { + const details = queryMap.get(registry); + console.log("Details:", details); + return <VOServiceResults catalog={registry} />; + })} + </Container> + ); + } else { + 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"> + <Row> + <Col> + <h4>List of registries</h4> + </Col> + <Col> + {selectedRegistry.length === 0 ? ( + <></> + ) : ( + <Button + type="submit" + onClick={() => { + setQueryStep("run-query"); + }} + > + Query selected registry + </Button> + )} + </Col> + </Row> + <QueryResults catalog={catalog} /> + </div> + ); + })} + </Container> + ); + } } diff --git a/src/components/query/VOServiceResults.js b/src/components/query/VOServiceResults.js new file mode 100644 index 0000000000000000000000000000000000000000..af5fec62fd8b37b04d8c3f5112ae9e8fc35c09fb --- /dev/null +++ b/src/components/query/VOServiceResults.js @@ -0,0 +1,40 @@ +import React, { useContext } from "react"; +import { Alert, Table } from "react-bootstrap"; +import { QueryContext } from "../../contexts/QueryContext"; + +export default function VORegistryResults({ catalog }) { + const { queryMap } = useContext(QueryContext); + + if (!queryMap.get(catalog)) return null; + console.log("VO service queryMap:", queryMap.get(catalog)); + if (queryMap.get(catalog).status === "fetched") { + if (queryMap.get(catalog).results.length === 0) + return <Alert variant="warning">No matching results found!</Alert>; + return ( + <div> + <h1>Results from {catalog}</h1> + <Table className="mt-3" responsive> + <thead> + <tr className="bg-light"> + <th>Link to data</th> + </tr> + </thead> + <tbody> + {queryMap.get(catalog).results.query_results.map((result) => { + return ( + <tr key={result.result}> + <td> + <a href={result.result} rel="noopener noreferrer" download> + {result.result} + </a> + </td> + </tr> + ); + })} + </tbody> + </Table> + </div> + ); + } + return null; +} diff --git a/src/contexts/IVOAContext.js b/src/contexts/IVOAContext.js index 03a60bee39284cda3e02a8c08afa413e4febf72f..6064404a513f537fb93444f855f9b879d78aa199 100644 --- a/src/contexts/IVOAContext.js +++ b/src/contexts/IVOAContext.js @@ -4,8 +4,25 @@ import useMap from "../hooks/useMap"; export const IVOAContext = createContext(); export function IVOAContextProvider({ children }) { - const [selectedRegistry, setSelectedRegistry] = useState([]); const [registryList, setRegistryList] = useState([]); + //const [selectedRegistry, setSelectedRegistry] = useState([]); + //const [queryStep, setQueryStep] = useState("get-services"); + /* + IVOA query steps: + 1. get-services + 2. run-query: query selected registry + */ + + // For testing purpose + // start manual setup block + const [queryStep, setQueryStep] = useState("run-query"); + const [selectedRegistry, setSelectedRegistry] = useState([ + // "http://astron.nl/tap", + // "http://aip.gavo.org/tap", + // "http://archive.stsci.edu/caomtap", + "http://vao.stsci.edu/CAOMTAP/TapService.aspx", + ]); + // end block function handleAddRegistry(access_url) { setSelectedRegistry([...selectedRegistry, access_url]); @@ -25,6 +42,8 @@ export function IVOAContextProvider({ children }) { removeRegistry: handleRemoveRegistry, registryList, setRegistryList, + queryStep, + setQueryStep, }} > {children} diff --git a/src/routes/Routes.js b/src/routes/Routes.js index 5cea8e2796c348ba82c3f4e7cccb50a87a2ced3d..c1b375ce8bfad0479601fc369c206cd613ed666b 100644 --- a/src/routes/Routes.js +++ b/src/routes/Routes.js @@ -44,7 +44,9 @@ export default function Routes() { <Route exact path="/archives/:uri" component={ArchiveDetails} /> <Route exact path="/archives/:uri/query"> <QueryContextProvider> - <QueryCatalogs /> + <IVOAContextProvider> + <QueryCatalogs /> + </IVOAContextProvider> </QueryContextProvider> </Route> </Switch> diff --git a/src/utils/form/parseVOServiceForm.js b/src/utils/form/parseVOServiceForm.js new file mode 100644 index 0000000000000000000000000000000000000000..eba08e89669973ab4b34f26102346630503ad5ca --- /dev/null +++ b/src/utils/form/parseVOServiceForm.js @@ -0,0 +1,40 @@ +export default function ParseVOServiceForm(formData, access_url) { + 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; + } + } + query += `${`${query}` ? "&" : ""}` + "access_url=" + access_url; + + 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 service_type = formInput.find(([key]) => key === "service_type")[1]; + + let esapquery = + "run-query/?" + query + `${`${query}` ? "&" : ""}dataset_uri=` + catalog; + + queries.push({ + catalog: access_url, + service_type: service_type, + esapquery: esapquery, + }); + + console.log("Queries:", queries); + return queries; +}