Repository: PROCEED-Labs/proceed
Pull request #486: Performer property panel
Author: login: MaxiLein
Created: 2025-02-12T15:41:09Z | Merged: 2025-03-29T10:47:37Z

<!--
  Thank you for your contribution to this project!

  Please provide the following information about your changes,
  in order for us to approve and merge your proposal as quickly as possible.
-->

## Summary
- Added PotentialOwner and  ResponsibleParty to Modeler and BPMN
- both can be added via the Property panel in the editor
- Added a user and role mapping via PotentailOwnerStore

<!--
  Please give a concise description of your proposal.
-->

## Details
Elements are displayed as described in #420 & #451
closes [#420](https://github.com/PROCEED-Labs/proceed/issues/420)
closes [#451](https://github.com/PROCEED-Labs/proceed/issues/451)
<!--
  A list of changes and any additional information that could be relevant for this pull request.

Example:
- Function foobar() now takes an optional third parameter
- The version of dependency dep-js was changed to 1.3.7
-->



Diff:
diff --git a/src/helper-modules/bpmn-helper/customSchema.json b/src/helper-modules/bpmn-helper/customSchema.json
index 904651c7a..4c0d3f732 100644
--- a/src/helper-modules/bpmn-helper/customSchema.json
+++ b/src/helper-modules/bpmn-helper/customSchema.json
@@ -494,6 +494,23 @@
         }
       ]
     },
+
+    {
+      "name": "ResponsibleParty",
+      "superClass": ["bpmn:ResourceRole"]
+    },
+    {
+      "name": "ResposibilityElements",
+      "extends": ["bpmn:Gateway", "bpmn:Event"],
+      "properties": [
+        {
+          "name": "resources",
+          "type": "ResourceRole",
+          "isMany": true
+        }
+      ]
+    },
+
     {
       "name": "placeholder",
       "extends": ["bpmn:Task"],
diff --git a/src/helper-modules/bpmn-helper/src/PROCEED-CONSTANTS.js b/src/helper-modules/bpmn-helper/src/PROCEED-CONSTANTS.js
index fb2a0f994..5ca176959 100644
--- a/src/helper-modules/bpmn-helper/src/PROCEED-CONSTANTS.js
+++ b/src/helper-modules/bpmn-helper/src/PROCEED-CONSTANTS.js
@@ -101,6 +101,13 @@ function initXml(
   processId = `Process_${generateBpmnId()}`,
   startEventId = `StartEvent_${generateBpmnId()}`,
 ) {
+  /* If we decide to reintroduce resources: */
+  // <resource id="resource_user" name="User">
+  //     <resourceParameter id="user_id" isRequired="true" name="id"/>
+  //   </resource>
+  //   <resource id="resource_role" name="Role">
+  //     <resourceParameter id="role_id" isRequired="true" name="id"/>
+  //   </resource>
   const bpmn = `
   <?xml version="1.0" encoding="UTF-8"?>
   <definitions
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx
index 59b7c1047..310f79d76 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/page.tsx
@@ -1,6 +1,6 @@
 import { getCurrentEnvironment } from '@/components/auth';
 import Content from '@/components/content';
-import { getRoleById } from '@/lib/data/DTOs';
+import { getRoleWithMembersById } from '@/lib/data/DTOs';
 import UnauthorizedFallback from '@/components/unauthorized-fallback';
 import { toCaslResource } from '@/lib/ability/caslAbility';
 import { getMembers } from '@/lib/data/DTOs';
@@ -20,7 +20,7 @@ const Page = async ({
   params: { roleId: string; environmentId: string };
 }) => {
   const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
-  const role = await getRoleById(roleId, ability);
+  const role = await getRoleWithMembersById(roleId, ability);
   if (role && !ability.can('manage', toCaslResource('Role', role))) return <UnauthorizedFallback />;
 
   if (!role)
@@ -30,9 +30,7 @@ const Page = async ({
       </Content>
     );
 
-  const usersInRole = (await Promise.all(
-    role.members.map((member) => getUserById(member.userId)),
-  )) as AuthenticatedUser[];
+  const usersInRole = role.members;
   const roleUserSet = new Set(usersInRole.map((member) => member.id));
 
   const memberships = await getMembers(activeEnvironment.spaceId, ability);
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/role-members.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/role-members.tsx
index 0c797c569..0ad77442f 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/role-members.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/[roleId]/role-members.tsx
@@ -7,7 +7,7 @@ import { App, Button, Modal, Tooltip } from 'antd';
 import ConfirmationButton from '@/components/confirmation-button';
 import { addRoleMappings, deleteRoleMappings } from '@/lib/data/role-mappings';
 import { useRouter } from 'next/navigation';
-import { Role } from '@/lib/data/role-schema';
+import { Role, RoleWithMembers } from '@/lib/data/role-schema';
 import { AuthenticatedUser } from '@/lib/data/user-schema';
 import { useEnvironment } from '@/components/auth-can';
 import { wrapServerCall } from '@/lib/wrap-server-call';
@@ -89,7 +89,7 @@ const AddUserModal: FC<{
 
 const RoleMembers: FC<{
   role: Role;
-  usersInRole: AuthenticatedUser[];
+  usersInRole: RoleWithMembers['members'];
   usersNotInRole: AuthenticatedUser[];
 }> = ({ role, usersInRole, usersNotInRole }) => {
   const [addUserModalOpen, setAddUserModalOpen] = useState(false);
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
index 2d7f3459d..8690a755e 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
@@ -1,26 +1,19 @@
 import { getCurrentEnvironment } from '@/components/auth';
 import Content from '@/components/content';
-import { getRoles } from '@/lib/data/DTOs';
+import { getRolesWithMembers } from '@/lib/data/DTOs';
 import RolesPage from './role-page';
 import UnauthorizedFallback from '@/components/unauthorized-fallback';
-import { ComponentProps } from 'react';
-import { getUserById } from '@/lib/data/DTOs';
 
 const Page = async ({ params }: { params: { environmentId: string } }) => {
   const { ability, activeEnvironment } = await getCurrentEnvironment(params.environmentId);
 
   if (!ability.can('manage', 'Role')) return <UnauthorizedFallback />;
 
-  const roles = await getRoles(activeEnvironment.spaceId, ability);
-
-  for (let i = 0; i < roles.length; i++) {
-    // @ts-ignore
-    roles[i] = { members: roles[i].members.map(({ userId }) => getUserById(userId)), ...roles[i] };
-  }
+  const roles = await getRolesWithMembers(activeEnvironment.spaceId, ability);
 
   return (
     <Content title="Identity and Access Management">
-      <RolesPage roles={roles as ComponentProps<typeof RolesPage>['roles']} />
+      <RolesPage roles={roles} />
     </Content>
   );
 };
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-page.tsx
index b4e1588b6..1484ffbce 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-page.tsx
@@ -20,13 +20,17 @@ import ConfirmationButton from '@/components/confirmation-button';
 import { useRouter } from 'next/navigation';
 import RoleSidePanel from './role-side-panel';
 import { deleteRoles as serverDeleteRoles } from '@/lib/data/roles';
-import { Role } from '@/lib/data/role-schema';
+import { RoleWithMembers } from '@/lib/data/role-schema';
 import { useEnvironment } from '@/components/auth-can';
 import styles from './role-page.module.scss';
 import { useUserPreferences } from '@/lib/user-preferences';
 import cn from 'classnames';
 
-function getMembersRepresentation(members: Role['members']) {
+import { wrapServerCall } from '@/lib/wrap-server-call';
+import SelectionActions from '@/components/selection-actions';
+import { spaceURL, userRepresentation } from '@/lib/utils';
+
+function getMembersRepresentation(members: RoleWithMembers['members']) {
   if (members.length === 0) return undefined;
 
   return members.map((member) => userRepresentation(member)).join(', ');
@@ -34,15 +38,10 @@ function getMembersRepresentation(members: Role['members']) {
 
 const numberOfRows =
   typeof window !== 'undefined' ? Math.floor((window?.innerHeight - 410) / 47) : 10;
-import { wrapServerCall } from '@/lib/wrap-server-call';
-import SelectionActions from '@/components/selection-actions';
-import { spaceURL, userRepresentation } from '@/lib/utils';
-import { AuthenticatedUser } from '@/lib/data/user-schema';
 
-type ModifiedRole = Role & { members: AuthenticatedUser[] };
-export type FilteredRole = ReplaceKeysWithHighlighted<ModifiedRole, 'name'>;
+export type FilteredRole = ReplaceKeysWithHighlighted<RoleWithMembers, 'name'>;
 
-const RolesPage = ({ roles }: { roles: ModifiedRole[] }) => {
+const RolesPage = ({ roles }: { roles: RoleWithMembers[] }) => {
   const app = App.useApp();
   const ability = useAbilityStore((store) => store.ability);
   const router = useRouter();
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-side-panel.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-side-panel.tsx
index b995c49ec..d1ad1fca8 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-side-panel.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/role-side-panel.tsx
@@ -6,12 +6,14 @@ import CollapsibleCard from '@/components/collapsible-card';
 import { useUserPreferences } from '@/lib/user-preferences';
 import ResizableElement, { ResizableElementRefType } from '@/components/ResizableElement';
 import { FilteredRole } from './role-page';
-import { AuthenticatedUser } from '@/lib/data/user-schema';
 import UserAvatar from '@/components/user-avatar';
 import { userRepresentation } from '@/lib/utils';
+import { RoleWithMembers } from '@/lib/data/role-schema';
+
+export type MemberInfo = RoleWithMembers['members'][number];
 
 const RoleContent: FC<{
-  role: (Omit<FilteredRole, 'members'> & { members: AuthenticatedUser[] }) | null;
+  role: (Omit<FilteredRole, 'members'> & { members: MemberInfo[] }) | null;
 }> = ({ role }) => {
   return (
     <>
@@ -46,7 +48,7 @@ const RoleContent: FC<{
             <>
               <Typography.Title>Members</Typography.Title>
               {role.members.map((user) => (
-                <Space>
+                <Space key={user.id}>
                   <UserAvatar user={user} avatarProps={{ size: 30 }} />
                   <Typography.Text>{userRepresentation(user)}</Typography.Text>
                 </Space>
@@ -62,7 +64,7 @@ const RoleContent: FC<{
 };
 
 type RoleSidePanelProps = PropsWithChildren<{
-  role: (FilteredRole & { members: AuthenticatedUser[] }) | null;
+  role: (FilteredRole & { members: MemberInfo[] }) | null;
   setShowMobileRoleSider: Dispatch<SetStateAction<boolean>>;
   showMobileRoleSider: boolean;
 }>;
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/user-side-panel.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/user-side-panel.tsx
index ca495c2e7..61305c1de 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/user-side-panel.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/iam/users/user-side-panel.tsx
@@ -41,6 +41,7 @@ const UserSiderContent: FC<{ user: (ListUser & { roles?: Role[] }) | null }> = (
           <Typography.Title>Roles</Typography.Title>
           {user.roles.map((role) => (
             <SpaceLink
+              key={role.id}
               href={`/iam/roles/${role.id}`}
               style={{
                 display: 'block',
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/page.tsx
index f6a2bd315..b0174bc9f 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/page.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/page.tsx
@@ -4,9 +4,10 @@ import styles from './page.module.scss';
 import Modeler from './modeler';
 import { toCaslResource } from '@/lib/ability/caslAbility';
 import AddUserControls from '@/components/add-user-controls';
-import { getProcess, getProcesses } from '@/lib/data/DTOs';
+import { getProcess, getProcesses, getRolesWithMembers } from '@/lib/data/DTOs';
 import { getProcessBPMN } from '@/lib/data/processes';
 import { UnauthorizedError } from '@/lib/ability/abilityHelper';
+import { RoleType, UserType } from './use-potentialOwner-store';
 import type { Process } from '@/lib/data/process-schema';
 
 type ProcessProps = {
@@ -25,6 +26,22 @@ const Process = async ({ params: { processId, environmentId }, searchParams }: P
   const process = await getProcess(processId, !selectedVersionId);
   const processes = await getProcesses(activeEnvironment.spaceId, ability, false);
 
+  const rawRoles = activeEnvironment.isOrganization
+    ? await getRolesWithMembers(activeEnvironment.spaceId, ability)
+    : [];
+
+  const roles = rawRoles.reduce((acc, role) => ({ ...acc, [role.id]: role.name }), {} as RoleType);
+  const user = rawRoles.reduce((acc, role) => {
+    role.members.forEach((member) => {
+      acc[member.id] = {
+        userName: member.username,
+        name: member.firstName + ' ' + member.lastName,
+      };
+    });
+
+    return acc;
+  }, {} as UserType);
+
   if (!ability.can('view', toCaslResource('Process', process))) {
     throw new UnauthorizedError();
   }
@@ -40,7 +57,14 @@ const Process = async ({ params: { processId, environmentId }, searchParams }: P
   // client component from here.
   return (
     <>
-      <Wrapper processName={process.name} processes={processes}>
+      <Wrapper
+        processName={process.name}
+        processes={processes}
+        potentialOwner={{
+          roles,
+          user,
+        }}
+      >
         <Modeler
           className={styles.Modeler}
           process={{ ...process, bpmn: selectedVersionBpmn as string } as Process}
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/potential-owner.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/potential-owner.tsx
new file mode 100644
index 000000000..281900711
--- /dev/null
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/potential-owner.tsx
@@ -0,0 +1,308 @@
+import React, { FC, useEffect, useState } from 'react';
+import type { ElementLike } from 'diagram-js/lib/core/Types';
+import { Divider, Space, Tooltip, Button, Cascader } from 'antd';
+import { UserOutlined, TeamOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import { BPMNCanvasRef } from '@/components/bpmn-canvas';
+import type { CascaderProps, GetProp } from 'antd';
+import usePotentialOwnerStore, { RoleType, UserType } from './use-potentialOwner-store';
+import useModelerStateStore from './use-modeler-state-store';
+import { Shape } from 'bpmn-js/lib/model/Types';
+import Modeler from 'bpmn-js/lib/Modeler';
+import { is, isAny } from 'bpmn-js/lib/util/ModelUtil';
+
+type PotentialOwnerProps = {
+  selectedElement: ElementLike;
+  modeler: BPMNCanvasRef | null;
+};
+
+export type Option = {
+  value: string;
+  label: string | React.ReactNode;
+  children?: Option[];
+};
+
+export type DefaultOptionType = GetProp<CascaderProps, 'options'>[number];
+
+export function generateOptions(user: UserType, roles: RoleType): Option[] {
+  return [
+    {
+      label: (
+        <>
+          <UserOutlined />
+          User
+        </>
+      ),
+      value: 'all-user',
+      children: Object.entries(user).map(([id, { name, userName }]) => ({
+        value: `user|${id}`,
+        label: userName || name,
+        key: id,
+      })),
+    },
+    {
+      label: (
+        <>
+          <TeamOutlined />
+          Roles
+        </>
+      ),
+      value: 'all-roles',
+      children: Object.entries(roles).map(([id, name]) => ({
+        key: id,
+        value: `roles|${id}`,
+        label: name,
+      })),
+    },
+  ];
+}
+
+function updateResource(
+  type: string,
+  element: Shape,
+  modeler: BPMNCanvasRef,
+  availableResources: { user: UserType; roles: RoleType },
+  selected: string[][],
+) {
+  const resourceIds = selected
+    .map((v) => v[v.length - 1])
+    .reduce(
+      (acc, value) => {
+        if (value === 'all-user') {
+          acc.user = Object.keys(availableResources.user);
+        } else if (value === 'all-roles') {
+          acc.roles = Object.keys(availableResources.roles);
+        } else {
+          const [type, ...id] = value.split('|') as ['user' | 'roles', string];
+          acc[type].push(id.join('|'));
+        }
+        return acc;
+      },
+      { user: [], roles: [] } as { user: string[]; roles: string[] },
+    );
+
+  if (modeler) {
+    const modeling = modeler.getModeling();
+    const factory = modeler.getFactory();
+
+    const newResources = (element.businessObject?.resources || []).filter((r: any) => !is(r, type));
+
+    if (resourceIds.roles.length || resourceIds.user.length) {
+      const expression = factory.create('bpmn:Expression', {
+        body: JSON.stringify(resourceIds),
+      });
+      const resourceAssignmentExpression = factory.create('bpmn:ResourceAssignmentExpression', {
+        expression,
+      });
+      const resource = factory.create(type, {
+        resourceAssignmentExpression,
+      });
+      newResources.push(resource);
+    }
+
+    modeling.updateModdleProperties(element, element.businessObject, {
+      resources: newResources,
+    });
+  }
+}
+
+const filter = (inputValue: string, path: DefaultOptionType[]) =>
+  path.some((option) => `${option?.value}`.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
+
+export const PotentialOwner: FC<PotentialOwnerProps> = ({ selectedElement, modeler }) => {
+  const { user, roles } = usePotentialOwnerStore();
+
+  const { user: selectedUser, roles: selectedRoles } = useBPMNResources(
+    selectedElement,
+    'bpmn:PotentialOwner',
+  );
+
+  const options: Option[] = generateOptions(user, roles);
+
+  function setPotentialOwner(value: string[][]) {
+    if (modeler) {
+      updateResource(
+        'bpmn:PotentialOwner',
+        selectedElement as Shape,
+        modeler,
+        { user, roles },
+        value,
+      );
+    }
+  }
+
+  return (
+    <>
+      <Space
+        direction="vertical"
+        style={{ width: '100%' }}
+        role="group"
+        aria-labelledby="user-task-performer-selection"
+      >
+        {selectedElement.type === 'bpmn:UserTask' && (
+          <>
+            <Divider style={{ display: 'flex', alignItems: 'center', fontSize: '0.85rem' }}>
+              <span style={{ marginRight: '0.3em', marginBottom: '0.1rem' }}>
+                Possible Performers
+              </span>
+              <Tooltip
+                title={
+                  <div>
+                    Select User and Roles who will see this User-Task in their Tasklist. They can
+                    claim and work on it.
+                  </div>
+                }
+              >
+                <Button type="text" icon={<QuestionCircleOutlined />} />
+              </Tooltip>
+            </Divider>
+            {/* User and Role selection */}
+            <Cascader
+              options={options}
+              placeholder="Select User or Roles that can claim this task"
+              style={{ width: '100%' }}
+              multiple
+              showSearch={{ filter }}
+              // @ts-ignore
+              onChange={setPotentialOwner}
+              value={[...selectedUser, ...selectedRoles]}
+            />
+          </>
+        )}
+      </Space>
+    </>
+  );
+};
+
+type ResponsibilityProps = {
+  selectedElement: ElementLike;
+  modeler: BPMNCanvasRef | null;
+};
+
+export const ResponsibleParty: FC<ResponsibilityProps> = ({ selectedElement, modeler }) => {
+  const { user, roles } = usePotentialOwnerStore();
+
+  const { user: selectedUser, roles: selectedRoles } = useBPMNResources(
+    selectedElement,
+    'proceed:ResponsibleParty',
+  );
+
+  if (!isAny(selectedElement, ['bpmn:Activity', 'bpmn:Gateway', 'bpmn:Event', 'bpmn:Process']))
+    return null;
+
+  const options: Option[] = generateOptions(user, roles);
+
+  const setResponsible = (value: string[][]) => {
+    if (modeler) {
+      updateResource(
+        'proceed:ResponsibleParty',
+        selectedElement as Shape,
+        modeler,
+        { user, roles },
+        value,
+      );
+    }
+  };
+
+  return (
+    <>
+      <Space
+        direction="vertical"
+        style={{ width: '100%' }}
+        role="group"
+        aria-labelledby="responsibility-selection"
+      >
+        <>
+          <Divider style={{ display: 'flex', alignItems: 'center', fontSize: '0.85rem' }}>
+            <span style={{ marginRight: '0.3em', marginBottom: '0.1rem' }}>
+              Responsible Persons
+            </span>
+            <Tooltip
+              title={
+                <div>
+                  Select User and Roles responsible for the execution (i.e. not necessarily the User
+                  or Role that executes it).
+                </div>
+              }
+            >
+              <Button type="text" icon={<QuestionCircleOutlined />} />
+            </Tooltip>
+          </Divider>
+          {/* User and Role selection */}
+          <Cascader
+            options={options}
+            placeholder="Select User or Roles responsible"
+            style={{ width: '100%' }}
+            multiple
+            showSearch={{ filter }}
+            // @ts-ignore
+            onChange={setResponsible}
+            value={[...selectedUser, ...selectedRoles]}
+          />
+        </>
+      </Space>
+    </>
+  );
+};
+
+export function useBPMNResources(
+  selectedElement: ElementLike,
+  resourcetype: 'bpmn:PotentialOwner' | 'proceed:ResponsibleParty',
+) {
+  const [roles, setRoles] = useState<string[][]>([]);
+  const [user, setUser] = useState<string[][]>([]);
+
+  const modeler = useModelerStateStore((state) => state.modeler);
+
+  useEffect(() => {
+    const eventBus = modeler?.getEventBus();
+
+    if (eventBus) {
+      const getResources = (el: Shape) => {
+        const user: string[][] = [],
+          roles: string[][] = [];
+        el?.businessObject?.resources?.forEach((resource: any) => {
+          if (
+            resource.$type === resourcetype &&
+            resource.resourceAssignmentExpression?.expression?.body
+          ) {
+            try {
+              const { user: _user, roles: _roles } = JSON.parse(
+                resource.resourceAssignmentExpression.expression.body,
+              ) as {
+                user: string[];
+                roles: string[];
+              };
+              _user.forEach((id) => user.push(['all-user', `user|${id}`]));
+              _roles.forEach((id) => roles.push(['all-roles', `roles|${id}`]));
+            } catch {
+              // console.error('Error parsing expression body');
+            }
+          }
+        });
+        return { user, roles };
+      };
+
+      const { user, roles } = getResources(selectedElement as Shape);
+      setUser(user);
+      setRoles(roles);
+
+      const onUpdate = (event: any) => {
+        if (event.context.properties?.resources) {
+          const { user, roles } = getResources(event.context.element as Shape);
+          setUser(user);
+          setRoles(roles);
+        }
+      };
+
+      eventBus.on('commandStack.element.updateModdleProperties.postExecuted', onUpdate);
+
+      return () => {
+        eventBus.off('commandStack.element.updateModdleProperties.postExecuted', onUpdate);
+      };
+    }
+  }, [selectedElement, modeler]);
+
+  return { user, roles };
+}
+
+export default PotentialOwner;
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/properties-panel.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/properties-panel.tsx
index c17fa816b..0dd699654 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/properties-panel.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/properties-panel.tsx
@@ -1,10 +1,11 @@
 'use client';
 
 import useModelerStateStore from './use-modeler-state-store';
-import React, { FocusEvent, useEffect, useRef, useState } from 'react';
+import React, { FocusEvent, use, useEffect, useRef, useState } from 'react';
 import styles from './properties-panel.module.scss';
 
-import { Input, ColorPicker, Space, Grid, Divider, Modal } from 'antd';
+import { Input, ColorPicker, Space, Grid, Divider, Modal, Tabs } from 'antd';
+import type { TabsProps } from 'antd';
 import type { ElementLike } from 'diagram-js/lib/core/Types';
 
 import { CloseOutlined } from '@ant-design/icons';
@@ -25,6 +26,8 @@ import DescriptionSection from './description-section';
 import PlannedCostInput from './planned-cost-input';
 import { updateProcess } from '@/lib/data/processes';
 import { useEnvironment } from '@/components/auth-can';
+import { PotentialOwner, ResponsibleParty } from './potential-owner';
+import { EnvVarsContext } from '@/components/env-vars-context';
 import { getBackgroundColor, getBorderColor, getTextColor } from '@/lib/helpers/bpmn-js-helpers';
 import { Shape } from 'bpmn-js/lib/model/Types';
 
@@ -35,6 +38,9 @@ type PropertiesPanelContentProperties = {
 const PropertiesPanelContent: React.FC<PropertiesPanelContentProperties> = ({
   selectedElement,
 }) => {
+  const env = use(EnvVarsContext);
+  const environment = useEnvironment();
+
   const { spaceId } = useEnvironment();
   const metaData = getMetaDataFromElement(selectedElement.businessObject);
   const backgroundColor = getBackgroundColor(selectedElement as Shape);
@@ -152,131 +158,182 @@ const PropertiesPanelContent: React.FC<PropertiesPanelContentProperties> = ({
     });
   };
 
-  return (
-    <Space
-      direction="vertical"
-      size="large"
-      style={{ width: '100%', fontSize: '0.75rem' }}
-      className={styles.PropertiesPanel}
-    >
-      <Space
-        direction="vertical"
-        style={{ width: '100%' }}
-        role="group"
-        aria-labelledby="general-title"
-      >
-        <Divider>
+  /* TABS: */
+  const [activeTab, setActiveTab] = useState('Property-Panel-General');
+
+  const changeToTab = (key: string) => {
+    setActiveTab(key);
+  };
+
+  const tabs: TabsProps['items'] = [
+    {
+      key: 'Property-Panel-General',
+      label: 'General Properties',
+      children: (
+        <>
+          <Space
+            direction="vertical"
+            style={{ width: '100%' }}
+            role="group"
+            aria-labelledby="general-title"
+            aria-label="General Properties"
+          >
+            {/* <Divider>
           <span id="general-title" style={{ fontSize: '0.85rem' }}>
             General
           </span>
-        </Divider>
-        <Input
-          name="Name"
-          placeholder="Element Name"
-          style={{ fontSize: '0.85rem' }}
-          addonBefore="Name"
-          value={name}
-          onChange={(e) => setName(e.target.value)}
-          onBlur={handleNameChange}
-        />
+        </Divider> */}
+            <Input
+              name="Name"
+              placeholder="Element Name"
+              style={{ fontSize: '0.85rem' }}
+              addonBefore="Name"
+              value={name}
+              onChange={(e) => setName(e.target.value)}
+              onBlur={handleNameChange}
+            />
 
-        <div
-          style={{
-            width: '75%',
-            display: 'flex',
-            justifyContent: 'center',
-            margin: 'auto',
-            marginTop: '1rem',
-          }}
-        >
-          <ImageSelectionSection
-            imageFileName={metaData.overviewImage && metaData.overviewImage.split('/images/').pop()}
-            onImageUpdate={(imageFileName) => {
-              updateMetaData('overviewImage', imageFileName);
-            }}
-          ></ImageSelectionSection>
-        </div>
-      </Space>
+            <div
+              style={{
+                width: '75%',
+                display: 'flex',
+                justifyContent: 'center',
+                margin: 'auto',
+                marginTop: '1rem',
+              }}
+            >
+              <ImageSelectionSection
+                imageFileName={
+                  metaData.overviewImage && metaData.overviewImage.split('/images/').pop()
+                }
+                onImageUpdate={(imageFileName) => {
+                  updateMetaData('overviewImage', imageFileName);
+                }}
+              ></ImageSelectionSection>
+            </div>
+          </Space>
+          <DescriptionSection selectedElement={selectedElement}></DescriptionSection>
+          {/* Responsibility */}
+          <ResponsibleParty selectedElement={selectedElement} modeler={modeler} />
+          <MilestoneSelectionSection selectedElement={selectedElement}></MilestoneSelectionSection>
+          <Space direction="vertical" style={{ width: '100%' }}>
+            <Divider style={{ fontSize: '0.85rem' }}>Properties</Divider>
+            <PlannedCostInput
+              costsPlanned={
+                costsPlanned
+                  ? { value: costsPlanned.value, currency: costsPlanned.unit }
+                  : { currency: 'EUR' }
+              }
+              onInput={({ value, currency }) => {
+                updateMetaData('costsPlanned', value, { unit: currency });
+              }}
+            ></PlannedCostInput>
+            <PlannedDurationInput
+              onChange={(changedTimePlannedDuration) => {
+                updateMetaData('timePlannedDuration', changedTimePlannedDuration);
+              }}
+              timePlannedDuration={timePlannedDuration || ''}
+            ></PlannedDurationInput>
+          </Space>
 
-      <DescriptionSection selectedElement={selectedElement}></DescriptionSection>
+          <CustomPropertySection
+            metaData={metaData}
+            onChange={(name, value, oldName) => {
+              updateMetaData(
+                'property',
+                { value: value, attributes: { name } },
+                undefined,
+                oldName
+                  ? {
+                      name: oldName,
+                    }
+                  : undefined,
+              );
+            }}
+          ></CustomPropertySection>
 
-      <MilestoneSelectionSection selectedElement={selectedElement}></MilestoneSelectionSection>
+          {selectedElement.type !== 'bpmn:Process' &&
+            selectedElement.type !== 'bpmn:Collaboration' && (
+              <Space direction="vertical" style={{ width: '100%' }}>
+                <Divider style={{ fontSize: '0.85rem' }}>Colors</Divider>
+                <Space>
+                  <ColorPicker
+                    size="small"
+                    disabledAlpha
+                    presets={colorPickerPresets}
+                    value={backgroundColor}
+                    onChange={(_, hex) => updateBackgroundColor(hex)}
+                  />
+                  <span>Background Color</span>
+                </Space>
+                <Space>
+                  <ColorPicker
+                    size="small"
+                    disabledAlpha
+                    presets={colorPickerPresets}
+                    value={borderColor}
+                    onChange={(_, hex) => updateBorderColor(hex)}
+                  />
+                  <span>Border Color</span>
+                </Space>
 
-      <Space direction="vertical" style={{ width: '100%' }}>
-        <Divider style={{ fontSize: '0.85rem' }}>Properties</Divider>
-        <PlannedCostInput
-          costsPlanned={
-            costsPlanned
-              ? { value: costsPlanned.value, currency: costsPlanned.unit }
-              : { currency: 'EUR' }
-          }
-          onInput={({ value, currency }) => {
-            updateMetaData('costsPlanned', value, { unit: currency });
-          }}
-        ></PlannedCostInput>
-        <PlannedDurationInput
-          onChange={(changedTimePlannedDuration) => {
-            updateMetaData('timePlannedDuration', changedTimePlannedDuration);
-          }}
-          timePlannedDuration={timePlannedDuration || ''}
-        ></PlannedDurationInput>
-      </Space>
+                {selectedElement?.di?.label && (
+                  <Space>
+                    <ColorPicker
+                      size="small"
+                      disabledAlpha
+                      presets={colorPickerPresets}
+                      value={textColor}
+                      onChange={(_, hex) => updateTextColor(hex)}
+                    />
+                    <span>Text Color</span>
+                  </Space>
+                )}
+              </Space>
+            )}
+        </>
+      ),
+    },
+  ];
 
-      <CustomPropertySection
-        metaData={metaData}
-        onChange={(name, value, oldName) => {
-          updateMetaData(
-            'property',
-            { value: value, attributes: { name } },
-            undefined,
-            oldName
-              ? {
-                  name: oldName,
-                }
-              : undefined,
-          );
-        }}
-      ></CustomPropertySection>
-
-      {selectedElement.type !== 'bpmn:Process' && selectedElement.type !== 'bpmn:Collaboration' && (
-        <Space direction="vertical" style={{ width: '100%' }}>
-          <Divider style={{ fontSize: '0.85rem' }}>Colors</Divider>
-          <Space>
-            <ColorPicker
-              size="small"
-              disabledAlpha
-              presets={colorPickerPresets}
-              value={backgroundColor}
-              onChange={(_, hex) => updateBackgroundColor(hex)}
-            />
-            <span>Background Color</span>
-          </Space>
-          <Space>
-            <ColorPicker
-              size="small"
-              disabledAlpha
-              presets={colorPickerPresets}
-              value={borderColor}
-              onChange={(_, hex) => updateBorderColor(hex)}
-            />
-            <span>Border Color</span>
+  if (env.PROCEED_PUBLIC_ENABLE_EXECUTION) {
+    tabs.push({
+      key: 'Property-Panel-Execution',
+      label: 'Automation Properties',
+      children: (
+        <>
+          <Space
+            direction="vertical"
+            style={{ width: '100%' }}
+            role="group"
+            aria-labelledby="general-title"
+          >
+            {environment?.isOrganization && (
+              <PotentialOwner selectedElement={selectedElement} modeler={modeler} />
+            )}
           </Space>
+        </>
+      ),
+    });
+  }
+  /* ---- */
 
-          {selectedElement?.di?.label && (
-            <Space>
-              <ColorPicker
-                size="small"
-                disabledAlpha
-                presets={colorPickerPresets}
-                value={textColor}
-                onChange={(_, hex) => updateTextColor(hex)}
-              />
-              <span>Text Color</span>
-            </Space>
-          )}
-        </Space>
-      )}
-    </Space>
+  return (
+    <>
+      <Space
+        direction="vertical"
+        size="large"
+        style={{ width: '100%', fontSize: '0.75rem', marginTop: '-40px' }}
+        className={styles.PropertiesPanel}
+      >
+        <Tabs
+          defaultActiveKey={activeTab}
+          items={tabs}
+          onChange={changeToTab}
+          activeKey={activeTab}
+        />
+      </Space>
+    </>
   );
 };
 
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/use-potentialOwner-store.ts b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/use-potentialOwner-store.ts
new file mode 100644
index 000000000..3edf1a207
--- /dev/null
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/use-potentialOwner-store.ts
@@ -0,0 +1,36 @@
+import { create } from 'zustand';
+import { immer } from 'zustand/middleware/immer';
+
+export type UserType = {
+  [key: string]: {
+    userName?: string;
+    name: string;
+  };
+};
+
+export type RoleType = Record<string, string>;
+
+type PotentailOwnerStore = {
+  user: UserType;
+  roles: RoleType;
+
+  setUser: (user: UserType) => void;
+  setRoles: (roles: RoleType) => void;
+};
+
+const usePotentialOwnerStore = create<PotentailOwnerStore>()(
+  immer((set) => ({
+    user: {},
+    roles: {},
+    setUser: (user) =>
+      set((state) => {
+        state.user = user;
+      }),
+    setRoles: (roles) =>
+      set((state) => {
+        state.roles = roles;
+      }),
+  })),
+);
+
+export default usePotentialOwnerStore;
diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/wrapper.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/wrapper.tsx
index d60ca172e..e705c1748 100644
--- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/wrapper.tsx
+++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/wrapper.tsx
@@ -30,6 +30,7 @@ import { isPlane } from 'bpmn-js/lib/util/DrilldownUtil';
 import { Root } from 'bpmn-js/lib/model/Types';
 import { spaceURL } from '@/lib/utils';
 import { updateProcess } from '@/lib/data/processes';
+import usePotentialOwnerStore, { UserType, RoleType } from './use-potentialOwner-store';
 
 type SubprocessInfo = {
   id?: string;
@@ -39,15 +40,29 @@ type SubprocessInfo = {
 type WrapperProps = PropsWithChildren<{
   processName: string;
   processes: { id: string; name: string }[];
+  potentialOwner: { user: UserType; roles: RoleType };
 }>;
 
-const Wrapper = ({ children, processName, processes }: WrapperProps) => {
+const Wrapper = ({
+  children,
+  processName,
+  processes,
+  potentialOwner: { user, roles },
+}: WrapperProps) => {
   // TODO: check if params is correct after fix release. And maybe don't need
   // refresh in processes.tsx anymore?
   const { processId } = useParams();
   const { spaceId } = useEnvironment();
   const pathname = usePathname();
   const environment = useEnvironment();
+  const setUser = usePotentialOwnerStore((state) => state.setUser);
+  const setRoles = usePotentialOwnerStore((state) => state.setRoles);
+
+  useEffect(() => {
+    setUser(user);
+    setRoles(roles);
+  }, []);
+
   const [closed, setClosed] = useState(false);
   const router = useRouter();
   const modeler = useModelerStateStore((state) => state.modeler);
diff --git a/src/management-system-v2/components/bpmn-canvas.tsx b/src/management-system-v2/components/bpmn-canvas.tsx
index 42cf4de4a..71ea43ba5 100644
--- a/src/management-system-v2/components/bpmn-canvas.tsx
+++ b/src/management-system-v2/components/bpmn-canvas.tsx
@@ -4,6 +4,7 @@ import React, { forwardRef, use, useEffect, useImperativeHandle, useRef } from '
 import type ModelerType from 'bpmn-js/lib/Modeler';
 import type NavigatedViewerType from 'bpmn-js/lib/NavigatedViewer';
 import type ViewerType from 'bpmn-js/lib/Viewer';
+import type EventBus from 'diagram-js/lib/core/EventBus';
 import type Canvas from 'diagram-js/lib/core/Canvas';
 import type ZoomScroll from 'diagram-js/lib/navigation/zoomscroll/ZoomScroll';
 import type Selection from 'diagram-js/lib/features/selection/Selection';
@@ -96,6 +97,7 @@ export interface BPMNCanvasRef {
   getElement: (id: string) => Element | undefined;
   getAllElements: () => ElementLike[];
   getCurrentRoot: () => Element | undefined;
+  getEventBus: () => EventBus;
   getCanvas: () => Canvas;
   getZoomScroll: () => ZoomScroll;
   getSelection: () => Selection;
@@ -168,6 +170,9 @@ const BPMNCanvas = forwardRef<BPMNCanvasRef, BPMNCanvasProps>(
             modeler.current!.get<Canvas>('canvas').getRootElement().businessObject.id,
           ) as Element;
       },
+      getEventBus: () => {
+        return modeler.current!.get<EventBus>('eventBus');
+      },
       getCanvas: () => {
         return modeler.current!.get<Canvas>('canvas');
       },
@@ -336,7 +341,6 @@ const BPMNCanvas = forwardRef<BPMNCanvasRef, BPMNCanvasProps>(
 
         // Import the new bpmn.
         await m.importXML(bpmn.bpmn);
-
         loadingXML.current = false;
 
         if (m !== modeler.current) {
diff --git a/src/management-system-v2/components/user-list.tsx b/src/management-system-v2/components/user-list.tsx
index 644085a3d..54678ac1a 100644
--- a/src/management-system-v2/components/user-list.tsx
+++ b/src/management-system-v2/components/user-list.tsx
@@ -5,18 +5,15 @@ import { Space, Button, Table, Grid } from 'antd';
 import { UnorderedListOutlined, AppstoreOutlined } from '@ant-design/icons';
 import useFuzySearch, { ReplaceKeysWithHighlighted } from '@/lib/useFuzySearch';
 import Bar from '@/components/bar';
-import { AuthenticatedUser } from '@/lib/data/user-schema';
 import styles from './user-list.module.scss';
 import { useUserPreferences } from '@/lib/user-preferences';
 import cn from 'classnames';
 import ElementList from './item-list-view';
 import UserAvatar from './user-avatar';
 import SelectionActions from './selection-actions';
+import { RoleWithMembers } from '@/lib/data/role-schema';
 
-type _ListUser = Partial<
-  Omit<AuthenticatedUser, 'id' | 'firstName' | 'lastName' | 'username' | 'email'>
-> &
-  Pick<AuthenticatedUser, 'id' | 'firstName' | 'lastName' | 'username' | 'email'> & {};
+type _ListUser = RoleWithMembers['members'][number];
 export type ListUser = ReplaceKeysWithHighlighted<
   _ListUser,
   'firstName' | 'lastName' | 'username' | 'email'
diff --git a/src/management-system-v2/lib/data/DTOs.ts b/src/management-system-v2/lib/data/DTOs.ts
index e5d1587fd..79759ad8a 100644
--- a/src/management-system-v2/lib/data/DTOs.ts
+++ b/src/management-system-v2/lib/data/DTOs.ts
@@ -123,12 +123,24 @@ export async function getRoleById(roleId: string, ability?: Ability) {
     : await rolesModuleLegacy.getRoleById(roleId, ability);
 }
 
+export async function getRoleWithMembersById(roleId: string, ability?: Ability) {
+  return enableUseDB
+    ? await rolesModuleDB.getRoleWithMembersById(roleId, ability)
+    : await rolesModuleLegacy.getRoleWithMembersById(roleId, ability);
+}
+
 export async function getRoles(environmentId: string, ability?: Ability) {
   return enableUseDB
     ? await rolesModuleDB.getRoles(environmentId, ability)
     : await rolesModuleLegacy.getRoles(environmentId, ability);
 }
 
+export async function getRolesWithMembers(environmentId?: string, ability?: Ability) {
+  return enableUseDB
+    ? await rolesModuleDB.getRolesWithMembers(environmentId, ability)
+    : await rolesModuleLegacy.getRolesWithMembers(environmentId, ability);
+}
+
 export async function organizationHasLogo(organisationId: string) {
   return enableUseDB
     ? await environmentModuleDB.organizationHasLogo(organisationId)
diff --git a/src/management-system-v2/lib/data/db/iam/roles.ts b/src/management-system-v2/lib/data/db/iam/roles.ts
index 32f55768b..b43eda806 100644
--- a/src/management-system-v2/lib/data/db/iam/roles.ts
+++ b/src/management-system-v2/lib/data/db/iam/roles.ts
@@ -1,13 +1,24 @@
 import { v4 } from 'uuid';
 import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
 import { toCaslResource } from '@/lib/ability/caslAbility';
-import { Role, RoleInput, RoleInputSchema } from '../../role-schema';
+import { Role, RoleInput, RoleInputSchema, RoleWithMembers } from '../../role-schema';
 import { rulesCacheDeleteAll } from '@/lib/authorization/authorization';
 import db from '@/lib/data/db';
 import { Prisma } from '@prisma/client';
 
 /** Returns all roles in form of an array */
 export async function getRoles(environmentId?: string, ability?: Ability) {
+  const roles = await db.role.findMany({
+    where: environmentId ? { environmentId: environmentId } : undefined,
+  });
+
+  const filteredRoles = ability ? ability.filter('view', 'Role', roles) : roles;
+
+  return filteredRoles as Role[];
+}
+
+/** Returns all roles in form of an array including the members of each role included in its data */
+export async function getRolesWithMembers(environmentId?: string, ability?: Ability) {
   const roles = await db.role.findMany({
     where: environmentId ? { environmentId: environmentId } : undefined,
     include: {
@@ -27,9 +38,18 @@ export async function getRoles(environmentId?: string, ability?: Ability) {
     },
   });
 
-  const filteredRoles = ability ? ability.filter('view', 'Role', roles) : roles;
+  const mappedRoles = roles.map((role) => ({
+    ...role,
+    members: role.members.map((member) => member.user),
+  })) as RoleWithMembers[];
 
-  return filteredRoles as Role[];
+  const filteredRoles = ability
+    ? ability
+        .filter('view', 'Role', mappedRoles)
+        .map((role) => ({ ...role, members: ability.filter('view', 'User', role.members) }))
+    : mappedRoles;
+
+  return filteredRoles;
 }
 
 /**
@@ -64,9 +84,6 @@ export async function getRoleById(roleId: string, ability?: Ability) {
     where: {
       id: roleId,
     },
-    include: {
-      members: true,
-    },
   });
 
   if (!ability) return role as Role;
@@ -76,6 +93,50 @@ export async function getRoleById(roleId: string, ability?: Ability) {
   return role as Role;
 }
 
+/**
+ * Returns a role based on role id including information about the roles members
+ *
+ * @throws {UnauthorizedError}
+ */
+export async function getRoleWithMembersById(roleId: string, ability?: Ability) {
+  const role = await db.role.findUnique({
+    where: {
+      id: roleId,
+    },
+    include: {
+      members: {
+        include: {
+          user: {
+            select: {
+              id: true,
+              username: true,
+              firstName: true,
+              lastName: true,
+              email: true,
+            },
+          },
+        },
+      },
+    },
+  });
+
+  if (!role) return null;
+
+  const mappedRole = {
+    ...role,
+    members: role.members.map((member) => member.user),
+  } as RoleWithMembers;
+
+  if (!ability) return mappedRole;
+
+  if (mappedRole && !ability.can('view', toCaslResource('Role', mappedRole)))
+    throw new UnauthorizedError();
+
+  return ability
+    ? { ...mappedRole, members: ability.filter('view', 'User', mappedRole.members) }
+    : mappedRole;
+}
+
 /**
  * Adds a new role for the PROCEED MS
  *
diff --git a/src/management-system-v2/lib/data/legacy/iam/role-mappings.ts b/src/management-system-v2/lib/data/legacy/iam/role-mappings.ts
index 95c1831d1..caa72ded4 100644
--- a/src/management-system-v2/lib/data/legacy/iam/role-mappings.ts
+++ b/src/management-system-v2/lib/data/legacy/iam/role-mappings.ts
@@ -4,8 +4,8 @@ import { getRoleById, roleMetaObjects } from './roles';
 import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
 import { toCaslResource } from '@/lib/ability/caslAbility';
 import { z } from 'zod';
-import { getUserById, usersMetaObject } from './users';
-import { environmentsMetaObject, getEnvironmentById } from './environments';
+import { usersMetaObject } from './users';
+import { environmentsMetaObject } from './environments';
 
 const RoleMappingInputSchema = z.object({
   roleId: z.string(),
diff --git a/src/management-system-v2/lib/data/legacy/iam/roles.ts b/src/management-system-v2/lib/data/legacy/iam/roles.ts
index d198c47d7..dda3aa172 100644
--- a/src/management-system-v2/lib/data/legacy/iam/roles.ts
+++ b/src/management-system-v2/lib/data/legacy/iam/roles.ts
@@ -1,17 +1,21 @@
 import { v4 } from 'uuid';
 import store from '../store.js';
-import { mergeIntoObject } from '../../../helpers/javascriptHelpers';
+import { asyncMap, mergeIntoObject } from '../../../helpers/javascriptHelpers';
 import { roleMappingsMetaObjects } from './role-mappings';
 import Ability, { UnauthorizedError } from '@/lib/ability/abilityHelper';
 import { ResourceType, toCaslResource } from '@/lib/ability/caslAbility';
-import { Role, RoleInput, RoleInputSchema } from '../../role-schema';
+import { Role, RoleInput, RoleInputSchema, RoleWithMembers } from '../../role-schema';
 import { rulesCacheDeleteAll } from '@/lib/authorization/authorization';
 import { getFolderById } from '../folders';
+import { getUsersInRole } from './users';
+import { on } from 'events';
+
+type RoleWithUserIds = Role & { members: { userId: string }[] };
 
 // @ts-ignore
 let firstInit = !global.roleMetaObjects;
 
-export let roleMetaObjects: Record<string, Role> =
+export let roleMetaObjects: Record<string, RoleWithUserIds> =
   // @ts-ignore
   global.roleMetaObjects || (global.roleMetaObjects = {});
 
@@ -25,7 +29,7 @@ export function init() {
   inited = true;
 
   // get roles that were persistently stored
-  const storedRoles = store.get('roles') as Role[];
+  const storedRoles = store.get('roles') as RoleWithUserIds[];
 
   // set roles store
   store.set('roles', 'roles', storedRoles);
@@ -44,6 +48,19 @@ export async function getRoles(environmentId?: string, ability?: Ability) {
   return ability ? ability.filter('view', 'Role', roles) : roles;
 }
 
+/** Returns all roles in form of an array including the members of each role included in its data */
+export async function getRolesWithMembers(environmentId?: string, ability?: Ability) {
+  const roles = await getRoles(environmentId, ability);
+
+  const rolesWithMembers = await asyncMap(roles, async (role) => {
+    const members = await getUsersInRole(role.id, ability);
+
+    return { ...role, members };
+  });
+
+  return rolesWithMembers as RoleWithMembers[];
+}
+
 /**
  * Returns a role by name
  *
@@ -77,6 +94,20 @@ export async function getRoleById(roleId: string, ability?: Ability) {
   return role as Role;
 }
 
+/**
+ * Returns a role based on role id including information about the roles members
+ *
+ * @throws {UnauthorizedError}
+ */
+export async function getRoleWithMembersById(roleId: string, ability?: Ability) {
+  const role = await getRoleById(roleId, ability);
+
+  const members = await getUsersInRole(roleId, ability);
+  const roleWithMembers = { ...role, members };
+
+  return roleWithMembers as RoleWithMembers;
+}
+
 /**
  * Adds a new role for the PROCEED MS
  *
diff --git a/src/management-system-v2/lib/data/legacy/iam/users.ts b/src/management-system-v2/lib/data/legacy/iam/users.ts
index 5e2d38f28..dba87b97e 100644
--- a/src/management-system-v2/lib/data/legacy/iam/users.ts
+++ b/src/management-system-v2/lib/data/legacy/iam/users.ts
@@ -7,15 +7,14 @@ import {
   OauthAccount,
   AuthenticatedUser,
   AuthenticatedUserSchema,
-  GuestUser,
-  GuestUserSchema,
 } from '../../user-schema';
 import { addEnvironment, deleteEnvironment } from './environments';
 import { OptionalKeys } from '@/lib/typescript-utils.js';
 import { getUserOrganizationEnvironments, removeMember } from './memberships';
-import { getRoleMappingByUserId } from './role-mappings';
+import { getRoleMappingByUserId, getRoleMappings } from './role-mappings';
 import { getRoles } from './roles';
 import { addSystemAdmin, getSystemAdmins } from './system-admins';
+import Ability from '@/lib/ability/abilityHelper.js';
 
 // @ts-ignore
 let firstInit = !global.usersMetaObject || !global.accountsMetaObject;
@@ -48,6 +47,14 @@ export function getUsers(page: number = 1, pageSize: number = 10) {
   };
 }
 
+export async function getUsersInRole(roleId: string, ability?: Ability) {
+  const roleMappings = await getRoleMappings(ability);
+
+  const userIds = roleMappings.filter((m) => m.roleId === roleId).map((m) => m.userId);
+
+  return userIds.map((id) => usersMetaObject[id]);
+}
+
 export async function getUserById(id: string, opts?: { throwIfNotFound?: boolean }) {
   const user = usersMetaObject[id];
   if (!user && opts?.throwIfNotFound) throw new Error('User not found');
diff --git a/src/management-system-v2/lib/data/role-schema.ts b/src/management-system-v2/lib/data/role-schema.ts
index 029ae2685..92e7828f8 100644
--- a/src/management-system-v2/lib/data/role-schema.ts
+++ b/src/management-system-v2/lib/data/role-schema.ts
@@ -1,5 +1,6 @@
 import { z } from 'zod';
 import { resources, ResourceType } from '../ability/caslAbility';
+import { AuthenticatedUser } from './user-schema';
 
 type Permissions = Record<ResourceType, z.ZodNumber>;
 const perms: Partial<Permissions> = {};
@@ -21,13 +22,10 @@ export type RoleInput = z.infer<typeof RoleInputSchema>;
 
 export type Role = RoleInput & {
   id: string;
-  members: {
-    userId: string;
-    username?: string;
-    firstName?: string;
-    lastName?: string;
-    email?: string;
-  }[];
   createdOn: Date;
   lastEditedOn: Date;
 };
+
+export type RoleWithMembers = Role & {
+  members: Pick<AuthenticatedUser, 'id' | 'email' | 'username' | 'firstName' | 'lastName'>[];
+};
diff --git a/src/management-system-v2/lib/schema.tsx b/src/management-system-v2/lib/schema.tsx
index a5b67397e..ba841b5f9 100644
--- a/src/management-system-v2/lib/schema.tsx
+++ b/src/management-system-v2/lib/schema.tsx
@@ -494,6 +494,23 @@ export const schema = {
         },
       ],
     },
+
+    {
+      name: 'ResponsibleParty',
+      superClass: ['bpmn:ResourceRole'],
+    },
+    {
+      name: 'ResposibilityElements',
+      extends: ['bpmn:Gateway', 'bpmn:Event'],
+      properties: [
+        {
+          name: 'resources',
+          type: 'ResourceRole',
+          isMany: true,
+        },
+      ],
+    },
+
     {
       name: 'placeholder',
       extends: ['bpmn:Task'],
diff --git a/tests/ms2/processes/process-modeler/properties-panel/properties-panel.page.ts b/tests/ms2/processes/process-modeler/properties-panel/properties-panel.page.ts
index 61850686f..cbe7fa348 100644
--- a/tests/ms2/processes/process-modeler/properties-panel/properties-panel.page.ts
+++ b/tests/ms2/processes/process-modeler/properties-panel/properties-panel.page.ts
@@ -12,7 +12,7 @@ export class PropertiesPanelPage {
 
   constructor(page: Page) {
     this.page = page;
-    this.generalSection = page.getByRole('group', { name: 'General' });
+    this.generalSection = page.getByRole('group', { name: 'General Properties' });
     this.imageSection = page.getByRole('group', { name: 'image-section' });
     this.descriptionSection = page.getByRole('group', { name: 'Description' });
     this.milestonesSection = page.getByRole('group', { name: 'Milestones' });


Reviews:
user: login: FelipeTrost
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:03:00Z
user: login: FelipeTrost
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:04:08Z
user: login: FelipeTrost
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-29T10:47:28Z

Review comments:
diff_hunk: @@ -48,6 +49,22 @@ export function getUsers(page: number = 1, pageSize: number = 10) {
   };
 }
 
+export async function getAuthenticatedUsersInSpace(environmentId: string, ability?: Ability) {
+  const members = await getMembers(environmentId, ability);
+
+  return members
+    .map((member) => usersMetaObject[member.userId])
+    .filter((user) => !user.isGuest) as AuthenticatedUser[];
+}
path: src/management-system-v2/lib/data/legacy/iam/users.ts
user: login: FelipeTrost
body: This check shouldn't be necessary, our logic doesn't allow guests to be part of an organization
created_at: 2025-03-25T17:03:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -13,14 +13,26 @@ const Page = async ({ params }: { params: { environmentId: string } }) => {
 
   const roles = await getRoles(activeEnvironment.spaceId, ability);
 
-  for (let i = 0; i < roles.length; i++) {
-    // @ts-ignore
-    roles[i] = { members: roles[i].members.map(({ userId }) => getUserById(userId)), ...roles[i] };
-  }
+  const extendedRoles = await asyncMap(roles, async (role) => {
+    const roleUsers = await getUsersInRole(role.id, ability);
+    const authenticatedMembers = roleUsers.filter(
+      (member) => member && !member.isGuest,
+    ) as AuthenticatedUser[];
+    return {
+      ...role,
+      members: authenticatedMembers.map(({ id, firstName, lastName, username, email }) => ({
+        id,
+        firstName,
+        lastName,
+        username,
+        email,
+      })),
+    };
+  });
path: src/management-system-v2/app/(dashboard)/[environmentId]/iam/roles/page.tsx
user: login: FelipeTrost
body: It would be better to do this in one db call like before
created_at: 2025-03-25T17:04:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-02-12T15:48:56Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/13289189213)

✅ Successfully created Preview Deployment.

https://pr-486---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-02-13T14:42:25Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/13310062673)

✅ Successfully created Preview Deployment.

https://pr-486---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-18T06:06:40Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/13916439554)

✅ Successfully created Preview Deployment.

https://pr-486---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-18T20:45:58Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/13933049915)

✅ Successfully created Preview Deployment.

https://pr-486---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-25T10:58:53Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/14057820812)

✅ Successfully created Preview Deployment.

https://pr-486---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-27T13:16:23Z
body: <!-- preview-cloudrun -->
[![CLOUDRUN](https://img.shields.io/static/v1?label=CloudRun&message=Revisions&color=success&style=flat)](https://console.cloud.google.com/run/detail/europe-west1/ms-server-staging/revisions?project=proceed-bpms
) [![ACTIONS](https://img.shields.io/static/v1?label=CloudRun&message=Actions_Log&style=flat)](https://github.com/PROCEED-Labs/proceed/actions/runs/14107399187)

✅ Successfully created Preview Deployment.

https://pr-486---ms-server-staging-c4f6qdpj7q-ew.a.run.app
author_association: NONE