Skip to content

Commit

Permalink
Merge pull request #965 from Jappzy/autosave
Browse files Browse the repository at this point in the history
Auto-Save Changes in the Workflow Composer
  • Loading branch information
arm4b committed Oct 21, 2022
2 parents 0011c47 + 0b10474 commit 45a9c44
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 29 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -20,6 +20,10 @@ Added

Contributed by @Jappzy and @cded from @Bitovi

* Added an optional auto-save capability in the workflow composer. #965, #993

Contributed by @Jappzy and @cded from @Bitovi

Changed
~~~~~~~
* Updated nodejs from `14.16.1` to `14.20.1`, fixing the local build under ARM processor architecture. #880
Expand Down
21 changes: 21 additions & 0 deletions apps/st2-workflows/store.js
Expand Up @@ -273,6 +273,18 @@ const flowReducer = (state = {}, input) => {
};
}

case 'PUSH_WARNING': {
const { message, link, source } = input;

return {
...state,
notifications: [
...notifications.filter(n => !source || n.source !== source),
{ type: 'warning', message, source, link, id: uniqueId() },
],
};
}

case 'PUSH_SUCCESS': {
const { message, link, source } = input;

Expand Down Expand Up @@ -391,6 +403,15 @@ const flowReducer = (state = {}, input) => {
};
}

case 'TOGGLE_AUTOSAVE': {
const { autosaveEnabled } = input;

return {
...state,
autosaveEnabled,
};
}

default:
return state;
}
Expand Down
44 changes: 35 additions & 9 deletions apps/st2-workflows/workflows.component.js
Expand Up @@ -230,7 +230,7 @@ export default class Workflows extends Component {
}

save() {
const { pack, meta, actions, workflowSource, metaSource } = this.props;
const { pack, meta, actions, workflowSource, metaSource, sendSuccess, sendError } = this.props;
const existingAction = actions.find(e => e.name === meta.name && e.pack === pack);

if (!meta.name) {
Expand Down Expand Up @@ -272,14 +272,31 @@ export default class Workflows extends Component {
// don't need to return anything to the store. the handler will change dirty.
return {};
})();

store.dispatch({
const saveRes = store.dispatch({
type: 'SAVE_WORKFLOW',
promise,
});

saveRes.then(({ status }) => status === 'success' ? sendSuccess('Workflow saved.') : sendError('Error saving workflow.'));

return promise;
}

timer;

autosave(func) {
func.apply(this);
clearTimeout(this.timer);
this.timer = setTimeout(() => {
const { autosaveEnabled } = store.getState();

if (autosaveEnabled) {
this.save();
}
}, 1000);
}

style = style

keyHandlers = {
Expand Down Expand Up @@ -340,24 +357,33 @@ export default class Workflows extends Component {
<div
style={{ flex: 1}}
>
<Canvas className="canvas" location={location} match={match} fetchActionscalled={e => this.props.fetchActions()} save={this.keyHandlers.save} dirtyflag={this.props.dirty} undo={this.keyHandlers.undo} redo={this.keyHandlers.redo}>
<Canvas
className="canvas"
location={location}
match={match}
dirtyflag={this.props.dirty}
fetchActionscalled={e => this.props.fetchActions()}
saveData={e => this.autosave(() => null)}
save={this.keyHandlers.save}
undo={() => this.autosave(() => this.keyHandlers.undo())}
redo={() => this.autosave(() => this.keyHandlers.redo())}
>
<Toolbar>
<ToolbarButton key="undo" icon="icon-redirect" title="Undo" errorMessage="Could not undo." onClick={() => undo()} />
<ToolbarButton key="redo" icon="icon-redirect2" title="Redo" errorMessage="Could not redo." onClick={() => redo()} />
<ToolbarButton key="undo" icon="icon-redirect" title="Undo" errorMessage="Could not undo." onClick={() => this.autosave(() => undo())} />
<ToolbarButton key="redo" icon="icon-redirect2" title="Redo" errorMessage="Could not redo." onClick={() => this.autosave(() => redo())} />
<ToolbarButton
key="rearrange"
icon="icon-arrange"
title="Rearrange tasks"
successMessage="Rearrange complete."
errorMessage="Error rearranging workflows."
onClick={() => layout()}
onClick={() => this.autosave(() => layout())}
/>
<ToolbarButton
key="save"
className={cx(dirty && 'glow')}
icon="icon-save"
title="Save workflow"
successMessage="Workflow saved."
errorMessage="Error saving workflow."
onClick={() => this.save()}
/>
Expand Down Expand Up @@ -391,7 +417,7 @@ export default class Workflows extends Component {
</Toolbar>
</Canvas>
</div>
{ !isCollapsed.details && <Details className="details" actions={actions} /> }
{ !isCollapsed.details && <Details className="details" actions={actions} onChange={() => this.autosave(() => null)} /> }
</div>
</div>

Expand Down
38 changes: 30 additions & 8 deletions modules/st2flow-canvas/index.js
Expand Up @@ -29,9 +29,10 @@ import { PropTypes } from 'prop-types';
import cx from 'classnames';
import fp from 'lodash/fp';
import { uniqueId, uniq } from 'lodash';
import isEqual from 'lodash/isEqual';

import Notifications from '@stackstorm/st2flow-notifications';
import {HotKeys} from 'react-hotkeys';
import { HotKeys } from 'react-hotkeys';

import { BoundingBox } from './routing-graph';
import Task from './task';
Expand All @@ -46,6 +47,8 @@ import PoissonRectangleSampler from './poisson-rect';

import { origin } from './const';

import store from '../../apps/st2-workflows/store';

import style from './style.css';
type DOMMatrix = {
m11: number,
Expand Down Expand Up @@ -257,8 +260,10 @@ export default class Canvas extends Component {
this.handleUpdate();
}

componentDidUpdate() {
componentDidUpdate(prevProps) {
this.handleUpdate();

this.handleAutoSaveUpdates(prevProps);
}

componentWillUnmount() {
Expand Down Expand Up @@ -387,11 +392,26 @@ export default class Canvas extends Component {
// finally, place the unplaced tasks. using handleTaskMove will also ensure
// that the placement gets set on the model and the YAML.
needsCoords.forEach(({task, transitionsTo}) => {
this.handleTaskMove(task, sampler.getNext(task.name, transitionsTo),true);
this.handleTaskMove(task, sampler.getNext(task.name, transitionsTo));
});
}
}

handleAutoSaveUpdates(prevProps) {
const {saveData, transitions, tasks} = this.props;
const { autosaveEnabled } = store.getState();

if (autosaveEnabled) {
if(!isEqual(prevProps.transitions, transitions)) {
saveData();
}

if(!isEqual(prevProps.tasks, tasks)) {
this.props.saveData();
}
}
}

handleMouseWheel = (e: Wheel): ?false => {
// considerations on scale factor (BM, 2019-02-07)
// on Chrome Mac and Safari Mac:
Expand Down Expand Up @@ -576,16 +596,18 @@ export default class Canvas extends Component {
return false;
}

handleTaskMove = async (task: TaskRefInterface, points: CanvasPoint,autoSave) => {
handleTaskMove = async (task: TaskRefInterface, points: CanvasPoint) => {
const x = points.x;
const y = points.y;
const coords = {x, y};
this.props.issueModelCommand('updateTask', task, { coords });

const { autosaveEnabled } = store.getState();

if(autoSave && !this.props.dirtyflag) {
await this.props.fetchActionscalled();
if (autosaveEnabled && this.props.dirtyflag) {
this.props.saveData();
}
await this.props.fetchActionscalled();
}

}

Expand Down Expand Up @@ -807,7 +829,7 @@ export default class Canvas extends Component {
task={task}
selected={task.name === navigation.task && !selectedTransitionGroups.length}
scale={scale}
onMove={(...a) => this.handleTaskMove(task, ...a,false)}
onMove={(...a) => this.handleTaskMove(task, ...a)}
onConnect={(...a) => this.handleTaskConnect(task, ...a)}
onClick={() => this.handleTaskSelect(task)}
onDelete={() => this.handleTaskDelete(task)}
Expand Down
36 changes: 32 additions & 4 deletions modules/st2flow-details/index.js
Expand Up @@ -32,6 +32,7 @@ import TaskDetails from './task-details';
import TaskList from './task-list';

import style from './style.css';
import store from '../../apps/st2-workflows/store';

@connect(
editorConnect
Expand Down Expand Up @@ -91,6 +92,8 @@ export default class Details extends Component<{
navigate: PropTypes.func,

actions: PropTypes.array,

onChange: PropTypes.func,
}

sections = [{
Expand All @@ -111,11 +114,20 @@ export default class Details extends Component<{
this.props.navigate({ toTasks: undefined, task: undefined });
}

toggleAutosave = (autosaveEnabled) => {
store.dispatch({
type: 'TOGGLE_AUTOSAVE',
autosaveEnabled,
});
}

render() {
const { actions, navigation, navigate } = this.props;
const { actions, navigation, navigate, onChange } = this.props;

const { type = 'metadata', asCode } = navigation;

const { autosaveEnabled } = store.getState();

return (
<div className={cx(this.props.className, this.style.component, asCode && 'code')}>
<Toolbar>
Expand All @@ -131,20 +143,36 @@ export default class Details extends Component<{
);
})
}
<div
style={{display: 'flex'}} title="Automatically save the workflow on every change"
>
<input
id='autosave-checkbox'
name='autosave-checkbox'
type='checkbox'
onChange={(e) => {
this.toggleAutosave(e.target.checked);
onChange();
}}
className={cx(style.autosave)}
defaultChecked={autosaveEnabled}
/>
<label id='autosave-checkbox__label' htmlFor='autosave-checkbox' className={cx(style.autosave)}>Autosave</label>
</div>
<ToolbarButton className={cx(style.code, 'icon-code')} selected={asCode} onClick={() => navigate({ asCode: !asCode })} />
</Toolbar>
{
type === 'metadata' && (
asCode
&& <MetaEditor />
&& <MetaEditor onChange={() => onChange()} />
// $FlowFixMe Model is populated via decorator
|| <Meta />
|| <Meta onChange={() => onChange()} />
)
}
{
type === 'execution' && (
asCode
&& <WorkflowEditor selectedTaskName={navigation.task} onTaskSelect={this.handleTaskSelect} />
&& <WorkflowEditor selectedTaskName={navigation.task} onTaskSelect={this.handleTaskSelect} onChange={() => onChange()} />
|| navigation.task
// $FlowFixMe ^^
&& <TaskDetails onBack={this.handleBack} selected={navigation.task} actions={actions} />
Expand Down
57 changes: 50 additions & 7 deletions modules/st2flow-details/meta-panel.js
Expand Up @@ -96,14 +96,30 @@ export default class Meta extends Component {
actions: PropTypes.array,
vars: PropTypes.array,
setVars: PropTypes.func,

onChange: PropTypes.func,
}

componentDidUpdate() {
componentDidUpdate(prevProps) {
const { meta, setMeta } = this.props;

if (!meta.runner_type) {
setMeta('runner_type', default_runner_type);
}

this.handleAutoSaveUpdates(prevProps);
}

handleAutoSaveUpdates(prevProps) {
const { meta, vars, onChange } = this.props;

if(prevProps.meta !== meta) {
onChange();
}

if(prevProps.vars !== vars) {
onChange();
}
}

handleSectionSwitch(section: string) {
Expand Down Expand Up @@ -191,12 +207,39 @@ export default class Meta extends Component {
</Toolbar>,
section === 'meta' && (
<Panel key="meta">
<EnumField name="Runner Type" value={meta.runner_type} spec={{enum: [ ...new Set([ 'mistral-v2', 'orquesta' ]) ], default: default_runner_type}} onChange={(v) => setMeta('runner_type', v)} />
<EnumField name="Pack" value={pack} spec={{enum: packs}} onChange={(v) => setPack(v)} />
<StringField name="Name" value={meta.name} onChange={(v) => this.setMetaNew('name', v || '')} />
<StringField name="Description" value={meta.description} onChange={(v) => setMeta('description', v)} />
<BooleanField name="Enabled" value={meta.enabled} spec={{}} onChange={(v) => setMeta('enabled', v)} />
<StringField name="Entry point" value={meta.entry_point !=='undefined' ? meta.entry_point:`workflows/${meta.name}.yaml`} onChange={(v) => setMeta('entry_point', v || '')} />
<EnumField
name="Runner Type"
value={meta.runner_type}
spec={{enum: [ ...new Set([ 'mistral-v2', 'orquesta' ]) ], default: default_runner_type}}
onChange={(v) => setMeta('runner_type', v)}
/>
<EnumField
name="Pack"
value={pack}
spec={{enum: packs}}
onChange={(v) => setPack(v)}
/>
<StringField
name="Name"
value={meta.name}
onChange={(v) => this.setMetaNew('name', v || '')}
/>
<StringField
name="Description"
value={meta.description}
onChange={(v) => setMeta('description', v)}
/>
<BooleanField
name="Enabled"
value={meta.enabled}
spec={{}}
onChange={(v) => setMeta('enabled', v)}
/>
<StringField
name="Entry point"
value={meta.entry_point !=='undefined' ? meta.entry_point:`workflows/${meta.name}.yaml`}
onChange={(v) => setMeta('entry_point', v || '')}
/>
</Panel>
),
section === 'parameters' && (
Expand Down
7 changes: 6 additions & 1 deletion modules/st2flow-details/style.css
Expand Up @@ -396,4 +396,9 @@ limitations under the License.
}
.tooltip {
position: relative;
}
}

.autosave {
cursor: pointer;
margin-left: 4px;
}

0 comments on commit 45a9c44

Please sign in to comment.