add settings permissions update (#11377)
Fixes https://github.com/twentyhq/core-team-issues/issues/710
This commit is contained in:
@ -948,7 +948,7 @@ export type Mutation = {
|
|||||||
uploadProfilePicture: Scalars['String']['output'];
|
uploadProfilePicture: Scalars['String']['output'];
|
||||||
uploadWorkspaceLogo: Scalars['String']['output'];
|
uploadWorkspaceLogo: Scalars['String']['output'];
|
||||||
upsertOneObjectPermission: ObjectPermission;
|
upsertOneObjectPermission: ObjectPermission;
|
||||||
upsertOneSettingPermission: SettingPermission;
|
upsertSettingPermissions: Array<SettingPermission>;
|
||||||
userLookupAdminPanel: UserLookup;
|
userLookupAdminPanel: UserLookup;
|
||||||
validateApprovedAccessDomain: ApprovedAccessDomain;
|
validateApprovedAccessDomain: ApprovedAccessDomain;
|
||||||
};
|
};
|
||||||
@ -1308,8 +1308,8 @@ export type MutationUpsertOneObjectPermissionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUpsertOneSettingPermissionArgs = {
|
export type MutationUpsertSettingPermissionsArgs = {
|
||||||
upsertSettingPermissionInput: UpsertSettingPermissionInput;
|
upsertSettingPermissionsInput: UpsertSettingPermissionsInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1849,6 +1849,7 @@ export type Role = {
|
|||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
isEditable: Scalars['Boolean']['output'];
|
isEditable: Scalars['Boolean']['output'];
|
||||||
label: Scalars['String']['output'];
|
label: Scalars['String']['output'];
|
||||||
|
settingPermissions?: Maybe<Array<SettingPermission>>;
|
||||||
workspaceMembers: Array<WorkspaceMember>;
|
workspaceMembers: Array<WorkspaceMember>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1958,7 +1959,6 @@ export enum ServerlessFunctionSyncStatus {
|
|||||||
|
|
||||||
export type SettingPermission = {
|
export type SettingPermission = {
|
||||||
__typename?: 'SettingPermission';
|
__typename?: 'SettingPermission';
|
||||||
canUpdateSetting?: Maybe<Scalars['Boolean']['output']>;
|
|
||||||
id: Scalars['String']['output'];
|
id: Scalars['String']['output'];
|
||||||
roleId: Scalars['String']['output'];
|
roleId: Scalars['String']['output'];
|
||||||
setting: SettingPermissionType;
|
setting: SettingPermissionType;
|
||||||
@ -2262,10 +2262,9 @@ export type UpsertObjectPermissionInput = {
|
|||||||
roleId: Scalars['String']['input'];
|
roleId: Scalars['String']['input'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpsertSettingPermissionInput = {
|
export type UpsertSettingPermissionsInput = {
|
||||||
canUpdateSetting?: InputMaybe<Scalars['Boolean']['input']>;
|
|
||||||
roleId: Scalars['String']['input'];
|
roleId: Scalars['String']['input'];
|
||||||
setting: SettingPermissionType;
|
settingPermissionKeys: Array<SettingPermissionType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
|
|||||||
@ -864,7 +864,7 @@ export type Mutation = {
|
|||||||
uploadProfilePicture: Scalars['String'];
|
uploadProfilePicture: Scalars['String'];
|
||||||
uploadWorkspaceLogo: Scalars['String'];
|
uploadWorkspaceLogo: Scalars['String'];
|
||||||
upsertOneObjectPermission: ObjectPermission;
|
upsertOneObjectPermission: ObjectPermission;
|
||||||
upsertOneSettingPermission: SettingPermission;
|
upsertSettingPermissions: Array<SettingPermission>;
|
||||||
userLookupAdminPanel: UserLookup;
|
userLookupAdminPanel: UserLookup;
|
||||||
validateApprovedAccessDomain: ApprovedAccessDomain;
|
validateApprovedAccessDomain: ApprovedAccessDomain;
|
||||||
};
|
};
|
||||||
@ -1174,8 +1174,8 @@ export type MutationUpsertOneObjectPermissionArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUpsertOneSettingPermissionArgs = {
|
export type MutationUpsertSettingPermissionsArgs = {
|
||||||
upsertSettingPermissionInput: UpsertSettingPermissionInput;
|
upsertSettingPermissionsInput: UpsertSettingPermissionsInput;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -1644,6 +1644,7 @@ export type Role = {
|
|||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
isEditable: Scalars['Boolean'];
|
isEditable: Scalars['Boolean'];
|
||||||
label: Scalars['String'];
|
label: Scalars['String'];
|
||||||
|
settingPermissions?: Maybe<Array<SettingPermission>>;
|
||||||
workspaceMembers: Array<WorkspaceMember>;
|
workspaceMembers: Array<WorkspaceMember>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1753,7 +1754,6 @@ export enum ServerlessFunctionSyncStatus {
|
|||||||
|
|
||||||
export type SettingPermission = {
|
export type SettingPermission = {
|
||||||
__typename?: 'SettingPermission';
|
__typename?: 'SettingPermission';
|
||||||
canUpdateSetting?: Maybe<Scalars['Boolean']>;
|
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
roleId: Scalars['String'];
|
roleId: Scalars['String'];
|
||||||
setting: SettingPermissionType;
|
setting: SettingPermissionType;
|
||||||
@ -2049,10 +2049,9 @@ export type UpsertObjectPermissionInput = {
|
|||||||
roleId: Scalars['String'];
|
roleId: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpsertSettingPermissionInput = {
|
export type UpsertSettingPermissionsInput = {
|
||||||
canUpdateSetting?: InputMaybe<Scalars['Boolean']>;
|
|
||||||
roleId: Scalars['String'];
|
roleId: Scalars['String'];
|
||||||
setting: SettingPermissionType;
|
settingPermissionKeys: Array<SettingPermissionType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
@ -2601,6 +2600,8 @@ export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', upda
|
|||||||
|
|
||||||
export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean };
|
export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean };
|
||||||
|
|
||||||
|
export type SettingPermissionFragmentFragment = { __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string };
|
||||||
|
|
||||||
export type CreateOneRoleMutationVariables = Exact<{
|
export type CreateOneRoleMutationVariables = Exact<{
|
||||||
createRoleInput: CreateRoleInput;
|
createRoleInput: CreateRoleInput;
|
||||||
}>;
|
}>;
|
||||||
@ -2623,10 +2624,17 @@ 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 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 UpsertSettingPermissionsMutationVariables = Exact<{
|
||||||
|
upsertSettingPermissionsInput: UpsertSettingPermissionsInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type UpsertSettingPermissionsMutation = { __typename?: 'Mutation', upsertSettingPermissions: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> };
|
||||||
|
|
||||||
export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetRolesQuery = { __typename?: 'Query', getRoles: 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, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> }> };
|
export type GetRolesQuery = { __typename?: 'Query', getRoles: 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, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, settingPermissions?: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> | null }> };
|
||||||
|
|
||||||
export type CreateApprovedAccessDomainMutationVariables = Exact<{
|
export type CreateApprovedAccessDomainMutationVariables = Exact<{
|
||||||
input: CreateApprovedAccessDomainInput;
|
input: CreateApprovedAccessDomainInput;
|
||||||
@ -2941,6 +2949,13 @@ export const AvailableSsoIdentityProvidersFragmentFragmentDoc = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const SettingPermissionFragmentFragmentDoc = gql`
|
||||||
|
fragment SettingPermissionFragment on SettingPermission {
|
||||||
|
id
|
||||||
|
setting
|
||||||
|
roleId
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const WorkspaceMemberQueryFragmentFragmentDoc = gql`
|
export const WorkspaceMemberQueryFragmentFragmentDoc = gql`
|
||||||
fragment WorkspaceMemberQueryFragment on WorkspaceMember {
|
fragment WorkspaceMemberQueryFragment on WorkspaceMember {
|
||||||
id
|
id
|
||||||
@ -4737,6 +4752,41 @@ export function useUpdateWorkspaceMemberRoleMutation(baseOptions?: Apollo.Mutati
|
|||||||
export type UpdateWorkspaceMemberRoleMutationHookResult = ReturnType<typeof useUpdateWorkspaceMemberRoleMutation>;
|
export type UpdateWorkspaceMemberRoleMutationHookResult = ReturnType<typeof useUpdateWorkspaceMemberRoleMutation>;
|
||||||
export type UpdateWorkspaceMemberRoleMutationResult = Apollo.MutationResult<UpdateWorkspaceMemberRoleMutation>;
|
export type UpdateWorkspaceMemberRoleMutationResult = Apollo.MutationResult<UpdateWorkspaceMemberRoleMutation>;
|
||||||
export type UpdateWorkspaceMemberRoleMutationOptions = Apollo.BaseMutationOptions<UpdateWorkspaceMemberRoleMutation, UpdateWorkspaceMemberRoleMutationVariables>;
|
export type UpdateWorkspaceMemberRoleMutationOptions = Apollo.BaseMutationOptions<UpdateWorkspaceMemberRoleMutation, UpdateWorkspaceMemberRoleMutationVariables>;
|
||||||
|
export const UpsertSettingPermissionsDocument = gql`
|
||||||
|
mutation UpsertSettingPermissions($upsertSettingPermissionsInput: UpsertSettingPermissionsInput!) {
|
||||||
|
upsertSettingPermissions(
|
||||||
|
upsertSettingPermissionsInput: $upsertSettingPermissionsInput
|
||||||
|
) {
|
||||||
|
...SettingPermissionFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${SettingPermissionFragmentFragmentDoc}`;
|
||||||
|
export type UpsertSettingPermissionsMutationFn = Apollo.MutationFunction<UpsertSettingPermissionsMutation, UpsertSettingPermissionsMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useUpsertSettingPermissionsMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useUpsertSettingPermissionsMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useUpsertSettingPermissionsMutation` 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 [upsertSettingPermissionsMutation, { data, loading, error }] = useUpsertSettingPermissionsMutation({
|
||||||
|
* variables: {
|
||||||
|
* upsertSettingPermissionsInput: // value for 'upsertSettingPermissionsInput'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useUpsertSettingPermissionsMutation(baseOptions?: Apollo.MutationHookOptions<UpsertSettingPermissionsMutation, UpsertSettingPermissionsMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<UpsertSettingPermissionsMutation, UpsertSettingPermissionsMutationVariables>(UpsertSettingPermissionsDocument, options);
|
||||||
|
}
|
||||||
|
export type UpsertSettingPermissionsMutationHookResult = ReturnType<typeof useUpsertSettingPermissionsMutation>;
|
||||||
|
export type UpsertSettingPermissionsMutationResult = Apollo.MutationResult<UpsertSettingPermissionsMutation>;
|
||||||
|
export type UpsertSettingPermissionsMutationOptions = Apollo.BaseMutationOptions<UpsertSettingPermissionsMutation, UpsertSettingPermissionsMutationVariables>;
|
||||||
export const GetRolesDocument = gql`
|
export const GetRolesDocument = gql`
|
||||||
query GetRoles {
|
query GetRoles {
|
||||||
getRoles {
|
getRoles {
|
||||||
@ -4744,10 +4794,14 @@ export const GetRolesDocument = gql`
|
|||||||
workspaceMembers {
|
workspaceMembers {
|
||||||
...WorkspaceMemberQueryFragment
|
...WorkspaceMemberQueryFragment
|
||||||
}
|
}
|
||||||
|
settingPermissions {
|
||||||
|
...SettingPermissionFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
${RoleFragmentFragmentDoc}
|
${RoleFragmentFragmentDoc}
|
||||||
${WorkspaceMemberQueryFragmentFragmentDoc}`;
|
${WorkspaceMemberQueryFragmentFragmentDoc}
|
||||||
|
${SettingPermissionFragmentFragmentDoc}`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __useGetRolesQuery__
|
* __useGetRolesQuery__
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
import { settingsPersistedRoleFamilyState } from '@/settings/roles/states/settingsPersistedRoleFamilyState';
|
||||||
import { settingsRoleIdsState } from '@/settings/roles/states/settingsRoleIdsState';
|
import { settingsRoleIdsState } from '@/settings/roles/states/settingsRoleIdsState';
|
||||||
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
import { settingsRolesIsLoadingState } from '@/settings/roles/states/settingsRolesIsLoadingState';
|
||||||
@ -21,6 +22,7 @@ export const SettingsRolesQueryEffect = () => {
|
|||||||
const roleIds = roles.map((role) => role.id);
|
const roleIds = roles.map((role) => role.id);
|
||||||
set(settingsRoleIdsState, roleIds);
|
set(settingsRoleIdsState, roleIds);
|
||||||
roles.forEach((role) => {
|
roles.forEach((role) => {
|
||||||
|
set(settingsDraftRoleFamilyState(role.id), role);
|
||||||
set(settingsPersistedRoleFamilyState(role.id), role);
|
set(settingsPersistedRoleFamilyState(role.id), role);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const SETTING_PERMISSION_FRAGMENT = gql`
|
||||||
|
fragment SettingPermissionFragment on SettingPermission {
|
||||||
|
id
|
||||||
|
setting
|
||||||
|
roleId
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment';
|
||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const UPSERT_SETTING_PERMISSIONS = gql`
|
||||||
|
${SETTING_PERMISSION_FRAGMENT}
|
||||||
|
mutation UpsertSettingPermissions(
|
||||||
|
$upsertSettingPermissionsInput: UpsertSettingPermissionsInput!
|
||||||
|
) {
|
||||||
|
upsertSettingPermissions(
|
||||||
|
upsertSettingPermissionsInput: $upsertSettingPermissionsInput
|
||||||
|
) {
|
||||||
|
...SettingPermissionFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -1,16 +1,21 @@
|
|||||||
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||||
|
import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment';
|
||||||
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const GET_ROLES = gql`
|
export const GET_ROLES = gql`
|
||||||
${WORKSPACE_MEMBER_QUERY_FRAGMENT}
|
${WORKSPACE_MEMBER_QUERY_FRAGMENT}
|
||||||
${ROLE_FRAGMENT}
|
${ROLE_FRAGMENT}
|
||||||
|
${SETTING_PERMISSION_FRAGMENT}
|
||||||
query GetRoles {
|
query GetRoles {
|
||||||
getRoles {
|
getRoles {
|
||||||
...RoleFragment
|
...RoleFragment
|
||||||
workspaceMembers {
|
workspaceMembers {
|
||||||
...WorkspaceMemberQueryFragment
|
...WorkspaceMemberQueryFragment
|
||||||
}
|
}
|
||||||
|
settingPermissions {
|
||||||
|
...SettingPermissionFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle';
|
||||||
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader';
|
import { SettingsRolePermissionsObjectsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableHeader';
|
||||||
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
import { SettingsRolePermissionsObjectsTableRow } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsObjectsTableRow';
|
||||||
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableHeader';
|
import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/components/SettingsRolePermissionsSettingsTableHeader';
|
||||||
@ -5,10 +6,10 @@ import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-p
|
|||||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
import { SettingsRolePermissionsObjectPermission } from '@/settings/roles/types/SettingsRolePermissionsObjectPermission';
|
||||||
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { SettingPermissionType } from '~/generated-metadata/graphql';
|
|
||||||
import {
|
import {
|
||||||
H2Title,
|
H2Title,
|
||||||
IconCode,
|
IconCode,
|
||||||
@ -23,7 +24,11 @@ import {
|
|||||||
IconTrashX,
|
IconTrashX,
|
||||||
IconUsers,
|
IconUsers,
|
||||||
} from 'twenty-ui/display';
|
} from 'twenty-ui/display';
|
||||||
import { Section } from 'twenty-ui/layout';
|
import { Card, Section } from 'twenty-ui/layout';
|
||||||
|
import {
|
||||||
|
FeatureFlagKey,
|
||||||
|
SettingPermissionType,
|
||||||
|
} from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledRolePermissionsContainer = styled.div`
|
const StyledRolePermissionsContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -40,6 +45,10 @@ const StyledTableRows = styled.div`
|
|||||||
padding-top: ${({ theme }) => theme.spacing(2)};
|
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledCard = styled(Card)`
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
type SettingsRolePermissionsProps = {
|
type SettingsRolePermissionsProps = {
|
||||||
roleId: string;
|
roleId: string;
|
||||||
isEditable: boolean;
|
isEditable: boolean;
|
||||||
@ -110,53 +119,50 @@ export const SettingsRolePermissions = ({
|
|||||||
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
key: SettingPermissionType.API_KEYS_AND_WEBHOOKS,
|
||||||
name: t`API Keys & Webhooks`,
|
name: t`API Keys & Webhooks`,
|
||||||
description: t`Manage API keys and webhooks`,
|
description: t`Manage API keys and webhooks`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconCode,
|
Icon: IconCode,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingPermissionType.WORKSPACE,
|
key: SettingPermissionType.WORKSPACE,
|
||||||
name: t`Workspace`,
|
name: t`Workspace`,
|
||||||
description: t`Set global workspace preferences`,
|
description: t`Set global workspace preferences`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconSettings,
|
Icon: IconSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
key: SettingPermissionType.WORKSPACE_MEMBERS,
|
||||||
name: t`Users`,
|
name: t`Users`,
|
||||||
description: t`Add or remove users`,
|
description: t`Add or remove users`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconUsers,
|
Icon: IconUsers,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingPermissionType.ROLES,
|
key: SettingPermissionType.ROLES,
|
||||||
name: t`Roles`,
|
name: t`Roles`,
|
||||||
description: t`Define user roles and access levels`,
|
description: t`Define user roles and access levels`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconLockOpen,
|
Icon: IconLockOpen,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingPermissionType.DATA_MODEL,
|
key: SettingPermissionType.DATA_MODEL,
|
||||||
name: t`Data Model`,
|
name: t`Data Model`,
|
||||||
description: t`Edit CRM data structure and fields`,
|
description: t`Edit CRM data structure and fields`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconHierarchy,
|
Icon: IconHierarchy,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingPermissionType.ADMIN_PANEL,
|
key: SettingPermissionType.ADMIN_PANEL,
|
||||||
name: t`Admin Panel`,
|
name: t`Admin Panel`,
|
||||||
description: t`Admin settings and system tools`,
|
description: t`Admin settings and system tools`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconServer,
|
Icon: IconServer,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SettingPermissionType.SECURITY,
|
key: SettingPermissionType.SECURITY,
|
||||||
name: t`Security`,
|
name: t`Security`,
|
||||||
description: t`Manage security policies`,
|
description: t`Manage security policies`,
|
||||||
value: settingsDraftRole.canUpdateAllSettings,
|
|
||||||
Icon: IconKey,
|
Icon: IconKey,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRolePermissionsContainer>
|
<StyledRolePermissionsContainer>
|
||||||
<Section>
|
<Section>
|
||||||
@ -183,15 +189,32 @@ export const SettingsRolePermissions = ({
|
|||||||
</Section>
|
</Section>
|
||||||
<Section>
|
<Section>
|
||||||
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
||||||
|
{isPermissionsV2Enabled && (
|
||||||
|
<StyledCard rounded>
|
||||||
|
<SettingsOptionCardContentToggle
|
||||||
|
Icon={IconSettings}
|
||||||
|
title={t`Settings All Access`}
|
||||||
|
description={t`Ability to edit all settings`}
|
||||||
|
checked={settingsDraftRole.canUpdateAllSettings}
|
||||||
|
disabled={!isEditable}
|
||||||
|
onChange={() => {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</StyledCard>
|
||||||
|
)}
|
||||||
<StyledTable>
|
<StyledTable>
|
||||||
<SettingsRolePermissionsSettingsTableHeader
|
<SettingsRolePermissionsSettingsTableHeader />
|
||||||
allPermissions={settingsDraftRole.canUpdateAllSettings}
|
|
||||||
/>
|
|
||||||
<StyledTableRows>
|
<StyledTableRows>
|
||||||
{settingsPermissionsConfig.map((permission) => (
|
{settingsPermissionsConfig.map((permission) => (
|
||||||
<SettingsRolePermissionsSettingsTableRow
|
<SettingsRolePermissionsSettingsTableRow
|
||||||
key={permission.key}
|
key={permission.key}
|
||||||
|
roleId={roleId}
|
||||||
permission={permission}
|
permission={permission}
|
||||||
|
isEditable={isEditable}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</StyledTableRows>
|
</StyledTableRows>
|
||||||
|
|||||||
@ -1,42 +1,11 @@
|
|||||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { Checkbox } from 'twenty-ui/input';
|
|
||||||
|
|
||||||
const StyledNameHeader = styled(TableHeader)`
|
export const SettingsRolePermissionsSettingsTableHeader = () => (
|
||||||
flex: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTypeHeader = styled(TableHeader)`
|
|
||||||
flex: 1;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledActionsHeader = styled(TableHeader)`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
padding-right: ${({ theme }) => theme.spacing(4)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type SettingsRolePermissionsSettingsTableHeaderProps = {
|
|
||||||
allPermissions: boolean;
|
|
||||||
onToggleAll?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SettingsRolePermissionsSettingsTableHeader = ({
|
|
||||||
allPermissions,
|
|
||||||
onToggleAll,
|
|
||||||
}: SettingsRolePermissionsSettingsTableHeaderProps) => (
|
|
||||||
<TableRow gridAutoColumns="3fr 4fr 24px">
|
<TableRow gridAutoColumns="3fr 4fr 24px">
|
||||||
<StyledNameHeader>{t`Name`}</StyledNameHeader>
|
<TableHeader>{t`Name`}</TableHeader>
|
||||||
<StyledTypeHeader>{t`Description`}</StyledTypeHeader>
|
<TableHeader>{t`Description`}</TableHeader>
|
||||||
<StyledActionsHeader aria-label={t`Actions`}>
|
<TableHeader></TableHeader>
|
||||||
<Checkbox
|
|
||||||
checked={allPermissions}
|
|
||||||
disabled={!onToggleAll}
|
|
||||||
onChange={onToggleAll}
|
|
||||||
/>
|
|
||||||
</StyledActionsHeader>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
|
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||||
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/types/SettingsRolePermissionsSettingPermission';
|
||||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useRecoilState } from 'recoil';
|
||||||
import { Checkbox } from 'twenty-ui/input';
|
import { Checkbox } from 'twenty-ui/input';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
const StyledName = styled.span`
|
const StyledName = styled.span`
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
@ -34,13 +39,54 @@ const StyledIconContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type SettingsRolePermissionsSettingsTableRowProps = {
|
type SettingsRolePermissionsSettingsTableRowProps = {
|
||||||
|
roleId: string;
|
||||||
permission: SettingsRolePermissionsSettingPermission;
|
permission: SettingsRolePermissionsSettingPermission;
|
||||||
|
isEditable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsRolePermissionsSettingsTableRow = ({
|
export const SettingsRolePermissionsSettingsTableRow = ({
|
||||||
|
roleId,
|
||||||
permission,
|
permission,
|
||||||
|
isEditable,
|
||||||
}: SettingsRolePermissionsSettingsTableRowProps) => {
|
}: SettingsRolePermissionsSettingsTableRowProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||||
|
settingsDraftRoleFamilyState(roleId),
|
||||||
|
);
|
||||||
|
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
);
|
||||||
|
const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings;
|
||||||
|
|
||||||
|
const isSettingPermissionEnabled =
|
||||||
|
settingsDraftRole.settingPermissions?.some(
|
||||||
|
(settingPermission) => settingPermission.setting === permission.key,
|
||||||
|
) ?? false;
|
||||||
|
|
||||||
|
const handleChange = (value: boolean) => {
|
||||||
|
const currentPermissions = settingsDraftRole.settingPermissions ?? [];
|
||||||
|
|
||||||
|
if (value === true) {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
settingPermissions: [
|
||||||
|
...currentPermissions,
|
||||||
|
{
|
||||||
|
id: v4(),
|
||||||
|
setting: permission.key,
|
||||||
|
roleId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setSettingsDraftRole({
|
||||||
|
...settingsDraftRole,
|
||||||
|
settingPermissions: currentPermissions.filter(
|
||||||
|
(p) => p.setting !== permission.key,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={permission.key} gridAutoColumns="3fr 4fr 24px">
|
<TableRow key={permission.key} gridAutoColumns="3fr 4fr 24px">
|
||||||
@ -58,7 +104,13 @@ export const SettingsRolePermissionsSettingsTableRow = ({
|
|||||||
<StyledDescription>{permission.description}</StyledDescription>
|
<StyledDescription>{permission.description}</StyledDescription>
|
||||||
</StyledPermissionCell>
|
</StyledPermissionCell>
|
||||||
<StyledCheckboxCell>
|
<StyledCheckboxCell>
|
||||||
<Checkbox checked={permission.value} disabled />
|
<Checkbox
|
||||||
|
checked={isSettingPermissionEnabled || canUpdateAllSettings}
|
||||||
|
disabled={
|
||||||
|
!isEditable || canUpdateAllSettings || !isPermissionsV2Enabled
|
||||||
|
}
|
||||||
|
onChange={(event) => handleChange(event.target.checked)}
|
||||||
|
/>
|
||||||
</StyledCheckboxCell>
|
</StyledCheckboxCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||||
|
import { GET_ROLES } from '@/settings/roles/graphql/queries/getRolesQuery';
|
||||||
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
|
import { useUpdateWorkspaceMemberRole } from '@/settings/roles/hooks/useUpdateWorkspaceMemberRole';
|
||||||
import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/components/SettingsRoleAssignment';
|
import { SettingsRoleAssignment } from '@/settings/roles/role-assignment/components/SettingsRoleAssignment';
|
||||||
import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions';
|
import { SettingsRolePermissions } from '@/settings/roles/role-permissions/components/SettingsRolePermissions';
|
||||||
@ -14,26 +15,41 @@ import { TabList } from '@/ui/layout/tab/components/TabList';
|
|||||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import {
|
import {
|
||||||
FeatureFlagKey,
|
FeatureFlagKey,
|
||||||
|
Role,
|
||||||
useCreateOneRoleMutation,
|
useCreateOneRoleMutation,
|
||||||
useUpdateOneRoleMutation,
|
useUpdateOneRoleMutation,
|
||||||
|
useUpsertSettingPermissionsMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
|
import { getDirtyFields } from '~/utils/getDirtyFields';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display';
|
|
||||||
|
|
||||||
type SettingsRoleProps = {
|
type SettingsRoleProps = {
|
||||||
roleId: string;
|
roleId: string;
|
||||||
isCreateMode: boolean;
|
isCreateMode: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ROLE_BASIC_KEYS: Array<keyof Role> = [
|
||||||
|
'label',
|
||||||
|
'description',
|
||||||
|
'icon',
|
||||||
|
'canUpdateAllSettings',
|
||||||
|
'canReadAllObjectRecords',
|
||||||
|
'canUpdateAllObjectRecords',
|
||||||
|
'canSoftDeleteAllObjectRecords',
|
||||||
|
'canDestroyAllObjectRecords',
|
||||||
|
];
|
||||||
|
|
||||||
export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||||
const activeTabId = useRecoilComponentValueV2(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
activeTabIdComponentState,
|
activeTabIdComponentState,
|
||||||
@ -48,6 +64,11 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
|
|
||||||
const [createRole] = useCreateOneRoleMutation();
|
const [createRole] = useCreateOneRoleMutation();
|
||||||
const [updateRole] = useUpdateOneRoleMutation();
|
const [updateRole] = useUpdateOneRoleMutation();
|
||||||
|
const [upsertSettingPermissions] = useUpsertSettingPermissionsMutation();
|
||||||
|
|
||||||
|
const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId);
|
||||||
|
|
||||||
|
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
|
||||||
|
|
||||||
const settingsDraftRole = useRecoilValue(
|
const settingsDraftRole = useRecoilValue(
|
||||||
settingsDraftRoleFamilyState(roleId),
|
settingsDraftRoleFamilyState(roleId),
|
||||||
@ -57,10 +78,6 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
settingsPersistedRoleFamilyState(roleId),
|
settingsPersistedRoleFamilyState(roleId),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { addWorkspaceMembersToRole } = useUpdateWorkspaceMemberRole(roleId);
|
|
||||||
|
|
||||||
const settingsRolesIsLoading = useRecoilValue(settingsRolesIsLoadingState);
|
|
||||||
|
|
||||||
if (!isDefined(settingsRolesIsLoading)) {
|
if (!isDefined(settingsRolesIsLoading)) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -87,7 +104,12 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
|
|
||||||
const isDirty = !isDeeplyEqual(settingsDraftRole, settingsPersistedRole);
|
const isDirty = !isDeeplyEqual(settingsDraftRole, settingsPersistedRole);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = async () => {
|
||||||
|
const dirtyFields = getDirtyFields(
|
||||||
|
settingsDraftRole,
|
||||||
|
settingsPersistedRole,
|
||||||
|
);
|
||||||
|
|
||||||
if (isCreateMode) {
|
if (isCreateMode) {
|
||||||
const roleId = v4();
|
const roleId = v4();
|
||||||
|
|
||||||
@ -116,33 +138,63 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await upsertSettingPermissions({
|
||||||
|
variables: {
|
||||||
|
upsertSettingPermissionsInput: {
|
||||||
|
roleId: data.createOneRole.id,
|
||||||
|
settingPermissionKeys:
|
||||||
|
settingsDraftRole.settingPermissions?.map(
|
||||||
|
(settingPermission) => settingPermission.setting,
|
||||||
|
) ?? [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||||
|
});
|
||||||
|
|
||||||
navigateSettings(SettingsPath.RoleDetail, {
|
navigateSettings(SettingsPath.RoleDetail, {
|
||||||
roleId: data.createOneRole.id,
|
roleId: data.createOneRole.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
updateRole({
|
if (ROLE_BASIC_KEYS.some((key) => key in dirtyFields)) {
|
||||||
variables: {
|
await updateRole({
|
||||||
updateRoleInput: {
|
variables: {
|
||||||
id: roleId,
|
updateRoleInput: {
|
||||||
update: {
|
id: roleId,
|
||||||
label: settingsDraftRole.label,
|
update: {
|
||||||
description: settingsDraftRole.description,
|
label: settingsDraftRole.label,
|
||||||
icon: settingsDraftRole.icon,
|
description: settingsDraftRole.description,
|
||||||
canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings,
|
icon: settingsDraftRole.icon,
|
||||||
canReadAllObjectRecords:
|
canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings,
|
||||||
settingsDraftRole.canReadAllObjectRecords,
|
canReadAllObjectRecords:
|
||||||
canUpdateAllObjectRecords:
|
settingsDraftRole.canReadAllObjectRecords,
|
||||||
settingsDraftRole.canUpdateAllObjectRecords,
|
canUpdateAllObjectRecords:
|
||||||
canSoftDeleteAllObjectRecords:
|
settingsDraftRole.canUpdateAllObjectRecords,
|
||||||
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
canSoftDeleteAllObjectRecords:
|
||||||
canDestroyAllObjectRecords:
|
settingsDraftRole.canSoftDeleteAllObjectRecords,
|
||||||
settingsDraftRole.canDestroyAllObjectRecords,
|
canDestroyAllObjectRecords:
|
||||||
|
settingsDraftRole.canDestroyAllObjectRecords,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
if (isDefined(dirtyFields.settingPermissions)) {
|
||||||
|
await upsertSettingPermissions({
|
||||||
|
variables: {
|
||||||
|
upsertSettingPermissionsInput: {
|
||||||
|
roleId: roleId,
|
||||||
|
settingPermissionKeys:
|
||||||
|
settingsDraftRole.settingPermissions?.map(
|
||||||
|
(settingPermission) => settingPermission.setting,
|
||||||
|
) ?? [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
refetchQueries: [getOperationName(GET_ROLES) ?? ''],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -15,5 +15,6 @@ export const settingsDraftRoleFamilyState = createFamilyState<Role, string>({
|
|||||||
canUpdateAllSettings: false,
|
canUpdateAllSettings: false,
|
||||||
isEditable: false,
|
isEditable: false,
|
||||||
workspaceMembers: [],
|
workspaceMembers: [],
|
||||||
|
settingPermissions: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { IconComponent } from 'twenty-ui/display';
|
import { IconComponent } from 'twenty-ui/display';
|
||||||
|
import { SettingPermissionType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export type SettingsRolePermissionsSettingPermission = {
|
export type SettingsRolePermissionsSettingPermission = {
|
||||||
key: string;
|
key: SettingPermissionType;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
value: boolean;
|
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
};
|
};
|
||||||
|
|||||||
115
packages/twenty-front/src/utils/getDirtyFields.spec.ts
Normal file
115
packages/twenty-front/src/utils/getDirtyFields.spec.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { getDirtyFields } from './getDirtyFields';
|
||||||
|
|
||||||
|
describe('getDirtyFields', () => {
|
||||||
|
it('should return all defined fields from draft when persisted is null', () => {
|
||||||
|
const draft = { a: 1, b: 'hello', c: undefined, d: null };
|
||||||
|
const persisted = null;
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({
|
||||||
|
a: 1,
|
||||||
|
b: 'hello',
|
||||||
|
d: null,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return all defined fields from draft when persisted is undefined', () => {
|
||||||
|
const draft = { a: 1, b: 'hello', c: undefined, d: false };
|
||||||
|
const persisted = undefined;
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({
|
||||||
|
a: 1,
|
||||||
|
b: 'hello',
|
||||||
|
d: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty object when draft and persisted are identical', () => {
|
||||||
|
const draft = { a: 1, b: { c: 2 }, d: [1, 2] };
|
||||||
|
const persisted = { a: 1, b: { c: 2 }, d: [1, 2] };
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect simple value changes', () => {
|
||||||
|
const draft = { a: 1, b: 'world', c: true };
|
||||||
|
const persisted = { a: 1, b: 'hello', c: false };
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({ b: 'world', c: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect nested object changes', () => {
|
||||||
|
const draft = { a: { b: { c: 3 } } };
|
||||||
|
const persisted = { a: { b: { c: 2 } } };
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({ a: { b: { c: 3 } } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect array changes', () => {
|
||||||
|
const draft = { a: [1, 3] };
|
||||||
|
const persisted = { a: [1, 2] };
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({ a: [1, 3] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect added fields', () => {
|
||||||
|
const draft = { a: 1, b: 2 };
|
||||||
|
const persisted = { a: 1 };
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({ b: 2 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect removed fields (value becomes undefined)', () => {
|
||||||
|
const draft = { a: 1 };
|
||||||
|
const persisted = { a: 1, b: 2 };
|
||||||
|
// When a field is removed, its value in draft effectively becomes undefined
|
||||||
|
// Cast persisted to any to satisfy TS in this test scenario
|
||||||
|
expect(getDirtyFields(draft, persisted as any)).toEqual({ b: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect fields set to undefined', () => {
|
||||||
|
const draft = { a: 1, b: undefined };
|
||||||
|
const persisted = { a: 1, b: 2 };
|
||||||
|
// Cast persisted to any to satisfy TS in this test scenario
|
||||||
|
expect(getDirtyFields(draft, persisted as any)).toEqual({ b: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should detect fields set to null', () => {
|
||||||
|
const draft = { a: 1, b: null };
|
||||||
|
const persisted = { a: 1, b: 2 };
|
||||||
|
// Cast persisted to any to satisfy TS in this test scenario
|
||||||
|
expect(getDirtyFields(draft, persisted as any)).toEqual({ b: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex nested structures with mixed changes', () => {
|
||||||
|
const draft = {
|
||||||
|
id: 1,
|
||||||
|
name: 'new name', // changed
|
||||||
|
details: {
|
||||||
|
status: 'active', // same
|
||||||
|
tags: ['tag1', 'tag3'], // changed
|
||||||
|
metadata: { key: 'newValue' }, // changed
|
||||||
|
},
|
||||||
|
settings: { enabled: true }, // new
|
||||||
|
};
|
||||||
|
const persisted = {
|
||||||
|
id: 1,
|
||||||
|
name: 'old name',
|
||||||
|
details: {
|
||||||
|
status: 'active',
|
||||||
|
tags: ['tag1', 'tag2'],
|
||||||
|
metadata: { key: 'oldValue' },
|
||||||
|
},
|
||||||
|
archived: true, // removed
|
||||||
|
};
|
||||||
|
// Cast persisted to any to satisfy TS in this test scenario
|
||||||
|
expect(getDirtyFields(draft, persisted as any)).toEqual({
|
||||||
|
name: 'new name',
|
||||||
|
details: {
|
||||||
|
status: 'active',
|
||||||
|
tags: ['tag1', 'tag3'],
|
||||||
|
metadata: { key: 'newValue' },
|
||||||
|
},
|
||||||
|
settings: { enabled: true },
|
||||||
|
archived: undefined,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty object for deeply equal but different reference objects', () => {
|
||||||
|
const draft = { a: { b: 1 } };
|
||||||
|
const persisted = JSON.parse(JSON.stringify(draft)); // Deep clone, different reference
|
||||||
|
expect(getDirtyFields(draft, persisted)).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
26
packages/twenty-front/src/utils/getDirtyFields.ts
Normal file
26
packages/twenty-front/src/utils/getDirtyFields.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { isDeeplyEqual } from './isDeeplyEqual';
|
||||||
|
|
||||||
|
export const getDirtyFields = <T extends Record<string, any>>(
|
||||||
|
draft: T,
|
||||||
|
persisted: T | null | undefined,
|
||||||
|
): Partial<T> => {
|
||||||
|
if (!persisted) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(draft).filter(([, value]) => value !== undefined),
|
||||||
|
) as Partial<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dirty: Partial<T> = {};
|
||||||
|
const allKeys = new Set([...Object.keys(draft), ...Object.keys(persisted)]);
|
||||||
|
|
||||||
|
for (const key of allKeys) {
|
||||||
|
const draftValue = draft[key as keyof T];
|
||||||
|
const persistedValue = persisted[key as keyof T];
|
||||||
|
|
||||||
|
if (!isDeeplyEqual(draftValue, persistedValue)) {
|
||||||
|
dirty[key as keyof T] = draftValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirty;
|
||||||
|
};
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class RemoveCanUpdateSettingFromSettingPermission1743605310126
|
||||||
|
implements MigrationInterface
|
||||||
|
{
|
||||||
|
name = 'RemoveCanUpdateSettingFromSettingPermission1743605310126';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."settingPermission" DROP COLUMN "canUpdateSetting"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "metadata"."settingPermission" ADD "canUpdateSetting" boolean`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import { Relation } from 'typeorm';
|
|||||||
|
|
||||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
|
||||||
|
|
||||||
@ObjectType('Role')
|
@ObjectType('Role')
|
||||||
export class RoleDTO {
|
export class RoleDTO {
|
||||||
@ -42,4 +43,7 @@ export class RoleDTO {
|
|||||||
|
|
||||||
@Field({ nullable: false })
|
@Field({ nullable: false })
|
||||||
canDestroyAllObjectRecords: boolean;
|
canDestroyAllObjectRecords: boolean;
|
||||||
|
|
||||||
|
@Field(() => [SettingPermissionDTO], { nullable: true })
|
||||||
|
settingPermissions?: SettingPermissionDTO[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
|||||||
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
|
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
|
||||||
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
||||||
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
|
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
|
||||||
import { UpsertSettingPermissionInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
|
import { UpsertSettingPermissionsInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
|
||||||
import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service';
|
import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
@ -154,17 +154,17 @@ export class RoleResolver {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => SettingPermissionDTO)
|
@Mutation(() => [SettingPermissionDTO])
|
||||||
async upsertOneSettingPermission(
|
async upsertSettingPermissions(
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args('upsertSettingPermissionInput')
|
@Args('upsertSettingPermissionsInput')
|
||||||
upsertSettingPermissionInput: UpsertSettingPermissionInput,
|
upsertSettingPermissionsInput: UpsertSettingPermissionsInput,
|
||||||
) {
|
) {
|
||||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
|
|
||||||
return this.settingPermissionService.upsertSettingPermission({
|
return this.settingPermissionService.upsertSettingPermissions({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
input: upsertSettingPermissionInput,
|
input: upsertSettingPermissionsInput,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export class RoleService {
|
|||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
},
|
},
|
||||||
relations: ['userWorkspaceRoles'],
|
relations: ['userWorkspaceRoles', 'settingPermissions'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ export class RoleService {
|
|||||||
id,
|
id,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
},
|
},
|
||||||
relations: ['userWorkspaceRoles'],
|
relations: ['userWorkspaceRoles', 'settingPermissions'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,4 @@ export class SettingPermissionDTO {
|
|||||||
|
|
||||||
@Field({ nullable: false })
|
@Field({ nullable: false })
|
||||||
setting: SettingPermissionType;
|
setting: SettingPermissionType;
|
||||||
|
|
||||||
@Field({ nullable: true })
|
|
||||||
canUpdateSetting?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,18 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
import {
|
import { IsArray, IsEnum, IsNotEmpty, IsUUID } from 'class-validator';
|
||||||
IsBoolean,
|
|
||||||
IsNotEmpty,
|
|
||||||
IsOptional,
|
|
||||||
IsString,
|
|
||||||
IsUUID,
|
|
||||||
} from 'class-validator';
|
|
||||||
|
|
||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
export class UpsertSettingPermissionInput {
|
export class UpsertSettingPermissionsInput {
|
||||||
@IsUUID()
|
@IsUUID()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@Field()
|
@Field()
|
||||||
roleId: string;
|
roleId: string;
|
||||||
|
|
||||||
@IsString()
|
@IsArray()
|
||||||
@IsNotEmpty()
|
@IsEnum(SettingPermissionType, { each: true })
|
||||||
@Field({ nullable: false })
|
@Field(() => [SettingPermissionType])
|
||||||
setting: SettingPermissionType;
|
settingPermissionKeys: SettingPermissionType[];
|
||||||
|
|
||||||
@IsBoolean()
|
|
||||||
@IsOptional()
|
|
||||||
@Field({ nullable: true })
|
|
||||||
canUpdateSetting?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,9 +31,6 @@ export class SettingPermissionEntity {
|
|||||||
@Column({ nullable: false, type: 'varchar' })
|
@Column({ nullable: false, type: 'varchar' })
|
||||||
setting: SettingPermissionType;
|
setting: SettingPermissionType;
|
||||||
|
|
||||||
@Column({ nullable: true, type: 'boolean' })
|
|
||||||
canUpdateSetting?: boolean;
|
|
||||||
|
|
||||||
@Column({ nullable: false, type: 'uuid' })
|
@Column({ nullable: false, type: 'uuid' })
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
import { DataSource, In, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
import {
|
import {
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { UpsertSettingPermissionInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
|
import { UpsertSettingPermissionsInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
|
||||||
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
|
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
|
||||||
|
|
||||||
export class SettingPermissionService {
|
export class SettingPermissionService {
|
||||||
@ -19,55 +19,90 @@ export class SettingPermissionService {
|
|||||||
private readonly settingPermissionRepository: Repository<SettingPermissionEntity>,
|
private readonly settingPermissionRepository: Repository<SettingPermissionEntity>,
|
||||||
@InjectRepository(RoleEntity, 'metadata')
|
@InjectRepository(RoleEntity, 'metadata')
|
||||||
private readonly roleRepository: Repository<RoleEntity>,
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
|
@InjectDataSource('metadata')
|
||||||
|
private readonly metadataDataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async upsertSettingPermission({
|
public async upsertSettingPermissions({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
input,
|
input,
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
input: UpsertSettingPermissionInput;
|
input: UpsertSettingPermissionsInput;
|
||||||
}): Promise<SettingPermissionEntity | null | undefined> {
|
}): Promise<SettingPermissionEntity[]> {
|
||||||
await this.validateRoleIsEditableOrThrow({
|
await this.validateRoleIsEditableOrThrow({
|
||||||
roleId: input.roleId,
|
roleId: input.roleId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!Object.values(SettingPermissionType).includes(input.setting)) {
|
const invalidSettings = input.settingPermissionKeys.filter(
|
||||||
|
(setting) => !Object.values(SettingPermissionType).includes(setting),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invalidSettings.length > 0) {
|
||||||
throw new PermissionsException(
|
throw new PermissionsException(
|
||||||
PermissionsExceptionMessage.INVALID_SETTING,
|
`${PermissionsExceptionMessage.INVALID_SETTING}: ${invalidSettings.join(', ')}`,
|
||||||
PermissionsExceptionCode.INVALID_SETTING,
|
PermissionsExceptionCode.INVALID_SETTING,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queryRunner = this.metadataDataSource.createQueryRunner();
|
||||||
|
|
||||||
|
await queryRunner.connect();
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.settingPermissionRepository.upsert(
|
const existingPermissions = await queryRunner.manager.find(
|
||||||
|
SettingPermissionEntity,
|
||||||
{
|
{
|
||||||
workspaceId,
|
where: {
|
||||||
...input,
|
roleId: input.roleId,
|
||||||
},
|
workspaceId,
|
||||||
{
|
},
|
||||||
conflictPaths: ['setting', 'roleId'],
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const existingSettings = new Set(
|
||||||
|
existingPermissions.map((p) => p.setting),
|
||||||
|
);
|
||||||
|
const inputSettings = new Set(input.settingPermissionKeys);
|
||||||
|
|
||||||
const settingPermissionId = result.generatedMaps?.[0]?.id;
|
const settingsToAdd = input.settingPermissionKeys.filter(
|
||||||
|
(setting) => !existingSettings.has(setting),
|
||||||
|
);
|
||||||
|
const permissionsToRemove = existingPermissions.filter(
|
||||||
|
(permission) => !inputSettings.has(permission.setting),
|
||||||
|
);
|
||||||
|
|
||||||
if (!isDefined(settingPermissionId)) {
|
if (permissionsToRemove.length > 0) {
|
||||||
throw new Error('Failed to upsert setting permission');
|
await queryRunner.manager.delete(SettingPermissionEntity, {
|
||||||
|
id: In(permissionsToRemove.map((p) => p.id)),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.settingPermissionRepository.findOne({
|
if (settingsToAdd.length > 0) {
|
||||||
where: {
|
const newPermissions = settingsToAdd.map((setting) =>
|
||||||
id: settingPermissionId,
|
queryRunner.manager.create(SettingPermissionEntity, {
|
||||||
},
|
workspaceId,
|
||||||
|
roleId: input.roleId,
|
||||||
|
setting,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await queryRunner.manager.save(SettingPermissionEntity, newPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryRunner.commitTransaction();
|
||||||
|
|
||||||
|
return queryRunner.manager.find(SettingPermissionEntity, {
|
||||||
|
where: { roleId: input.roleId, workspaceId },
|
||||||
|
order: { setting: 'ASC' },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
await queryRunner.rollbackTransaction();
|
||||||
|
|
||||||
if (error.message.includes('violates foreign key constraint')) {
|
if (error.message.includes('violates foreign key constraint')) {
|
||||||
const role = await this.roleRepository.findOne({
|
const role = await this.roleRepository.findOne({
|
||||||
where: {
|
where: { id: input.roleId },
|
||||||
id: input.roleId,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isDefined(role)) {
|
if (!isDefined(role)) {
|
||||||
@ -77,8 +112,9 @@ export class SettingPermissionService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -550,25 +550,24 @@ describe('roles permissions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('upsertSettingPermission', () => {
|
describe('upsertSettingPermissions', () => {
|
||||||
const upsertSettingPermissionMutation = ({
|
const upsertSettingPermissionsMutation = ({
|
||||||
roleId,
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
roleId: string;
|
roleId: string;
|
||||||
}) => `
|
}) => `
|
||||||
mutation UpsertSettingPermissions {
|
mutation UpsertSettingPermissions {
|
||||||
upsertOneSettingPermission(upsertSettingPermissionInput: {roleId: "${roleId}", setting: ${SettingPermissionType.DATA_MODEL}, canUpdateSetting: true}) {
|
upsertSettingPermissions(upsertSettingPermissionsInput: {roleId: "${roleId}", settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}]}) {
|
||||||
id
|
id
|
||||||
roleId
|
roleId
|
||||||
setting
|
setting
|
||||||
canUpdateSetting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
it('should throw a permission error when user does not have permission to upsert object permission (member role)', async () => {
|
it('should throw a permission error when user does not have permission to upsert setting permission (member role)', async () => {
|
||||||
const query = {
|
const query = {
|
||||||
query: upsertSettingPermissionMutation({
|
query: upsertSettingPermissionsMutation({
|
||||||
roleId: guestRoleId,
|
roleId: guestRoleId,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -578,7 +577,7 @@ describe('roles permissions', () => {
|
|||||||
|
|
||||||
it('should throw an error when role is not editable', async () => {
|
it('should throw an error when role is not editable', async () => {
|
||||||
const query = {
|
const query = {
|
||||||
query: upsertSettingPermissionMutation({
|
query: upsertSettingPermissionsMutation({
|
||||||
roleId: adminRoleId,
|
roleId: adminRoleId,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -602,7 +601,7 @@ describe('roles permissions', () => {
|
|||||||
|
|
||||||
it('should upsert a setting permission when user has permission', async () => {
|
it('should upsert a setting permission when user has permission', async () => {
|
||||||
const query = {
|
const query = {
|
||||||
query: upsertSettingPermissionMutation({
|
query: upsertSettingPermissionsMutation({
|
||||||
roleId: createdEditableRoleId,
|
roleId: createdEditableRoleId,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -615,14 +614,13 @@ describe('roles permissions', () => {
|
|||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
expect(res.body.data).toBeDefined();
|
expect(res.body.data).toBeDefined();
|
||||||
expect(res.body.errors).toBeUndefined();
|
expect(res.body.errors).toBeUndefined();
|
||||||
expect(res.body.data.upsertOneSettingPermission.roleId).toBe(
|
expect(res.body.data.upsertSettingPermissions).toEqual(
|
||||||
createdEditableRoleId,
|
expect.arrayContaining([
|
||||||
);
|
expect.objectContaining({
|
||||||
expect(
|
roleId: createdEditableRoleId,
|
||||||
res.body.data.upsertOneSettingPermission.canUpdateSetting,
|
setting: SettingPermissionType.DATA_MODEL,
|
||||||
).toBe(true);
|
}),
|
||||||
expect(res.body.data.upsertOneSettingPermission.setting).toBe(
|
]),
|
||||||
SettingPermissionType.DATA_MODEL,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user