client/app/pages/queries/QueryView.jsx (212 lines of code) (raw):

import React, { useState, useEffect, useCallback } from "react"; import PropTypes from "prop-types"; import cx from "classnames"; import useMedia from "use-media"; import Button from "antd/lib/button"; import Icon from "antd/lib/icon"; import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession"; import EditInPlace from "@/components/EditInPlace"; import Parameters from "@/components/Parameters"; import DataSource from "@/services/data-source"; import { ExecutionStatus } from "@/services/query-result"; import routes from "@/services/routes"; import useQueryResultData from "@/lib/useQueryResultData"; import QueryPageHeader from "./components/QueryPageHeader"; import QueryVisualizationTabs from "./components/QueryVisualizationTabs"; import QueryExecutionStatus from "./components/QueryExecutionStatus"; import QueryMetadata from "./components/QueryMetadata"; import wrapQueryPage from "./components/wrapQueryPage"; import QueryViewButton from "./components/QueryViewButton"; import QueryExecutionMetadata from "./components/QueryExecutionMetadata"; import useVisualizationTabHandler from "./hooks/useVisualizationTabHandler"; import useQueryExecute from "./hooks/useQueryExecute"; import useUpdateQueryDescription from "./hooks/useUpdateQueryDescription"; import useQueryFlags from "./hooks/useQueryFlags"; import useQueryParameters from "./hooks/useQueryParameters"; import useEditScheduleDialog from "./hooks/useEditScheduleDialog"; import useEditVisualizationDialog from "./hooks/useEditVisualizationDialog"; import useDeleteVisualization from "./hooks/useDeleteVisualization"; import useFullscreenHandler from "../../lib/hooks/useFullscreenHandler"; import "./QueryView.less"; function QueryView(props) { const [query, setQuery] = useState(props.query); const [dataSource, setDataSource] = useState(); const queryFlags = useQueryFlags(query, dataSource); const [parameters, areParametersDirty, updateParametersDirtyFlag] = useQueryParameters(query); const [selectedVisualization, setSelectedVisualization] = useVisualizationTabHandler(query.visualizations); const isDesktop = useMedia({ minWidth: 768 }); const isFixedLayout = useMedia({ minHeight: 500 }) && isDesktop; const [fullscreen, toggleFullscreen] = useFullscreenHandler(isDesktop); const [addingDescription, setAddingDescription] = useState(false); const { queryResult, loadedInitialResults, isExecuting, executionStatus, executeQuery, error: executionError, cancelCallback: cancelExecution, isCancelling: isExecutionCancelling, updatedAt, } = useQueryExecute(query); const queryResultData = useQueryResultData(queryResult); const updateQueryDescription = useUpdateQueryDescription(query, setQuery); const editSchedule = useEditScheduleDialog(query, setQuery); const addVisualization = useEditVisualizationDialog(query, queryResult, (newQuery, visualization) => { setQuery(newQuery); setSelectedVisualization(visualization.id); }); const editVisualization = useEditVisualizationDialog(query, queryResult, newQuery => setQuery(newQuery)); const deleteVisualization = useDeleteVisualization(query, setQuery); const doExecuteQuery = useCallback( (skipParametersDirtyFlag = false) => { if (!queryFlags.canExecute || (!skipParametersDirtyFlag && (areParametersDirty || isExecuting))) { return; } executeQuery(); }, [areParametersDirty, executeQuery, isExecuting, queryFlags.canExecute] ); useEffect(() => { document.title = query.name; }, [query.name]); useEffect(() => { DataSource.get({ id: query.data_source_id }).then(setDataSource); }, [query.data_source_id]); return ( <div className={cx("query-page-wrapper", { "query-view-fullscreen": fullscreen, "query-fixed-layout": isFixedLayout, })}> <div className="container w-100"> <QueryPageHeader query={query} dataSource={dataSource} onChange={setQuery} selectedVisualization={selectedVisualization} headerExtra={ <QueryViewButton className="m-r-5" type="primary" shortcut="mod+enter, alt+enter, ctrl+enter" disabled={!queryFlags.canExecute || isExecuting || areParametersDirty} onClick={doExecuteQuery}> Refresh </QueryViewButton> } tagsExtra={ !query.description && queryFlags.canEdit && !addingDescription && !fullscreen && ( <a className="label label-tag hidden-xs" role="none" onClick={() => setAddingDescription(true)}> <i className="zmdi zmdi-plus m-r-5" /> Add description </a> ) } /> {(query.description || addingDescription) && ( <div className={cx("m-t-5", { hidden: fullscreen })}> <EditInPlace className="w-100" value={query.description} isEditable={queryFlags.canEdit} onDone={updateQueryDescription} onStopEditing={() => setAddingDescription(false)} placeholder="Add description" ignoreBlanks={false} editorProps={{ autosize: { minRows: 2, maxRows: 4 } }} defaultEditing={addingDescription} multiline /> </div> )} </div> <div className="query-view-content"> {query.hasParameters() && ( <div className={cx("bg-white tiled p-15 m-t-15 m-l-15 m-r-15", { hidden: fullscreen })}> <Parameters parameters={parameters} onValuesChange={() => { updateParametersDirtyFlag(false); doExecuteQuery(true); }} onPendingValuesChange={() => updateParametersDirtyFlag()} /> </div> )} <div className="query-results m-t-15"> {loadedInitialResults && ( <QueryVisualizationTabs queryResult={queryResult} visualizations={query.visualizations} showNewVisualizationButton={queryFlags.canEdit && queryResultData.status === ExecutionStatus.DONE} canDeleteVisualizations={queryFlags.canEdit} selectedTab={selectedVisualization} onChangeTab={setSelectedVisualization} onAddVisualization={addVisualization} onDeleteVisualization={deleteVisualization} refreshButton={ <Button type="primary" disabled={!queryFlags.canExecute || areParametersDirty} loading={isExecuting} onClick={doExecuteQuery}> {!isExecuting && <i className="zmdi zmdi-refresh m-r-5" aria-hidden="true" />} Refresh Now </Button> } /> )} <div className="query-results-footer"> {queryResult && !queryResult.getError() && ( <QueryExecutionMetadata query={query} queryResult={queryResult} selectedVisualization={selectedVisualization} isQueryExecuting={isExecuting} showEditVisualizationButton={queryFlags.canEdit} onEditVisualization={editVisualization} extraActions={ <QueryViewButton className="icon-button m-r-5 hidden-xs" title="Toggle Fullscreen" type="default" shortcut="alt+f" onClick={toggleFullscreen}> <Icon type={fullscreen ? "fullscreen-exit" : "fullscreen"} /> </QueryViewButton> } /> )} {(executionError || isExecuting) && ( <div className="query-execution-status"> <QueryExecutionStatus status={executionStatus} error={executionError} isCancelling={isExecutionCancelling} onCancel={cancelExecution} updatedAt={updatedAt} /> </div> )} </div> </div> <div className={cx("p-t-15 p-r-15 p-l-15", { hidden: fullscreen })}> <QueryMetadata layout="horizontal" query={query} dataSource={dataSource} onEditSchedule={editSchedule} /> </div> </div> </div> ); } QueryView.propTypes = { query: PropTypes.object.isRequired }; // eslint-disable-line react/forbid-prop-types const QueryViewPage = wrapQueryPage(QueryView); routes.register( "Queries.View", routeWithUserSession({ path: "/queries/:queryId", render: pageProps => <QueryViewPage {...pageProps} />, }) );