Files
twenty/packages/twenty-front/src/pages/settings/roles/SettingsRoleAddObjectLevel.tsx
T
Félix Malfait 783fe8d387 Agent role edition (#15914)
## 🐛 Issues Fixed

### 1. Role Editing Not Working for New Agents
When creating a new agent and then creating a role for it, the role
permissions appeared as non-editable. This was caused by:
- In create mode, `agentId = ''` (empty string from `useParams`)
- Empty string is **falsy** but `isDefined('')` returns **true**
- This caused incorrect behavior in role exclusivity checks and API
calls

### 2. Missing Role Data Loading
The agent form wasn't loading role data needed for permission editing
because it was missing the `SettingsRolesQueryEffect` component.

### 3. Navigation Conflicts
The `useSaveDraftRoleToDB` hook contained navigation logic that caused
conflicts when used from different contexts (role detail page vs agent
form).

### 4. Linting Errors
Unused `useIcons` import in `SettingsAgentRoleTab.tsx`.

---

## 🔧 Changes Made

### Agent Role Tab (`SettingsAgentRoleTab.tsx`)
-  Use `isNonEmptyString(agentId)` to validate agentId (follows
codebase patterns)
-  Improved role exclusivity logic to handle both create and edit
modes:
  - **Edit mode**: Role must be assigned exclusively to this agent
  - **Create mode**: Role must not be assigned to anyone yet
-  Only call `assignRoleToAgent` when a valid agentId exists
-  Pass `undefined` instead of empty string for `fromAgentId` prop

### Role Hook (`useSaveDraftRoleToDB.ts`)
-  Removed navigation logic from the hook (better separation of
concerns)
-  Removed `useNavigateSettings` and `SettingsPath` dependencies
-  Hook now only handles data persistence, not navigation

### Role Component (`SettingsRole.tsx`)
-  Added navigation after successful role creation in create mode
-  Navigation now handled by the component using the hook

### Agent Form (`SettingsAgentForm.tsx`)
-  Added `SettingsRolesQueryEffect` to ensure role data is loaded
-  Agent form handles its own navigation after save

---

##  Testing Scenarios

All these scenarios now work correctly:
1.  Create new agent → Create role → Edit permissions → Save agent
2.  Edit existing agent → Create role → Edit permissions → Save
3.  Edit existing agent → Try to edit shared role (shows warning
message)
4.  Navigate to object-level permissions from agent form with proper
breadcrumbs

---

## 📝 Technical Details

### Root Cause Analysis
The main issue was that empty string was being treated differently in
different checks:
- `agentId &&` → evaluates to `false` (empty string is falsy)
- `isDefined(agentId)` → returns `true` (empty string is defined)

This inconsistency caused the role to appear as non-editable and
attempted invalid API calls.

### Solution
Used `isNonEmptyString()` from `@sniptt/guards` which properly checks
both that the value is defined AND not empty, following codebase
conventions.

---

## 🎯 Impact

- Fixes critical bug preventing role permission configuration for new
agents
- Improves code quality with better separation of concerns
- Makes navigation logic more predictable and maintainable
- Follows codebase patterns and guidelines
2025-11-19 11:26:05 +01:00

80 lines
2.7 KiB
TypeScript

import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsRolesQueryEffect } from '@/settings/roles/components/SettingsRolesQueryEffect';
import { SettingsRolePermissionsObjectLevelObjectPicker } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { t } from '@lingui/core/macro';
import { Navigate, useParams, useSearchParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { useFindOneAgentQuery } from '~/generated-metadata/graphql';
export const SettingsRoleAddObjectLevel = () => {
const { roleId } = useParams();
const [searchParams] = useSearchParams();
const fromAgentId = searchParams.get('fromAgent');
const settingsDraftRole = useRecoilValue(
settingsDraftRoleFamilyState(roleId ?? ''),
);
const { data: agentData } = useFindOneAgentQuery({
variables: { id: fromAgentId || '' },
skip: !fromAgentId,
});
const agent = agentData?.findOneAgent;
if (!roleId) {
return <Navigate to={getSettingsPath(SettingsPath.Roles)} />;
}
const breadcrumbLinks =
fromAgentId && isDefined(agent)
? [
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: t`AI`,
href: getSettingsPath(SettingsPath.AI),
},
{
children: agent.label,
href: getSettingsPath(SettingsPath.AIAgentDetail, {
agentId: agent.id,
}),
},
{
children: t`Add object permission`,
},
]
: [
{ children: t`Roles`, href: getSettingsPath(SettingsPath.Roles) },
{
children: settingsDraftRole.label ?? '',
href: getSettingsPath(SettingsPath.RoleDetail, { roleId }),
},
{
children: t`Add object permission`,
href: getSettingsPath(SettingsPath.RoleAddObjectLevel, { roleId }),
},
];
return (
<>
<SettingsRolesQueryEffect />
<SubMenuTopBarContainer
title={t`1. Select an object`}
links={breadcrumbLinks}
>
<SettingsPageContainer>
<SettingsRolePermissionsObjectLevelObjectPicker roleId={roleId} />
</SettingsPageContainer>
</SubMenuTopBarContainer>
</>
);
};