Skip to content

Commit

Permalink
Add QueryView actions and separate query properties
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrieldutra committed Dec 20, 2019
1 parent c45095b commit 40310f1
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 74 deletions.
170 changes: 97 additions & 73 deletions client/app/pages/queries/QueryView.jsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,90 @@
import React, { useMemo, useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
import { find } from "lodash";
import { find, isArray, intersection } from "lodash";
import { react2angular } from "react2angular";
import Divider from "antd/lib/divider";
import Button from "antd/lib/button";

import { EditInPlace } from "@/components/EditInPlace";
import { Parameters } from "@/components/Parameters";
import { TimeAgo } from "@/components/TimeAgo";
import { SchedulePhrase } from "@/components/queries/SchedulePhrase";
import { QueryControlDropdown } from "@/components/EditVisualizationButton/QueryControlDropdown";
import EmbedQueryDialog from "@/components/queries/EmbedQueryDialog";
import AddToDashboardDialog from "@/components/queries/AddToDashboardDialog";
import EditVisualizationDialog from "@/visualizations/EditVisualizationDialog";
import { EditVisualizationButton } from "@/components/EditVisualizationButton";
import ScheduleDialog from "@/components/queries/ScheduleDialog";
import QueryPageHeader from "./components/QueryPageHeader";

import { clientConfig } from "@/services/auth";
import { policy } from "@/services/policy";
import { IMG_ROOT, DataSource } from "@/services/data-source";
import recordEvent from "@/services/recordEvent";
import QueryVisualizationTabs from "./components/QueryVisualizationTabs";
import { EditVisualizationButton } from "@/components/EditVisualizationButton";
import useQueryResult from "@/lib/hooks/useQueryResult";
import { pluralize, durationHumanize } from "@/filters";
import { updateQuery } from "./utils";
import { updateQuery, deleteQueryVisualization, addQueryVisualization, editQueryVisualization, changeQueryDescription } from "./utils";
import useVisualizationTabHandler from "./utils/useVisualizationTabHandler";
import useQueryExecute from "./utils/useQueryExecute";

import "./query-view.less";
import useVisualizationTabHandler from "./utils/useVisualizationTabHandler";

function QueryPropertyList({ query, dataSource, onClickSchedulePhrase }) {
return (
<div className="query-property-list">
<div className="query-property">
<img src={query.user.profile_image_url} className="profile__image_thumb" alt={query.user.name} />
<strong>{query.user.name}</strong>
{" created "}
<TimeAgo date={query.created_at} />
</div>
<div className="query-property">
<img
src={query.last_modified_by.profile_image_url}
className="profile__image_thumb"
alt={query.last_modified_by.name}
/>
<strong>{query.last_modified_by.name}</strong>
{" updated "}
<TimeAgo date={query.updated_at} />
</div>
{dataSource && (
<div className="query-property">
<img src={`${IMG_ROOT}/${dataSource.type}.png`} width="20" alt={dataSource.type} />
{dataSource.name}
</div>
)}
<span className="flex-fill" />
<div className="query-property">
<i className="zmdi zmdi-refresh m-r-5" />
Refresh Schedule
<a className="clickable m-l-5" onClick={onClickSchedulePhrase}>
<SchedulePhrase schedule={query.schedule} isNew={false} />
</a>
</div>
</div>
);
}

function QueryView(props) {
const [query, setQuery] = useState(props.query);
const [selectedTab, setSelectedTab] = useVisualizationTabHandler(query.visualizations);
const currentVisualization = useMemo(() => find(query.visualizations, { id: selectedTab }), [query.visualizations, selectedTab])
const currentVisualization = useMemo(() => find(query.visualizations, { id: selectedTab }), [
query.visualizations,
selectedTab,
]);
const parameters = useMemo(() => query.getParametersDefs(), [query]);
const [dirtyParameters, setDirtyParameters] = useState(query.$parameters.hasPendingValues());
const [dataSource, setDataSource] = useState();
const queryResult = useMemo(() => query.getQueryResult(), [query]);
const queryResultData = useQueryResult(queryResult);

const saveDescription = useCallback(
(description) => {
recordEvent("edit_description", "query", query.id);
updateQuery(query, { description }).then(setQuery);
},
[query],
);
const { queryResult, queryResultData, isQueryExecuting, executeQuery } = useQueryExecute(query);

const openVisualizationEditor = useCallback(
(visId) => {
EditVisualizationDialog.showModal({
query,
visualization: find(query.visualizations, { id: visId }),
queryResult,
}).result.then(visualization => {
setSelectedTab(visualization.id);
// TODO: Properly update state
});
},
[query, queryResult, setSelectedTab],
);
const openScheduleDialog = useCallback(() => {
const intervals = clientConfig.queryRefreshIntervals;
const allowedIntervals = policy.getQueryRefreshIntervals();
const refreshOptions = isArray(allowedIntervals) ? intersection(intervals, allowedIntervals) : intervals;
ScheduleDialog.showModal({
schedule: query.schedule,
refreshOptions,
}).result.then(schedule => updateQuery(query, { schedule }).then(setQuery));
}, [query]);

useEffect(() => {
document.title = query.name;
Expand All @@ -72,66 +102,61 @@ function QueryView(props) {
className="w-100"
value={query.description}
isEditable={query.can_edit}
onDone={saveDescription}
onDone={description => changeQueryDescription(query, description).then(setQuery)}
editor="textarea"
placeholder="Add description"
ignoreBlanks={false}
/>
<Divider />
<div className="query-property-list">
<div className="query-property">
<img src={query.user.profile_image_url} className="profile__image_thumb" alt={query.user.name} />
<strong>{query.user.name}</strong>
{" created "}
<TimeAgo date={query.created_at} />
</div>
<div className="query-property">
<img
src={query.last_modified_by.profile_image_url}
className="profile__image_thumb"
alt={query.last_modified_by.name}
/>
<strong>{query.last_modified_by.name}</strong>
{" updated "}
<TimeAgo date={query.updated_at} />
</div>
{dataSource && (
<div className="query-property">
<img src={`${IMG_ROOT}/${dataSource.type}.png`} width="20" alt={dataSource.type} />
{dataSource.name}
</div>
)}
<span className="flex-fill" />
<div className="query-property">
<i className="zmdi zmdi-refresh m-r-5" />Refresh Schedule
<a className="clickable m-l-5">
<SchedulePhrase schedule={query.schedule} isNew={false} />
</a>
</div>
</div>
<QueryPropertyList query={query} dataSource={dataSource} onClickSchedulePhrase={openScheduleDialog} />
</div>
<div className="query-content tiled bg-white p-15 m-t-15">
{query.hasParameters() && <Parameters parameters={parameters} />}
{query.hasParameters() && (
<Parameters
parameters={parameters}
onValuesChange={() => { setDirtyParameters(false); executeQuery(); }}
onPendingValuesChange={() => setDirtyParameters(query.$parameters.hasPendingValues())}
/>
)}
<QueryVisualizationTabs
queryResult={queryResult}
visualizations={query.visualizations}
showNewVisualizationButton={query.can_edit}
canDeleteVisualizations={query.can_edit}
selectedTab={selectedTab}
onChangeTab={setSelectedTab}
onClickNewVisualization={openVisualizationEditor}
onClickNewVisualization={() =>
addQueryVisualization(query, queryResult).then(({ query, visualization }) => {
setQuery(query);
setSelectedTab(visualization.id);
})
}
onDeleteVisualization={visualization =>
deleteQueryVisualization(query, visualization).then(setQuery)
}
/>
<Divider />
<div className="d-flex align-items-center">
<EditVisualizationButton selectedTab={selectedTab} openVisualizationEditor={openVisualizationEditor} />
<EditVisualizationButton
selectedTab={selectedTab}
openVisualizationEditor={visId =>
editQueryVisualization(
query,
queryResult,
find(query.visualizations, { id: visId })
).then(({ query }) => setQuery(query))
}
/>
<QueryControlDropdown
query={query}
queryResult={queryResult}
queryExecuting={false} /* TODO: Replace with executing state */
queryExecuting={isQueryExecuting}
showEmbedDialog={() => EmbedQueryDialog.showEmbedDialog({ query, visualization: currentVisualization })}
openAddToDashboardForm={() => AddToDashboardDialog.showModal({
visualization: currentVisualization,
})}
openAddToDashboardForm={() =>
AddToDashboardDialog.showModal({
visualization: currentVisualization,
})
}
/>
{queryResultData.status === "done" && (
<>
Expand All @@ -147,11 +172,10 @@ function QueryView(props) {
<span className="flex-fill" />
{queryResultData.status === "done" && (
<span className="m-r-10 hidden-xs">
Updated{" "}
<TimeAgo date={queryResult.query_result.retrieved_at} />
Updated <TimeAgo date={queryResult.query_result.retrieved_at} />
</span>
)}
<Button type="primary">Execute</Button>
<Button type="primary" loading={isQueryExecuting} disabled={dirtyParameters} onClick={executeQuery}>Execute</Button>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion client/app/pages/queries/utils/editQueryVisualization.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clone, extend, filter, get } from "lodash";
import { clone, extend, filter } from "lodash";
import EditVisualizationDialog from "@/visualizations/EditVisualizationDialog";

export function editQueryVisualization(query, queryResult, visualization) {
Expand Down
6 changes: 6 additions & 0 deletions client/app/pages/queries/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ function renameQuery(query, name) {
return updateQuery(query, changes, options);
}

function changeQueryDescription(query, description) {
recordEvent("edit_description", "query", query.id);
return updateQuery(query, { description });
}

export {
updateQuery,
archiveQuery,
duplicateQuery,
publishQuery,
unpublishQuery,
renameQuery,
changeQueryDescription,
deleteQueryVisualization,
addQueryVisualization,
editQueryVisualization,
Expand Down

0 comments on commit 40310f1

Please sign in to comment.