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:
Vadim Ogievetsky 2020-02-04 15:33:52 -08:00 committed by GitHub
parent 556a3861ed
commit 7e53f23f07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 148 additions and 8 deletions

View File

@ -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>
`;

View File

@ -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();
});
});

View File

@ -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>
);
});

View File

@ -30,6 +30,7 @@ import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import React, { ReactNode, useState } from 'react';
import { WarningChecklist } from '../../components/warning-checklist/warning-checklist';
import { AppToaster } from '../../singletons/toaster';
import './async-action-dialog.scss';
@ -46,6 +47,7 @@ export interface AsyncActionDialogProps {
intent?: Intent;
successText: string;
failText: string;
warningChecks?: string[];
children?: ReactNode;
}
@ -64,11 +66,16 @@ export const AsyncActionDialog = React.memo(function AsyncActionDialog(
confirmButtonText,
confirmButtonDisabled,
cancelButtonText,
warningChecks,
children,
} = props;
const [working, setWorking] = useState(false);
const [allWarningsChecked, setAllWarningsChecked] = useState(false);
const needsMoreChecks = Boolean(warningChecks && !allWarningsChecked);
async function handleConfirm() {
if (needsMoreChecks) return;
setWorking(true);
try {
await action();
@ -108,7 +115,12 @@ export const AsyncActionDialog = React.memo(function AsyncActionDialog(
) : (
<>
{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>
@ -121,7 +133,7 @@ export const AsyncActionDialog = React.memo(function AsyncActionDialog(
intent={intent}
text={confirmButtonText}
onClick={handleConfirm}
disabled={confirmButtonDisabled}
disabled={confirmButtonDisabled || needsMoreChecks}
/>
<Button text={cancelButtonText || 'Cancel'} onClick={onClose} />
</>

View File

@ -418,7 +418,7 @@ ORDER BY "rank" DESC, "created_time" DESC`;
},
{
icon: IconNames.STEP_BACKWARD,
title: 'Reset',
title: 'Hard reset',
intent: Intent.DANGER,
onAction: () => this.setState({ resetSupervisorId: id }),
},
@ -503,9 +503,9 @@ ORDER BY "rank" DESC, "created_time" DESC`;
);
return resp.data;
}}
confirmButtonText="Reset supervisor"
successText="Supervisor has been reset"
failText="Could not reset supervisor"
confirmButtonText="Hard reset supervisor"
successText="Supervisor has been hard reset"
failText="Could not hard reset supervisor"
intent={Intent.DANGER}
onClose={() => {
this.setState({ resetSupervisorId: undefined });
@ -513,9 +513,17 @@ ORDER BY "rank" DESC, "created_time" DESC`;
onSuccess={() => {
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>Resetting a supervisor will lead to data loss or data duplication.</p>
<p>{`Are you sure you want to hard reset supervisor '${resetSupervisorId}'?`}</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>
);
}