object level override form (#11672)
This commit is contained in:
@ -517,6 +517,15 @@ export type CustomDomainValidRecords = {
|
||||
records: Array<CustomDomainRecord>;
|
||||
};
|
||||
|
||||
/** Database Event Action */
|
||||
export enum DatabaseEventAction {
|
||||
CREATED = 'CREATED',
|
||||
DELETED = 'DELETED',
|
||||
DESTROYED = 'DESTROYED',
|
||||
RESTORED = 'RESTORED',
|
||||
UPDATED = 'UPDATED'
|
||||
}
|
||||
|
||||
export type DateFilter = {
|
||||
eq?: InputMaybe<Scalars['Date']['input']>;
|
||||
gt?: InputMaybe<Scalars['Date']['input']>;
|
||||
@ -1004,7 +1013,7 @@ export type Mutation = {
|
||||
uploadImage: Scalars['String']['output'];
|
||||
uploadProfilePicture: Scalars['String']['output'];
|
||||
uploadWorkspaceLogo: Scalars['String']['output'];
|
||||
upsertOneObjectPermission: ObjectPermission;
|
||||
upsertObjectPermissions: Array<ObjectPermission>;
|
||||
upsertSettingPermissions: Array<SettingPermission>;
|
||||
userLookupAdminPanel: UserLookup;
|
||||
validateApprovedAccessDomain: ApprovedAccessDomain;
|
||||
@ -1368,8 +1377,8 @@ export type MutationUploadWorkspaceLogoArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpsertOneObjectPermissionArgs = {
|
||||
upsertObjectPermissionInput: UpsertObjectPermissionInput;
|
||||
export type MutationUpsertObjectPermissionsArgs = {
|
||||
upsertObjectPermissionsInput: UpsertObjectPermissionsInput;
|
||||
};
|
||||
|
||||
|
||||
@ -1481,6 +1490,14 @@ export type ObjectPermission = {
|
||||
roleId: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type ObjectPermissionInput = {
|
||||
canDestroyObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
canReadObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
canSoftDeleteObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
canUpdateObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
objectMetadataId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type ObjectRecordFilterInput = {
|
||||
and?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
||||
createdAt?: InputMaybe<DateFilter>;
|
||||
@ -1500,6 +1517,21 @@ export type ObjectStandardOverrides = {
|
||||
translations?: Maybe<Scalars['JSON']['output']>;
|
||||
};
|
||||
|
||||
export type OnDbEventDto = {
|
||||
__typename?: 'OnDbEventDTO';
|
||||
action: DatabaseEventAction;
|
||||
eventDate: Scalars['DateTime']['output'];
|
||||
objectNameSingular: Scalars['String']['output'];
|
||||
record: Scalars['JSON']['output'];
|
||||
updatedFields?: Maybe<Array<Scalars['String']['output']>>;
|
||||
};
|
||||
|
||||
export type OnDbEventInput = {
|
||||
action?: InputMaybe<DatabaseEventAction>;
|
||||
objectNameSingular?: InputMaybe<Scalars['String']['input']>;
|
||||
recordId?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
/** Onboarding status */
|
||||
export enum OnboardingStatus {
|
||||
COMPLETED = 'COMPLETED',
|
||||
@ -2090,6 +2122,16 @@ export type SubmitFormStepInput = {
|
||||
workflowRunId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type Subscription = {
|
||||
__typename?: 'Subscription';
|
||||
onDbEvent: OnDbEventDto;
|
||||
};
|
||||
|
||||
|
||||
export type SubscriptionOnDbEventArgs = {
|
||||
input: OnDbEventInput;
|
||||
};
|
||||
|
||||
export enum SubscriptionInterval {
|
||||
Day = 'Day',
|
||||
Month = 'Month',
|
||||
@ -2321,12 +2363,8 @@ export type UpdateWorkspaceInput = {
|
||||
subdomain?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
|
||||
export type UpsertObjectPermissionInput = {
|
||||
canDestroyObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
canReadObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
canSoftDeleteObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
canUpdateObjectRecords?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
objectMetadataId: Scalars['String']['input'];
|
||||
export type UpsertObjectPermissionsInput = {
|
||||
objectPermissions: Array<ObjectPermissionInput>;
|
||||
roleId: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
@ -929,7 +929,7 @@ export type Mutation = {
|
||||
uploadImage: Scalars['String'];
|
||||
uploadProfilePicture: Scalars['String'];
|
||||
uploadWorkspaceLogo: Scalars['String'];
|
||||
upsertOneObjectPermission: ObjectPermission;
|
||||
upsertObjectPermissions: Array<ObjectPermission>;
|
||||
upsertSettingPermissions: Array<SettingPermission>;
|
||||
userLookupAdminPanel: UserLookup;
|
||||
validateApprovedAccessDomain: ApprovedAccessDomain;
|
||||
@ -1243,8 +1243,8 @@ export type MutationUploadWorkspaceLogoArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpsertOneObjectPermissionArgs = {
|
||||
upsertObjectPermissionInput: UpsertObjectPermissionInput;
|
||||
export type MutationUpsertObjectPermissionsArgs = {
|
||||
upsertObjectPermissionsInput: UpsertObjectPermissionsInput;
|
||||
};
|
||||
|
||||
|
||||
@ -1356,6 +1356,14 @@ export type ObjectPermission = {
|
||||
roleId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ObjectPermissionInput = {
|
||||
canDestroyObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canSoftDeleteObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canUpdateObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
objectMetadataId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ObjectRecordFilterInput = {
|
||||
and?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
||||
createdAt?: InputMaybe<DateFilter>;
|
||||
@ -2142,12 +2150,8 @@ export type UpdateWorkspaceInput = {
|
||||
subdomain?: InputMaybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type UpsertObjectPermissionInput = {
|
||||
canDestroyObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canReadObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canSoftDeleteObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
canUpdateObjectRecords?: InputMaybe<Scalars['Boolean']>;
|
||||
objectMetadataId: Scalars['String'];
|
||||
export type UpsertObjectPermissionsInput = {
|
||||
objectPermissions: Array<ObjectPermissionInput>;
|
||||
roleId: Scalars['String'];
|
||||
};
|
||||
|
||||
@ -2759,6 +2763,13 @@ export type UpdateWorkspaceMemberRoleMutationVariables = Exact<{
|
||||
|
||||
export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, roles?: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } };
|
||||
|
||||
export type UpsertObjectPermissionsMutationVariables = Exact<{
|
||||
upsertObjectPermissionsInput: UpsertObjectPermissionsInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpsertObjectPermissionsMutation = { __typename?: 'Mutation', upsertObjectPermissions: Array<{ __typename?: 'ObjectPermission', id: string, objectMetadataId: string, roleId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> };
|
||||
|
||||
export type UpsertSettingPermissionsMutationVariables = Exact<{
|
||||
upsertSettingPermissionsInput: UpsertSettingPermissionsInput;
|
||||
}>;
|
||||
@ -5058,6 +5069,41 @@ export function useUpdateWorkspaceMemberRoleMutation(baseOptions?: Apollo.Mutati
|
||||
export type UpdateWorkspaceMemberRoleMutationHookResult = ReturnType<typeof useUpdateWorkspaceMemberRoleMutation>;
|
||||
export type UpdateWorkspaceMemberRoleMutationResult = Apollo.MutationResult<UpdateWorkspaceMemberRoleMutation>;
|
||||
export type UpdateWorkspaceMemberRoleMutationOptions = Apollo.BaseMutationOptions<UpdateWorkspaceMemberRoleMutation, UpdateWorkspaceMemberRoleMutationVariables>;
|
||||
export const UpsertObjectPermissionsDocument = gql`
|
||||
mutation UpsertObjectPermissions($upsertObjectPermissionsInput: UpsertObjectPermissionsInput!) {
|
||||
upsertObjectPermissions(
|
||||
upsertObjectPermissionsInput: $upsertObjectPermissionsInput
|
||||
) {
|
||||
...ObjectPermissionFragment
|
||||
}
|
||||
}
|
||||
${ObjectPermissionFragmentFragmentDoc}`;
|
||||
export type UpsertObjectPermissionsMutationFn = Apollo.MutationFunction<UpsertObjectPermissionsMutation, UpsertObjectPermissionsMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpsertObjectPermissionsMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpsertObjectPermissionsMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpsertObjectPermissionsMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [upsertObjectPermissionsMutation, { data, loading, error }] = useUpsertObjectPermissionsMutation({
|
||||
* variables: {
|
||||
* upsertObjectPermissionsInput: // value for 'upsertObjectPermissionsInput'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpsertObjectPermissionsMutation(baseOptions?: Apollo.MutationHookOptions<UpsertObjectPermissionsMutation, UpsertObjectPermissionsMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpsertObjectPermissionsMutation, UpsertObjectPermissionsMutationVariables>(UpsertObjectPermissionsDocument, options);
|
||||
}
|
||||
export type UpsertObjectPermissionsMutationHookResult = ReturnType<typeof useUpsertObjectPermissionsMutation>;
|
||||
export type UpsertObjectPermissionsMutationResult = Apollo.MutationResult<UpsertObjectPermissionsMutation>;
|
||||
export type UpsertObjectPermissionsMutationOptions = Apollo.BaseMutationOptions<UpsertObjectPermissionsMutation, UpsertObjectPermissionsMutationVariables>;
|
||||
export const UpsertSettingPermissionsDocument = gql`
|
||||
mutation UpsertSettingPermissions($upsertSettingPermissionsInput: UpsertSettingPermissionsInput!) {
|
||||
upsertSettingPermissions(
|
||||
|
||||
@ -2,10 +2,12 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||
import { settingsRoleIdsState } from '@/settings/roles/states/settingsRoleIdsState';
|
||||
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Role, useGetRolesQuery } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const SettingsRolesQueryEffect = () => {
|
||||
const { data, loading } = useGetRolesQuery({
|
||||
@ -17,11 +19,18 @@ export const SettingsRolesQueryEffect = () => {
|
||||
);
|
||||
|
||||
const populateRoles = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({ set, snapshot }) =>
|
||||
(roles: Role[]) => {
|
||||
const roleIds = roles.map((role) => role.id);
|
||||
set(settingsRoleIdsState, roleIds);
|
||||
roles.forEach((role) => {
|
||||
const persistedRole = getSnapshotValue(
|
||||
snapshot,
|
||||
settingsPersistedRoleFamilyState(role.id),
|
||||
);
|
||||
if (isDeeplyEqual(role, persistedRole)) {
|
||||
return;
|
||||
}
|
||||
set(settingsDraftRoleFamilyState(role.id), role);
|
||||
set(settingsPersistedRoleFamilyState(role.id), role);
|
||||
});
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPSERT_OBJECT_PERMISSIONS = gql`
|
||||
${OBJECT_PERMISSION_FRAGMENT}
|
||||
mutation UpsertObjectPermissions(
|
||||
$upsertObjectPermissionsInput: UpsertObjectPermissionsInput!
|
||||
) {
|
||||
upsertObjectPermissions(
|
||||
upsertObjectPermissionsInput: $upsertObjectPermissionsInput
|
||||
) {
|
||||
...ObjectPermissionFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -14,11 +14,13 @@ const StyledRolePermissionsContainer = styled.div`
|
||||
type SettingsRolePermissionsProps = {
|
||||
roleId: string;
|
||||
isEditable: boolean;
|
||||
isCreateMode: boolean;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissions = ({
|
||||
roleId,
|
||||
isEditable,
|
||||
isCreateMode,
|
||||
}: SettingsRolePermissionsProps) => {
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
@ -30,7 +32,7 @@ export const SettingsRolePermissions = ({
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
{isPermissionsV2Enabled && (
|
||||
{isPermissionsV2Enabled && !isCreateMode && (
|
||||
<SettingsRolePermissionsObjectLevelSection
|
||||
roleId={roleId}
|
||||
isEditable={isEditable}
|
||||
|
||||
@ -3,10 +3,10 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
import { PENDING_ROLE_ID } from '~/pages/settings/roles/SettingsRoleCreate';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
import { getRolesMock } from '~/testing/mock-data/roles';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
|
||||
const SettingsRolePermissionsWrapper = (
|
||||
args: React.ComponentProps<typeof SettingsRolePermissions>,
|
||||
@ -25,6 +25,7 @@ const SettingsRolePermissionsWrapper = (
|
||||
<SettingsRolePermissions
|
||||
roleId={args.roleId}
|
||||
isEditable={args.isEditable}
|
||||
isCreateMode={args.isCreateMode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -42,6 +43,7 @@ export const Default: Story = {
|
||||
args: {
|
||||
roleId: '1',
|
||||
isEditable: true,
|
||||
isCreateMode: false,
|
||||
},
|
||||
};
|
||||
|
||||
@ -49,11 +51,14 @@ export const ReadOnly: Story = {
|
||||
args: {
|
||||
roleId: '1',
|
||||
isEditable: false,
|
||||
isCreateMode: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const PendingRole: Story = {
|
||||
args: {
|
||||
roleId: PENDING_ROLE_ID,
|
||||
isEditable: true,
|
||||
isCreateMode: true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -19,7 +19,8 @@ export const SettingsRolePermissionsObjectLevelObjectPickerDropdownContent = ({
|
||||
}: SettingsRolePermissionsObjectLevelObjectPickerDropdownContentProps) => {
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
|
||||
const { objectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
const { alphaSortedActiveObjectMetadataItems: objectMetadataItems } =
|
||||
useFilteredObjectMetadataItems();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
|
||||
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { useTheme } from '@emotion/react';
|
||||
@ -46,12 +47,8 @@ export const SettingsRolePermissionsObjectLevelOverrideCell = ({
|
||||
settingsDraftRoleFamilyState(objectPermission.roleId),
|
||||
);
|
||||
|
||||
const permissionMappings = {
|
||||
canReadObjectRecords: 'canReadAllObjectRecords',
|
||||
canUpdateObjectRecords: 'canUpdateAllObjectRecords',
|
||||
canSoftDeleteObjectRecords: 'canSoftDeleteAllObjectRecords',
|
||||
canDestroyObjectRecords: 'canDestroyAllObjectRecords',
|
||||
} as const;
|
||||
const permissionMappings =
|
||||
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
||||
|
||||
type ObjectPermissionKey = keyof typeof permissionMappings;
|
||||
|
||||
|
||||
@ -1,24 +1,30 @@
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsRolePermissionsObjectLevelObjectPickerDropdownContent } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPickerDropdownContent';
|
||||
import { SettingsRolePermissionsObjectLevelTableHeader } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableHeader';
|
||||
import { SettingsRolePermissionsObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelTableRow';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { H2Title, IconPlus } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { v4 } from 'uuid';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
// const StyledCreateObjectOverrideSection = styled(Section)`
|
||||
// border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
// display: flex;
|
||||
// justify-content: flex-end;
|
||||
// padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
// padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
// `;
|
||||
const StyledCreateObjectOverrideSection = styled(Section)`
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
@ -36,11 +42,14 @@ const StyledNoOverride = styled(TableCell)`
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
roleId,
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsObjectLevelSectionProps) => {
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const navigate = useNavigateSettings();
|
||||
|
||||
const objectMetadataItems = useObjectMetadataItems();
|
||||
|
||||
const objectMetadataMap = objectMetadataItems.objectMetadataItems.reduce(
|
||||
@ -51,29 +60,52 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
{} as Record<string, ObjectMetadataItem>,
|
||||
);
|
||||
|
||||
const objectPermissions = settingsDraftRole.objectPermissions;
|
||||
const filteredObjectPermissions = settingsDraftRole.objectPermissions?.filter(
|
||||
(objectPermission) =>
|
||||
(isDefined(objectPermission.canReadObjectRecords) &&
|
||||
objectPermission.canReadObjectRecords !==
|
||||
settingsDraftRole.canReadAllObjectRecords) ||
|
||||
(isDefined(objectPermission.canUpdateObjectRecords) &&
|
||||
objectPermission.canUpdateObjectRecords !==
|
||||
settingsDraftRole.canUpdateAllObjectRecords) ||
|
||||
(isDefined(objectPermission.canSoftDeleteObjectRecords) &&
|
||||
objectPermission.canSoftDeleteObjectRecords !==
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords) ||
|
||||
(isDefined(objectPermission.canDestroyObjectRecords) &&
|
||||
objectPermission.canDestroyObjectRecords !==
|
||||
settingsDraftRole.canDestroyAllObjectRecords),
|
||||
);
|
||||
|
||||
// const handleSelectObjectMetadata = (objectMetadataId: string) => {
|
||||
// setSettingsDraftRole((draftRole) => ({
|
||||
// ...draftRole,
|
||||
// objectPermissions: [
|
||||
// ...(draftRole.objectPermissions ?? []),
|
||||
// { objectMetadataId, roleId, id: v4() },
|
||||
// ],
|
||||
// }));
|
||||
// };
|
||||
const handleSelectObjectMetadata = (objectMetadataId: string) => {
|
||||
setSettingsDraftRole((draftRole) => ({
|
||||
...draftRole,
|
||||
objectPermissions: [
|
||||
...(draftRole.objectPermissions ?? []).filter(
|
||||
(permission) =>
|
||||
permission.objectMetadataId !== objectMetadataId ||
|
||||
permission.roleId !== roleId,
|
||||
),
|
||||
{ objectMetadataId, roleId, id: v4() },
|
||||
],
|
||||
}));
|
||||
navigate(SettingsPath.RoleObjectLevel, {
|
||||
roleId,
|
||||
objectMetadataId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Object-Level Permissions`}
|
||||
description={t`Set additional object-level permissions`}
|
||||
description={t`Ability to interact with specific objects`}
|
||||
/>
|
||||
<Table>
|
||||
<SettingsRolePermissionsObjectLevelTableHeader />
|
||||
<StyledTableRows>
|
||||
{isDefined(objectPermissions) && objectPermissions?.length > 0 ? (
|
||||
objectPermissions?.map((objectPermission) => (
|
||||
{isDefined(filteredObjectPermissions) &&
|
||||
filteredObjectPermissions?.length > 0 ? (
|
||||
filteredObjectPermissions?.map((objectPermission) => (
|
||||
<SettingsRolePermissionsObjectLevelTableRow
|
||||
key={objectPermission.id}
|
||||
objectPermission={objectPermission}
|
||||
@ -87,14 +119,14 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
)}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
{/* <StyledCreateObjectOverrideSection>
|
||||
<StyledCreateObjectOverrideSection>
|
||||
<Dropdown
|
||||
dropdownId="role-object-select"
|
||||
dropdownHotkeyScope={{ scope: 'roleObject' }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add Object`}
|
||||
title={t`Add rule`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={!isEditable}
|
||||
@ -102,12 +134,16 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
}
|
||||
dropdownComponents={
|
||||
<SettingsRolePermissionsObjectLevelObjectPickerDropdownContent
|
||||
excludedObjectMetadataIds={[]}
|
||||
excludedObjectMetadataIds={
|
||||
filteredObjectPermissions?.map(
|
||||
(objectPermission) => objectPermission.objectMetadataId,
|
||||
) ?? []
|
||||
}
|
||||
onSelect={handleSelectObjectMetadata}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</StyledCreateObjectOverrideSection> */}
|
||||
</StyledCreateObjectOverrideSection>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,9 +3,9 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelTableHeader = () => (
|
||||
<TableRow>
|
||||
<TableRow gridAutoColumns="180px 1fr 1fr">
|
||||
<TableHeader>{t`Object`}</TableHeader>
|
||||
<TableHeader>{t`Permission overrides`}</TableHeader>
|
||||
<TableHeader>{t`Permissions`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@ -5,7 +5,11 @@ import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconChevronRight, useIcons } from 'twenty-ui/display';
|
||||
import {
|
||||
IconChevronRight,
|
||||
OverflowingTextWithTooltip,
|
||||
useIcons,
|
||||
} from 'twenty-ui/display';
|
||||
import { ObjectPermission } from '~/generated/graphql';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
@ -44,6 +48,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
roleId: objectPermission.roleId,
|
||||
objectMetadataId: objectPermission.objectMetadataId,
|
||||
})}
|
||||
gridAutoColumns="180px 1fr 1fr"
|
||||
>
|
||||
<StyledNameTableCell>
|
||||
{!!Icon && (
|
||||
@ -54,7 +59,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
/>
|
||||
)}
|
||||
<StyledNameLabel title={objectMetadataItem.labelPlural}>
|
||||
{objectMetadataItem.labelPlural}
|
||||
<OverflowingTextWithTooltip text={objectMetadataItem.labelPlural} />
|
||||
</StyledNameLabel>
|
||||
</StyledNameTableCell>
|
||||
<TableCell>
|
||||
|
||||
@ -0,0 +1,95 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconReload, IconX } from 'twenty-ui/display';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
|
||||
export type OverridableCheckboxType = 'default' | 'override' | 'no_cta';
|
||||
|
||||
const StyledOverridableCheckboxContainer = styled.div`
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
justify-content: flex-start;
|
||||
width: 48px;
|
||||
`;
|
||||
|
||||
const StyledOverridableCheckboxContainerItem = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 24px;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
`;
|
||||
|
||||
const StyledIconWrapper = styled.div<{
|
||||
isDisabled?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
cursor: ${({ isDisabled }) => (isDisabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
opacity: ${({ isDisabled }) => (isDisabled ? 0.5 : 1)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export type OverridableCheckboxProps = {
|
||||
type?: OverridableCheckboxType;
|
||||
onChange: () => void;
|
||||
checked: boolean;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export const OverridableCheckbox = ({
|
||||
type = 'default',
|
||||
onChange,
|
||||
checked,
|
||||
disabled,
|
||||
}: OverridableCheckboxProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<StyledOverridableCheckboxContainer>
|
||||
{type === 'default' && (
|
||||
<>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<Checkbox checked={true} disabled={true} />
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<StyledIconWrapper
|
||||
onClick={disabled ? undefined : onChange}
|
||||
isDisabled={disabled}
|
||||
>
|
||||
<IconX
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.secondary}
|
||||
/>
|
||||
</StyledIconWrapper>
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
</>
|
||||
)}
|
||||
{type === 'override' && (
|
||||
<>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<Checkbox checked={false} disabled={true} />
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<StyledIconWrapper
|
||||
onClick={disabled ? undefined : onChange}
|
||||
isDisabled={disabled}
|
||||
>
|
||||
<IconReload
|
||||
size={theme.icon.size.md}
|
||||
color={theme.adaptiveColors.orange4}
|
||||
/>
|
||||
</StyledIconWrapper>
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
</>
|
||||
)}
|
||||
{type === 'no_cta' && (
|
||||
<StyledOverridableCheckboxContainerItem>
|
||||
<Checkbox checked={checked} disabled={disabled} onChange={onChange} />
|
||||
</StyledOverridableCheckboxContainerItem>
|
||||
)}
|
||||
</StyledOverridableCheckboxContainer>
|
||||
);
|
||||
};
|
||||
@ -1,13 +1,14 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader';
|
||||
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow';
|
||||
import { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTable = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
@ -27,7 +28,7 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||
roleId,
|
||||
objectMetadataItem,
|
||||
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelProps) => {
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
@ -42,40 +43,56 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||
|
||||
const objectLabel = objectMetadataItem.labelPlural;
|
||||
|
||||
const objectPermissionsConfig: SettingsRolePermissionsObjectPermission[] = [
|
||||
{
|
||||
key: 'canReadObjectRecords',
|
||||
label: t`See Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canReadObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
const updateObjectPermission = (
|
||||
permissionKey: keyof ObjectPermission,
|
||||
value: boolean | null,
|
||||
) => {
|
||||
setSettingsDraftRole((currentRole) => {
|
||||
const updatedPermissions = currentRole.objectPermissions?.map((perm) => {
|
||||
if (perm.objectMetadataId === objectMetadataItem.id) {
|
||||
return { ...perm, [permissionKey]: value };
|
||||
}
|
||||
return perm;
|
||||
});
|
||||
return { ...currentRole, objectPermissions: updatedPermissions };
|
||||
});
|
||||
};
|
||||
|
||||
const objectPermissionsConfig: SettingsRolePermissionsObjectLevelPermission[] =
|
||||
[
|
||||
{
|
||||
key: 'canReadObjectRecords',
|
||||
label: t`See Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canReadObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canReadObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'canUpdateObjectRecords',
|
||||
label: t`Edit Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canUpdateObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
{
|
||||
key: 'canUpdateObjectRecords',
|
||||
label: t`Edit Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canUpdateObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canUpdateObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'canSoftDeleteObjectRecords',
|
||||
label: t`Delete Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canSoftDeleteObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
{
|
||||
key: 'canSoftDeleteObjectRecords',
|
||||
label: t`Delete Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canSoftDeleteObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canSoftDeleteObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'canDestroyObjectRecords',
|
||||
label: t`Destroy Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canDestroyObjectRecords,
|
||||
setValue: (_value: boolean) => {
|
||||
// TODO: Implement
|
||||
{
|
||||
key: 'canDestroyObjectRecords',
|
||||
label: t`Destroy Records on ${objectLabel}`,
|
||||
value: settingsDraftRoleObjectPermissions.canDestroyObjectRecords,
|
||||
setValue: (value: boolean | null) => {
|
||||
updateObjectPermission('canDestroyObjectRecords', value);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
|
||||
return (
|
||||
<Section>
|
||||
@ -84,13 +101,17 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevel = ({
|
||||
description={t`Ability to interact with this specific object`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader />
|
||||
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader />
|
||||
<StyledTableRows>
|
||||
{objectPermissionsConfig.map((permission) => (
|
||||
<SettingsRolePermissionsObjectsTableRow
|
||||
<SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow
|
||||
key={permission.key}
|
||||
permission={permission}
|
||||
isEditable={settingsDraftRole.isEditable}
|
||||
settingsDraftRoleObjectPermissions={
|
||||
settingsDraftRoleObjectPermissions
|
||||
}
|
||||
roleId={roleId}
|
||||
/>
|
||||
))}
|
||||
</StyledTableRows>
|
||||
|
||||
@ -2,9 +2,9 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelHeader =
|
||||
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableHeader =
|
||||
() => (
|
||||
<TableRow gridAutoColumns="1fr 24px">
|
||||
<TableRow gridAutoColumns="1fr 48px">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader aria-label={t`Actions`}></TableHeader>
|
||||
</TableRow>
|
||||
@ -0,0 +1,141 @@
|
||||
import { OverridableCheckbox } from '@/settings/roles/role-permissions/object-level-permissions/object-form/components/OverridableCheckbox';
|
||||
import { SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectLevelPermissionToRoleObjectPermissionMapping';
|
||||
import { SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG } from '@/settings/roles/role-permissions/objects-permissions/constants/settingsRoleObjectPermissionIconConfig';
|
||||
import { SettingsRolePermissionsObjectLevelPermission } from '@/settings/roles/role-permissions/objects-permissions/types/SettingsRolePermissionsObjectPermission';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ObjectPermission } from '~/generated-metadata/graphql';
|
||||
import type { Role } from '~/generated/graphql';
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledOverrideInfo = styled.span`
|
||||
background: ${({ theme }) => theme.adaptiveColors.orange1};
|
||||
border-radius: ${({ theme }) => theme.spacing(1)};
|
||||
color: ${({ theme }) => theme.color.orange};
|
||||
padding: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
|
||||
|
||||
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
|
||||
permission: SettingsRolePermissionsObjectLevelPermission;
|
||||
isEditable: boolean;
|
||||
settingsDraftRoleObjectPermissions: ObjectPermission;
|
||||
roleId: string;
|
||||
};
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
|
||||
({
|
||||
permission,
|
||||
isEditable,
|
||||
settingsDraftRoleObjectPermissions,
|
||||
roleId,
|
||||
}: SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const settingsDraftRole = useRecoilValue(
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
|
||||
const label = permission.label;
|
||||
|
||||
const { Icon } =
|
||||
SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
||||
|
||||
const permissionMappings =
|
||||
SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING;
|
||||
|
||||
const settingsDraftRoleObjectPermissionValue =
|
||||
settingsDraftRoleObjectPermissions[
|
||||
permission.key as keyof ObjectPermission
|
||||
];
|
||||
|
||||
const rolePermission =
|
||||
permissionMappings[permission.key as keyof typeof permissionMappings];
|
||||
|
||||
const settingsDraftRoleGlobalPermissionValue =
|
||||
settingsDraftRole[rolePermission as keyof Role];
|
||||
|
||||
const isChecked = !!settingsDraftRoleObjectPermissionValue;
|
||||
|
||||
const isRevoked =
|
||||
isDefined(settingsDraftRoleObjectPermissionValue) &&
|
||||
settingsDraftRoleGlobalPermissionValue === true &&
|
||||
isChecked === false;
|
||||
|
||||
let checkboxType: OverridableCheckboxType;
|
||||
|
||||
if (
|
||||
settingsDraftRoleGlobalPermissionValue === true &&
|
||||
settingsDraftRoleObjectPermissionValue === false
|
||||
) {
|
||||
checkboxType = 'override';
|
||||
} else if (settingsDraftRoleGlobalPermissionValue === false) {
|
||||
checkboxType = 'no_cta';
|
||||
} else {
|
||||
checkboxType = 'default';
|
||||
}
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
if (!isEditable) return;
|
||||
|
||||
if (checkboxType === 'default') {
|
||||
permission.setValue(false);
|
||||
} else if (checkboxType === 'override') {
|
||||
permission.setValue(null);
|
||||
} else if (checkboxType === 'no_cta') {
|
||||
permission.setValue(!isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTableRow>
|
||||
<StyledPermissionCell>
|
||||
<Icon size={theme.icon.size.sm} />
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
{isRevoked ? (
|
||||
<StyledOverrideInfo>
|
||||
{t`Revoked for this object`}
|
||||
</StyledOverrideInfo>
|
||||
) : null}
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell>
|
||||
<OverridableCheckbox
|
||||
onChange={handleCheckboxChange}
|
||||
disabled={!isEditable}
|
||||
type={checkboxType}
|
||||
checked={isChecked}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
@ -5,7 +5,6 @@ import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDr
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
@ -37,12 +36,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canReadObjectRecords',
|
||||
label: t`See Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canReadObjectRecords) &&
|
||||
permission.canReadObjectRecords !==
|
||||
settingsDraftRole.canReadAllObjectRecords,
|
||||
permission.canReadObjectRecords === false &&
|
||||
settingsDraftRole.canReadAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canReadAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -55,12 +53,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canUpdateObjectRecords',
|
||||
label: t`Edit Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canUpdateObjectRecords) &&
|
||||
permission.canUpdateObjectRecords !==
|
||||
settingsDraftRole.canUpdateAllObjectRecords,
|
||||
permission.canUpdateObjectRecords === false &&
|
||||
settingsDraftRole.canUpdateAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canUpdateAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -73,12 +70,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canSoftDeleteObjectRecords',
|
||||
label: t`Delete Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canSoftDeleteObjectRecords) &&
|
||||
permission.canSoftDeleteObjectRecords !==
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
permission.canSoftDeleteObjectRecords === false &&
|
||||
settingsDraftRole.canSoftDeleteAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -91,12 +87,11 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
{
|
||||
key: 'canDestroyObjectRecords',
|
||||
label: t`Destroy Records on All Objects`,
|
||||
overriddenBy:
|
||||
revokedBy:
|
||||
objectPermissions?.filter(
|
||||
(permission) =>
|
||||
isDefined(permission.canDestroyObjectRecords) &&
|
||||
permission.canDestroyObjectRecords !==
|
||||
settingsDraftRole.canDestroyAllObjectRecords,
|
||||
permission.canDestroyObjectRecords === false &&
|
||||
settingsDraftRole.canDestroyAllObjectRecords === true,
|
||||
)?.length ?? 0,
|
||||
value: settingsDraftRole.canDestroyAllObjectRecords,
|
||||
setValue: (value: boolean) => {
|
||||
@ -112,7 +107,7 @@ export const SettingsRolePermissionsObjectsSection = ({
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Objects`}
|
||||
description={t`Ability to interact with each object`}
|
||||
description={t`Actions you can perform on all objects`}
|
||||
/>
|
||||
<StyledTable>
|
||||
<SettingsRolePermissionsObjectsTableHeader
|
||||
|
||||
@ -50,10 +50,10 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
}: SettingsRolePermissionsObjectsTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isOverriddenBy = permission.overriddenBy;
|
||||
const isOverridden = isOverriddenBy && isOverriddenBy > 0;
|
||||
const revokedBy = permission.revokedBy;
|
||||
const isRevoked = revokedBy && revokedBy > 0;
|
||||
const label = permission.label;
|
||||
const pluralizedObject = pluralize('object', isOverriddenBy);
|
||||
const pluralizedObject = pluralize('object', revokedBy);
|
||||
|
||||
const { Icon } = SETTINGS_ROLE_OBJECT_PERMISSION_ICON_CONFIG[permission.key];
|
||||
|
||||
@ -62,9 +62,9 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
<StyledPermissionCell>
|
||||
<Icon size={theme.icon.size.sm} />
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
{isOverridden ? (
|
||||
{isRevoked ? (
|
||||
<StyledOverrideInfo>
|
||||
{t`Overridden on ${isOverriddenBy} ${pluralizedObject}`}
|
||||
{t`Revoked on ${revokedBy} ${pluralizedObject}`}
|
||||
</StyledOverrideInfo>
|
||||
) : null}
|
||||
</StyledPermissionCell>
|
||||
@ -73,7 +73,7 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
checked={permission.value ?? false}
|
||||
onChange={() => permission.setValue(!permission.value)}
|
||||
disabled={!isEditable}
|
||||
accent={isOverridden ? CheckboxAccent.Orange : CheckboxAccent.Blue}
|
||||
accent={isRevoked ? CheckboxAccent.Orange : CheckboxAccent.Blue}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
export const SETTINGS_ROLE_OBJECT_LEVEL_PERMISSION_TO_ROLE_OBJECT_PERMISSION_MAPPING =
|
||||
{
|
||||
canReadObjectRecords: 'canReadAllObjectRecords',
|
||||
canUpdateObjectRecords: 'canUpdateAllObjectRecords',
|
||||
canSoftDeleteObjectRecords: 'canSoftDeleteAllObjectRecords',
|
||||
canDestroyObjectRecords: 'canDestroyAllObjectRecords',
|
||||
} as const;
|
||||
@ -2,7 +2,14 @@ import { ReactNode } from 'react';
|
||||
export type SettingsRolePermissionsObjectPermission = {
|
||||
key: string;
|
||||
label: string | ReactNode;
|
||||
value?: boolean | null;
|
||||
value?: boolean;
|
||||
setValue: (value: boolean) => void;
|
||||
overriddenBy?: number;
|
||||
revokedBy?: number;
|
||||
};
|
||||
|
||||
export type SettingsRolePermissionsObjectLevelPermission = {
|
||||
key: string;
|
||||
label: string | ReactNode;
|
||||
value?: boolean | null;
|
||||
setValue: (value: boolean | null) => void;
|
||||
};
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
Role,
|
||||
useCreateOneRoleMutation,
|
||||
useUpdateOneRoleMutation,
|
||||
useUpsertObjectPermissionsMutation,
|
||||
useUpsertSettingPermissionsMutation,
|
||||
} from '~/generated/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
@ -67,6 +68,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
const [createRole] = useCreateOneRoleMutation();
|
||||
const [updateRole] = useUpdateOneRoleMutation();
|
||||
const [upsertSettingPermissions] = useUpsertSettingPermissionsMutation();
|
||||
const [upsertObjectPermissions] = useUpsertObjectPermissionsMutation();
|
||||
|
||||
const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId);
|
||||
|
||||
@ -142,25 +144,54 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
},
|
||||
},
|
||||
onCompleted: async (data) => {
|
||||
await addWorkspaceMembersToRole({
|
||||
roleId: data.createOneRole.id,
|
||||
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
|
||||
(member) => member.id,
|
||||
),
|
||||
});
|
||||
if (isDefined(dirtyFields.workspaceMembers)) {
|
||||
await addWorkspaceMembersToRole({
|
||||
roleId: data.createOneRole.id,
|
||||
workspaceMemberIds: settingsDraftRole.workspaceMembers.map(
|
||||
(member) => member.id,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
await upsertSettingPermissions({
|
||||
variables: {
|
||||
upsertSettingPermissionsInput: {
|
||||
roleId: data.createOneRole.id,
|
||||
settingPermissionKeys:
|
||||
settingsDraftRole.settingPermissions?.map(
|
||||
(settingPermission) => settingPermission.setting,
|
||||
) ?? [],
|
||||
if (isDefined(dirtyFields.settingPermissions)) {
|
||||
await upsertSettingPermissions({
|
||||
variables: {
|
||||
upsertSettingPermissionsInput: {
|
||||
roleId: data.createOneRole.id,
|
||||
settingPermissionKeys:
|
||||
settingsDraftRole.settingPermissions?.map(
|
||||
(settingPermission) => settingPermission.setting,
|
||||
) ?? [],
|
||||
},
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(dirtyFields.objectPermissions)) {
|
||||
await upsertObjectPermissions({
|
||||
variables: {
|
||||
upsertObjectPermissionsInput: {
|
||||
roleId: data.createOneRole.id,
|
||||
objectPermissions:
|
||||
settingsDraftRole.objectPermissions?.map(
|
||||
(objectPermission) => ({
|
||||
objectMetadataId: objectPermission.objectMetadataId,
|
||||
canReadObjectRecords:
|
||||
objectPermission.canReadObjectRecords,
|
||||
canUpdateObjectRecords:
|
||||
objectPermission.canUpdateObjectRecords,
|
||||
canSoftDeleteObjectRecords:
|
||||
objectPermission.canSoftDeleteObjectRecords,
|
||||
canDestroyObjectRecords:
|
||||
objectPermission.canDestroyObjectRecords,
|
||||
}),
|
||||
) ?? [],
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
navigateSettings(SettingsPath.RoleDetail, {
|
||||
roleId: data.createOneRole.id,
|
||||
@ -206,6 +237,30 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(dirtyFields.objectPermissions)) {
|
||||
await upsertObjectPermissions({
|
||||
variables: {
|
||||
upsertObjectPermissionsInput: {
|
||||
roleId: roleId,
|
||||
objectPermissions:
|
||||
settingsDraftRole.objectPermissions?.map(
|
||||
(objectPermission) => ({
|
||||
objectMetadataId: objectPermission.objectMetadataId,
|
||||
canReadObjectRecords: objectPermission.canReadObjectRecords,
|
||||
canUpdateObjectRecords:
|
||||
objectPermission.canUpdateObjectRecords,
|
||||
canSoftDeleteObjectRecords:
|
||||
objectPermission.canSoftDeleteObjectRecords,
|
||||
canDestroyObjectRecords:
|
||||
objectPermission.canDestroyObjectRecords,
|
||||
}),
|
||||
) ?? [],
|
||||
},
|
||||
},
|
||||
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -251,6 +306,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
<SettingsRolePermissions
|
||||
roleId={roleId}
|
||||
isEditable={isRoleEditable}
|
||||
isCreateMode={isCreateMode}
|
||||
/>
|
||||
)}
|
||||
{activeTabId === SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.SETTINGS && (
|
||||
|
||||
@ -2,10 +2,13 @@ import { SETTINGS_ROLE_DETAIL_TABS } from '@/settings/roles/role/constants/Setti
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Role } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
type SettingsRoleEditEffectProps = {
|
||||
roleId: string;
|
||||
@ -17,24 +20,35 @@ export const SettingsRoleEditEffect = ({
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
const role = useRecoilValue(settingsPersistedRoleFamilyState(roleId));
|
||||
const setDraftRole = useSetRecoilState(settingsDraftRoleFamilyState(roleId));
|
||||
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const updateDraftRoleIfNeeded = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(newRole: Role) => {
|
||||
const currentPersistedRole = getSnapshotValue(
|
||||
snapshot,
|
||||
settingsPersistedRoleFamilyState(newRole.id),
|
||||
);
|
||||
|
||||
if (!isDeeplyEqual(newRole, currentPersistedRole)) {
|
||||
set(settingsDraftRoleFamilyState(newRole.id), newRole);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
if (isInitialized || !isDefined(role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTabId(SETTINGS_ROLE_DETAIL_TABS.TABS_IDS.ASSIGNMENT);
|
||||
|
||||
if (isDefined(role)) {
|
||||
setDraftRole(role);
|
||||
setIsInitialized(true);
|
||||
}
|
||||
}, [isInitialized, role, setActiveTabId, setDraftRole]);
|
||||
updateDraftRoleIfNeeded(role);
|
||||
setIsInitialized(true);
|
||||
}, [isInitialized, role, setActiveTabId, updateDraftRoleIfNeeded]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@ -1,14 +1,30 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
@InputType()
|
||||
export class UpsertObjectPermissionInput {
|
||||
export class UpsertObjectPermissionsInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
roleId: string;
|
||||
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@IsNotEmpty()
|
||||
@Field(() => [ObjectPermissionInput])
|
||||
objectPermissions: ObjectPermissionInput[];
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class ObjectPermissionInput {
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
@ -1,10 +1,10 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { UpsertObjectPermissionInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permission-input';
|
||||
import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import {
|
||||
PermissionsException,
|
||||
@ -25,24 +25,29 @@ export class ObjectPermissionService {
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
) {}
|
||||
|
||||
public async upsertObjectPermission({
|
||||
public async upsertObjectPermissions({
|
||||
workspaceId,
|
||||
input,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
input: UpsertObjectPermissionInput;
|
||||
}): Promise<ObjectPermissionEntity | null> {
|
||||
input: UpsertObjectPermissionsInput;
|
||||
}): Promise<ObjectPermissionEntity[]> {
|
||||
try {
|
||||
await this.validateRoleIsEditableOrThrow({
|
||||
roleId: input.roleId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const result = await this.objectPermissionRepository.upsert(
|
||||
{
|
||||
const objectPermissions = input.objectPermissions.map(
|
||||
(objectPermission) => ({
|
||||
...objectPermission,
|
||||
roleId: input.roleId,
|
||||
workspaceId,
|
||||
...input,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await this.objectPermissionRepository.upsert(
|
||||
objectPermissions,
|
||||
{
|
||||
conflictPaths: ['objectMetadataId', 'roleId'],
|
||||
},
|
||||
@ -61,9 +66,14 @@ export class ObjectPermissionService {
|
||||
},
|
||||
);
|
||||
|
||||
return this.objectPermissionRepository.findOne({
|
||||
return this.objectPermissionRepository.find({
|
||||
where: {
|
||||
id: objectPermissionId,
|
||||
roleId: input.roleId,
|
||||
objectMetadataId: In(
|
||||
input.objectPermissions.map(
|
||||
(objectPermission) => objectPermission.objectMetadataId,
|
||||
),
|
||||
),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
@ -71,7 +81,9 @@ export class ObjectPermissionService {
|
||||
error,
|
||||
roleId: input.roleId,
|
||||
workspaceId,
|
||||
objectMetadataId: input.objectMetadataId,
|
||||
objectMetadataIds: input.objectPermissions.map(
|
||||
(objectPermission) => objectPermission.objectMetadataId,
|
||||
),
|
||||
});
|
||||
|
||||
throw error;
|
||||
@ -82,12 +94,12 @@ export class ObjectPermissionService {
|
||||
error,
|
||||
roleId,
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
objectMetadataIds,
|
||||
}: {
|
||||
error: Error;
|
||||
roleId: string;
|
||||
workspaceId: string;
|
||||
objectMetadataId: string;
|
||||
objectMetadataIds: string[];
|
||||
}) {
|
||||
if (error.message.includes('violates foreign key constraint')) {
|
||||
const role = await this.roleRepository.findOne({
|
||||
@ -104,14 +116,14 @@ export class ObjectPermissionService {
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
const objectMetadata = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
id: objectMetadataId,
|
||||
id: In(objectMetadataIds),
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(objectMetadata)) {
|
||||
if (objectMetadata.length !== objectMetadataIds.length) {
|
||||
throw new PermissionsException(
|
||||
PermissionsExceptionMessage.OBJECT_METADATA_NOT_FOUND,
|
||||
PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
|
||||
@ -17,7 +17,7 @@ import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { UpsertObjectPermissionInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permission-input';
|
||||
import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import {
|
||||
@ -147,21 +147,18 @@ export class RoleResolver {
|
||||
return deletedRoleId;
|
||||
}
|
||||
|
||||
@Mutation(() => ObjectPermissionDTO)
|
||||
async upsertOneObjectPermission(
|
||||
@Mutation(() => [ObjectPermissionDTO])
|
||||
async upsertObjectPermissions(
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('upsertObjectPermissionInput')
|
||||
upsertObjectPermissionInput: UpsertObjectPermissionInput,
|
||||
) {
|
||||
@Args('upsertObjectPermissionsInput')
|
||||
upsertObjectPermissionsInput: UpsertObjectPermissionsInput,
|
||||
): Promise<ObjectPermissionDTO[]> {
|
||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||
|
||||
const objectPermission =
|
||||
await this.objectPermissionService.upsertObjectPermission({
|
||||
workspaceId: workspace.id,
|
||||
input: upsertObjectPermissionInput,
|
||||
});
|
||||
|
||||
return objectPermission;
|
||||
return this.objectPermissionService.upsertObjectPermissions({
|
||||
workspaceId: workspace.id,
|
||||
input: upsertObjectPermissionsInput,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => [SettingPermissionDTO])
|
||||
@ -169,7 +166,7 @@ export class RoleResolver {
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Args('upsertSettingPermissionsInput')
|
||||
upsertSettingPermissionsInput: UpsertSettingPermissionsInput,
|
||||
) {
|
||||
): Promise<SettingPermissionDTO[]> {
|
||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||
|
||||
return this.settingPermissionService.upsertSettingPermissions({
|
||||
|
||||
@ -72,19 +72,12 @@ describe('roles permissions', () => {
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const disablePermissionsQuery = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
'IsPermissionsEnabled',
|
||||
false,
|
||||
);
|
||||
|
||||
const disablePermissionsV2Query = updateFeatureFlagFactory(
|
||||
SEED_APPLE_WORKSPACE_ID,
|
||||
'IsPermissionsV2Enabled',
|
||||
false,
|
||||
);
|
||||
|
||||
await makeGraphqlAPIRequest(disablePermissionsQuery);
|
||||
await makeGraphqlAPIRequest(disablePermissionsV2Query);
|
||||
});
|
||||
|
||||
@ -480,9 +473,10 @@ describe('roles permissions', () => {
|
||||
roleId: string;
|
||||
}) => `
|
||||
mutation UpsertObjectPermissions {
|
||||
upsertOneObjectPermission(upsertObjectPermissionInput: {objectMetadataId: "${objectMetadataId}", roleId: "${roleId}", canUpdateObjectRecords: true}) {
|
||||
upsertObjectPermissions(upsertObjectPermissionsInput: { roleId: "${roleId}", objectPermissions: [{objectMetadataId: "${objectMetadataId}", canUpdateObjectRecords: true}]}) {
|
||||
id
|
||||
roleId
|
||||
objectMetadataId
|
||||
canUpdateObjectRecords
|
||||
}
|
||||
}
|
||||
@ -540,12 +534,15 @@ describe('roles permissions', () => {
|
||||
.expect((res) => {
|
||||
expect(res.body.data).toBeDefined();
|
||||
expect(res.body.errors).toBeUndefined();
|
||||
expect(res.body.data.upsertOneObjectPermission.roleId).toBe(
|
||||
createdEditableRoleId,
|
||||
expect(res.body.data.upsertObjectPermissions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
roleId: createdEditableRoleId,
|
||||
objectMetadataId: listingObjectId,
|
||||
canUpdateObjectRecords: true,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
res.body.data.upsertOneObjectPermission.canUpdateObjectRecords,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user