Skip to content

Commit

Permalink
Add JobOverview and modals to query interface
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenklar committed Jun 11, 2024
1 parent dd19233 commit 031fe8c
Show file tree
Hide file tree
Showing 18 changed files with 826 additions and 12 deletions.
30 changes: 30 additions & 0 deletions daiquiri/core/assets/js/components/CodeMirrorDisplay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'

import ReactCodeMirror from '@uiw/react-codemirror'
import { EditorView } from '@codemirror/view'
import { sql } from '@codemirror/lang-sql'

const CodeMirrorDisplay = ({ value }) => {
// check https://www.npmjs.com/package/@uiw/react-codemirror#Props for props

return (
<ReactCodeMirror
className="codemirror"
value={value}
extensions={[sql(), EditorView.lineWrapping]}
editable={false}
basicSetup={{
lineNumbers: false,
foldGutter: false,
highlightActiveLine: false,
}}
/>
)
}

CodeMirrorDisplay.propTypes = {
value: PropTypes.string
}

export default CodeMirrorDisplay
5 changes: 5 additions & 0 deletions daiquiri/core/assets/js/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useReducer } from 'react'

export const useToggle = (initialValue = false) => {
return useReducer((value) => (!value), initialValue)
}
18 changes: 18 additions & 0 deletions daiquiri/core/assets/js/hooks/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useRef } from 'react'
import { Modal } from 'bootstrap'

export const useModal = () => {
const ref = useRef()

const showModal = () => {
const modal = new Modal(ref.current, {})
modal.show()
}

const hideModal = () => {
const modal = Modal.getInstance(ref.current)
modal.hide()
}

return [ref, showModal, hideModal]
}
5 changes: 5 additions & 0 deletions daiquiri/core/assets/scss/layout.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
h1 {
margin-top: 60px;
}
.modal-body {
:last-child {
margin-bottom: 0;
}
}
5 changes: 4 additions & 1 deletion daiquiri/core/assets/scss/typography.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
a {
a,
.btn-link {
text-decoration: var(--bs-link-decoration);

&:hover {
Expand All @@ -7,6 +8,8 @@ a {
}

.btn-link {
margin: 0;
padding: 0;
border-radius: 0;
border: none;
font-size: var(--bs-body-font-size);
Expand Down
12 changes: 12 additions & 0 deletions daiquiri/query/assets/js/query/api/QueryApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ class QueryApi extends BaseApi {
return this.get('/query/api/functions/user/')
}

static updateJob(id, values) {
return this.put(`/query/api/jobs/${id}/`, values)
}

static abortJob(id) {
return this.put(`/query/api/jobs/${id}/abort/`)
}

static archiveJob(id) {
return this.delete(`/query/api/jobs/${id}/`)
}

}

export default QueryApi
64 changes: 60 additions & 4 deletions daiquiri/query/assets/js/query/components/Job.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,74 @@
import React from 'react'
import PropTypes from 'prop-types'
import { isNil } from 'lodash'
import classNames from 'classnames'

import { useJobQuery } from '../hooks/query'

const Job = ({ jobId }) => {
import { useLsState } from '../../../../../core/assets/js/hooks/ls'

import JobOverview from './JobOverview'
import JobResults from './JobResults'
import JobPlot from './JobPlot'
import JobDownload from './JobDownload'

const Job = ({ jobId, loadJob, loadForm }) => {
const { data: job } = useJobQuery(jobId)

const [activeTab, setActiveTab] = useLsState('daiquiri.query.job.activeTab', 'overview')

const handleClick = (event, tab) => {
event.preventDefault()
setActiveTab(tab)
}

return isNil(job) ? (
<span>{gettext('Loading ...')}</span>
) : (
<div className="job">
<h2>{job.table_name}</h2>
<pre>{job.id}</pre>
<pre>{job.query}</pre>
<h2 className="mb-4">
{interpolate(gettext('Query job `%s`'), [job.table_name])}
</h2>
<ul className="job-tabs nav nav-tabs">
<li className="nav-item">
<a className={classNames('nav-link', {'active': activeTab === 'overview'})} href="#"
onClick={(event) => handleClick(event, 'overview')}>
{gettext('Job overview')}
</a>
</li>
<li className="nav-item">
<a className={classNames('nav-link', {'active': activeTab === 'results'})} href="#"
onClick={(event) => handleClick(event, 'results')}>
{gettext('Results table')}
</a>
</li>
<li className="nav-item">
<a className={classNames('nav-link', {'active': activeTab === 'plot'})} href="#"
onClick={(event) => handleClick(event, 'plot')}>
{gettext('Plot')}
</a>
</li>
<li className="nav-item">
<a className={classNames('nav-link', {'active': activeTab === 'download'})} href="#"
onClick={(event) => handleClick(event, 'download')}>
{gettext('Download')}
</a>
</li>
</ul>
<div className="job-tab-content mt-4">
{
activeTab === 'overview' && <JobOverview job={job} />
}
{
activeTab === 'results' && <JobResults job={job} />
}
{
activeTab === 'plot' && <JobPlot job={job} />
}
{
activeTab === 'download' && <JobDownload job={job} />
}
</div>
</div>
)
}
Expand Down
64 changes: 64 additions & 0 deletions daiquiri/query/assets/js/query/components/JobAbortModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'

import { useModal } from '../../../../../core/assets/js/hooks/modal'

import { useAbortJobMutation } from '../hooks/query'

const JobAbortModal = ({ job, show, toggle }) => {
const [ref, showModal, hideModal] = useModal()

useEffect(() => {
if (show) {
showModal()
}
}, [show])

const mutation = useAbortJobMutation()

const handleSubmit = () => {
mutation.mutate({job, onSuccess: handleClose})
}

const handleClose = () => {
hideModal()
toggle()
}

return (
<div ref={ref} className="modal" tabIndex="-1">
<div className="modal-dialog modal-lg">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">{gettext('Archive job')}</h5>
<button type="button" className="btn-close" onClick={handleClose}></button>
</div>
<div className="modal-body">
<p dangerouslySetInnerHTML={{
__html: interpolate(gettext('You are about to abort the job <code>%s</code>.'), [job.id])
}} />
<p className="text-danger">
{gettext('This action cannot be undone!')}
</p>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-sm btn-secondary" onClick={handleClose}>
{gettext('Close')}
</button>
<button type="button" className="btn btn-sm btn-danger" onClick={handleSubmit}>
{gettext('Abort')}
</button>
</div>
</div>
</div>
</div>
)
}

JobAbortModal.propTypes = {
job: PropTypes.object.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired
}

export default JobAbortModal
64 changes: 64 additions & 0 deletions daiquiri/query/assets/js/query/components/JobArchiveModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useEffect } from 'react'
import PropTypes from 'prop-types'

import { useModal } from '../../../../../core/assets/js/hooks/modal'

import { useArchiveJobMutation } from '../hooks/query'

const JobArchiveModal = ({ job, show, toggle }) => {
const [ref, showModal, hideModal] = useModal()

useEffect(() => {
if (show) {
showModal()
}
}, [show])

const mutation = useArchiveJobMutation()

const handleSubmit = () => {
mutation.mutate({job, onSuccess: handleClose})
}

const handleClose = () => {
hideModal()
toggle()
}

return (
<div ref={ref} className="modal" tabIndex="-1">
<div className="modal-dialog modal-lg">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">{gettext('Archive job')}</h5>
<button type="button" className="btn-close" onClick={handleClose}></button>
</div>
<div className="modal-body">
<p dangerouslySetInnerHTML={{
__html: interpolate(gettext('You are about to archive the job <code>%s</code>.'), [job.id])
}} />
<p className="text-danger">
{gettext('This action cannot be undone!')}
</p>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-sm btn-secondary" onClick={handleClose}>
{gettext('Close')}
</button>
<button type="button" className="btn btn-sm btn-danger" onClick={handleSubmit}>
{gettext('Archive')}
</button>
</div>
</div>
</div>
</div>
)
}

JobArchiveModal.propTypes = {
job: PropTypes.object.isRequired,
show: PropTypes.bool.isRequired,
toggle: PropTypes.func.isRequired
}

export default JobArchiveModal
12 changes: 12 additions & 0 deletions daiquiri/query/assets/js/query/components/JobDownload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'

const JobDownload = ({ job }) => {
return <pre>Download {job.id}</pre>
}

JobDownload.propTypes = {
job: PropTypes.object.isRequired
}

export default JobDownload
Loading

0 comments on commit 031fe8c

Please sign in to comment.