Commit df6a3352 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Added archive code buttons to GUI.

parent e2db43e5
Pipeline #67311 failed with stages
in 13 minutes and 13 seconds
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@material-ui/core'
import { withStyles, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, Button, Tooltip, Typography } from '@material-ui/core'
import CodeIcon from '@material-ui/icons/Code'
import ReactJson from 'react-json-view'
import Markdown from './Markdown'
import { CopyToClipboard } from 'react-copy-to-clipboard'
import ClipboardIcon from '@material-ui/icons/Assignment'
class ApiDialogUnstyled extends React.Component {
static propTypes = {
......@@ -16,49 +19,98 @@ class ApiDialogUnstyled extends React.Component {
content: {
paddingBottom: 0
},
raw: {
margin: 0, padding: 0
json: {
marginTop: theme.spacing.unit * 2,
marginBottom: theme.spacing.unit * 2
},
codeContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start'
},
code: {
flexGrow: 1,
marginRight: theme.spacing.unit,
overflow: 'hidden'
},
codeActions: {
marginTop: theme.spacing.unit * 3
}
})
state = {
showRaw: false
}
constructor(props) {
super(props)
this.handleToggleRaw = this.handleToggleRaw.bind(this)
}
handleToggleRaw() {
this.setState({showRaw: !this.state.showRaw})
}
render() {
const { classes, title, data, onClose, ...dialogProps } = this.props
const { showRaw } = this.state
return (
<Dialog {...dialogProps}>
<DialogTitle>{title || 'API'}</DialogTitle>
<Dialog maxWidth="lg" fullWidth {...dialogProps}>
<DialogTitle>{title || 'API Code'}</DialogTitle>
<DialogContent classes={{root: classes.content}}>
{showRaw
? <code>
<pre className={classes.raw}>
{JSON.stringify(data, null, 4)}
</pre>
</code> : <ReactJson
src={data}
enableClipboard={false}
collapsed={2}
displayObjectSize={false}
/>
}
<Typography>Access the archive as JSON via <i>curl</i>:</Typography>
<div className={classes.codeContainer}>
<div className={classes.code}>
<Markdown>{`
\`\`\`
${data.curl}
\`\`\`
`}</Markdown>
</div>
<div className={classes.codeActions}>
<CopyToClipboard text={data.curl} onCopy={() => null}>
<Tooltip title="Copy to clipboard">
<IconButton>
<ClipboardIcon />
</IconButton>
</Tooltip>
</CopyToClipboard>
</div>
</div>
<Typography>Access the archive in <i>python</i>:</Typography>
<div className={classes.codeContainer}>
<div className={classes.code}>
<Markdown>{`
\`\`\`
${data.python}
\`\`\`
`}</Markdown>
</div>
<div className={classes.codeActions}>
<CopyToClipboard text={data.python} onCopy={() => null}>
<Tooltip title="Copy to clipboard">
<IconButton>
<ClipboardIcon />
</IconButton>
</Tooltip>
</CopyToClipboard>
</div>
</div>
<Typography>The repository API response as JSON:</Typography>
<div className={classes.codeContainer}>
<div className={classes.code}>
<div className={classes.json}>
<ReactJson
src={data}
enableClipboard={false}
collapsed={2}
displayObjectSize={false}
/>
</div>
</div>
<div className={classes.codeActions}>
<CopyToClipboard text={data} onCopy={() => null}>
<Tooltip title="Copy to clipboard">
<IconButton>
<ClipboardIcon />
</IconButton>
</Tooltip>
</CopyToClipboard>
</div>
</div>
</DialogContent>
<DialogActions>
<Button onClick={this.handleToggleRaw}>
{showRaw ? 'show tree' : 'show raw JSON'}
</Button>
<Button onClick={onClose}>
Close
</Button>
......@@ -101,9 +153,11 @@ class ApiDialogButtonUnstyled extends React.Component {
return (
<div className={classes.root}>
{component ? component({onClick: this.handleShowDialog}) : <IconButton onClick={this.handleShowDialog}>
<CodeIcon />
</IconButton>
{component ? component({onClick: this.handleShowDialog}) : <Tooltip title="Show API code">
<IconButton onClick={this.handleShowDialog}>
<CodeIcon />
</IconButton>
</Tooltip>
}
<ApiDialog
{...dialogProps} open={showDialog}
......
......@@ -14,6 +14,7 @@ import PeriodicTable from './PeriodicTable'
import ReloadIcon from '@material-ui/icons/Cached'
import UploadList from './UploadsList'
import GroupList from './GroupList'
import ApiDialogButton from '../ApiDialogButton'
class Search extends React.Component {
static tabs = {
......@@ -451,7 +452,12 @@ class SearchEntryList extends React.Component {
editable={query.owner === 'staging' || query.owner === 'user'}
data={response}
onChange={setRequest}
actions={<ReRunSearchButton/>}
actions={
<React.Fragment>
<ReRunSearchButton/>
<ApiDialogButton data={response} />
</React.Fragment>
}
{...request}
{...this.props}
/>
......
......@@ -20,7 +20,7 @@ The archive API of the nomad@FAIRDI APIs. This API is about serving processed
from typing import Dict, Any
from io import BytesIO
import os.path
from flask import send_file
from flask import send_file, request
from flask_restplus import abort, Resource, fields
import json
import importlib
......@@ -276,9 +276,8 @@ class ArchiveQueryResource(Resource):
abort(400, str(e))
# build python code and curl snippet
uri = os.path.join(api.base_url, ns.name, 'query')
results['python'] = query_api_python(args, uri)
results['curl'] = query_api_curl(args, uri)
results['python'] = query_api_python('archive', 'query', query_string=request.args)
results['curl'] = query_api_curl('archive', 'query', query_string=request.args)
data = []
calcs = results['results']
......
......@@ -19,11 +19,12 @@ from typing import Callable, IO, Set, Tuple, Iterable, Dict, Any
from flask_restplus import fields
import zipstream
from flask import stream_with_context, Response, g, abort
from urllib.parse import urlencode
import sys
import os.path
from nomad import search
from nomad import search, config
from nomad.app.optimade import filterparser
from nomad.app.utils import RFC3339DateTime, rfc3339DateTime
from nomad.files import Restricted
......@@ -245,59 +246,34 @@ def streamed_zipfile(
return response
def resolve_query_api_url(args: Dict[str, Any], base_url: str):
def query_api_url(*args, query_string: Dict[str, Any] = None):
"""
Generates a uri from query parameters and base url.
Creates a API URL.
Arguments:
*args: URL path segments after the API base URL
query_string: A dict with query string parameters
"""
args_keys = list(args.keys())
args_keys.sort()
if args_keys == ['calc_id', 'upload_id']:
url = '"%s"' % os.path.join(base_url, args['upload_id'], args['calc_id'])
else:
url = '"%s?%s" % (base_url, urlencode(args))'
url = os.path.join(config.api_url(False), *args)
if query_string is not None:
url = '%s?%s' % (url, urlencode(query_string))
return url
def query_api_python(args: Dict[str, Any], base_url: str):
def query_api_python(*args, **kwargs):
"""
Creates a string of python code to execute a search query to the repository using
the requests library.
Arguments:
args: A dict of search parameters that will be encoded in the uri
base_url: The resource url which is prepended to the uri
"""
str_code = 'import requests\n'
str_code += 'from urllib.parse import urlencode\n'
str_code += '\n\n'
str_code += 'def query_repository(args, base_url):\n'
str_code += ' url = %s\n' % resolve_query_api_url(args, base_url)
str_code += ' response = requests.get(url)\n'
str_code += ' if response.status_code != 200:\n'
str_code += ' raise Exception("nomad return status %d" % response.status_code)\n'
str_code += ' return response.json()\n'
str_code += '\n\n'
str_code += 'args = {'
for key, val in args.items():
if val is None:
continue
if isinstance(val, str):
str_code += '"%s": "%s", ' % (key, val)
else:
str_code += '"%s": %s, ' % (key, val)
str_code += '}\n'
str_code += 'base_url = "%s"\n' % base_url
str_code += 'JSON_DATA = query_repository(args, base_url)\n'
return str_code
def query_api_curl(args: Dict[str, Any], base_url: str):
url = query_api_url(*args, **kwargs)
return '''import requests
response = requests.get("{}")
response.json()'''.format(url)
def query_api_curl(*args, **kwargs):
"""
Creates a string of curl command to execute a search query to the repository.
Arguments:
args: A dict of search parameters that will be encoded in the uri
base_url: The resource url which is prepended to the uri
"""
args = {key: val for key, val in args.items() if val is not None}
uri = resolve_query_api_url(args, base_url)
return 'curl -X GET %s -H "accept: application/json" --output "nomad.json"' % uri
url = query_api_url(*args, **kwargs)
return 'curl -X GET %s -H "accept: application/json" --output "nomad.json"' % url
......@@ -66,9 +66,8 @@ class RepoCalcResource(Resource):
abort(401, message='Not authorized to access %s/%s.' % (upload_id, calc_id))
result = calc.to_dict()
uri = os.path.join(api.base_url, ns.name, '')
result['python'] = query_api_python({'upload_id': upload_id, 'calc_id': calc_id}, uri)
result['curl'] = query_api_curl({'upload_id': upload_id, 'calc_id': calc_id}, uri)
result['python'] = query_api_python('archive', upload_id, calc_id)
result['curl'] = query_api_curl('archive', upload_id, calc_id)
return result, 200
......@@ -231,9 +230,11 @@ class RepoCalcsResource(Resource):
results[group_name] = quantities[group_quantity]
# build python code/curl snippet
uri = os.path.join(api.base_url, ns.name, '')
results['curl'] = query_api_curl(args, uri)
results['python'] = query_api_python(args, uri)
code_args = dict(request.args)
if 'statistics' in code_args:
del(code_args['statistics'])
results['curl'] = query_api_curl('archive', 'query', query_string=code_args)
results['python'] = query_api_python('archive', 'query', query_string=code_args)
return results, 200
except search.ScrollIdNotFound:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment