Custom Activity UI
Workflow Designer provides simple generative UI based on registered activities. However, depending on your use case, you may want to do more thant just displaying simple input fields. For example, you may want to populate your combobox with data pull from backend API. For that you need to write a custom activity UI in the workflow designer source code.
Development
- Workflow Engine repository is a monorepo containing both backend and frontend. What you need is the frontend source
code which is inside the
webdirectory.
git clone https://github.com/mssfoobar/wfe.git
cd wfe/web
- Copy
.env.sampleto.envand fill in the required values.
Replaced DEV_USER, DEV_PASSWORD, and DEV_DOMAIN values with your env value set in
Quick Start Guide.
IAM_URL=http://iams-keycloak.{DEV_DOMAIN}/realms/aoh/.well-known/openid-configuration
IAM_CLIENT_ID={DEV_USER}
IAM_CLIENT_SECRET={DEV_PASSWORD}
PUBLIC_DOMAIN=localhost
PUBLIC_COOKIE_ACCESS_TOKEN=wfe_access_token
PUBLIC_COOKIE_REFRESH_TOKEN=wfe_refresh_token
ORIGIN=http://localhost:5173
OIDC_ALLOW_INSECURE_REQUESTS=1
PUBLIC_SECURE_MODE="0"
LOGIN_DESTINATION=/aoh/wfd
WFM_URL=http://wfm.{DEV_DOMAIN}
- Workflow Designer is built using web-base and if you are already familiar with
it, you will notice its structure. Your custom activity ui should be placed inside the
src/lib/wfd/activitiesfolder.
src
├── lib
│ ├── wfd
│ │ ├── activities
│ │ │ └── {ACTIVITY_TYPE}
│ │ │ └── index.svelte
ACTIVITY_TYPE is the name of your activity. For example, if your activity is registered as helloworld in
service_activity table, create a file called index.svelte inside src/lib/wfd/activities/helloworld folder.
- Using
activitySDK/apiinternal library, you can set/get values for the activity input fields. For example, your activityhelloworldhave one input field calledname, you can set the value using below code.
Note that you must declare the props with let { activity }: {activity: Activity} = $props();.
<script lang="ts">
import { type Activity } from "$lib/aoh/wfd/activitySDK/api";
// must declare this props to receive activity from parent
let { activity }: {activity: Activity} = $props();
let value: string = $state(activity.getParameter("name") as string || "");
</script>
<input type="text" bind:value onblur={() => activity.setParameter("name", value)} />
activity.setParameter- this api set the value of the input field with parameter nameactivity.getParameter- this api get the value of the input field with parameter name
- Once you have the custom activity ui written, you can run application using
npm run devcommand. It will be available athttp://localhost:5173
npm run dev
When prompted for user credentials, use DEV_USER and DEV_PASSWORD value set in the
Quick Start Guide.
Integration with backend service
If you want to integrate your custom activity UI with backend service, you need to create api endpoint in web
application server. For example, if your custom activity UI is for helloworld activity, you can create an api
endpoint at /src/routes/(private)/aoh/wfd/api/helloworld/+server.ts and it will be available at
http://localhost:5173/api/helloworld.
src
└── routes
├── (private)
│ ├── aoh
│ │ └── wfd
│ │ ├── api
│ │ │ └── {ACTIVITY_TYPE}
│ │ │ └── +server.ts
Your +server.ts file should look like below:
export const GET: RequestHandler = async ({ locals }) => {
const promise = await fetch("{internal-backend-service-endpoint}", {
method: "GET",
});
const response = await promise;
const data = await response.json();
return json(data);
};
Inside your custom activity source code, you can call this api endpoint using fetch api. Following the same
helloWorld activity example, your index.svelte file should look like below:
Note that you must declare the props with let { activity }: {activity: Activity} = $props();.
<script lang="ts">
import { type Activity } from "$lib/aoh/wfd/activitySDK/api";
// must declare this props to receive activity from parent
let { activity }: {activity: Activity} = $props();
let value: string = $state(activity.getParameter("name") || "");
onMount(async () => {
if (activity.getUiState("data") == null) {
const response = await fetch('wfd/api/helloworld', {
method: 'GET',
})
const data = await response.json();
activity.setUiState("data", data);
}
});
</script>
<input type="text" bind:value onblur={() => activity.setParameter("name", value)} />
activity.setUiState- use this api to persist the value other than input fieldactivity.getUiState- use this api to get the value other than input field
More Examples
Workflow Designer comes with InAppNotification activity which is a custom activity UI. You can find the source code
here to
learn more about how to write a custom activity UI.