Skip to content

Commit dfe5bd4

Browse files
merge
2 parents d91dba7 + 31cbd2d commit dfe5bd4

File tree

13 files changed

+209
-143
lines changed

13 files changed

+209
-143
lines changed

README.md

Lines changed: 51 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,89 @@
1-
# workflow-manager 🚀
1+
# Volto Workflow Manager
22

3-
[![Built with Cookieplone](https://img.shields.io/badge/built%20with-Cookieplone-0083be.svg?logo=cookiecutter)](https://github.com/plone/cookieplone-templates/)
4-
[![Black code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
5-
[![Backend Tests](https://github.com/collective/workflow-manager/actions/workflows/backend.yml/badge.svg)](https://github.com/collective/workflow-manager/actions/workflows/backend.yml)
6-
[![Frontend Tests](https://github.com/collective/workflow-manager/actions/workflows/frontend.yml/badge.svg)](https://github.com/collective/workflow-manager/actions/workflows/frontend.yml)
3+
Volto Workflow Manager is a visual workflow editor for Plone.
4+
It lets you create, manage, and edit workflows inside the Volto UI using a simple graph-based interface.
75

8-
A new project using Plone 6.
6+
---
97

10-
## Quick Start 🏁
8+
## Installation
119

12-
### Prerequisites ✅
10+
This package is a Volto add-on. To install it in your Volto project:
1311

14-
- An [operating system](https://6.docs.plone.org/install/create-project-cookieplone.html#prerequisites-for-installation) that runs all the requirements mentioned.
15-
- [uv](https://6.docs.plone.org/install/create-project-cookieplone.html#uv)
16-
- [nvm](https://6.docs.plone.org/install/create-project-cookieplone.html#nvm)
17-
- [Node.js and pnpm](https://6.docs.plone.org/install/create-project.html#node-js) 22
18-
- [Make](https://6.docs.plone.org/install/create-project-cookieplone.html#make)
19-
- [Git](https://6.docs.plone.org/install/create-project-cookieplone.html#git)
20-
- [Docker](https://docs.docker.com/get-started/get-docker/) (optional)
12+
1. Add `workflow-manager` to your project's `package.json`:
2113

22-
23-
### Installation 🔧
24-
25-
1. Clone this repository, then change your working directory.
26-
27-
```shell
28-
git clone [email protected]:collective/workflow-manager.git
29-
cd workflow-manager
14+
```json
15+
{
16+
"addons": ["workflow-manager"]
17+
}
3018
```
3119

32-
2. Install this code base.
20+
2. Install dependencies:
3321

34-
```shell
35-
make install
22+
```bash
23+
pnpm install
3624
```
3725

26+
3. Start the Volto development server:
3827

39-
### Fire Up the Servers 🔥
40-
41-
1. Create a new Plone site on your first run.
42-
43-
```shell
44-
make backend-create-site
28+
```bash
29+
pnpm start
4530
```
4631

47-
2. Start the backend at http://localhost:8080/.
32+
---
4833

49-
```shell
50-
make backend-start
51-
```
34+
## How to Use
5235

53-
3. In a new shell session, start the frontend at http://localhost:3000/.
36+
1. Open the **Control Panel** and select **Workflow Manager**.
37+
2. Create a new workflow if one does not exist.
38+
3. Add states and transitions using the graph interface.
39+
4. Edit state and transition details from the sidebar.
40+
5. Assign permissions and roles as needed.
41+
6. Save changes using the Volto toolbar.
5442

55-
```shell
56-
make frontend-start
57-
```
58-
59-
Voila! Your Plone site should be live and kicking! 🎉
60-
61-
### Local Stack Deployment 📦
43+
---
6244

63-
Deploy a local `Docker Compose` environment that includes:
45+
## Development Setup
6446

65-
- Docker images for Backend and Frontend 🖼️
66-
- A stack with a Traefik router and a Postgres database 🗃️
67-
- Accessible at [http://workflow-manager.localhost](http://workflow-manager.localhost) 🌐
47+
If you want to contribute or customize the add-on locally:
6848

69-
Execute the following:
70-
71-
```shell
72-
make stack-start
73-
make stack-create-site
74-
```
49+
## Requirements
7550

76-
And... you're all set! Your Plone site is up and running locally! 🚀
51+
- [Node.js 22](https://6.docs.plone.org/install/create-project.html#node-js)
52+
- [pnpm](https://pnpm.io/)
53+
- [UV](https://6.docs.plone.org/install/create-project-cookieplone.html#uv)
7754

78-
## Project Structure 🏗️
55+
### Installation
7956

80-
This monorepo consists of the following distinct sections:
57+
1. Clone the repository:
8158

82-
- **backend**: Houses the API and Plone installation, utilizing pip instead of buildout, and includes a policy package named workflow.manager.
83-
- **frontend**: Contains the React (Volto) package.
84-
- **devops**: Encompasses Docker Stack, Ansible playbooks, and Cache settings.
85-
- **docs**: Scaffold for writing documentation for your project.
86-
87-
### Why This Structure? 🤔
88-
89-
- All necessary codebases to run the site are contained within the repo (excluding existing addons for Plone and React).
90-
- Specific GitHub Workflows are triggered based on changes in each codebase (refer to .github/workflows).
91-
- Simplifies the creation of Docker images for each codebase.
92-
- Demonstrates Plone installation/setup without buildout.
93-
94-
## Code Quality Assurance 🧐
95-
96-
To automatically format your code and ensure it adheres to quality standards, execute:
97-
98-
```shell
99-
make check
59+
```sh
60+
git clone https://github.com/Manas-Kenge/workflow-manager.git
61+
cd workflow-manager
10062
```
10163

102-
### Format the codebase
64+
2. Install dependencies for both Backend and Frontend:
10365

104-
To format the codebase, it is possible to run `format`:
105-
106-
```shell
107-
make format
66+
```sh
67+
make install
10868
```
10969

110-
| Section | Tool | Description | Configuration |
111-
| --- | --- | --- | --- |
112-
| backend | Ruff | Python code formatting, imports sorting | [`backend/pyproject.toml`](./backend/pyproject.toml) |
113-
| backend | `zpretty` | XML and ZCML formatting | -- |
114-
| frontend | ESLint | Fixes most common frontend issues | [`frontend/.eslintrc.js`](.frontend/.eslintrc.js) |
115-
| frontend | prettier | Format JS and Typescript code | [`frontend/.prettierrc`](.frontend/.prettierrc) |
116-
| frontend | Stylelint | Format Styles (css, less, sass) | [`frontend/.stylelintrc`](.frontend/.stylelintrc) |
117-
118-
Formatters can also be run within the `backend` or `frontend` folders.
70+
### Start the Servers
11971

120-
### Linting the codebase
121-
or `lint`:
72+
1. Start the **Backend** (Plone) at [http://localhost:8080/](http://localhost:8080/):
12273

123-
```shell
124-
make lint
74+
```sh
75+
make backend-start
12576
```
12677

127-
| Section | Tool | Description | Configuration |
128-
| --- | --- | --- | --- |
129-
| backend | Ruff | Checks code formatting, imports sorting | [`backend/pyproject.toml`](./backend/pyproject.toml) |
130-
| backend | Pyroma | Checks Python package metadata | -- |
131-
| backend | check-python-versions | Checks Python version information | -- |
132-
| backend | `zpretty` | Checks XML and ZCML formatting | -- |
133-
| frontend | ESLint | Checks JS / Typescript lint | [`frontend/.eslintrc.js`](.frontend/.eslintrc.js) |
134-
| frontend | prettier | Check JS / Typescript formatting | [`frontend/.prettierrc`](.frontend/.prettierrc) |
135-
| frontend | Stylelint | Check Styles (css, less, sass) formatting | [`frontend/.stylelintrc`](.frontend/.stylelintrc) |
136-
137-
Linters can be run individually within the `backend` or `frontend` folders.
78+
2. In a new terminal, start the **Frontend** (Volto) at [http://localhost:3000/](http://localhost:3000/):
13879

139-
## Internationalization 🌐
80+
```sh
81+
make frontend-start
82+
```
14083

141-
Generate translation files for Plone and Volto with ease:
84+
Your Plone development environment is now live!
14285

143-
```shell
144-
make i18n
145-
```
14686

147-
## Credits and Acknowledgements 🙏
87+
# License
14888

149-
Generated using [Cookieplone (0.9.7)](https://github.com/plone/cookieplone) and [cookieplone-templates (684d5c7)](https://github.com/plone/cookieplone-templates/commit/684d5c7f43a6ebc3184e2a0106b0160820a96e66) on 2025-05-22 13:24:17.198737. A special thanks to all contributors and supporters!
89+
This project is licensed under the MIT License.

backend/src/workflow/manager/api/services/workflow/actions.py

Whitespace-only changes.

backend/src/workflow/manager/api/services/workflow/transition.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from Products.DCWorkflow.Transitions import TRIGGER_AUTOMATIC, TRIGGER_USER_ACTION
3+
from Products.DCWorkflow.Expression import Expression
34
from plone.protect.interfaces import IDisableCSRFProtection
45
from plone.restapi.deserializer import json_body
56
from plone.restapi.services import Service
@@ -290,13 +291,21 @@ def reply(self):
290291
guard = transition.getGuard()
291292
guard_data = body['guard']
292293
if 'permissions' in guard_data:
293-
guard.permissions = tuple(guard_data['permissions'])
294+
guard.permissions = tuple(
295+
Expression('string:%s' % p) for p in guard_data['permissions']
296+
)
294297
if 'roles' in guard_data:
295-
guard.roles = tuple(guard_data['roles'])
298+
guard.roles = tuple(
299+
Expression('string:%s' % r) for r in guard_data['roles']
300+
)
296301
if 'groups' in guard_data:
297-
guard.groups = tuple(guard_data['groups'])
298-
if 'expr' in guard_data:
299-
guard.expr = guard_data['expr']
302+
guard.groups = tuple(
303+
Expression('string:%s' % g) for g in guard_data['groups']
304+
)
305+
if 'expr' in guard_data and guard_data['expr']:
306+
guard.expr = Expression(guard_data['expr'])
307+
else:
308+
guard.expr = Expression('')
300309
transition.guard = guard
301310

302311
# Update states that have this transition

frontend/packages/volto-workflow-manager/src/components/States/State.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
Item,
1313
ProgressCircle,
1414
} from '@adobe/react-spectrum';
15-
import { listStates } from '../../actions/state';
15+
import { listStates, deleteState } from '../../actions/state';
1616
import { listTransitions } from '../../actions/transition';
1717
import PropertiesTab from './Tabs/PropertiesTab';
1818
import TransitionsTab from './Tabs/TransitionsTab';
@@ -137,8 +137,19 @@ const State: React.FC<StateProps> = ({
137137
});
138138
}, []);
139139

140+
const handleDeleteState = useCallback(
141+
(stateId: string) => {
142+
dispatch(deleteState(workflowId, stateId));
143+
setSelectedStateId(null);
144+
setLocalStateData(null);
145+
setInitialStateData(null);
146+
onDataChange(null);
147+
},
148+
[dispatch, workflowId, onDataChange],
149+
);
150+
140151
if (isLoadingData && !statesInfo.loaded) {
141-
return <ProgressCircle isIndeterminate />;
152+
return <ProgressCircle isIndeterminate aria-label="Loading states..." />;
142153
}
143154

144155
const areTabsDisabled = isDisabled || !localStateData;
@@ -153,6 +164,7 @@ const State: React.FC<StateProps> = ({
153164
>
154165
<Heading level={3}>Configure a State</Heading>
155166
<Picker
167+
aria-label="Choose state"
156168
placeholder="Choose a state..."
157169
items={statesInfo.data?.states || []}
158170
selectedKey={selectedStateId}
@@ -175,6 +187,8 @@ const State: React.FC<StateProps> = ({
175187
<PropertiesTab
176188
key={`properties-${selectedStateId}`}
177189
data={localStateData?.properties}
190+
handleDeleteState={handleDeleteState}
191+
selectedStateId={selectedStateId}
178192
schema={propertiesSchema}
179193
onChange={(properties) => handleStateChange({ properties })}
180194
isDisabled={areTabsDisabled}

frontend/packages/volto-workflow-manager/src/components/States/Tabs/PropertiesTab.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
import React, { useCallback } from 'react';
2-
import { View, Text } from '@adobe/react-spectrum';
3-
import Form from '@plone/volto/components/manage/Form/Form';
2+
import {
3+
View,
4+
Text,
5+
Flex,
6+
DialogTrigger,
7+
AlertDialog,
8+
Button,
9+
} from '@adobe/react-spectrum';
10+
import { BlockDataForm } from '@plone/volto/components/manage/Form';
11+
import Icon from '@plone/volto/components/theme/Icon/Icon';
12+
import deleteIcon from '@plone/volto/icons/delete.svg';
13+
414
import type { PropertiesTabProps } from '../../../types/state';
515

616
const PropertiesTab: React.FC<PropertiesTabProps> = ({
717
data,
818
schema,
919
onChange,
20+
handleDeleteState,
21+
selectedStateId,
1022
isDisabled,
1123
}) => {
1224
const handleChangeField = useCallback(
@@ -25,12 +37,27 @@ const PropertiesTab: React.FC<PropertiesTabProps> = ({
2537

2638
return (
2739
<View>
28-
<Form
29-
key={JSON.stringify(data)} // Force re-render when data changes
30-
schema={schema}
40+
<Flex justifyContent="end" margin="size-100">
41+
<DialogTrigger>
42+
<Button variant="negative" isDisabled={isDisabled}>
43+
<Icon name={deleteIcon} size="20px" />
44+
</Button>
45+
<AlertDialog
46+
title="Delete State"
47+
variant="destructive"
48+
primaryActionLabel="Delete"
49+
cancelLabel="Cancel"
50+
onPrimaryAction={() => handleDeleteState(selectedStateId)}
51+
>
52+
Are you sure you want to delete '{data.title}' state? This action
53+
cannot be undone.
54+
</AlertDialog>
55+
</DialogTrigger>
56+
</Flex>
57+
<BlockDataForm
3158
formData={data}
59+
schema={schema}
3260
onChangeField={handleChangeField}
33-
hideActions
3461
/>
3562
</View>
3663
);

frontend/packages/volto-workflow-manager/src/components/Transitions/Tabs/GuardsTab.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ const GuardsTab: React.FC<GuardsTabProps> = ({
4747
items={roleItems}
4848
selectedKeys={new Set(data.roles)}
4949
onSelectionChange={(keys) => handleSelectionChange(keys, 'roles')}
50-
isDisabled={isDisabled}
5150
height="size-2000"
5251
>
5352
{(item) => <Item key={item.id}>{item.name}</Item>}
@@ -62,7 +61,6 @@ const GuardsTab: React.FC<GuardsTabProps> = ({
6261
items={availableGroups}
6362
selectedKeys={new Set(data.groups)}
6463
onSelectionChange={(keys) => handleSelectionChange(keys, 'groups')}
65-
isDisabled={isDisabled}
6664
height="size-2000"
6765
>
6866
{(item) => <Item key={item.id}>{item.title}</Item>}
@@ -79,7 +77,6 @@ const GuardsTab: React.FC<GuardsTabProps> = ({
7977
onSelectionChange={(keys) =>
8078
handleSelectionChange(keys, 'permissions')
8179
}
82-
isDisabled={isDisabled}
8380
height="size-2000"
8481
>
8582
{(item) => <Item key={item.perm}>{item.name}</Item>}

0 commit comments

Comments
 (0)