mirror of https://github.com/apache/druid.git
Web console: make supervisor reset really scary in the UI (#9253)
* make supervisor reset really scary * change warnings * add text
This commit is contained in:
parent
556a3861ed
commit
7e53f23f07
|
@ -0,0 +1,41 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`warning checklist matches snapshot 1`] = `
|
||||||
|
<div
|
||||||
|
class="warning-checklist"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="bp3-control bp3-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="bp3-control-indicator"
|
||||||
|
/>
|
||||||
|
Check A
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="bp3-control bp3-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="bp3-control-indicator"
|
||||||
|
/>
|
||||||
|
Check B
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="bp3-control bp3-checkbox"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="bp3-control-indicator"
|
||||||
|
/>
|
||||||
|
I am totes sure
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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 { render } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { WarningChecklist } from './warning-checklist';
|
||||||
|
|
||||||
|
describe('warning checklist', () => {
|
||||||
|
it('matches snapshot', () => {
|
||||||
|
const warningChecklist = (
|
||||||
|
<WarningChecklist checks={['Check A', 'Check B', 'I am totes sure']} onChange={() => {}} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const { container } = render(warningChecklist);
|
||||||
|
expect(container.firstChild).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* 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 { Checkbox } from '@blueprintjs/core';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
export interface WarningChecklistProps {
|
||||||
|
checks: string[];
|
||||||
|
onChange: (allChecked: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WarningChecklist = React.memo(function WarningChecklist(props: WarningChecklistProps) {
|
||||||
|
const { checks, onChange } = props;
|
||||||
|
const [checked, setChecked] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
function doCheck(check: string) {
|
||||||
|
const newChecked = Object.assign({}, checked);
|
||||||
|
newChecked[check] = !newChecked[check];
|
||||||
|
setChecked(newChecked);
|
||||||
|
|
||||||
|
onChange(checks.every(check => newChecked[check]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="warning-checklist">
|
||||||
|
{checks.map((check, i) => {
|
||||||
|
return <Checkbox key={i} label={check} onChange={() => doCheck(check)} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
|
@ -30,6 +30,7 @@ import { IconNames } from '@blueprintjs/icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { ReactNode, useState } from 'react';
|
import React, { ReactNode, useState } from 'react';
|
||||||
|
|
||||||
|
import { WarningChecklist } from '../../components/warning-checklist/warning-checklist';
|
||||||
import { AppToaster } from '../../singletons/toaster';
|
import { AppToaster } from '../../singletons/toaster';
|
||||||
|
|
||||||
import './async-action-dialog.scss';
|
import './async-action-dialog.scss';
|
||||||
|
@ -46,6 +47,7 @@ export interface AsyncActionDialogProps {
|
||||||
intent?: Intent;
|
intent?: Intent;
|
||||||
successText: string;
|
successText: string;
|
||||||
failText: string;
|
failText: string;
|
||||||
|
warningChecks?: string[];
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +66,16 @@ export const AsyncActionDialog = React.memo(function AsyncActionDialog(
|
||||||
confirmButtonText,
|
confirmButtonText,
|
||||||
confirmButtonDisabled,
|
confirmButtonDisabled,
|
||||||
cancelButtonText,
|
cancelButtonText,
|
||||||
|
warningChecks,
|
||||||
children,
|
children,
|
||||||
} = props;
|
} = props;
|
||||||
const [working, setWorking] = useState(false);
|
const [working, setWorking] = useState(false);
|
||||||
|
const [allWarningsChecked, setAllWarningsChecked] = useState(false);
|
||||||
|
const needsMoreChecks = Boolean(warningChecks && !allWarningsChecked);
|
||||||
|
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
|
if (needsMoreChecks) return;
|
||||||
|
|
||||||
setWorking(true);
|
setWorking(true);
|
||||||
try {
|
try {
|
||||||
await action();
|
await action();
|
||||||
|
@ -108,7 +115,12 @@ export const AsyncActionDialog = React.memo(function AsyncActionDialog(
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{icon && <Icon icon={icon} />}
|
{icon && <Icon icon={icon} />}
|
||||||
<div className={Classes.ALERT_CONTENTS}>{children}</div>
|
<div className={Classes.ALERT_CONTENTS}>
|
||||||
|
{children}
|
||||||
|
{warningChecks && (
|
||||||
|
<WarningChecklist checks={warningChecks} onChange={setAllWarningsChecked} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -121,7 +133,7 @@ export const AsyncActionDialog = React.memo(function AsyncActionDialog(
|
||||||
intent={intent}
|
intent={intent}
|
||||||
text={confirmButtonText}
|
text={confirmButtonText}
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
disabled={confirmButtonDisabled}
|
disabled={confirmButtonDisabled || needsMoreChecks}
|
||||||
/>
|
/>
|
||||||
<Button text={cancelButtonText || 'Cancel'} onClick={onClose} />
|
<Button text={cancelButtonText || 'Cancel'} onClick={onClose} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -418,7 +418,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: IconNames.STEP_BACKWARD,
|
icon: IconNames.STEP_BACKWARD,
|
||||||
title: 'Reset',
|
title: 'Hard reset',
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
onAction: () => this.setState({ resetSupervisorId: id }),
|
onAction: () => this.setState({ resetSupervisorId: id }),
|
||||||
},
|
},
|
||||||
|
@ -503,9 +503,9 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
);
|
);
|
||||||
return resp.data;
|
return resp.data;
|
||||||
}}
|
}}
|
||||||
confirmButtonText="Reset supervisor"
|
confirmButtonText="Hard reset supervisor"
|
||||||
successText="Supervisor has been reset"
|
successText="Supervisor has been hard reset"
|
||||||
failText="Could not reset supervisor"
|
failText="Could not hard reset supervisor"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
this.setState({ resetSupervisorId: undefined });
|
this.setState({ resetSupervisorId: undefined });
|
||||||
|
@ -513,9 +513,17 @@ ORDER BY "rank" DESC, "created_time" DESC`;
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
this.supervisorQueryManager.rerunLastQuery();
|
this.supervisorQueryManager.rerunLastQuery();
|
||||||
}}
|
}}
|
||||||
|
warningChecks={[
|
||||||
|
`I understand that resetting ${resetSupervisorId} will clear checkpoints and therefore lead to data loss or duplication.`,
|
||||||
|
'I understand that this operation cannot be undone.',
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<p>{`Are you sure you want to reset supervisor '${resetSupervisorId}'?`}</p>
|
<p>{`Are you sure you want to hard reset supervisor '${resetSupervisorId}'?`}</p>
|
||||||
<p>Resetting a supervisor will lead to data loss or data duplication.</p>
|
<p>Hard resetting a supervisor will lead to data loss or data duplication.</p>
|
||||||
|
<p>
|
||||||
|
The reason for using this operation is to recover from a state in which the supervisor
|
||||||
|
ceases operating due to missing offsets.
|
||||||
|
</p>
|
||||||
</AsyncActionDialog>
|
</AsyncActionDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue