Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confirmation for stop with visual feedback on request for Job List, Job Detail #245

Merged
merged 8 commits into from
Nov 1, 2022
Merged
72 changes: 72 additions & 0 deletions src/components/confirm-buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Box, Button, Chip, IconButton, SvgIconTypeMap } from '@mui/material';
import React, { useState } from 'react';
import { useTranslator } from '../hooks';
import CloseIcon from '@mui/icons-material/Close';
import { OverridableComponent } from '@mui/material/OverridableComponent';

export function ConfirmButton(props: {
onConfirm: () => void;
confirmationText: string;
icon:
| JSX.Element
| (OverridableComponent<SvgIconTypeMap<unknown, 'svg'>> & {
muiName: string;
});
name?: string | undefined;
remainAfterConfirmation?: boolean;
remainText?: string;
}): JSX.Element | null {
const [clicked, setClicked] = useState(false);
const [confirmed, setConfirmed] = useState(false);

return (
<Box sx={{ width: '6em' }}>
{clicked ? (
props.remainAfterConfirmation && confirmed ? (
<Chip label={props.remainText} />
) : (
<Button
variant="contained"
color="error"
title={props.name}
onClick={_ => {
props.onConfirm();
if (props.remainAfterConfirmation) {
setConfirmed(true);
}
}}
onBlur={_ => setClicked(false)}
style={{ visibility: clicked ? 'visible' : 'hidden' }}
autoFocus
>
{props.confirmationText}
</Button>
)
) : (
<IconButton title={props.name} onClick={_ => setClicked(true)}>
{props.icon}
</IconButton>
)}
</Box>
);
}

export function ConfirmDeleteButton(props: {
clickHandler: () => void;
name?: string;
}): JSX.Element | null {
const trans = useTranslator('jupyterlab');

const buttonTitle = props.name
? trans.__('Delete "%1"', props.name)
: trans.__('Delete job');

return (
<ConfirmButton
name={buttonTitle}
onConfirm={props.clickHandler}
confirmationText={trans.__('Delete')}
icon={<CloseIcon fontSize="small" />}
/>
);
}
62 changes: 0 additions & 62 deletions src/components/confirm-delete-button.tsx

This file was deleted.

39 changes: 0 additions & 39 deletions src/components/confirm-delete-icon.tsx

This file was deleted.

105 changes: 105 additions & 0 deletions src/components/confirm-dialog-buttons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React, { useState } from 'react';

import {
Button,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
useTheme
} from '@mui/material';
import { useTranslator } from '../hooks';

export const ConfirmDialogButton = (props: {
onConfirm: () => Promise<void>;
title: string;
dialogText: string;
dialogConfirmText: string;
variant?: 'text' | 'contained' | 'outlined';
errorColor?: boolean;
pendingConfirmText?: string;
}): JSX.Element => {
const [open, setOpen] = useState(false);

const trans = useTranslator('jupyterlab');
const theme = useTheme();

const handleClose = () => {
setOpen(false);
};

return (
<>
<Button
variant={props.variant ?? 'contained'}
color={props.errorColor ? 'error' : 'primary'}
onClick={_ => setOpen(true)}
>
{props.title}
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{props.title}</DialogTitle>
<DialogContent>
<DialogContentText>{props.dialogText}</DialogContentText>
</DialogContent>
<DialogActions>
<Button
variant="contained"
sx={{ backgroundColor: theme.palette.grey[600] }}
onClick={handleClose}
autoFocus
>
{trans.__('Cancel')}
</Button>
<Button
variant="contained"
color="error"
onClick={async _ => {
handleClose();
await props.onConfirm();
}}
>
{props.dialogConfirmText}
</Button>
</DialogActions>
</Dialog>
</>
);
};

export const ConfirmDialogDeleteButton = (props: {
handleDelete: () => Promise<void>;
title: string;
dialogText: string;
}): JSX.Element => {
const trans = useTranslator('jupyterlab');
return (
<ConfirmDialogButton
onConfirm={props.handleDelete}
title={props.title}
dialogText={props.dialogText}
dialogConfirmText={trans.__('Delete')}
pendingConfirmText={trans.__('Deleting')}
errorColor
/>
);
};

export const ConfirmDialogStopButton = (props: {
handleStop: () => Promise<void>;
title: string;
dialogText: string;
}): JSX.Element => {
const trans = useTranslator('jupyterlab');
return (
<ConfirmDialogButton
onConfirm={props.handleStop}
title={props.title}
dialogText={props.dialogText}
dialogConfirmText={trans.__('Stop')}
pendingConfirmText={trans.__('Stopping')}
variant="outlined"
/>
);
};
4 changes: 2 additions & 2 deletions src/components/job-definition-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import TableCell from '@mui/material/TableCell';
import { Scheduler, SchedulerService } from '../handler';
import { useTranslator } from '../hooks';
import { TranslationBundle } from '@jupyterlab/translation';
import { ConfirmDeleteIcon } from './confirm-delete-icon';
import { ConfirmDeleteButton } from './confirm-buttons';

function CreatedAt(props: {
job: Scheduler.IDescribeJobDefinition;
Expand Down Expand Up @@ -117,7 +117,7 @@ export function buildJobDefinitionRow(
forceReload();
}}
/>
<ConfirmDeleteIcon
<ConfirmDeleteButton
name={jobDef.name}
clickHandler={async () => {
await ss.deleteJobDefinition(jobDef.job_definition_id);
Expand Down
41 changes: 21 additions & 20 deletions src/components/job-row.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import React, { useState } from 'react';

import { JupyterFrontEnd } from '@jupyterlab/application';

import DownloadIcon from '@mui/icons-material/Download';
import StopIcon from '@mui/icons-material/Stop';
import IconButton from '@mui/material/IconButton';
import { Stack, TableCell, TableRow } from '@mui/material';

import { ConfirmDeleteIcon } from './confirm-delete-icon';
import { ConfirmDeleteButton, ConfirmButton } from './confirm-buttons';
import { JobFileLink } from './job-file-link';

import { CommandIDs } from '..';
import { Scheduler } from '../handler';
import { useTranslator } from '../hooks';
import { ICreateJobModel } from '../model';
import DownloadIcon from '@mui/icons-material/Download';
import StopIcon from '@mui/icons-material/Stop';
import { IconButton, Stack, TableCell, TableRow } from '@mui/material';

function StopButton(props: {
job: Scheduler.IDescribeJob;
Expand All @@ -28,9 +24,14 @@ function StopButton(props: {
<div
style={props.job.status !== 'IN_PROGRESS' ? { visibility: 'hidden' } : {}}
>
<IconButton onClick={props.clickHandler} title={buttonTitle}>
<StopIcon fontSize="small" />
</IconButton>
<ConfirmButton
name={buttonTitle}
onConfirm={props.clickHandler}
confirmationText={trans.__('Stop')}
icon={<StopIcon fontSize="small" />}
remainAfterConfirmation
remainText={trans.__('Stopping')}
/>
</div>
);
}
Expand Down Expand Up @@ -117,15 +118,7 @@ export function buildJobRow(
<Timestamp job={job} />,
translateStatus(job.status),
<Stack spacing={1} direction="row">
<StopButton
job={job}
clickHandler={() =>
app.commands.execute(CommandIDs.stopJob, {
id: job.job_id
})
}
/>
<ConfirmDeleteIcon
<ConfirmDeleteButton
name={job.name}
clickHandler={() => {
// optimistic delete for now, no verification on whether the delete
Expand All @@ -136,6 +129,14 @@ export function buildJobRow(
deleteRow(job.job_id);
}}
/>
<StopButton
job={job}
clickHandler={() =>
app.commands.execute(CommandIDs.stopJob, {
id: job.job_id
})
}
/>
</Stack>
];

Expand Down
6 changes: 3 additions & 3 deletions src/mainviews/detail-view/job-definition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ListJobsTable } from '../list-jobs';
import { Scheduler as SchedulerTokens } from '../../tokens';

import { Button, Card, CardContent, FormLabel, Stack } from '@mui/material';
import { ConfirmDeleteButton } from '../../components/confirm-delete-button';
import { ConfirmDialogDeleteButton } from '../../components/confirm-dialog-buttons';
import { JupyterFrontEnd } from '@jupyterlab/application';
import {
ILabeledValueProps,
Expand Down Expand Up @@ -97,10 +97,10 @@ export function JobDefinition(props: IJobDefinitionProps): JSX.Element {
>
{trans.__('Edit Job Definition')}
</Button>
<ConfirmDeleteButton
<ConfirmDialogDeleteButton
handleDelete={handleDeleteJobDefinition}
title={trans.__('Delete Job Definition')}
text={trans.__(
dialogText={trans.__(
'Are you sure that you want to delete this job definition?'
)}
/>
Expand Down
Loading