Files
twenty/packages/twenty-front/src/modules/settings/roles/role-assignment/components/SettingsRoleAssignment.tsx
T
Félix Malfait 30b8663a74 chore: remove IS_AI_ENABLED feature flag (#19916)
## Summary
- AI is now GA, so the public/lab `IS_AI_ENABLED` flag is removed from
`FeatureFlagKey`, the public flag catalog, and the dev seeder.
- Drops every backend `@RequireFeatureFlag(IS_AI_ENABLED)` guard (agent,
agent chat, chat subscription, role-to-agent assignment, workflow AI
step creation) and the now-unused `FeatureFlagModule`/`FeatureFlagGuard`
wiring in the AI and workflow modules.
- Removes frontend gating from settings nav, role
permissions/assignment/applicability, command menu hotkeys, side panel,
mobile/drawer nav, and the agent chat provider so AI UI is always on.
Tests and generated GraphQL/SDK schemas updated accordingly.

## Test plan
- [x] `npx nx typecheck twenty-shared`
- [x] `npx nx typecheck twenty-server`
- [x] `npx nx typecheck twenty-front`
- [x] `npx nx lint:diff-with-main twenty-server`
- [x] `npx nx lint:diff-with-main twenty-front`
- [x] `npx jest --config=packages/twenty-server/jest.config.mjs
feature-flag`
- [x] `npx jest --config=packages/twenty-server/jest.config.mjs
workspace-entity-manager`
- [ ] Manual smoke test: AI features still accessible without any flag
row in `featureFlag`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:49:46 +02:00

233 lines
7.8 KiB
TypeScript

import { isDefined } from 'twenty-shared/utils';
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useUpdateAgentRole } from '@/settings/roles/hooks/useUpdateAgentRole';
import { useUpdateApiKeyRole } from '@/settings/roles/hooks/useUpdateApiKeyRole';
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
import { RoleAssignmentSection } from '@/settings/roles/role-assignment/components/RoleAssignmentSection';
import { SettingsRoleAssignmentConfirmationModal } from '@/settings/roles/role-assignment/components/SettingsRoleAssignmentConfirmationModal';
import { type SettingsRoleAssignmentConfirmationModalSelectedRoleTarget } from '@/settings/roles/role-assignment/types/SettingsRoleAssignmentConfirmationModalSelectedRoleTarget';
import { useSettingsAllRoles } from '@/settings/roles/hooks/useSettingsAllRoles';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { isModalOpenedComponentState } from '@/ui/layout/modal/states/isModalOpenedComponentState';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useState } from 'react';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SettingsPath } from 'twenty-shared/types';
import { useQuery } from '@apollo/client/react';
import {
type Agent,
type ApiKeyForRole,
FindManyAgentsDocument,
GetApiKeysDocument,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { type PartialWorkspaceMember } from '@/settings/roles/types/RoleWithPartialMembers';
import { ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID } from '@/settings/roles/role-assignment/constants/RoleAssignmentConfirmationModalId';
import { ROLE_TARGET_CONFIG } from '@/settings/roles/role-assignment/constants/RoleTargetConfig';
import { buildRoleMaps } from '@/settings/roles/role-assignment/utils/buildRoleMaps';
type SettingsRoleAssignmentProps = {
roleId: string;
isCreateMode?: boolean;
};
export const SettingsRoleAssignment = ({
roleId,
isCreateMode,
}: SettingsRoleAssignmentProps) => {
const settingsDraftRole = useAtomFamilyStateValue(
settingsDraftRoleFamilyState,
roleId,
);
const navigateSettings = useNavigateSettings();
const {
addWorkspaceMemberToRoleAndUpdateState,
updateWorkspaceMemberRoleDraftState,
} = useUpdateWorkspaceMemberRole(roleId);
const { addAgentToRoleAndUpdateState, updateAgentRoleDraftState } =
useUpdateAgentRole(roleId);
const { addApiKeyToRoleAndUpdateState, updateApiKeyRoleDraftState } =
useUpdateApiKeyRole(roleId);
const { data: agentsData } = useQuery(FindManyAgentsDocument);
const { data: apiKeysData } = useQuery(GetApiKeysDocument);
const { openModal, closeModal } = useModal();
const [selectedRoleTarget, setSelectRoleTarget] =
useState<SettingsRoleAssignmentConfirmationModalSelectedRoleTarget | null>(
null,
);
const currentWorkspaceMembers = useAtomStateValue(
currentWorkspaceMembersState,
);
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
const settingsAllRoles = useSettingsAllRoles();
const roleMaps = buildRoleMaps(settingsAllRoles);
const assignedWorkspaceMemberIds =
ROLE_TARGET_CONFIG.member.getAssignedIds(settingsDraftRole);
const assignableWorkspaceMembers = currentWorkspaceMembers.filter(
(member) => member.id !== currentWorkspaceMember?.id,
);
const allWorkspaceMembersHaveThisRole = assignableWorkspaceMembers.every(
(member) => assignedWorkspaceMemberIds.includes(member.id),
);
const handleModalClose = () => {
setSelectRoleTarget(null);
};
const isModalOpened = useAtomComponentStateValue(
isModalOpenedComponentState,
ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID,
);
const handleConfirm = async () => {
if (!selectedRoleTarget || !isModalOpened) return;
if (!isCreateMode) {
switch (selectedRoleTarget.entityType) {
case 'member':
await addWorkspaceMemberToRoleAndUpdateState({
workspaceMemberId: selectedRoleTarget.id,
});
break;
case 'agent':
await addAgentToRoleAndUpdateState({
agentId: selectedRoleTarget.id,
});
break;
case 'apiKey':
await addApiKeyToRoleAndUpdateState({
apiKeyId: selectedRoleTarget.id,
});
break;
}
} else {
switch (selectedRoleTarget.entityType) {
case 'member': {
const member = currentWorkspaceMembers.find(
(member) => member.id === selectedRoleTarget.id,
);
if (!member) {
throw new Error('Workspace member not found');
}
updateWorkspaceMemberRoleDraftState({
workspaceMember: {
id: member.id,
name: member.name,
userEmail: member.userEmail,
avatarUrl: member.avatarUrl,
},
});
break;
}
case 'agent': {
const agent = agentsData?.findManyAgents.find(
(agent) => agent.id === selectedRoleTarget.id,
);
if (!agent) {
throw new Error('Agent not found');
}
updateAgentRoleDraftState({
agent,
});
break;
}
case 'apiKey': {
const apiKeyData = apiKeysData?.apiKeys.find(
(apiKey) => apiKey.id === selectedRoleTarget.id,
);
if (!apiKeyData) {
throw new Error('API key not found');
}
updateApiKeyRoleDraftState({
apiKey: apiKeyData as ApiKeyForRole,
});
break;
}
}
}
handleModalClose();
};
const handleSelectEntity = (
entity: PartialWorkspaceMember | Agent | ApiKeyForRole,
entityType: keyof typeof ROLE_TARGET_CONFIG,
) => {
const config = ROLE_TARGET_CONFIG[entityType];
const existingRole = config.getRoleMap(roleMaps).get(entity.id);
const name = config.getName(entity as never);
setSelectRoleTarget({
id: entity.id,
name,
role: existingRole,
entityType: entityType as 'member' | 'agent' | 'apiKey',
});
openModal(ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID);
};
const handleRoleClick = (roleId: string) => {
navigateSettings(SettingsPath.RoleDetail, { roleId });
handleModalClose();
closeModal(ROLE_ASSIGNMENT_CONFIRMATION_MODAL_ID);
};
if (!isDefined(settingsDraftRole)) {
return null;
}
return (
<>
{Object.keys(ROLE_TARGET_CONFIG).map((roleTargetType) => (
<RoleAssignmentSection
key={roleTargetType}
roleTargetType={roleTargetType as keyof typeof ROLE_TARGET_CONFIG}
roleId={roleId}
settingsDraftRole={settingsDraftRole}
currentWorkspaceMember={
roleTargetType === 'member'
? currentWorkspaceMember || undefined
: undefined
}
onSelect={handleSelectEntity}
allWorkspaceMembersHaveThisRole={
roleTargetType === 'member'
? allWorkspaceMembersHaveThisRole
: false
}
/>
))}
{selectedRoleTarget && (
<SettingsRoleAssignmentConfirmationModal
selectedRoleTarget={selectedRoleTarget}
onClose={handleModalClose}
onConfirm={handleConfirm}
onRoleClick={handleRoleClick}
newRoleName={settingsDraftRole.label}
/>
)}
</>
);
};