Fixes some rbac issues in the workflow toolbar and start screen. Add/edit-related buttons should be hidden for users that cannot edit the workflow.

This commit is contained in:
mabashian 2020-09-01 13:32:28 -04:00
parent a3e08a3d09
commit 945dfbb648
6 changed files with 140 additions and 62 deletions

View File

@ -424,6 +424,8 @@ function Visualizer({ template, i18n }) {
);
}
const readOnly = !template?.summary_fields?.user_capabilities?.edit;
return (
<WorkflowStateContext.Provider value={state}>
<WorkflowDispatchContext.Provider value={dispatch}>
@ -433,13 +435,12 @@ function Visualizer({ template, i18n }) {
onSave={handleVisualizerSave}
hasUnsavedChanges={unsavedChanges}
template={template}
readOnly={readOnly}
/>
{links.length > 0 ? (
<VisualizerGraph
readOnly={!template.summary_fields.user_capabilities.edit}
/>
<VisualizerGraph readOnly={readOnly} />
) : (
<VisualizerStartScreen />
<VisualizerStartScreen readOnly={readOnly} />
)}
</Wrapper>
{nodeToDelete && <NodeDeleteModal />}

View File

@ -271,6 +271,7 @@ function VisualizerGraph({ i18n, readOnly }) {
key="start"
showActionTooltip={!readOnly}
onUpdateHelpText={setHelpText}
readOnly={readOnly}
/>,
links.map(link => {
if (

View File

@ -30,23 +30,31 @@ const StartPanelWrapper = styled.div`
justify-content: center;
`;
function VisualizerStartScreen({ i18n }) {
function VisualizerStartScreen({ i18n, readOnly }) {
const dispatch = useContext(WorkflowDispatchContext);
return (
<div css="flex: 1">
<StartPanelWrapper>
<StartPanel>
<p>{i18n._(t`Please click the Start button to begin.`)}</p>
<Button
id="visualizer-start"
aria-label={i18n._(t`Start`)}
onClick={() =>
dispatch({ type: 'START_ADD_NODE', sourceNodeId: 1 })
}
variant="primary"
>
{i18n._(t`Start`)}
</Button>
{readOnly ? (
<p>
{i18n._(t`This workflow does not have any nodes configured.`)}
</p>
) : (
<>
<p>{i18n._(t`Please click the Start button to begin.`)}</p>
<Button
id="visualizer-start"
aria-label={i18n._(t`Start`)}
onClick={() =>
dispatch({ type: 'START_ADD_NODE', sourceNodeId: 1 })
}
variant="primary"
>
{i18n._(t`Start`)}
</Button>
</>
)}
</StartPanel>
</StartPanelWrapper>
</div>

View File

@ -19,4 +19,12 @@ describe('VisualizerStartScreen', () => {
sourceNodeId: 1,
});
});
test('start button hidden in read-only mode', () => {
const wrapper = mountWithContexts(
<WorkflowDispatchContext.Provider value={dispatch}>
<VisualizerStartScreen readOnly />
</WorkflowDispatchContext.Provider>
);
expect(wrapper.find('Button').length).toBe(0);
});
});

View File

@ -56,14 +56,13 @@ function VisualizerToolbar({
onSave,
template,
hasUnsavedChanges,
readOnly,
}) {
const dispatch = useContext(WorkflowDispatchContext);
const { nodes, showLegend, showTools } = useContext(WorkflowStateContext);
const totalNodes = nodes.reduce((n, node) => n + !node.isDeleted, 0) - 1;
const canLaunch =
template.summary_fields?.user_capabilities?.start && !hasUnsavedChanges;
return (
<div id="visualizer-toolbar">
@ -112,43 +111,49 @@ function VisualizerToolbar({
>
<BookIcon />
</ActionButton>
<LaunchButton resource={template} aria-label={i18n._(t`Launch`)}>
{({ handleLaunch }) => (
<ActionButton
id="visualizer-launch"
variant="plain"
isDisabled={!canLaunch || totalNodes === 0}
onClick={handleLaunch}
{template.summary_fields?.user_capabilities?.start && (
<LaunchButton resource={template} aria-label={i18n._(t`Launch`)}>
{({ handleLaunch }) => (
<ActionButton
id="visualizer-launch"
variant="plain"
isDisabled={hasUnsavedChanges || totalNodes === 0}
onClick={handleLaunch}
>
<RocketIcon />
</ActionButton>
)}
</LaunchButton>
)}
{!readOnly && (
<>
<Tooltip content={i18n._(t`Delete All Nodes`)} position="bottom">
<ActionButton
id="visualizer-delete-all"
aria-label={i18n._(t`Delete all nodes`)}
isDisabled={totalNodes === 0}
onClick={() =>
dispatch({
type: 'SET_SHOW_DELETE_ALL_NODES_MODAL',
value: true,
})
}
variant="plain"
>
<TrashAltIcon />
</ActionButton>
</Tooltip>
<Button
id="visualizer-save"
css="margin: 0 32px"
aria-label={i18n._(t`Save`)}
variant="primary"
onClick={onSave}
>
<RocketIcon />
</ActionButton>
)}
</LaunchButton>
<Tooltip content={i18n._(t`Delete All Nodes`)} position="bottom">
<ActionButton
id="visualizer-delete-all"
aria-label={i18n._(t`Delete all nodes`)}
isDisabled={totalNodes === 0}
onClick={() =>
dispatch({
type: 'SET_SHOW_DELETE_ALL_NODES_MODAL',
value: true,
})
}
variant="plain"
>
<TrashAltIcon />
</ActionButton>
</Tooltip>
<Button
id="visualizer-save"
css="margin: 0 32px"
aria-label={i18n._(t`Save`)}
variant="primary"
onClick={onSave}
>
{i18n._(t`Save`)}
</Button>
{i18n._(t`Save`)}
</Button>
</>
)}
<Button
id="visualizer-close"
aria-label={i18n._(t`Close`)}
@ -168,6 +173,7 @@ VisualizerToolbar.propTypes = {
onSave: func.isRequired,
template: shape().isRequired,
hasUnsavedChanges: bool.isRequired,
readOnly: bool.isRequired,
};
export default withI18n()(VisualizerToolbar);

View File

@ -47,6 +47,7 @@ describe('VisualizerToolbar', () => {
onSave={save}
template={template}
hasUnsavedChanges={false}
readOnly={false}
/>
</WorkflowStateContext.Provider>
</WorkflowDispatchContext.Provider>
@ -96,6 +97,45 @@ describe('VisualizerToolbar', () => {
});
});
test('Save button calls expected function', () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
expect(save).toHaveBeenCalled();
});
test('Close button calls expected function', () => {
wrapper.find('TimesIcon').simulate('click');
expect(close).toHaveBeenCalled();
});
test('Launch button should be hidden when user cannot start workflow', () => {
const nodes = [
{
id: 1,
},
];
const toolbar = mountWithContexts(
<WorkflowDispatchContext.Provider value={dispatch}>
<WorkflowStateContext.Provider value={{ ...workflowContext, nodes }}>
<VisualizerToolbar
onClose={close}
onSave={save}
template={{
...template,
summary_fields: {
user_capabilities: {
start: false,
},
},
}}
hasUnsavedChanges
readOnly={false}
/>
</WorkflowStateContext.Provider>
</WorkflowDispatchContext.Provider>
);
expect(toolbar.find('LaunchButton button').length).toBe(0);
});
test('Launch button should be disabled when there are unsaved changes', () => {
expect(wrapper.find('LaunchButton button').prop('disabled')).toEqual(false);
const nodes = [
@ -111,6 +151,7 @@ describe('VisualizerToolbar', () => {
onSave={save}
template={template}
hasUnsavedChanges
readOnly={false}
/>
</WorkflowStateContext.Provider>
</WorkflowDispatchContext.Provider>
@ -120,13 +161,26 @@ describe('VisualizerToolbar', () => {
).toEqual(true);
});
test('Save button calls expected function', () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
expect(save).toHaveBeenCalled();
});
test('Close button calls expected function', () => {
wrapper.find('TimesIcon').simulate('click');
expect(close).toHaveBeenCalled();
test('Buttons should be hidden when user cannot edit workflow', () => {
const nodes = [
{
id: 1,
},
];
const toolbar = mountWithContexts(
<WorkflowDispatchContext.Provider value={dispatch}>
<WorkflowStateContext.Provider value={{ ...workflowContext, nodes }}>
<VisualizerToolbar
onClose={close}
onSave={save}
template={template}
hasUnsavedChanges={false}
readOnly
/>
</WorkflowStateContext.Provider>
</WorkflowDispatchContext.Provider>
);
expect(toolbar.find('#visualizer-delete-all').length).toBe(0);
expect(toolbar.find('#visualizer-save').length).toBe(0);
});
});