This is an automated email from the ASF dual-hosted git repository.
cwylie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git
The following commit(s) were added to refs/heads/master by this push:
new 18e5167 Add history dialog in snitch dialog to allow viewing the
editing history (#7321)
18e5167 is described below
commit 18e51672452b5229e17a10e9c83d896bfd86e0f3
Author: Qi Shu <[email protected]>
AuthorDate: Wed Mar 27 17:52:44 2019 -0700
Add history dialog in snitch dialog to allow viewing the editing history
(#7321)
* Add history dialog in snitch dialog to allow viewing the editing history
* Improved CSS; better animation
* Use position: absolute instead of float: right to position element
* Removed author for history changes
---
web-console/src/components/filler.tsx | 54 +++++++++++--
.../src/components/table-column-selection.scss | 6 +-
.../src/dialogs/coordinator-dynamic-config.scss | 5 +-
.../src/dialogs/coordinator-dynamic-config.tsx | 31 ++++++--
web-console/src/dialogs/history-dialog.scss | 88 +++++++++++++++++++++
web-console/src/dialogs/history-dialog.tsx | 91 ++++++++++++++++++++++
.../src/dialogs/overlord-dynamic-config.tsx | 31 ++++++--
web-console/src/dialogs/retention-dialog.tsx | 32 ++++++--
web-console/src/dialogs/snitch-dialog.tsx | 78 +++++++++----------
web-console/src/views/datasource-view.tsx | 4 +-
10 files changed, 353 insertions(+), 67 deletions(-)
diff --git a/web-console/src/components/filler.tsx
b/web-console/src/components/filler.tsx
index decea1f..422ae00 100644
--- a/web-console/src/components/filler.tsx
+++ b/web-console/src/components/filler.tsx
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { Button } from '@blueprintjs/core';
+import { Button, Collapse } from '@blueprintjs/core';
import classNames from 'classnames';
import * as React from 'react';
import AceEditor from "react-ace";
@@ -47,7 +47,7 @@ export const IconNames = {
ARROW_LEFT: "arrow-left" as "arrow-left",
CARET_RIGHT: "caret-right" as "caret-right",
TICK: "tick" as "tick",
- ARROW_RIGHT: "right-arrow" as "right-arrow",
+ ARROW_RIGHT: "arrow-right" as "arrow-right",
TRASH: "trash" as "trash",
CARET_DOWN: "caret-down" as "caret-down",
ARROW_UP: "arrow-up" as "arrow-up",
@@ -152,13 +152,15 @@ export class HTMLSelect extends React.Component<{ key?:
string; style?: any; onC
}
}
-export class TextArea extends React.Component<{ className?: string; onChange?:
any; value?: string }, {}> {
+export class TextArea extends React.Component<{ className?: string; onChange?:
any; value?: string, readOnly?: boolean, style?: any}, {}> {
render() {
- const { className, value, onChange } = this.props;
+ const { className, value, onChange, readOnly, style } = this.props;
return <textarea
+ readOnly={readOnly}
className={classNames("pt-input", className)}
value={value}
onChange={onChange}
+ style={style}
/>;
}
}
@@ -267,6 +269,7 @@ interface JSONInputProps extends React.Props<any> {
onChange: (newJSONValue: any) => void;
value: any;
updateInputValidity: (valueValid: boolean) => void;
+ height?: string;
}
interface JSONInputState {
@@ -298,7 +301,7 @@ export class JSONInput extends
React.Component<JSONInputProps, JSONInputState> {
}
render() {
- const { onChange, updateInputValidity } = this.props;
+ const { onChange, updateInputValidity, height } = this.props;
const { stringValue } = this.state;
return <AceEditor
className={"bp3-fill"}
@@ -314,7 +317,7 @@ export class JSONInput extends
React.Component<JSONInputProps, JSONInputState> {
focus
fontSize={12}
width={'100%'}
- height={"8vh"}
+ height={height ? height : "8vh"}
showPrintMargin={false}
showGutter={false}
value={stringValue}
@@ -330,3 +333,42 @@ export class JSONInput extends
React.Component<JSONInputProps, JSONInputState> {
/>;
}
}
+
+interface JSONCollapseProps extends React.Props<any> {
+ stringValue: string;
+ buttonText: string;
+}
+
+interface JSONCollapseState {
+ isOpen: boolean;
+}
+
+export class JSONCollapse extends React.Component<JSONCollapseProps,
JSONCollapseState> {
+ constructor(props: any) {
+ super(props);
+ this.state = {
+ isOpen: false
+ };
+ }
+
+ render() {
+ const { stringValue, buttonText} = this.props;
+ const { isOpen } = this.state;
+ const prettyValue = JSON.stringify(JSON.parse(stringValue), undefined, 2);
+ return <div className={"json-collapse"}>
+ <Button
+ className={`pt-minimal ${isOpen ? " pt-active" : ""}`}
+ onClick={() => this.setState({isOpen: !isOpen})}
+ text={buttonText}
+ />
+ <div>
+ <Collapse isOpen={isOpen}>
+ <TextArea
+ readOnly
+ value={prettyValue}
+ />
+ </Collapse>
+ </div>
+ </div>;
+ }
+}
diff --git a/web-console/src/components/table-column-selection.scss
b/web-console/src/components/table-column-selection.scss
index ddce873..c651a4d 100644
--- a/web-console/src/components/table-column-selection.scss
+++ b/web-console/src/components/table-column-selection.scss
@@ -17,8 +17,10 @@
*/
.table-column-selection {
-
- float: right;
+ &.pt-popover-target{
+ position: absolute;
+ right: 0;
+ }
.pt-popover-content {
padding: 10px 10px 1px 10px;
diff --git a/web-console/src/dialogs/coordinator-dynamic-config.scss
b/web-console/src/dialogs/coordinator-dynamic-config.scss
index 51eaa58..d4bb6d9 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config.scss
+++ b/web-console/src/dialogs/coordinator-dynamic-config.scss
@@ -17,7 +17,10 @@
*/
.coordinator-dynamic-config {
- margin-top: 5vh;
+ &.pt-dialog {
+ margin-top: 5vh;
+ top: 5%;
+ }
.pt-dialog-body {
max-height: 70vh;
diff --git a/web-console/src/dialogs/coordinator-dynamic-config.tsx
b/web-console/src/dialogs/coordinator-dynamic-config.tsx
index 59ee165..b7b2b61 100644
--- a/web-console/src/dialogs/coordinator-dynamic-config.tsx
+++ b/web-console/src/dialogs/coordinator-dynamic-config.tsx
@@ -23,7 +23,7 @@ import * as React from 'react';
import { AutoForm } from '../components/auto-form';
import { IconNames } from '../components/filler';
import { AppToaster } from '../singletons/toaster';
-import { getDruidErrorMessage } from '../utils';
+import { getDruidErrorMessage, QueryManager } from '../utils';
import { SnitchDialog } from './snitch-dialog';
@@ -35,18 +35,36 @@ export interface CoordinatorDynamicConfigDialogProps
extends React.Props<any> {
export interface CoordinatorDynamicConfigDialogState {
dynamicConfig: Record<string, any> | null;
+ historyRecords: any[];
}
export class CoordinatorDynamicConfigDialog extends
React.Component<CoordinatorDynamicConfigDialogProps,
CoordinatorDynamicConfigDialogState> {
+ private historyQueryManager: QueryManager<string, any>;
+
constructor(props: CoordinatorDynamicConfigDialogProps) {
super(props);
this.state = {
- dynamicConfig: null
+ dynamicConfig: null,
+ historyRecords: []
};
}
- componentDidMount(): void {
+ componentDidMount() {
this.getClusterConfig();
+
+ this.historyQueryManager = new QueryManager({
+ processQuery: async (query) => {
+ const historyResp = await
axios(`/druid/coordinator/v1/config/history?count=100`);
+ return historyResp.data;
+ },
+ onStateChange: ({ result, loading, error }) => {
+ this.setState({
+ historyRecords: result
+ });
+ }
+ });
+
+ this.historyQueryManager.runQuery(`dummy`);
}
async getClusterConfig() {
@@ -67,13 +85,13 @@ export class CoordinatorDynamicConfigDialog extends
React.Component<CoordinatorD
});
}
- private saveClusterConfig = async (author: string, comment: string) => {
+ private saveClusterConfig = async (comment: string) => {
const { onClose } = this.props;
const newState: any = this.state.dynamicConfig;
try {
await axios.post("/druid/coordinator/v1/config", newState, {
headers: {
- "X-Druid-Author": author,
+ "X-Druid-Author": "console",
"X-Druid-Comment": comment
}
});
@@ -94,7 +112,7 @@ export class CoordinatorDynamicConfigDialog extends
React.Component<CoordinatorD
render() {
const { onClose } = this.props;
- const { dynamicConfig } = this.state;
+ const { dynamicConfig, historyRecords } = this.state;
return <SnitchDialog
className="coordinator-dynamic-config"
@@ -102,6 +120,7 @@ export class CoordinatorDynamicConfigDialog extends
React.Component<CoordinatorD
onSave={this.saveClusterConfig}
onClose={onClose}
title="Coordinator dynamic config"
+ historyRecords={historyRecords}
>
<p>
Edit the coordinator dynamic configuration on the fly.
diff --git a/web-console/src/dialogs/history-dialog.scss
b/web-console/src/dialogs/history-dialog.scss
new file mode 100644
index 0000000..b16ebd0
--- /dev/null
+++ b/web-console/src/dialogs/history-dialog.scss
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.history-dialog {
+ &.pt-dialog {
+ width: 600px;
+ top: 5%;
+ }
+
+ .history-record-container {
+
+ padding: 15px 15px 0 15px;
+
+ h3 {
+ padding-left: 10px;
+ }
+
+ .no-record {
+ height: 25vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .history-record-entries {
+
+ margin-bottom: 10px;
+ max-height: 60vh;
+ overflow: scroll;
+
+ .history-record-entry {
+
+ padding: 5px;
+ border-style: dot-dash;
+ word-wrap: break-word;
+
+ hr {
+ margin: 5px 0 5px 0;
+ }
+
+ .pt-card {
+ padding-bottom: 10px;
+ }
+
+ .history-record-title {
+ justify-content: space-between;
+ display: flex;
+
+
+ }
+
+ .history-record-comment-title {
+ margin-bottom: 5px;
+ }
+
+ .json-collapse {
+ button {
+ position: relative;
+ left: 86%;
+ margin-bottom: 5px;
+ }
+
+ textarea {
+ width: 100%;
+ height: 30vh;
+ margin-bottom: 5px;
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/web-console/src/dialogs/history-dialog.tsx
b/web-console/src/dialogs/history-dialog.tsx
new file mode 100644
index 0000000..73ff6fe
--- /dev/null
+++ b/web-console/src/dialogs/history-dialog.tsx
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Dialog } from "@blueprintjs/core";
+import * as React from "react";
+
+import { Card, JSONCollapse } from "../components/filler";
+
+import "./history-dialog.scss";
+
+interface HistoryDialogProps extends React.Props<any> {
+ historyRecords: any;
+}
+
+interface HistoryDialogState {
+
+}
+
+export class HistoryDialog extends React.Component<HistoryDialogProps,
HistoryDialogState> {
+ constructor(props: HistoryDialogProps) {
+ super(props);
+ this.state = {
+
+ };
+ }
+
+ renderRecords() {
+ const {children, historyRecords} = this.props;
+ let content;
+ if (historyRecords.length === 0) {
+ content = <div className={"no-record"}>No history records
available</div>;
+ } else {
+ content = <>
+ <h3>History</h3>
+ <div className={"history-record-entries"}>
+ {
+ historyRecords.map((record: any) => {
+ const auditInfo = record.auditInfo;
+ const auditTime = record.auditTime;
+ const formattedTime = auditTime.replace("T", " ").substring(0,
auditTime.length - 5);
+
+ return <div key={record.auditTime}
className={"history-record-entry"}>
+ <Card>
+ <div className={"history-record-title"}>
+ <h5>Change</h5>
+ <p>{formattedTime}</p>
+ </div>
+ <hr/>
+ <p>{auditInfo.comment === "" ? "(No comment)" :
auditInfo.comment}</p>
+ <JSONCollapse
+ stringValue={record.payload}
+ buttonText={"Payload"}
+ />
+ </Card>
+ </div>;
+ })
+ }
+ </div>
+ </>;
+ }
+ return <div className={"history-record-container"}>
+ {content}
+ {children}
+ </div>;
+ }
+
+ render(): React.ReactNode {
+ return <Dialog
+ isOpen
+ {...this.props}
+ className={"history-dialog"}
+ >
+ {this.renderRecords()}
+ </Dialog>;
+ }
+}
diff --git a/web-console/src/dialogs/overlord-dynamic-config.tsx
b/web-console/src/dialogs/overlord-dynamic-config.tsx
index f3be789..f4be905 100644
--- a/web-console/src/dialogs/overlord-dynamic-config.tsx
+++ b/web-console/src/dialogs/overlord-dynamic-config.tsx
@@ -23,7 +23,7 @@ import * as React from "react";
import { AutoForm } from "../components/auto-form";
import { IconNames } from "../components/filler";
import { AppToaster } from "../singletons/toaster";
-import { getDruidErrorMessage } from "../utils";
+import { getDruidErrorMessage, QueryManager } from "../utils";
import { SnitchDialog } from "./snitch-dialog";
@@ -36,19 +36,37 @@ export interface OverlordDynamicConfigDialogProps extends
React.Props<any> {
export interface OverlordDynamicConfigDialogState {
dynamicConfig: Record<string, any> | null;
allJSONValid: boolean;
+ historyRecords: any[];
}
export class OverlordDynamicConfigDialog extends
React.Component<OverlordDynamicConfigDialogProps,
OverlordDynamicConfigDialogState> {
+ private historyQueryManager: QueryManager<string, any>;
+
constructor(props: OverlordDynamicConfigDialogProps) {
super(props);
this.state = {
dynamicConfig: null,
- allJSONValid: true
+ allJSONValid: true,
+ historyRecords: []
};
}
- componentDidMount(): void {
+ componentDidMount() {
this.getConfig();
+
+ this.historyQueryManager = new QueryManager({
+ processQuery: async (query) => {
+ const historyResp = await
axios(`/druid/indexer/v1/worker/history?count=100`);
+ return historyResp.data;
+ },
+ onStateChange: ({ result, loading, error }) => {
+ this.setState({
+ historyRecords: result
+ });
+ }
+ });
+
+ this.historyQueryManager.runQuery(`dummy`);
}
async getConfig() {
@@ -69,13 +87,13 @@ export class OverlordDynamicConfigDialog extends
React.Component<OverlordDynamic
});
}
- private saveConfig = async (author: string, comment: string) => {
+ private saveConfig = async (comment: string) => {
const { onClose } = this.props;
const newState: any = this.state.dynamicConfig;
try {
await axios.post("/druid/indexer/v1/worker", newState, {
headers: {
- "X-Druid-Author": author,
+ "X-Druid-Author": "console",
"X-Druid-Comment": comment
}
});
@@ -96,7 +114,7 @@ export class OverlordDynamicConfigDialog extends
React.Component<OverlordDynamic
render() {
const { onClose } = this.props;
- const { dynamicConfig, allJSONValid } = this.state;
+ const { dynamicConfig, allJSONValid, historyRecords } = this.state;
return <SnitchDialog
className="overlord-dynamic-config"
@@ -105,6 +123,7 @@ export class OverlordDynamicConfigDialog extends
React.Component<OverlordDynamic
onClose={onClose}
title="Overlord dynamic config"
saveDisabled={!allJSONValid}
+ historyRecords={historyRecords}
>
<p>
Edit the overlord dynamic configuration on the fly.
diff --git a/web-console/src/dialogs/retention-dialog.tsx
b/web-console/src/dialogs/retention-dialog.tsx
index 10d85f2..e427ce5 100644
--- a/web-console/src/dialogs/retention-dialog.tsx
+++ b/web-console/src/dialogs/retention-dialog.tsx
@@ -22,6 +22,7 @@ import * as React from 'react';
import { FormGroup, IconNames } from '../components/filler';
import { Rule, RuleEditor } from '../components/rule-editor';
+import { QueryManager } from "../utils";
import { SnitchDialog } from './snitch-dialog';
@@ -43,28 +44,48 @@ export interface RetentionDialogProps extends
React.Props<any> {
tiers: string[];
onEditDefaults: () => void;
onCancel: () => void;
- onSave: (datasource: string, newRules: any[], author: string, comment:
string) => void;
+ onSave: (datasource: string, newRules: any[], comment: string) => void;
}
export interface RetentionDialogState {
currentRules: any[];
+ historyRecords: any[];
}
export class RetentionDialog extends React.Component<RetentionDialogProps,
RetentionDialogState> {
+ private historyQueryManager: QueryManager<string, any>;
constructor(props: RetentionDialogProps) {
super(props);
this.state = {
- currentRules: props.rules
+ currentRules: props.rules,
+ historyRecords: []
};
}
- private save = (author: string, comment: string) => {
+ componentDidMount() {
+ const { datasource } = this.props;
+ this.historyQueryManager = new QueryManager({
+ processQuery: async (query) => {
+ const historyResp = await
axios(`/druid/coordinator/v1/rules/${datasource}/history`);
+ return historyResp.data;
+ },
+ onStateChange: ({ result, loading, error }) => {
+ this.setState({
+ historyRecords: result
+ });
+ }
+ });
+
+ this.historyQueryManager.runQuery(`dummy`);
+ }
+
+ private save = (comment: string) => {
const { datasource, onSave } = this.props;
const { currentRules } = this.state;
- onSave(datasource, currentRules, author, comment);
+ onSave(datasource, currentRules, comment);
}
private changeRule = (newRule: any, index: number) => {
@@ -140,7 +161,7 @@ export class RetentionDialog extends
React.Component<RetentionDialogProps, Reten
render() {
const { datasource, onCancel, onEditDefaults } = this.props;
- const { currentRules } = this.state;
+ const { currentRules, historyRecords } = this.state;
return <SnitchDialog
className="retention-dialog"
@@ -152,6 +173,7 @@ export class RetentionDialog extends
React.Component<RetentionDialogProps, Reten
title={`Edit retention rules: ${datasource}${datasource === '_default' ?
' (cluster defaults)' : ''}`}
onReset={this.reset}
onSave={this.save}
+ historyRecords={historyRecords}
>
<p>
Druid uses rules to determine what data should be retained in the
cluster.
diff --git a/web-console/src/dialogs/snitch-dialog.tsx
b/web-console/src/dialogs/snitch-dialog.tsx
index 46cb1f0..1d4cdb0 100644
--- a/web-console/src/dialogs/snitch-dialog.tsx
+++ b/web-console/src/dialogs/snitch-dialog.tsx
@@ -27,22 +27,23 @@ import {
import * as React from 'react';
import { FormGroup, IconNames } from '../components/filler';
-import { localStorageGet, localStorageSet } from "../utils";
-const druidEditingAuthor = "DRUID_EDITING_AUTHOR";
+import { HistoryDialog } from "./history-dialog";
export interface SnitchDialogProps extends IDialogProps {
- onSave: (author: string, comment: string) => void;
+ onSave: (comment: string) => void;
saveDisabled?: boolean;
onReset?: () => void;
+ historyRecords?: any[];
}
export interface SnitchDialogState {
- author: string;
comment: string;
showFinalStep?: boolean;
saveDisabled?: boolean;
+
+ showHistory?: boolean;
}
export class SnitchDialog extends React.Component<SnitchDialogProps,
SnitchDialogState> {
@@ -51,48 +52,24 @@ export class SnitchDialog extends
React.Component<SnitchDialogProps, SnitchDialo
this.state = {
comment: "",
- author: "",
saveDisabled: true
};
}
- componentDidMount(): void {
- this.getDefaultAuthor();
- }
-
save = () => {
const { onSave, onClose } = this.props;
- const { author, comment } = this.state;
+ const { comment } = this.state;
- onSave(author, comment);
+ onSave(comment);
if (onClose) onClose();
}
- getDefaultAuthor() {
- const author: string | null = localStorageGet(druidEditingAuthor);
- if (author) {
- this.setState({
- author
- });
- }
- }
-
- changeAuthor(newAuthor: string) {
- const { author, comment } = this.state;
-
- this.setState({
- author: newAuthor,
- saveDisabled: !newAuthor || !comment
- });
- localStorageSet(druidEditingAuthor, newAuthor);
- }
-
changeComment(newComment: string) {
- const { author, comment } = this.state;
+ const { comment } = this.state;
this.setState({
comment: newComment,
- saveDisabled: !author || !newComment
+ saveDisabled: !newComment
});
}
@@ -104,7 +81,8 @@ export class SnitchDialog extends
React.Component<SnitchDialogProps, SnitchDialo
back = () => {
this.setState({
- showFinalStep: false
+ showFinalStep: false,
+ showHistory: false
});
}
@@ -114,15 +92,18 @@ export class SnitchDialog extends
React.Component<SnitchDialogProps, SnitchDialo
});
}
+ goToHistory = () => {
+ this.setState({
+ showHistory: true
+ });
+ }
+
renderFinalStep() {
const { onClose, children } = this.props;
- const { saveDisabled, author, comment } = this.state;
+ const { saveDisabled, comment } = this.state;
return <Dialog {...this.props}>
<div className={`dialog-body ${Classes.DIALOG_BODY}`}>
- <FormGroup label={"Who is making this change?"}>
- <InputGroup value={author} onChange={(e: any) =>
this.changeAuthor(e.target.value)}/>
- </FormGroup>
<FormGroup label={"Why are you making this change?"}
className={"comment"}>
<InputGroup
className="pt-large"
@@ -139,11 +120,28 @@ export class SnitchDialog extends
React.Component<SnitchDialogProps, SnitchDialo
</Dialog>;
}
+ renderHistoryDialog() {
+ const { historyRecords } = this.props;
+ return <HistoryDialog
+ {...this.props}
+ historyRecords={historyRecords}
+ >
+ <div className={Classes.DIALOG_FOOTER_ACTIONS}>
+ <Button onClick={this.back}
iconName={IconNames.ARROW_LEFT}>Back</Button>
+ </div>
+ </HistoryDialog>;
+ }
+
renderActions(saveDisabled?: boolean) {
- const { onReset } = this.props;
+ const { onReset, historyRecords } = this.props;
const { showFinalStep } = this.state;
return <div className={Classes.DIALOG_FOOTER_ACTIONS}>
+ {showFinalStep || historyRecords === undefined
+ ? null
+ : <Button style={{position: "absolute", left: "5px"}}
className={"pt-minimal"} text="History" onClick={this.goToHistory}/>
+ }
+
{ showFinalStep
? <Button onClick={this.back}
iconName={IconNames.ARROW_LEFT}>Back</Button>
: onReset ? <Button onClick={this.reset} intent={"none" as
any}>Reset</Button> : null
@@ -158,14 +156,16 @@ export class SnitchDialog extends
React.Component<SnitchDialogProps, SnitchDialo
render() {
const { onClose, className, children, saveDisabled } = this.props;
- const { showFinalStep } = this.state;
+ const { showFinalStep, showHistory } = this.state;
if (showFinalStep) return this.renderFinalStep();
+ if (showHistory) return this.renderHistoryDialog();
return <Dialog isOpen inline {...this.props}>
<div className={Classes.DIALOG_BODY}>
{children}
</div>
+
<div className={Classes.DIALOG_FOOTER}>
{this.renderActions(saveDisabled)}
</div>
diff --git a/web-console/src/views/datasource-view.tsx
b/web-console/src/views/datasource-view.tsx
index 696a8f5..f8d38c4 100644
--- a/web-console/src/views/datasource-view.tsx
+++ b/web-console/src/views/datasource-view.tsx
@@ -249,11 +249,11 @@ GROUP BY 1`);
</AsyncActionDialog>;
}
- private saveRules = async (datasource: string, rules: any[], author: string,
comment: string) => {
+ private saveRules = async (datasource: string, rules: any[], comment:
string) => {
try {
await axios.post(`/druid/coordinator/v1/rules/${datasource}`, rules, {
headers: {
- "X-Druid-Author": author,
+ "X-Druid-Author": "console",
"X-Druid-Comment": comment
}
});
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]