Skip to content

Commit

Permalink
feat: rerun task action in execution page (flyteorg#488)
Browse files Browse the repository at this point in the history
* feat: task rerun done

Signed-off-by: Eugene Jahn <[email protected]>

* feat: fix initialParameters

Signed-off-by: Eugene Jahn <[email protected]>

* fix: remove a file

Signed-off-by: Eugene Jahn <[email protected]>

* feat: rerun task done

Signed-off-by: Eugene Jahn <[email protected]>

* feat: rerun fix literal type

Signed-off-by: Eugene Jahn <[email protected]>

* feat: small typo fixed

Signed-off-by: Eugene Jahn <[email protected]>

* feat: small typo fixed

Signed-off-by: Eugene Jahn <[email protected]>

* feat: fix test coverage

Signed-off-by: Eugene Jahn <[email protected]>
  • Loading branch information
eugenejahn authored May 26, 2022
1 parent 271cb65 commit 9c453eb
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 3 deletions.
26 changes: 26 additions & 0 deletions packages/composites/ui-atoms/src/Icons/RerunIcon/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as React from 'react';

interface IconProps {
size?: number;
className?: string;
onClick?: () => void;
}

export const RerunIcon = (props: IconProps): JSX.Element => {
const { size = 18, className, onClick } = props;
return (
<svg
className={className}
width={size}
height={size}
viewBox="0 0 13 11"
fill="none"
onClick={onClick}
>
<path
d="M4.8 1.4H8.8V0L12.6 2.2L8.8 4.4V3H4.8C3.04 3 1.6 4.44 1.6 6.2C1.6 7.96 3.04 9.4 4.8 9.4H8.8V11H4.8C2.16 11 0 8.84 0 6.2C0 3.56 2.16 1.4 4.8 1.4Z"
fill="#666666"
/>
</svg>
);
};
1 change: 1 addition & 0 deletions packages/composites/ui-atoms/src/Icons/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { FlyteLogo } from './FlyteLogo';
export { InfoIcon } from './InfoIcon';
export { RerunIcon } from './RerunIcon';
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Button } from '@material-ui/core';
import * as React from 'react';
import { ResourceIdentifier, Identifier, Variable } from 'models/Common/types';
import { getTask } from 'models/Task/api';
import { LaunchFormDialog } from 'components/Launch/LaunchForm/LaunchFormDialog';
import { NodeExecutionIdentifier } from 'models/Execution/types';
import { useNodeExecutionData } from 'components/hooks/useNodeExecution';
import { literalsToLiteralValueMap } from 'components/Launch/LaunchForm/utils';
import { TaskInitialLaunchParameters } from 'components/Launch/LaunchForm/types';
import { NodeExecutionDetails } from '../types';
import t from './strings';

interface ExecutionDetailsActionsProps {
className?: string;
details: NodeExecutionDetails;
nodeExecutionId: NodeExecutionIdentifier;
}

export const ExecutionDetailsActions = (props: ExecutionDetailsActionsProps): JSX.Element => {
const { className, details, nodeExecutionId } = props;

const [showLaunchForm, setShowLaunchForm] = React.useState<boolean>(false);
const [taskInputsTypes, setTaskInputsTypes] = React.useState<
Record<string, Variable> | undefined
>();

const executionData = useNodeExecutionData(nodeExecutionId);

const id = details.taskTemplate?.id as ResourceIdentifier | undefined;

React.useEffect(() => {
const fetchTask = async () => {
const task = await getTask(id as Identifier);
setTaskInputsTypes(task.closure.compiledTask.template?.interface?.inputs?.variables);
};
if (id) fetchTask();
}, [id]);

if (!id) {
return <></>;
}

const literals = executionData.value.fullInputs?.literals;

const initialParameters: TaskInitialLaunchParameters = {
values: literals && taskInputsTypes && literalsToLiteralValueMap(literals, taskInputsTypes),
taskId: id as Identifier | undefined,
};

const rerunOnClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setShowLaunchForm(true);
};

return (
<>
<div className={className}>
<Button variant="outlined" color="primary" onClick={rerunOnClick}>
{t('rerun')}
</Button>
</div>
<LaunchFormDialog
id={id}
initialParameters={initialParameters}
showLaunchForm={showLaunchForm}
setShowLaunchForm={setShowLaunchForm}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { getTaskExecutionDetailReasons } from './utils';
import { ExpandableMonospaceText } from '../../common/ExpandableMonospaceText';
import { fetchWorkflowExecution } from '../useWorkflowExecution';
import { NodeExecutionTabs } from './NodeExecutionTabs';
import { ExecutionDetailsActions } from './ExecutionDetailsActions';

const useStyles = makeStyles((theme: Theme) => {
const paddingVertical = `${theme.spacing(2)}px`;
Expand Down Expand Up @@ -93,6 +94,11 @@ const useStyles = makeStyles((theme: Theme) => {
marginTop: theme.spacing(2),
paddingTop: theme.spacing(2),
},
actionsContainer: {
borderTop: `1px solid ${theme.palette.divider}`,
marginTop: theme.spacing(2),
paddingTop: theme.spacing(2),
},
nodeTypeContent: {
minWidth: theme.spacing(9),
},
Expand Down Expand Up @@ -395,6 +401,13 @@ export const NodeExecutionDetailsPanelContent: React.FC<NodeExecutionDetailsProp
</Typography>
{statusContent}
{!dag && detailsContent}
{details && (
<ExecutionDetailsActions
className={styles.actionsContainer}
details={details}
nodeExecutionId={nodeExecutionId}
/>
)}
</div>
</header>
{dag ? <WorkflowTabs nodeId={nodeExecutionId.nodeId} dagData={dag} /> : tabsContent}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createLocalizedString } from '@flyteconsole/locale';

const str = {
rerun: 'RERUN',
};

export { patternKey } from '@flyteconsole/locale';
export default createLocalizedString(str);
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { IconButton, Tooltip } from '@material-ui/core';
import { NodeExecution } from 'models/Execution/types';
import * as React from 'react';
import InputsAndOutputsIcon from '@material-ui/icons/Tv';
import { RerunIcon } from '@flyteconsole/ui-atoms';
import { Identifier, ResourceIdentifier, Variable } from 'models/Common/types';
import { LaunchFormDialog } from 'components/Launch/LaunchForm/LaunchFormDialog';
import { getTask } from 'models/Task/api';
import { useNodeExecutionData } from 'components/hooks/useNodeExecution';
import { TaskInitialLaunchParameters } from 'components/Launch/LaunchForm/types';
import { literalsToLiteralValueMap } from 'components/Launch/LaunchForm/utils';
import { NodeExecutionsTableState } from './types';
import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails';
import { NodeExecutionDetails } from '../types';
import t from './strings';

interface NodeExecutionActionsProps {
execution: NodeExecution;
state: NodeExecutionsTableState;
}

export const NodeExecutionActions = (props: NodeExecutionActionsProps): JSX.Element => {
const { execution, state } = props;

const detailsContext = useNodeExecutionContext();
const [showLaunchForm, setShowLaunchForm] = React.useState<boolean>(false);
const [nodeExecutionDetails, setNodeExecutionDetails] = React.useState<
NodeExecutionDetails | undefined
>();
const [taskInputsTypes, setTaskInputsTypes] = React.useState<
Record<string, Variable> | undefined
>();

const executionData = useNodeExecutionData(execution.id);
const literals = executionData.value.fullInputs?.literals;
const id = nodeExecutionDetails?.taskTemplate?.id as ResourceIdentifier;

React.useEffect(() => {
detailsContext.getNodeExecutionDetails(execution).then((res) => {
setNodeExecutionDetails(res);
});
});

React.useEffect(() => {
const fetchTask = async () => {
const task = await getTask(id as Identifier);
setTaskInputsTypes(task.closure.compiledTask.template?.interface?.inputs?.variables);
};
if (id) fetchTask();
}, [id]);

// open the side panel for selected execution's detail
const inputsAndOutputsIconOnClick = (e: React.MouseEvent<HTMLElement>) => {
// prevent the parent row body onClick event trigger
e.stopPropagation();
// use null in case if there is no execution provided - when it is null will close panel
state.setSelectedExecution(execution?.id ?? null);
};

const rerunIconOnClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setShowLaunchForm(true);
};

const renderRerunAction = () => {
if (!id) {
return <></>;
}

const initialParameters: TaskInitialLaunchParameters = {
values: literals && taskInputsTypes && literalsToLiteralValueMap(literals, taskInputsTypes),
taskId: id as Identifier | undefined,
};
return (
<>
<Tooltip title={t('rerunTooltip')}>
<IconButton onClick={rerunIconOnClick}>
<RerunIcon />
</IconButton>
</Tooltip>
<LaunchFormDialog
id={id}
initialParameters={initialParameters}
showLaunchForm={showLaunchForm}
setShowLaunchForm={setShowLaunchForm}
/>
</>
);
};

return (
<div>
<Tooltip title={t('inputsAndOutputsTooltip')}>
<IconButton onClick={inputsAndOutputsIconOnClick}>
<InputsAndOutputsIcon />
</IconButton>
</Tooltip>
{renderRerunAction()}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useNodeExecutionContext } from '../contextProvider/NodeExecutionDetails
import { ExecutionStatusBadge } from '../ExecutionStatusBadge';
import { NodeExecutionCacheStatus } from '../NodeExecutionCacheStatus';
import { getNodeExecutionTimingMS } from '../utils';
import { NodeExecutionActions } from './NodeExecutionActions';
import { SelectNodeExecutionLink } from './SelectNodeExecutionLink';
import { useColumnStyles } from './styles';
import { NodeExecutionCellRendererData, NodeExecutionColumnDefinition } from './types';
Expand Down Expand Up @@ -201,11 +202,11 @@ export function generateColumns(
},
{
cellRenderer: ({ execution, state }) => (
<SelectNodeExecutionLink execution={execution} linkText="View Logs" state={state} />
<NodeExecutionActions execution={execution} state={state} />
),
className: styles.columnLogs,
key: 'logs',
label: 'logs',
key: 'actions',
label: '',
},
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createLocalizedString } from '@flyteconsole/locale';

const str = {
inputsAndOutputsTooltip: 'View Inputs & Outpus',
rerunTooltip: 'Rerun',
};

export { patternKey } from '@flyteconsole/locale';
export default createLocalizedString(str);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Dialog } from '@material-ui/core';
import * as React from 'react';
import { LaunchForm } from 'components/Launch/LaunchForm/LaunchForm';
import { ResourceIdentifier, ResourceType } from 'models/Common/types';
import {
TaskInitialLaunchParameters,
WorkflowInitialLaunchParameters,
} from 'components/Launch/LaunchForm/types';

interface LaunchFormDialogProps {
id: ResourceIdentifier;
initialParameters: TaskInitialLaunchParameters | WorkflowInitialLaunchParameters;
showLaunchForm: boolean;
setShowLaunchForm: React.Dispatch<React.SetStateAction<boolean>>;
}

function getLaunchProps(id: ResourceIdentifier) {
if (id.resourceType === ResourceType.TASK) {
return { taskId: id };
} else if (id.resourceType === ResourceType.WORKFLOW) {
return { workflowId: id };
}
throw new Error('Unknown Resource Type');
}

export const LaunchFormDialog = (props: LaunchFormDialogProps): JSX.Element => {
const { id, initialParameters, showLaunchForm, setShowLaunchForm } = props;

const onCancelLaunch = () => setShowLaunchForm(false);

// prevent child onclick event in the dialog triggers parent onclick event
const dialogOnClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
};

return (
<Dialog
scroll="paper"
maxWidth="sm"
fullWidth={true}
open={showLaunchForm}
onClick={dialogOnClick}
>
<LaunchForm
initialParameters={initialParameters}
onClose={onCancelLaunch}
{...getLaunchProps(id)}
/>
</Dialog>
);
};
20 changes: 20 additions & 0 deletions packages/zapp/console/src/components/Launch/LaunchForm/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LaunchPlan } from 'models/Launch/types';
import { Task } from 'models/Task/types';
import { Workflow } from 'models/Workflow/types';
import * as moment from 'moment';
import { LiteralValueMap } from 'components/Launch/LaunchForm/types';
import { simpleTypeToInputType, typeLabels } from './constants';
import { inputToLiteral } from './inputHelpers/inputHelpers';
import { typeIsSupported } from './inputHelpers/utils';
Expand Down Expand Up @@ -190,3 +191,22 @@ export function isEnterInputsState(state: BaseInterpretedLaunchState): boolean {
LaunchState.SUBMIT_SUCCEEDED,
].some(state.matches);
}

export function literalsToLiteralValueMap(
literals: {
[k: string]: Core.ILiteral;
},
nameToTypeMap: Record<string, Variable>,
): LiteralValueMap {
const literalValueMap: LiteralValueMap = new Map<string, Core.ILiteral>();

for (var i = 0; i < Object.keys(literals).length; i++) {
const name = Object.keys(literals)[i];
const type = nameToTypeMap[name].type;
const typeDefinition = getInputDefintionForLiteralType(type);
const inputKey = createInputCacheKey(name, typeDefinition);
literalValueMap.set(inputKey, literals[Object.keys(literals)[i]]);
}

return literalValueMap;
}

0 comments on commit 9c453eb

Please sign in to comment.