mirror of https://github.com/apache/druid.git
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
This commit is contained in:
parent
eeb3dbe79d
commit
18e5167245
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
*/
|
||||
|
||||
.table-column-selection {
|
||||
|
||||
float: right;
|
||||
&.pt-popover-target{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.pt-popover-content {
|
||||
padding: 10px 10px 1px 10px;
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
*/
|
||||
|
||||
.coordinator-dynamic-config {
|
||||
margin-top: 5vh;
|
||||
&.pt-dialog {
|
||||
margin-top: 5vh;
|
||||
top: 5%;
|
||||
}
|
||||
|
||||
.pt-dialog-body {
|
||||
max-height: 70vh;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue