Update enums to be all caps (#12372)

- Make custom domain public (remove from lab)
- Use ALL_CAPS definition for enums
This commit is contained in:
Félix Malfait
2025-05-29 14:08:36 +02:00
committed by GitHub
parent 76d0be7f81
commit 4485e8e3db
165 changed files with 845 additions and 847 deletions

View File

@ -292,8 +292,8 @@ export type Captcha = {
}; };
export enum CaptchaDriverType { export enum CaptchaDriverType {
GoogleRecaptcha = 'GoogleRecaptcha', GOOGLE_RECAPTCHA = 'GOOGLE_RECAPTCHA',
Turnstile = 'Turnstile' TURNSTILE = 'TURNSTILE'
} }
export type ClientConfig = { export type ClientConfig = {
@ -652,15 +652,14 @@ export type FeatureFlagDto = {
}; };
export enum FeatureFlagKey { export enum FeatureFlagKey {
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
IsCopilotEnabled = 'IsCopilotEnabled', IS_COPILOT_ENABLED = 'IS_COPILOT_ENABLED',
IsCustomDomainEnabled = 'IsCustomDomainEnabled', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
IsJsonFilterEnabled = 'IsJsonFilterEnabled', IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED',
IsPermissionsV2Enabled = 'IsPermissionsV2Enabled', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled', IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED'
IsWorkflowEnabled = 'IsWorkflowEnabled'
} }
export type Field = { export type Field = {
@ -1291,6 +1290,7 @@ export type MutationSignUpArgs = {
email: Scalars['String']['input']; email: Scalars['String']['input'];
locale?: InputMaybe<Scalars['String']['input']>; locale?: InputMaybe<Scalars['String']['input']>;
password: Scalars['String']['input']; password: Scalars['String']['input'];
verifyEmailNextPath?: InputMaybe<Scalars['String']['input']>;
workspaceId?: InputMaybe<Scalars['String']['input']>; workspaceId?: InputMaybe<Scalars['String']['input']>;
workspaceInviteHash?: InputMaybe<Scalars['String']['input']>; workspaceInviteHash?: InputMaybe<Scalars['String']['input']>;
workspacePersonalInviteToken?: InputMaybe<Scalars['String']['input']>; workspacePersonalInviteToken?: InputMaybe<Scalars['String']['input']>;

View File

@ -284,8 +284,8 @@ export type Captcha = {
}; };
export enum CaptchaDriverType { export enum CaptchaDriverType {
GoogleRecaptcha = 'GoogleRecaptcha', GOOGLE_RECAPTCHA = 'GOOGLE_RECAPTCHA',
Turnstile = 'Turnstile' TURNSTILE = 'TURNSTILE'
} }
export type ClientConfig = { export type ClientConfig = {
@ -583,15 +583,14 @@ export type FeatureFlagDto = {
}; };
export enum FeatureFlagKey { export enum FeatureFlagKey {
IsAirtableIntegrationEnabled = 'IsAirtableIntegrationEnabled', IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
IsCopilotEnabled = 'IsCopilotEnabled', IS_COPILOT_ENABLED = 'IS_COPILOT_ENABLED',
IsCustomDomainEnabled = 'IsCustomDomainEnabled', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
IsJsonFilterEnabled = 'IsJsonFilterEnabled', IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED',
IsPermissionsV2Enabled = 'IsPermissionsV2Enabled', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IsPostgreSQLIntegrationEnabled = 'IsPostgreSQLIntegrationEnabled', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IsStripeIntegrationEnabled = 'IsStripeIntegrationEnabled', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
IsUniqueIndexesEnabled = 'IsUniqueIndexesEnabled', IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED'
IsWorkflowEnabled = 'IsWorkflowEnabled'
} }
export type Field = { export type Field = {

View File

@ -13,7 +13,7 @@ import { FeatureFlagKey } from '~/generated/graphql';
export const useRunWorkflowRecordAgnosticActions = () => { export const useRunWorkflowRecordAgnosticActions = () => {
const isWorkflowEnabled = useIsFeatureEnabled( const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled, FeatureFlagKey.IS_WORKFLOW_ENABLED,
); );
const { actionMenuType } = useContext(ActionMenuContext); const { actionMenuType } = useContext(ActionMenuContext);

View File

@ -20,7 +20,7 @@ export const ActionMenuContextProvider = ({
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
const isWorkflowEnabled = useIsFeatureEnabled( const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled, FeatureFlagKey.IS_WORKFLOW_ENABLED,
); );
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2( const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(

View File

@ -59,7 +59,7 @@ export const useShouldActionBeRegisteredParams = ({
ContextStoreViewType.ShowPage; ContextStoreViewType.ShowPage;
const isWorkflowEnabled = useIsFeatureEnabled( const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled, FeatureFlagKey.IS_WORKFLOW_ENABLED,
); );
const numberOfSelectedRecords = useRecoilComponentValueV2( const numberOfSelectedRecords = useRecoilComponentValueV2(

View File

@ -1,5 +1,5 @@
import { Role, Workspace } from '~/generated/graphql';
import { createState } from 'twenty-ui/utilities'; import { createState } from 'twenty-ui/utilities';
import { Role, Workspace } from '~/generated/graphql';
export type CurrentWorkspace = Pick< export type CurrentWorkspace = Pick<
Workspace, Workspace,
@ -17,8 +17,8 @@ export type CurrentWorkspace = Pick<
| 'isGoogleAuthEnabled' | 'isGoogleAuthEnabled'
| 'isMicrosoftAuthEnabled' | 'isMicrosoftAuthEnabled'
| 'isPasswordAuthEnabled' | 'isPasswordAuthEnabled'
| 'hasValidEnterpriseKey'
| 'isCustomDomainEnabled' | 'isCustomDomainEnabled'
| 'hasValidEnterpriseKey'
| 'subdomain' | 'subdomain'
| 'customDomain' | 'customDomain'
| 'workspaceUrls' | 'workspaceUrls'

View File

@ -35,7 +35,7 @@ export const CaptchaProviderScriptLoaderEffect = () => {
scriptElement = document.createElement('script'); scriptElement = document.createElement('script');
scriptElement.src = scriptUrl; scriptElement.src = scriptUrl;
scriptElement.onload = () => { scriptElement.onload = () => {
if (captcha.provider === CaptchaDriverType.GoogleRecaptcha) { if (captcha.provider === CaptchaDriverType.GOOGLE_RECAPTCHA) {
window.grecaptcha?.ready(() => { window.grecaptcha?.ready(() => {
setIsCaptchaScriptLoaded(true); setIsCaptchaScriptLoaded(true);
}); });
@ -55,11 +55,11 @@ export const CaptchaProviderScriptLoaderEffect = () => {
let refreshInterval: NodeJS.Timeout; let refreshInterval: NodeJS.Timeout;
switch (captcha.provider) { switch (captcha.provider) {
case CaptchaDriverType.GoogleRecaptcha: case CaptchaDriverType.GOOGLE_RECAPTCHA:
// Google reCAPTCHA tokens expire after 120 seconds, refresh at 110 seconds // Google reCAPTCHA tokens expire after 120 seconds, refresh at 110 seconds
refreshInterval = setInterval(requestFreshCaptchaToken, 110 * 1000); refreshInterval = setInterval(requestFreshCaptchaToken, 110 * 1000);
break; break;
case CaptchaDriverType.Turnstile: case CaptchaDriverType.TURNSTILE:
// Cloudflare Turnstile tokens expire after 500 seconds, refresh at 480 seconds // Cloudflare Turnstile tokens expire after 500 seconds, refresh at 480 seconds
refreshInterval = setInterval(requestFreshCaptchaToken, 480 * 1000); refreshInterval = setInterval(requestFreshCaptchaToken, 480 * 1000);
break; break;

View File

@ -41,7 +41,7 @@ export const useRequestFreshCaptchaToken = () => {
let captchaWidget: any; let captchaWidget: any;
switch (captcha.provider) { switch (captcha.provider) {
case CaptchaDriverType.GoogleRecaptcha: case CaptchaDriverType.GOOGLE_RECAPTCHA:
window.grecaptcha window.grecaptcha
.execute(captcha.siteKey, { .execute(captcha.siteKey, {
action: 'submit', action: 'submit',
@ -51,7 +51,7 @@ export const useRequestFreshCaptchaToken = () => {
setIsRequestingCaptchaToken(false); setIsRequestingCaptchaToken(false);
}); });
break; break;
case CaptchaDriverType.Turnstile: case CaptchaDriverType.TURNSTILE:
captchaWidget = window.turnstile.render('#captcha-widget', { captchaWidget = window.turnstile.render('#captcha-widget', {
sitekey: captcha.siteKey, sitekey: captcha.siteKey,
}); });

View File

@ -7,7 +7,7 @@ import { getCaptchaUrlByProvider } from '../getCaptchaUrlByProvider';
describe('getCaptchaUrlByProvider', () => { describe('getCaptchaUrlByProvider', () => {
it('handles GoogleRecaptcha', async () => { it('handles GoogleRecaptcha', async () => {
const captchaUrl = getCaptchaUrlByProvider( const captchaUrl = getCaptchaUrlByProvider(
CaptchaDriverType.GoogleRecaptcha, CaptchaDriverType.GOOGLE_RECAPTCHA,
'siteKey', 'siteKey',
); );
@ -16,14 +16,14 @@ describe('getCaptchaUrlByProvider', () => {
); );
expect(() => expect(() =>
getCaptchaUrlByProvider(CaptchaDriverType.GoogleRecaptcha, ''), getCaptchaUrlByProvider(CaptchaDriverType.GOOGLE_RECAPTCHA, ''),
).toThrow( ).toThrow(
'SiteKey must be provided while generating url for GoogleRecaptcha provider', 'SiteKey must be provided while generating url for GoogleRecaptcha provider',
); );
}); });
it('handles Turnstile', async () => { it('handles Turnstile', async () => {
const captchaUrl = getCaptchaUrlByProvider(CaptchaDriverType.Turnstile, ''); const captchaUrl = getCaptchaUrlByProvider(CaptchaDriverType.TURNSTILE, '');
expect(captchaUrl).toEqual( expect(captchaUrl).toEqual(
'https://challenges.cloudflare.com/turnstile/v0/api.js', 'https://challenges.cloudflare.com/turnstile/v0/api.js',

View File

@ -7,14 +7,14 @@ export const getCaptchaUrlByProvider = (
siteKey: string, siteKey: string,
) => { ) => {
switch (name) { switch (name) {
case CaptchaDriverType.GoogleRecaptcha: case CaptchaDriverType.GOOGLE_RECAPTCHA:
if (!isNonEmptyString(siteKey)) { if (!isNonEmptyString(siteKey)) {
throw new Error( throw new Error(
'SiteKey must be provided while generating url for GoogleRecaptcha provider', 'SiteKey must be provided while generating url for GoogleRecaptcha provider',
); );
} }
return `https://www.google.com/recaptcha/api.js?render=${siteKey}`; return `https://www.google.com/recaptcha/api.js?render=${siteKey}`;
case CaptchaDriverType.Turnstile: case CaptchaDriverType.TURNSTILE:
return 'https://challenges.cloudflare.com/turnstile/v0/api.js'; return 'https://challenges.cloudflare.com/turnstile/v0/api.js';
default: default:
throw new Error('Unknown captcha provider'); throw new Error('Unknown captcha provider');

View File

@ -24,6 +24,7 @@ export const SentryInitEffect = () => {
const [isSentryInitialized, setIsSentryInitialized] = useState(false); const [isSentryInitialized, setIsSentryInitialized] = useState(false);
const [isSentryInitializing, setIsSentryInitializing] = useState(false); const [isSentryInitializing, setIsSentryInitializing] = useState(false);
const [isSentryUserDefined, setIsSentryUserDefined] = useState(false);
useEffect(() => { useEffect(() => {
if ( if (
@ -47,13 +48,14 @@ export const SentryInitEffect = () => {
setIsSentryInitializing(false); setIsSentryInitializing(false);
} }
if (isDefined(currentUser)) { if (isDefined(currentUser) && !isSentryUserDefined) {
setUser({ setUser({
email: currentUser?.email, email: currentUser?.email,
id: currentUser?.id, id: currentUser?.id,
workspaceId: currentWorkspace?.id, workspaceId: currentWorkspace?.id,
workspaceMemberId: currentWorkspaceMember?.id, workspaceMemberId: currentWorkspaceMember?.id,
}); });
setIsSentryUserDefined(true);
} else { } else {
setUser(null); setUser(null);
} }
@ -64,6 +66,7 @@ export const SentryInitEffect = () => {
currentUser, currentUser,
currentWorkspace, currentWorkspace,
currentWorkspaceMember, currentWorkspaceMember,
isSentryUserDefined,
]); ]);
return <></>; return <></>;
}; };

View File

@ -4,7 +4,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState'; import { currentUserState } from '@/auth/states/currentUserState';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath'; import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@ -44,7 +44,7 @@ const renderHooks = ({
viewGroups: [], viewGroups: [],
viewSorts: [], viewSorts: [],
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '', icon: '',
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,

View File

@ -28,8 +28,8 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
isGoogleAuthEnabled: true, isGoogleAuthEnabled: true,
isMicrosoftAuthEnabled: false, isMicrosoftAuthEnabled: false,
isPasswordAuthEnabled: true, isPasswordAuthEnabled: true,
isCustomDomainEnabled: false,
customDomain: 'my-custom-domain.com', customDomain: 'my-custom-domain.com',
isCustomDomainEnabled: true,
workspaceUrls: { workspaceUrls: {
subdomainUrl: 'https://twenty.twenty.com', subdomainUrl: 'https://twenty.twenty.com',
customUrl: 'https://my-custom-domain.com', customUrl: 'https://my-custom-domain.com',

View File

@ -7,7 +7,7 @@ import {
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem'; import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { DELETE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations'; import { DELETE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
import { useApolloMetadataClient } from './useApolloMetadataClient'; import { useApolloMetadataClient } from './useApolloMetadataClient';
@ -37,7 +37,7 @@ export const useDeleteOneFieldMetadataItem = () => {
) => { ) => {
if (recordIndexKanbanAggregateOperation?.fieldMetadataId === idToDelete) { if (recordIndexKanbanAggregateOperation?.fieldMetadataId === idToDelete) {
setRecordIndexKanbanAggregateOperation({ setRecordIndexKanbanAggregateOperation({
operation: AGGREGATE_OPERATIONS.count, operation: AggregateOperations.COUNT,
fieldMetadataId: null, fieldMetadataId: null,
}); });
} }

View File

@ -10,7 +10,7 @@ export const useFilteredObjectMetadataItems = () => {
const objectMetadataItems = useRecoilValue(objectMetadataItemsState); const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const isWorkflowEnabled = useIsFeatureEnabled( const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled, FeatureFlagKey.IS_WORKFLOW_ENABLED,
); );
const isWorkflowToBeFiltered = useCallback( const isWorkflowToBeFiltered = useCallback(

View File

@ -6,9 +6,9 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { isWorkflowRelatedObjectMetadata } from '@/object-metadata/utils/isWorkflowRelatedObjectMetadata'; import { isWorkflowRelatedObjectMetadata } from '@/object-metadata/utils/isWorkflowRelatedObjectMetadata';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql'; import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier'; import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier';
import { isDefined } from 'twenty-shared/utils';
export const useObjectMetadataItem = ({ export const useObjectMetadataItem = ({
objectNameSingular, objectNameSingular,
@ -21,7 +21,7 @@ export const useObjectMetadataItem = ({
); );
const isWorkflowEnabled = useIsFeatureEnabled( const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled, FeatureFlagKey.IS_WORKFLOW_ENABLED,
); );
const isWorkflowToBeFiltered = const isWorkflowToBeFiltered =

View File

@ -3,8 +3,8 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat
import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems'; import { getFilterFilterableFieldMetadataItems } from '@/object-metadata/utils/getFilterFilterableFieldMetadataItems';
import { checkIfFeatureFlagIsEnabledOnWorkspace } from '@/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace'; import { checkIfFeatureFlagIsEnabledOnWorkspace } from '@/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace';
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const availableFieldMetadataItemsForFilterFamilySelector = export const availableFieldMetadataItemsForFilterFamilySelector =
selectorFamily({ selectorFamily({
@ -24,7 +24,7 @@ export const availableFieldMetadataItemsForFilterFamilySelector =
} }
const isJsonFeatureFlagEnabled = checkIfFeatureFlagIsEnabledOnWorkspace( const isJsonFeatureFlagEnabled = checkIfFeatureFlagIsEnabledOnWorkspace(
FeatureFlagKey.IsJsonFilterEnabled, FeatureFlagKey.IS_JSON_FILTER_ENABLED,
currentWorkspace, currentWorkspace,
); );

View File

@ -5,7 +5,7 @@ import {
} from '@/object-record/hooks/__mocks__/useAggregateRecords'; } from '@/object-record/hooks/__mocks__/useAggregateRecords';
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords'; import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery'; import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
@ -20,9 +20,9 @@ const mockObjectMetadataItem = {
}; };
const mockGqlFieldToFieldMap = { const mockGqlFieldToFieldMap = {
sumAmount: ['amount', AGGREGATE_OPERATIONS.sum], sumAmount: ['amount', AggregateOperations.SUM],
avgAmount: ['amount', AGGREGATE_OPERATIONS.avg], avgAmount: ['amount', AggregateOperations.AVG],
totalCount: ['name', AGGREGATE_OPERATIONS.count], totalCount: ['name', AggregateOperations.COUNT],
}; };
describe('useAggregateRecords', () => { describe('useAggregateRecords', () => {
@ -48,19 +48,19 @@ describe('useAggregateRecords', () => {
useAggregateRecords({ useAggregateRecords({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum, AGGREGATE_OPERATIONS.avg], amount: [AggregateOperations.SUM, AggregateOperations.AVG],
name: [AGGREGATE_OPERATIONS.count], name: [AggregateOperations.COUNT],
}, },
}), }),
); );
expect(result.current.data).toEqual({ expect(result.current.data).toEqual({
amount: { amount: {
[AGGREGATE_OPERATIONS.sum]: 1000000, [AggregateOperations.SUM]: 1000000,
[AGGREGATE_OPERATIONS.avg]: 23800, [AggregateOperations.AVG]: 23800,
}, },
name: { name: {
[AGGREGATE_OPERATIONS.count]: 42, [AggregateOperations.COUNT]: 42,
}, },
}); });
expect(result.current.loading).toBe(false); expect(result.current.loading).toBe(false);
@ -78,7 +78,7 @@ describe('useAggregateRecords', () => {
useAggregateRecords({ useAggregateRecords({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum], amount: [AggregateOperations.SUM],
}, },
}), }),
); );
@ -99,7 +99,7 @@ describe('useAggregateRecords', () => {
useAggregateRecords({ useAggregateRecords({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum], amount: [AggregateOperations.SUM],
}, },
}), }),
); );
@ -113,7 +113,7 @@ describe('useAggregateRecords', () => {
useAggregateRecords({ useAggregateRecords({
objectNameSingular: 'opportunity', objectNameSingular: 'opportunity',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum], amount: [AggregateOperations.SUM],
}, },
skip: true, skip: true,
}), }),

View File

@ -2,7 +2,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery'; import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { generateAggregateQuery } from '@/object-record/utils/generateAggregateQuery'; import { generateAggregateQuery } from '@/object-record/utils/generateAggregateQuery';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { FieldMetadataType } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated/graphql';
@ -71,7 +71,7 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({ useAggregateRecordsQuery({
objectNameSingular: 'company', objectNameSingular: 'company',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
name: [AGGREGATE_OPERATIONS.count], name: [AggregateOperations.COUNT],
}, },
}), }),
); );
@ -92,7 +92,7 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({ useAggregateRecordsQuery({
objectNameSingular: 'company', objectNameSingular: 'company',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum], amount: [AggregateOperations.SUM],
}, },
}), }),
); );
@ -115,7 +115,7 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({ useAggregateRecordsQuery({
objectNameSingular: 'company', objectNameSingular: 'company',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
name: [AGGREGATE_OPERATIONS.sum], name: [AggregateOperations.SUM],
}, },
}), }),
), ),
@ -127,8 +127,8 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({ useAggregateRecordsQuery({
objectNameSingular: 'company', objectNameSingular: 'company',
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum], amount: [AggregateOperations.SUM],
name: [AGGREGATE_OPERATIONS.count], name: [AggregateOperations.COUNT],
}, },
}), }),
); );

View File

@ -3,7 +3,7 @@ import { RecordBoardColumnHeaderAggregateDropdownContext } from '@/object-record
import { RecordBoardColumnHeaderAggregateDropdownFieldsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent'; import { RecordBoardColumnHeaderAggregateDropdownFieldsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownFieldsContent';
import { RecordBoardColumnHeaderAggregateDropdownMenuContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent'; import { RecordBoardColumnHeaderAggregateDropdownMenuContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent';
import { RecordBoardColumnHeaderAggregateDropdownOptionsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownOptionsContent'; import { RecordBoardColumnHeaderAggregateDropdownOptionsContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownOptionsContent';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions'; import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions';
import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions'; import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions';
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
@ -47,10 +47,7 @@ export const AggregateDropdownContent = () => {
const datesAvailableAggregations: AvailableFieldsForAggregateOperation = const datesAvailableAggregations: AvailableFieldsForAggregateOperation =
getAvailableFieldsIdsForAggregationFromObjectFields( getAvailableFieldsIdsForAggregationFromObjectFields(
objectMetadataItem.fields, objectMetadataItem.fields,
[ [DateAggregateOperations.EARLIEST, DateAggregateOperations.LATEST],
DATE_AGGREGATE_OPERATIONS.earliest,
DATE_AGGREGATE_OPERATIONS.latest,
],
); );
return ( return (
<RecordBoardColumnHeaderAggregateDropdownOptionsContent <RecordBoardColumnHeaderAggregateDropdownOptionsContent

View File

@ -8,7 +8,7 @@ import { aggregateOperationComponentState } from '@/object-record/record-board/r
import { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState'; import { availableFieldIdsForAggregateOperationComponentState } from '@/object-record/record-board/record-board-column/states/availableFieldIdsForAggregateOperationComponentState';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope'; import { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation'; import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
@ -82,7 +82,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
key={`aggregate-dropdown-menu-content-${availableAggregationOperation}`} key={`aggregate-dropdown-menu-content-${availableAggregationOperation}`}
onContentChange={() => { onContentChange={() => {
if ( if (
availableAggregationOperation !== AGGREGATE_OPERATIONS.count availableAggregationOperation !== AggregateOperations.COUNT
) { ) {
setAggregateOperation( setAggregateOperation(
availableAggregationOperation as ExtendedAggregateOperations, availableAggregationOperation as ExtendedAggregateOperations,
@ -97,7 +97,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
kanbanAggregateOperationFieldMetadataId: kanbanAggregateOperationFieldMetadataId:
availableAggregationFieldsIdsForOperation[0], availableAggregationFieldsIdsForOperation[0],
kanbanAggregateOperation: kanbanAggregateOperation:
availableAggregationOperation as AGGREGATE_OPERATIONS, availableAggregationOperation as AggregateOperations,
}); });
closeDropdown(); closeDropdown();
} }
@ -106,15 +106,14 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
availableAggregationOperation as ExtendedAggregateOperations, availableAggregationOperation as ExtendedAggregateOperations,
)} )}
hasSubMenu={ hasSubMenu={
availableAggregationOperation === AGGREGATE_OPERATIONS.count availableAggregationOperation === AggregateOperations.COUNT
? false ? false
: true : true
} }
RightIcon={ RightIcon={
availableAggregationOperation === availableAggregationOperation === AggregateOperations.COUNT &&
AGGREGATE_OPERATIONS.count &&
recordIndexKanbanAggregateOperation?.operation === recordIndexKanbanAggregateOperation?.operation ===
AGGREGATE_OPERATIONS.count AggregateOperations.COUNT
? IconCheck ? IconCheck
: undefined : undefined
} }

View File

@ -2,7 +2,7 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView'; import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView';
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a'; const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
@ -47,7 +47,7 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
it('should build fields for numeric aggregate', () => { it('should build fields for numeric aggregate', () => {
const kanbanAggregateOperation: KanbanAggregateOperation = { const kanbanAggregateOperation: KanbanAggregateOperation = {
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
operation: AGGREGATE_OPERATIONS.sum, operation: AggregateOperations.SUM,
}; };
const result = buildRecordGqlFieldsAggregateForView({ const result = buildRecordGqlFieldsAggregateForView({
@ -56,14 +56,14 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
}); });
expect(result).toEqual({ expect(result).toEqual({
amount: [AGGREGATE_OPERATIONS.sum], amount: [AggregateOperations.SUM],
}); });
}); });
it('should default to count when no field is found', () => { it('should default to count when no field is found', () => {
const operation: KanbanAggregateOperation = { const operation: KanbanAggregateOperation = {
fieldMetadataId: 'non-existent-id', fieldMetadataId: 'non-existent-id',
operation: AGGREGATE_OPERATIONS.count, operation: AggregateOperations.COUNT,
}; };
const result = buildRecordGqlFieldsAggregateForView({ const result = buildRecordGqlFieldsAggregateForView({
@ -72,14 +72,14 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
}); });
expect(result).toEqual({ expect(result).toEqual({
id: [AGGREGATE_OPERATIONS.count], id: [AggregateOperations.COUNT],
}); });
}); });
it('should throw error for non-count operation with invalid field', () => { it('should throw error for non-count operation with invalid field', () => {
const operation: KanbanAggregateOperation = { const operation: KanbanAggregateOperation = {
fieldMetadataId: 'non-existent-id', fieldMetadataId: 'non-existent-id',
operation: AGGREGATE_OPERATIONS.sum, operation: AggregateOperations.SUM,
}; };
expect(() => expect(() =>
@ -88,7 +88,7 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
recordIndexKanbanAggregateOperation: operation, recordIndexKanbanAggregateOperation: operation,
}), }),
).toThrow( ).toThrow(
`No field found to compute aggregate operation ${AGGREGATE_OPERATIONS.sum} on object ${mockObjectMetadata.nameSingular}`, `No field found to compute aggregate operation ${AggregateOperations.SUM} on object ${mockObjectMetadata.nameSingular}`,
); );
}); });
}); });

View File

@ -4,8 +4,8 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords'; import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords';
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel'; import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { enUS } from 'date-fns/locale'; import { enUS } from 'date-fns/locale';
import { FieldMetadataType } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated/graphql';
@ -35,7 +35,7 @@ describe('computeAggregateValueAndLabel', () => {
data: {} as AggregateRecordsData, data: {} as AggregateRecordsData,
objectMetadataItem: mockObjectMetadata, objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum, aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });
@ -46,7 +46,7 @@ describe('computeAggregateValueAndLabel', () => {
it('should handle currency field with division by 1M', () => { it('should handle currency field with division by 1M', () => {
const mockData = { const mockData = {
amount: { amount: {
[AGGREGATE_OPERATIONS.sum]: 2000000, [AggregateOperations.SUM]: 2000000,
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -54,7 +54,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData, data: mockData,
objectMetadataItem: mockObjectMetadata, objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum, aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });
@ -84,7 +84,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockData = { const mockData = {
percentage: { percentage: {
[AGGREGATE_OPERATIONS.avg]: 0.3, [AggregateOperations.AVG]: 0.3,
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -92,7 +92,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData, data: mockData,
objectMetadataItem: mockObjectMetadataWithPercentageField, objectMetadataItem: mockObjectMetadataWithPercentageField,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.avg, aggregateOperation: AggregateOperations.AVG,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });
@ -122,7 +122,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockData = { const mockData = {
decimals: { decimals: {
[AGGREGATE_OPERATIONS.sum]: 0.009, [AggregateOperations.SUM]: 0.009,
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -130,7 +130,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData, data: mockData,
objectMetadataItem: mockObjectMetadataWithDecimalsField, objectMetadataItem: mockObjectMetadataWithDecimalsField,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum, aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });
@ -157,7 +157,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockFormattedData = { const mockFormattedData = {
createdAt: { createdAt: {
[DATE_AGGREGATE_OPERATIONS.earliest]: '2023-01-01T12:00:00Z', [DateAggregateOperations.EARLIEST]: '2023-01-01T12:00:00Z',
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -165,7 +165,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockFormattedData, data: mockFormattedData,
objectMetadataItem: mockObjectMetadataWithDatetimeField, objectMetadataItem: mockObjectMetadataWithDatetimeField,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest, aggregateOperation: DateAggregateOperations.EARLIEST,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });
@ -192,7 +192,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockFormattedData = { const mockFormattedData = {
updatedAt: { updatedAt: {
[DATE_AGGREGATE_OPERATIONS.latest]: '2023-12-31T23:59:59Z', [DateAggregateOperations.LATEST]: '2023-12-31T23:59:59Z',
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -200,7 +200,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockFormattedData, data: mockFormattedData,
objectMetadataItem: mockObjectMetadataWithDatetimeField, objectMetadataItem: mockObjectMetadataWithDatetimeField,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: DATE_AGGREGATE_OPERATIONS.latest, aggregateOperation: DateAggregateOperations.LATEST,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });
@ -215,7 +215,7 @@ describe('computeAggregateValueAndLabel', () => {
it('should default to count when field not found', () => { it('should default to count when field not found', () => {
const mockData = { const mockData = {
id: { id: {
[AGGREGATE_OPERATIONS.count]: 42, [AggregateOperations.COUNT]: 42,
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -236,7 +236,7 @@ describe('computeAggregateValueAndLabel', () => {
it('should handle undefined aggregate value', () => { it('should handle undefined aggregate value', () => {
const mockData = { const mockData = {
amount: { amount: {
[AGGREGATE_OPERATIONS.sum]: undefined, [AggregateOperations.SUM]: undefined,
}, },
} as AggregateRecordsData; } as AggregateRecordsData;
@ -244,7 +244,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData, data: mockData,
objectMetadataItem: mockObjectMetadata, objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID, fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum, aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS, localeCatalog: enUS,
...defaultParams, ...defaultParams,
}); });

View File

@ -1,45 +1,43 @@
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { expect } from '@storybook/test'; import { expect } from '@storybook/test';
describe('getAggregateOperationLabel', () => { describe('getAggregateOperationLabel', () => {
it('should return correct labels for each operation', () => { it('should return correct labels for each operation', () => {
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.min)).toBe('Min'); expect(getAggregateOperationLabel(AggregateOperations.MIN)).toBe('Min');
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.max)).toBe('Max'); expect(getAggregateOperationLabel(AggregateOperations.MAX)).toBe('Max');
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.avg)).toBe( expect(getAggregateOperationLabel(AggregateOperations.AVG)).toBe('Average');
'Average', expect(getAggregateOperationLabel(DateAggregateOperations.EARLIEST)).toBe(
);
expect(getAggregateOperationLabel(DATE_AGGREGATE_OPERATIONS.earliest)).toBe(
'Earliest date', 'Earliest date',
); );
expect(getAggregateOperationLabel(DATE_AGGREGATE_OPERATIONS.latest)).toBe( expect(getAggregateOperationLabel(DateAggregateOperations.LATEST)).toBe(
'Latest date', 'Latest date',
); );
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.sum)).toBe('Sum'); expect(getAggregateOperationLabel(AggregateOperations.SUM)).toBe('Sum');
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)).toBe( expect(getAggregateOperationLabel(AggregateOperations.COUNT)).toBe(
'Count all', 'Count all',
); );
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.countEmpty)).toBe( expect(getAggregateOperationLabel(AggregateOperations.COUNT_EMPTY)).toBe(
'Count empty', 'Count empty',
); );
expect(getAggregateOperationLabel(AGGREGATE_OPERATIONS.countNotEmpty)).toBe(
'Count not empty',
);
expect( expect(
getAggregateOperationLabel(AGGREGATE_OPERATIONS.countUniqueValues), getAggregateOperationLabel(AggregateOperations.COUNT_NOT_EMPTY),
).toBe('Count not empty');
expect(
getAggregateOperationLabel(AggregateOperations.COUNT_UNIQUE_VALUES),
).toBe('Count unique values'); ).toBe('Count unique values');
expect( expect(
getAggregateOperationLabel(AGGREGATE_OPERATIONS.percentageEmpty), getAggregateOperationLabel(AggregateOperations.PERCENTAGE_EMPTY),
).toBe('Percent empty'); ).toBe('Percent empty');
expect( expect(
getAggregateOperationLabel(AGGREGATE_OPERATIONS.percentageNotEmpty), getAggregateOperationLabel(AggregateOperations.PERCENTAGE_NOT_EMPTY),
).toBe('Percent not empty'); ).toBe('Percent not empty');
}); });
it('should throw error for unknown operation', () => { it('should throw error for unknown operation', () => {
expect(() => expect(() =>
getAggregateOperationLabel('INVALID' as AGGREGATE_OPERATIONS), getAggregateOperationLabel('INVALID' as AggregateOperations),
).toThrow('Unknown aggregate operation: INVALID'); ).toThrow('Unknown aggregate operation: INVALID');
}); });
}); });

View File

@ -1,7 +1,7 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate'; import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState'; import { KanbanAggregateOperation } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants'; import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
@ -23,7 +23,7 @@ export const buildRecordGqlFieldsAggregateForView = ({
if ( if (
isDefined(recordIndexKanbanAggregateOperation?.operation) && isDefined(recordIndexKanbanAggregateOperation?.operation) &&
recordIndexKanbanAggregateOperation.operation !== recordIndexKanbanAggregateOperation.operation !==
AGGREGATE_OPERATIONS.count AggregateOperations.COUNT
) { ) {
throw new Error( throw new Error(
`No field found to compute aggregate operation ${recordIndexKanbanAggregateOperation.operation} on object ${objectMetadataItem.nameSingular}`, `No field found to compute aggregate operation ${recordIndexKanbanAggregateOperation.operation} on object ${objectMetadataItem.nameSingular}`,
@ -31,7 +31,7 @@ export const buildRecordGqlFieldsAggregateForView = ({
} else { } else {
recordGqlFieldsAggregate = { recordGqlFieldsAggregate = {
[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: [ [FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: [
AGGREGATE_OPERATIONS.count, AggregateOperations.COUNT,
], ],
}; };
} }
@ -39,7 +39,7 @@ export const buildRecordGqlFieldsAggregateForView = ({
recordGqlFieldsAggregate = { recordGqlFieldsAggregate = {
[kanbanAggregateOperationFieldName]: [ [kanbanAggregateOperationFieldName]: [
recordIndexKanbanAggregateOperation?.operation ?? recordIndexKanbanAggregateOperation?.operation ??
AGGREGATE_OPERATIONS.count, AggregateOperations.COUNT,
], ],
}; };
} }

View File

@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords'; import { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel'; import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { getAggregateOperationShortLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel'; import { getAggregateOperationShortLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions'; import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions';
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
@ -48,12 +48,10 @@ export const computeAggregateValueAndLabel = ({
return { return {
value: value:
data?.[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]?.[ data?.[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]?.[
AGGREGATE_OPERATIONS.count AggregateOperations.COUNT
], ],
label: getAggregateOperationLabel(AGGREGATE_OPERATIONS.count), label: getAggregateOperationLabel(AggregateOperations.COUNT),
labelWithFieldName: getAggregateOperationLabel( labelWithFieldName: getAggregateOperationLabel(AggregateOperations.COUNT),
AGGREGATE_OPERATIONS.count,
),
}; };
} }
@ -69,7 +67,7 @@ export const computeAggregateValueAndLabel = ({
if ( if (
COUNT_AGGREGATE_OPERATION_OPTIONS.includes( COUNT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS, aggregateOperation as AggregateOperations,
) )
) { ) {
value = aggregateValue; value = aggregateValue;
@ -77,7 +75,7 @@ export const computeAggregateValueAndLabel = ({
value = '-'; value = '-';
} else if ( } else if (
PERCENT_AGGREGATE_OPERATION_OPTIONS.includes( PERCENT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS, aggregateOperation as AggregateOperations,
) )
) { ) {
value = `${formatNumber(Number(aggregateValue) * 100)}%`; value = `${formatNumber(Number(aggregateValue) * 100)}%`;
@ -128,8 +126,8 @@ export const computeAggregateValueAndLabel = ({
const aggregateLabel = t(getAggregateOperationShortLabel(aggregateOperation)); const aggregateLabel = t(getAggregateOperationShortLabel(aggregateOperation));
const fieldLabel = field.label; const fieldLabel = field.label;
const labelWithFieldName = const labelWithFieldName =
aggregateOperation === AGGREGATE_OPERATIONS.count aggregateOperation === AggregateOperations.COUNT
? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}` ? `${getAggregateOperationLabel(AggregateOperations.COUNT)}`
: t`${aggregateLabel} of ${fieldLabel}`; : t`${aggregateLabel} of ${fieldLabel}`;
return { return {

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
@ -7,33 +7,33 @@ export const getAggregateOperationLabel = (
operation: ExtendedAggregateOperations, operation: ExtendedAggregateOperations,
) => { ) => {
switch (operation) { switch (operation) {
case AGGREGATE_OPERATIONS.min: case AggregateOperations.MIN:
return t`Min`; return t`Min`;
case AGGREGATE_OPERATIONS.max: case AggregateOperations.MAX:
return t`Max`; return t`Max`;
case AGGREGATE_OPERATIONS.avg: case AggregateOperations.AVG:
return t`Average`; return t`Average`;
case AGGREGATE_OPERATIONS.sum: case AggregateOperations.SUM:
return t`Sum`; return t`Sum`;
case AGGREGATE_OPERATIONS.count: case AggregateOperations.COUNT:
return t`Count all`; return t`Count all`;
case AGGREGATE_OPERATIONS.countEmpty: case AggregateOperations.COUNT_EMPTY:
return t`Count empty`; return t`Count empty`;
case AGGREGATE_OPERATIONS.countNotEmpty: case AggregateOperations.COUNT_NOT_EMPTY:
return t`Count not empty`; return t`Count not empty`;
case AGGREGATE_OPERATIONS.countUniqueValues: case AggregateOperations.COUNT_UNIQUE_VALUES:
return t`Count unique values`; return t`Count unique values`;
case AGGREGATE_OPERATIONS.percentageEmpty: case AggregateOperations.PERCENTAGE_EMPTY:
return t`Percent empty`; return t`Percent empty`;
case AGGREGATE_OPERATIONS.percentageNotEmpty: case AggregateOperations.PERCENTAGE_NOT_EMPTY:
return t`Percent not empty`; return t`Percent not empty`;
case DATE_AGGREGATE_OPERATIONS.earliest: case DateAggregateOperations.EARLIEST:
return t`Earliest date`; return t`Earliest date`;
case DATE_AGGREGATE_OPERATIONS.latest: case DateAggregateOperations.LATEST:
return t`Latest date`; return t`Latest date`;
case AGGREGATE_OPERATIONS.countTrue: case AggregateOperations.COUNT_TRUE:
return t`Count true`; return t`Count true`;
case AGGREGATE_OPERATIONS.countFalse: case AggregateOperations.COUNT_FALSE:
return t`Count false`; return t`Count false`;
default: default:
throw new Error(`Unknown aggregate operation: ${operation}`); throw new Error(`Unknown aggregate operation: ${operation}`);

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { msg } from '@lingui/core/macro'; import { msg } from '@lingui/core/macro';
@ -7,31 +7,31 @@ export const getAggregateOperationShortLabel = (
operation: ExtendedAggregateOperations, operation: ExtendedAggregateOperations,
) => { ) => {
switch (operation) { switch (operation) {
case AGGREGATE_OPERATIONS.min: case AggregateOperations.MIN:
return msg`Min`; return msg`Min`;
case AGGREGATE_OPERATIONS.max: case AggregateOperations.MAX:
return msg`Max`; return msg`Max`;
case AGGREGATE_OPERATIONS.avg: case AggregateOperations.AVG:
return msg`Average`; return msg`Average`;
case AGGREGATE_OPERATIONS.sum: case AggregateOperations.SUM:
return msg`Sum`; return msg`Sum`;
case AGGREGATE_OPERATIONS.count: case AggregateOperations.COUNT:
return msg`All`; return msg`All`;
case AGGREGATE_OPERATIONS.countEmpty: case AggregateOperations.COUNT_EMPTY:
case AGGREGATE_OPERATIONS.percentageEmpty: case AggregateOperations.PERCENTAGE_EMPTY:
return msg`Empty`; return msg`Empty`;
case AGGREGATE_OPERATIONS.countNotEmpty: case AggregateOperations.COUNT_NOT_EMPTY:
case AGGREGATE_OPERATIONS.percentageNotEmpty: case AggregateOperations.PERCENTAGE_NOT_EMPTY:
return msg`Not empty`; return msg`Not empty`;
case AGGREGATE_OPERATIONS.countUniqueValues: case AggregateOperations.COUNT_UNIQUE_VALUES:
return msg`Unique`; return msg`Unique`;
case DATE_AGGREGATE_OPERATIONS.earliest: case DateAggregateOperations.EARLIEST:
return msg`Earliest`; return msg`Earliest`;
case DATE_AGGREGATE_OPERATIONS.latest: case DateAggregateOperations.LATEST:
return msg`Latest`; return msg`Latest`;
case AGGREGATE_OPERATIONS.countTrue: case AggregateOperations.COUNT_TRUE:
return msg`True`; return msg`True`;
case AGGREGATE_OPERATIONS.countFalse: case AggregateOperations.COUNT_FALSE:
return msg`False`; return msg`False`;
default: default:
throw new Error(`Unknown aggregate operation: ${operation}`); throw new Error(`Unknown aggregate operation: ${operation}`);

View File

@ -10,8 +10,6 @@ import { RecordLayoutTab } from '@/ui/layout/tab/types/RecordLayoutTab';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated/graphql';
import { import {
IconCalendarEvent, IconCalendarEvent,
IconHome, IconHome,
@ -19,6 +17,8 @@ import {
IconNotes, IconNotes,
IconSettings, IconSettings,
} from 'twenty-ui/display'; } from 'twenty-ui/display';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated/graphql';
export const useRecordShowContainerTabs = ( export const useRecordShowContainerTabs = (
loading: boolean, loading: boolean,
@ -152,7 +152,7 @@ export const useRecordShowContainerTabs = (
ifMobile: false, ifMobile: false,
ifDesktop: false, ifDesktop: false,
ifInRightDrawer: false, ifInRightDrawer: false,
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED],
ifRequiredObjectsInactive: [], ifRequiredObjectsInactive: [],
ifRelationsMissing: [], ifRelationsMissing: [],
}, },
@ -175,7 +175,7 @@ export const useRecordShowContainerTabs = (
ifMobile: false, ifMobile: false,
ifDesktop: false, ifDesktop: false,
ifInRightDrawer: false, ifInRightDrawer: false,
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED],
ifRequiredObjectsInactive: [], ifRequiredObjectsInactive: [],
ifRelationsMissing: [], ifRelationsMissing: [],
}, },
@ -197,7 +197,7 @@ export const useRecordShowContainerTabs = (
ifMobile: false, ifMobile: false,
ifDesktop: false, ifDesktop: false,
ifInRightDrawer: false, ifInRightDrawer: false,
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled], ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED],
ifRequiredObjectsInactive: [], ifRequiredObjectsInactive: [],
ifRelationsMissing: [], ifRelationsMissing: [],
}, },

View File

@ -13,7 +13,7 @@ import { RecordDetailRelationSectionDropdown } from '@/object-record/record-show
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection'; import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader'; import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector'; import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector'; import { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
@ -109,7 +109,7 @@ export const RecordDetailRelationSection = ({
filter: filtersForAggregate, filter: filtersForAggregate,
skip: !isToManyObjects, skip: !isToManyObjects,
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
id: [AGGREGATE_OPERATIONS.count], id: [AggregateOperations.COUNT],
}, },
}); });

View File

@ -1,14 +1,14 @@
export enum AGGREGATE_OPERATIONS { export enum AggregateOperations {
min = 'MIN', MIN = 'MIN',
max = 'MAX', MAX = 'MAX',
avg = 'AVG', AVG = 'AVG',
sum = 'SUM', SUM = 'SUM',
count = 'COUNT', COUNT = 'COUNT',
countEmpty = 'COUNT_EMPTY', COUNT_EMPTY = 'COUNT_EMPTY',
countNotEmpty = 'COUNT_NOT_EMPTY', COUNT_NOT_EMPTY = 'COUNT_NOT_EMPTY',
countUniqueValues = 'COUNT_UNIQUE_VALUES', COUNT_UNIQUE_VALUES = 'COUNT_UNIQUE_VALUES',
percentageEmpty = 'PERCENTAGE_EMPTY', PERCENTAGE_EMPTY = 'PERCENTAGE_EMPTY',
percentageNotEmpty = 'PERCENTAGE_NOT_EMPTY', PERCENTAGE_NOT_EMPTY = 'PERCENTAGE_NOT_EMPTY',
countTrue = 'COUNT_TRUE', COUNT_TRUE = 'COUNT_TRUE',
countFalse = 'COUNT_FALSE', COUNT_FALSE = 'COUNT_FALSE',
} }

View File

@ -1,4 +1,4 @@
export enum DATE_AGGREGATE_OPERATIONS { export enum DateAggregateOperations {
earliest = 'EARLIEST', EARLIEST = 'EARLIEST',
latest = 'LATEST', LATEST = 'LATEST',
} }

View File

@ -1,31 +1,31 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
export const FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION = { export const FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION = {
[AGGREGATE_OPERATIONS.min]: [ [AggregateOperations.MIN]: [
FieldMetadataType.NUMBER, FieldMetadataType.NUMBER,
FieldMetadataType.CURRENCY, FieldMetadataType.CURRENCY,
], ],
[AGGREGATE_OPERATIONS.max]: [ [AggregateOperations.MAX]: [
FieldMetadataType.NUMBER, FieldMetadataType.NUMBER,
FieldMetadataType.CURRENCY, FieldMetadataType.CURRENCY,
], ],
[AGGREGATE_OPERATIONS.avg]: [ [AggregateOperations.AVG]: [
FieldMetadataType.NUMBER, FieldMetadataType.NUMBER,
FieldMetadataType.CURRENCY, FieldMetadataType.CURRENCY,
], ],
[AGGREGATE_OPERATIONS.sum]: [ [AggregateOperations.SUM]: [
FieldMetadataType.NUMBER, FieldMetadataType.NUMBER,
FieldMetadataType.CURRENCY, FieldMetadataType.CURRENCY,
], ],
[AGGREGATE_OPERATIONS.countFalse]: [FieldMetadataType.BOOLEAN], [AggregateOperations.COUNT_FALSE]: [FieldMetadataType.BOOLEAN],
[AGGREGATE_OPERATIONS.countTrue]: [FieldMetadataType.BOOLEAN], [AggregateOperations.COUNT_TRUE]: [FieldMetadataType.BOOLEAN],
[DATE_AGGREGATE_OPERATIONS.earliest]: [ [DateAggregateOperations.EARLIEST]: [
FieldMetadataType.DATE_TIME, FieldMetadataType.DATE_TIME,
FieldMetadataType.DATE, FieldMetadataType.DATE,
], ],
[DATE_AGGREGATE_OPERATIONS.latest]: [ [DateAggregateOperations.LATEST]: [
FieldMetadataType.DATE_TIME, FieldMetadataType.DATE_TIME,
FieldMetadataType.DATE, FieldMetadataType.DATE,
], ],

View File

@ -1,6 +1,6 @@
import { useDropdown } from '@/dropdown/hooks/useDropdown'; import { useDropdown } from '@/dropdown/hooks/useDropdown';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { RecordTableColumnAggregateFooterDropdownSubmenuContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateDropdownSubmenuContent'; import { RecordTableColumnAggregateFooterDropdownSubmenuContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateDropdownSubmenuContent';
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext'; import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
import { RecordTableColumnAggregateFooterMenuContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent'; import { RecordTableColumnAggregateFooterMenuContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent';
@ -28,7 +28,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter( const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) => (aggregateOperation) =>
!STANDARD_AGGREGATE_OPERATION_OPTIONS.includes( !STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS, aggregateOperation as AggregateOperations,
), ),
); );
@ -43,7 +43,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter( const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) => (aggregateOperation) =>
COUNT_AGGREGATE_OPERATION_OPTIONS.includes( COUNT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS, aggregateOperation as AggregateOperations,
), ),
); );
return ( return (
@ -57,7 +57,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter( const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) => (aggregateOperation) =>
PERCENT_AGGREGATE_OPERATION_OPTIONS.includes( PERCENT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS, aggregateOperation as AggregateOperations,
), ),
); );
return ( return (
@ -71,7 +71,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter( const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) => (aggregateOperation) =>
DATE_AGGREGATE_OPERATION_OPTIONS.includes( DATE_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as DATE_AGGREGATE_OPERATIONS, aggregateOperation as DateAggregateOperations,
), ),
); );
return ( return (

View File

@ -1,4 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext'; import { RecordTableColumnAggregateFooterDropdownContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterDropdownContext';
import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions'; import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions';
@ -51,7 +51,7 @@ export const RecordTableColumnAggregateFooterMenuContent = () => {
const nonStandardAvailableAggregateOperation = const nonStandardAvailableAggregateOperation =
availableAggregateOperation.filter((aggregateOperation) => availableAggregateOperation.filter((aggregateOperation) =>
NON_STANDARD_AGGREGATE_OPERATION_OPTIONS.includes( NON_STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS, aggregateOperation as AggregateOperations,
), ),
); );

View File

@ -1,10 +1,10 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
export const COUNT_AGGREGATE_OPERATION_OPTIONS = [ export const COUNT_AGGREGATE_OPERATION_OPTIONS = [
AGGREGATE_OPERATIONS.count, AggregateOperations.COUNT,
AGGREGATE_OPERATIONS.countEmpty, AggregateOperations.COUNT_EMPTY,
AGGREGATE_OPERATIONS.countNotEmpty, AggregateOperations.COUNT_NOT_EMPTY,
AGGREGATE_OPERATIONS.countUniqueValues, AggregateOperations.COUNT_UNIQUE_VALUES,
AGGREGATE_OPERATIONS.countTrue, AggregateOperations.COUNT_TRUE,
AGGREGATE_OPERATIONS.countFalse, AggregateOperations.COUNT_FALSE,
]; ];

View File

@ -1,6 +1,6 @@
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
export const DATE_AGGREGATE_OPERATION_OPTIONS = [ export const DATE_AGGREGATE_OPERATION_OPTIONS = [
DATE_AGGREGATE_OPERATIONS.earliest, DateAggregateOperations.EARLIEST,
DATE_AGGREGATE_OPERATIONS.latest, DateAggregateOperations.LATEST,
]; ];

View File

@ -1,8 +1,8 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
export const NON_STANDARD_AGGREGATE_OPERATION_OPTIONS = [ export const NON_STANDARD_AGGREGATE_OPERATION_OPTIONS = [
AGGREGATE_OPERATIONS.min, AggregateOperations.MIN,
AGGREGATE_OPERATIONS.max, AggregateOperations.MAX,
AGGREGATE_OPERATIONS.avg, AggregateOperations.AVG,
AGGREGATE_OPERATIONS.sum, AggregateOperations.SUM,
]; ];

View File

@ -1,6 +1,6 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
export const PERCENT_AGGREGATE_OPERATION_OPTIONS = [ export const PERCENT_AGGREGATE_OPERATION_OPTIONS = [
AGGREGATE_OPERATIONS.percentageEmpty, AggregateOperations.PERCENTAGE_EMPTY,
AGGREGATE_OPERATIONS.percentageNotEmpty, AggregateOperations.PERCENTAGE_NOT_EMPTY,
]; ];

View File

@ -5,7 +5,7 @@ import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter'; import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter'; import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext'; import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState'; import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
@ -62,9 +62,9 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
isFieldMetadataDateKind(fieldMetadataItem.type) && isFieldMetadataDateKind(fieldMetadataItem.type) &&
isDefined(aggregateOperationForViewFieldWithProbableImpossibleValues) && isDefined(aggregateOperationForViewFieldWithProbableImpossibleValues) &&
(aggregateOperationForViewFieldWithProbableImpossibleValues === (aggregateOperationForViewFieldWithProbableImpossibleValues ===
AGGREGATE_OPERATIONS.min || AggregateOperations.MIN ||
aggregateOperationForViewFieldWithProbableImpossibleValues === aggregateOperationForViewFieldWithProbableImpossibleValues ===
AGGREGATE_OPERATIONS.max); AggregateOperations.MAX);
const aggregateOperationForViewField: const aggregateOperationForViewField:
| ExtendedAggregateOperations | ExtendedAggregateOperations

View File

@ -1,14 +1,14 @@
import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate'; import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
export const buildRecordGqlFieldsAggregateForRecordTable = ({ export const buildRecordGqlFieldsAggregateForRecordTable = ({
aggregateOperation, aggregateOperation,
fieldName, fieldName,
}: { }: {
fieldName: string; fieldName: string;
aggregateOperation?: AGGREGATE_OPERATIONS | null; aggregateOperation?: AggregateOperations | null;
}): RecordGqlFieldsAggregate => { }): RecordGqlFieldsAggregate => {
return { return {
[fieldName]: [aggregateOperation ?? AGGREGATE_OPERATIONS.count], [fieldName]: [aggregateOperation ?? AggregateOperations.COUNT],
}; };
}; };

View File

@ -1,10 +1,10 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION } from '@/object-record/record-table/constants/FieldTypesAvailableForNonStandardAggregateOperation'; import { FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION } from '@/object-record/record-table/constants/FieldTypesAvailableForNonStandardAggregateOperation';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { AggregateOperationsOmittingStandardOperations } from '@/object-record/types/AggregateOperationsOmittingStandardOperations'; import { AggregateOperationsOmittingStandardOperations } from '@/object-record/types/AggregateOperationsOmittingStandardOperations';
import { isFieldTypeValidForAggregateOperation } from '@/object-record/utils/isFieldTypeValidForAggregateOperation'; import { isFieldTypeValidForAggregateOperation } from '@/object-record/utils/isFieldTypeValidForAggregateOperation';
import { FieldMetadataType } from '~/generated/graphql';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated/graphql';
export const getAvailableAggregateOperationsForFieldMetadataType = ({ export const getAvailableAggregateOperationsForFieldMetadataType = ({
fieldMetadataType, fieldMetadataType,
@ -12,16 +12,16 @@ export const getAvailableAggregateOperationsForFieldMetadataType = ({
fieldMetadataType?: FieldMetadataType; fieldMetadataType?: FieldMetadataType;
}) => { }) => {
if (fieldMetadataType === FieldMetadataType.RELATION) { if (fieldMetadataType === FieldMetadataType.RELATION) {
return [AGGREGATE_OPERATIONS.count]; return [AggregateOperations.COUNT];
} }
const availableAggregateOperations = new Set<ExtendedAggregateOperations>([ const availableAggregateOperations = new Set<ExtendedAggregateOperations>([
AGGREGATE_OPERATIONS.count, AggregateOperations.COUNT,
AGGREGATE_OPERATIONS.countEmpty, AggregateOperations.COUNT_EMPTY,
AGGREGATE_OPERATIONS.countNotEmpty, AggregateOperations.COUNT_NOT_EMPTY,
AGGREGATE_OPERATIONS.countUniqueValues, AggregateOperations.COUNT_UNIQUE_VALUES,
AGGREGATE_OPERATIONS.percentageEmpty, AggregateOperations.PERCENTAGE_EMPTY,
AGGREGATE_OPERATIONS.percentageNotEmpty, AggregateOperations.PERCENTAGE_NOT_EMPTY,
]); ]);
if (!isDefined(fieldMetadataType)) { if (!isDefined(fieldMetadataType)) {

View File

@ -1,6 +1,6 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
export type ExtendedAggregateOperations = export type ExtendedAggregateOperations =
| AGGREGATE_OPERATIONS | AggregateOperations
| DATE_AGGREGATE_OPERATIONS; | DateAggregateOperations;

View File

@ -1,11 +1,11 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
export type AggregateOperationsOmittingStandardOperations = Exclude< export type AggregateOperationsOmittingStandardOperations = Exclude<
AGGREGATE_OPERATIONS, AggregateOperations,
| AGGREGATE_OPERATIONS.count | AggregateOperations.COUNT
| AGGREGATE_OPERATIONS.countEmpty | AggregateOperations.COUNT_EMPTY
| AGGREGATE_OPERATIONS.countNotEmpty | AggregateOperations.COUNT_NOT_EMPTY
| AGGREGATE_OPERATIONS.countUniqueValues | AggregateOperations.COUNT_UNIQUE_VALUES
| AGGREGATE_OPERATIONS.percentageEmpty | AggregateOperations.PERCENTAGE_EMPTY
| AGGREGATE_OPERATIONS.percentageNotEmpty | AggregateOperations.PERCENTAGE_NOT_EMPTY
>; >;

View File

@ -1,5 +1,5 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions'; import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions';
import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions'; import { NON_STANDARD_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/nonStandardAggregateOperationsOptions';
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
@ -23,43 +23,43 @@ jest.mock(
() => ({ () => ({
getAvailableAggregationsFromObjectFields: jest.fn().mockReturnValue({ getAvailableAggregationsFromObjectFields: jest.fn().mockReturnValue({
active: { active: {
[AGGREGATE_OPERATIONS.countTrue]: 'countTrueActive', [AggregateOperations.COUNT_TRUE]: 'countTrueActive',
[AGGREGATE_OPERATIONS.countFalse]: 'CountFalseActive', [AggregateOperations.COUNT_FALSE]: 'CountFalseActive',
}, },
amount: { amount: {
[AGGREGATE_OPERATIONS.sum]: 'sumAmount', [AggregateOperations.SUM]: 'sumAmount',
[AGGREGATE_OPERATIONS.avg]: 'avgAmount', [AggregateOperations.AVG]: 'avgAmount',
[AGGREGATE_OPERATIONS.min]: 'minAmount', [AggregateOperations.MIN]: 'minAmount',
[AGGREGATE_OPERATIONS.max]: 'maxAmount', [AggregateOperations.MAX]: 'maxAmount',
[AGGREGATE_OPERATIONS.count]: 'totalCount', [AggregateOperations.COUNT]: 'totalCount',
[AGGREGATE_OPERATIONS.countUniqueValues]: 'countUniqueValuesAmount', [AggregateOperations.COUNT_UNIQUE_VALUES]: 'countUniqueValuesAmount',
[AGGREGATE_OPERATIONS.countEmpty]: 'countEmptyAmount', [AggregateOperations.COUNT_EMPTY]: 'countEmptyAmount',
[AGGREGATE_OPERATIONS.countNotEmpty]: 'countNotEmptyAmount', [AggregateOperations.COUNT_NOT_EMPTY]: 'countNotEmptyAmount',
[AGGREGATE_OPERATIONS.percentageEmpty]: 'percentageEmptyAmount', [AggregateOperations.PERCENTAGE_EMPTY]: 'percentageEmptyAmount',
[AGGREGATE_OPERATIONS.percentageNotEmpty]: 'percentageNotEmptyAmount', [AggregateOperations.PERCENTAGE_NOT_EMPTY]: 'percentageNotEmptyAmount',
}, },
price: { price: {
[AGGREGATE_OPERATIONS.sum]: 'sumPriceAmountMicros', [AggregateOperations.SUM]: 'sumPriceAmountMicros',
[AGGREGATE_OPERATIONS.avg]: 'avgPriceAmountMicros', [AggregateOperations.AVG]: 'avgPriceAmountMicros',
[AGGREGATE_OPERATIONS.min]: 'minPriceAmountMicros', [AggregateOperations.MIN]: 'minPriceAmountMicros',
[AGGREGATE_OPERATIONS.max]: 'maxPriceAmountMicros', [AggregateOperations.MAX]: 'maxPriceAmountMicros',
[AGGREGATE_OPERATIONS.count]: 'totalCount', [AggregateOperations.COUNT]: 'totalCount',
[AGGREGATE_OPERATIONS.countUniqueValues]: [AggregateOperations.COUNT_UNIQUE_VALUES]:
'countUniqueValuesPriceAmountMicros', 'countUniqueValuesPriceAmountMicros',
[AGGREGATE_OPERATIONS.countEmpty]: 'countEmptyPriceAmountMicros', [AggregateOperations.COUNT_EMPTY]: 'countEmptyPriceAmountMicros',
[AGGREGATE_OPERATIONS.countNotEmpty]: 'countNotEmptyPriceAmountMicros', [AggregateOperations.COUNT_NOT_EMPTY]: 'countNotEmptyPriceAmountMicros',
[AGGREGATE_OPERATIONS.percentageEmpty]: [AggregateOperations.PERCENTAGE_EMPTY]:
'percentageEmptyPriceAmountMicros', 'percentageEmptyPriceAmountMicros',
[AGGREGATE_OPERATIONS.percentageNotEmpty]: [AggregateOperations.PERCENTAGE_NOT_EMPTY]:
'percentageNotEmptyPriceAmountMicros', 'percentageNotEmptyPriceAmountMicros',
}, },
name: { name: {
[AGGREGATE_OPERATIONS.count]: 'totalCount', [AggregateOperations.COUNT]: 'totalCount',
[AGGREGATE_OPERATIONS.countUniqueValues]: 'countUniqueValuesName', [AggregateOperations.COUNT_UNIQUE_VALUES]: 'countUniqueValuesName',
[AGGREGATE_OPERATIONS.countEmpty]: 'countEmptyName', [AggregateOperations.COUNT_EMPTY]: 'countEmptyName',
[AGGREGATE_OPERATIONS.countNotEmpty]: 'countNotEmptyName', [AggregateOperations.COUNT_NOT_EMPTY]: 'countNotEmptyName',
[AGGREGATE_OPERATIONS.percentageEmpty]: 'percentageEmptyName', [AggregateOperations.PERCENTAGE_EMPTY]: 'percentageEmptyName',
[AGGREGATE_OPERATIONS.percentageNotEmpty]: 'percentageNotEmptyName', [AggregateOperations.PERCENTAGE_NOT_EMPTY]: 'percentageNotEmptyName',
}, },
}), }),
}), }),

View File

@ -1,4 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION } from '@/object-record/record-table/constants/FieldTypesAvailableForNonStandardAggregateOperation'; import { FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION } from '@/object-record/record-table/constants/FieldTypesAvailableForNonStandardAggregateOperation';
import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions'; import { COUNT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/countAggregateOperationOptions';
import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions'; import { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
@ -10,7 +10,7 @@ describe('initializeAvailableFieldsForAggregateOperationMap', () => {
const result = initializeAvailableFieldsForAggregateOperationMap( const result = initializeAvailableFieldsForAggregateOperationMap(
Object.keys( Object.keys(
FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION, FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION,
) as AGGREGATE_OPERATIONS[], ) as AggregateOperations[],
); );
expect(Object.keys(result)).toEqual( expect(Object.keys(result)).toEqual(
@ -25,11 +25,11 @@ describe('initializeAvailableFieldsForAggregateOperationMap', () => {
const result = initializeAvailableFieldsForAggregateOperationMap( const result = initializeAvailableFieldsForAggregateOperationMap(
Object.keys( Object.keys(
FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION, FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION,
) as AGGREGATE_OPERATIONS[], ) as AggregateOperations[],
); );
expect( expect(
result[ result[
AGGREGATE_OPERATIONS.count as AggregateOperationsOmittingStandardOperations AggregateOperations.COUNT as AggregateOperationsOmittingStandardOperations
], ],
).toBeUndefined(); ).toBeUndefined();
}); });
@ -38,21 +38,21 @@ describe('initializeAvailableFieldsForAggregateOperationMap', () => {
const result = initializeAvailableFieldsForAggregateOperationMap( const result = initializeAvailableFieldsForAggregateOperationMap(
COUNT_AGGREGATE_OPERATION_OPTIONS, COUNT_AGGREGATE_OPERATION_OPTIONS,
); );
expect(result[AGGREGATE_OPERATIONS.count]).toEqual([]); expect(result[AggregateOperations.COUNT]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.countEmpty]).toEqual([]); expect(result[AggregateOperations.COUNT_EMPTY]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.countNotEmpty]).toEqual([]); expect(result[AggregateOperations.COUNT_NOT_EMPTY]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.countUniqueValues]).toEqual([]); expect(result[AggregateOperations.COUNT_UNIQUE_VALUES]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.min]).toBeUndefined(); expect(result[AggregateOperations.MIN]).toBeUndefined();
expect(result[AGGREGATE_OPERATIONS.percentageEmpty]).toBeUndefined(); expect(result[AggregateOperations.PERCENTAGE_EMPTY]).toBeUndefined();
}); });
it('should include percent operation when called with count aggregate operations', () => { it('should include percent operation when called with count aggregate operations', () => {
const result = initializeAvailableFieldsForAggregateOperationMap( const result = initializeAvailableFieldsForAggregateOperationMap(
PERCENT_AGGREGATE_OPERATION_OPTIONS, PERCENT_AGGREGATE_OPERATION_OPTIONS,
); );
expect(result[AGGREGATE_OPERATIONS.percentageEmpty]).toEqual([]); expect(result[AggregateOperations.PERCENTAGE_EMPTY]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.percentageNotEmpty]).toEqual([]); expect(result[AggregateOperations.PERCENTAGE_NOT_EMPTY]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.count]).toBeUndefined(); expect(result[AggregateOperations.COUNT]).toBeUndefined();
expect(result[AGGREGATE_OPERATIONS.min]).toBeUndefined(); expect(result[AggregateOperations.MIN]).toBeUndefined();
}); });
}); });

View File

@ -1,4 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { AggregateOperationsOmittingStandardOperations } from '@/object-record/types/AggregateOperationsOmittingStandardOperations'; import { AggregateOperationsOmittingStandardOperations } from '@/object-record/types/AggregateOperationsOmittingStandardOperations';
import { isFieldTypeValidForAggregateOperation } from '@/object-record/utils/isFieldTypeValidForAggregateOperation'; import { isFieldTypeValidForAggregateOperation } from '@/object-record/utils/isFieldTypeValidForAggregateOperation';
import { FieldMetadataType } from '~/generated/graphql'; import { FieldMetadataType } from '~/generated/graphql';
@ -8,14 +8,14 @@ describe('isFieldTypeValidForAggregateOperation', () => {
expect( expect(
isFieldTypeValidForAggregateOperation( isFieldTypeValidForAggregateOperation(
FieldMetadataType.NUMBER, FieldMetadataType.NUMBER,
AGGREGATE_OPERATIONS.sum, AggregateOperations.SUM,
), ),
).toBe(true); ).toBe(true);
expect( expect(
isFieldTypeValidForAggregateOperation( isFieldTypeValidForAggregateOperation(
FieldMetadataType.CURRENCY, FieldMetadataType.CURRENCY,
AGGREGATE_OPERATIONS.min, AggregateOperations.MIN,
), ),
).toBe(true); ).toBe(true);
}); });
@ -24,14 +24,14 @@ describe('isFieldTypeValidForAggregateOperation', () => {
expect( expect(
isFieldTypeValidForAggregateOperation( isFieldTypeValidForAggregateOperation(
FieldMetadataType.TEXT, FieldMetadataType.TEXT,
AGGREGATE_OPERATIONS.avg, AggregateOperations.AVG,
), ),
).toBe(false); ).toBe(false);
expect( expect(
isFieldTypeValidForAggregateOperation( isFieldTypeValidForAggregateOperation(
FieldMetadataType.BOOLEAN, FieldMetadataType.BOOLEAN,
AGGREGATE_OPERATIONS.max, AggregateOperations.MAX,
), ),
).toBe(false); ).toBe(false);
}); });
@ -39,10 +39,10 @@ describe('isFieldTypeValidForAggregateOperation', () => {
it('should handle all aggregate operations', () => { it('should handle all aggregate operations', () => {
const numericField = FieldMetadataType.NUMBER; const numericField = FieldMetadataType.NUMBER;
const operations = [ const operations = [
AGGREGATE_OPERATIONS.min, AggregateOperations.MIN,
AGGREGATE_OPERATIONS.max, AggregateOperations.MAX,
AGGREGATE_OPERATIONS.avg, AggregateOperations.AVG,
AGGREGATE_OPERATIONS.sum, AggregateOperations.SUM,
]; ];
operations.forEach((operation) => { operations.forEach((operation) => {

View File

@ -1,19 +1,19 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isFieldMetadataDateKind } from 'twenty-shared/utils'; import { isFieldMetadataDateKind } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const convertAggregateOperationToExtendedAggregateOperation = ( export const convertAggregateOperationToExtendedAggregateOperation = (
aggregateOperation: AGGREGATE_OPERATIONS, aggregateOperation: AggregateOperations,
fieldType?: FieldMetadataType, fieldType?: FieldMetadataType,
): ExtendedAggregateOperations => { ): ExtendedAggregateOperations => {
if (isFieldMetadataDateKind(fieldType) === true) { if (isFieldMetadataDateKind(fieldType) === true) {
if (aggregateOperation === AGGREGATE_OPERATIONS.min) { if (aggregateOperation === AggregateOperations.MIN) {
return DATE_AGGREGATE_OPERATIONS.earliest; return DateAggregateOperations.EARLIEST;
} }
if (aggregateOperation === AGGREGATE_OPERATIONS.max) { if (aggregateOperation === AggregateOperations.MAX) {
return DATE_AGGREGATE_OPERATIONS.latest; return DateAggregateOperations.LATEST;
} }
} }
return aggregateOperation; return aggregateOperation;

View File

@ -1,16 +1,16 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
export const convertExtendedAggregateOperationToAggregateOperation = ( export const convertExtendedAggregateOperationToAggregateOperation = (
extendedAggregateOperation: ExtendedAggregateOperations | null, extendedAggregateOperation: ExtendedAggregateOperations | null,
) => { ) => {
if (extendedAggregateOperation === DATE_AGGREGATE_OPERATIONS.earliest) { if (extendedAggregateOperation === DateAggregateOperations.EARLIEST) {
return AGGREGATE_OPERATIONS.min; return AggregateOperations.MIN;
} }
if (extendedAggregateOperation === DATE_AGGREGATE_OPERATIONS.latest) { if (extendedAggregateOperation === DateAggregateOperations.LATEST) {
return AGGREGATE_OPERATIONS.max; return AggregateOperations.MAX;
} }
return extendedAggregateOperation; return extendedAggregateOperation;
}; };

View File

@ -1,10 +1,10 @@
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { capitalize, isFieldMetadataDateKind } from 'twenty-shared/utils';
import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants'; import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants';
import { capitalize, isFieldMetadataDateKind } from 'twenty-shared/utils';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type NameForAggregation = { type NameForAggregation = {
[T in ExtendedAggregateOperations]?: string; [T in ExtendedAggregateOperations]?: string;
@ -25,53 +25,53 @@ export const getAvailableAggregationsFromObjectFields = (
if (field.type === FieldMetadataType.RELATION) { if (field.type === FieldMetadataType.RELATION) {
acc[field.name] = { acc[field.name] = {
[AGGREGATE_OPERATIONS.count]: 'totalCount', [AggregateOperations.COUNT]: 'totalCount',
}; };
return acc; return acc;
} }
acc[field.name] = { acc[field.name] = {
[AGGREGATE_OPERATIONS.countUniqueValues]: `countUniqueValues${capitalize(field.name)}`, [AggregateOperations.COUNT_UNIQUE_VALUES]: `countUniqueValues${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.countEmpty]: `countEmpty${capitalize(field.name)}`, [AggregateOperations.COUNT_EMPTY]: `countEmpty${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.countNotEmpty]: `countNotEmpty${capitalize(field.name)}`, [AggregateOperations.COUNT_NOT_EMPTY]: `countNotEmpty${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.percentageEmpty]: `percentageEmpty${capitalize(field.name)}`, [AggregateOperations.PERCENTAGE_EMPTY]: `percentageEmpty${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.percentageNotEmpty]: `percentageNotEmpty${capitalize(field.name)}`, [AggregateOperations.PERCENTAGE_NOT_EMPTY]: `percentageNotEmpty${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.count]: 'totalCount', [AggregateOperations.COUNT]: 'totalCount',
}; };
if (field.type === FieldMetadataType.NUMBER) { if (field.type === FieldMetadataType.NUMBER) {
acc[field.name] = { acc[field.name] = {
...acc[field.name], ...acc[field.name],
[AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}`, [AggregateOperations.MIN]: `min${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}`, [AggregateOperations.MAX]: `max${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}`, [AggregateOperations.AVG]: `avg${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}`, [AggregateOperations.SUM]: `sum${capitalize(field.name)}`,
}; };
} }
if (field.type === FieldMetadataType.CURRENCY) { if (field.type === FieldMetadataType.CURRENCY) {
acc[field.name] = { acc[field.name] = {
...acc[field.name], ...acc[field.name],
[AGGREGATE_OPERATIONS.min]: `min${capitalize(field.name)}AmountMicros`, [AggregateOperations.MIN]: `min${capitalize(field.name)}AmountMicros`,
[AGGREGATE_OPERATIONS.max]: `max${capitalize(field.name)}AmountMicros`, [AggregateOperations.MAX]: `max${capitalize(field.name)}AmountMicros`,
[AGGREGATE_OPERATIONS.avg]: `avg${capitalize(field.name)}AmountMicros`, [AggregateOperations.AVG]: `avg${capitalize(field.name)}AmountMicros`,
[AGGREGATE_OPERATIONS.sum]: `sum${capitalize(field.name)}AmountMicros`, [AggregateOperations.SUM]: `sum${capitalize(field.name)}AmountMicros`,
}; };
} }
if (field.type === FieldMetadataType.BOOLEAN) { if (field.type === FieldMetadataType.BOOLEAN) {
acc[field.name] = { acc[field.name] = {
...acc[field.name], ...acc[field.name],
[AGGREGATE_OPERATIONS.countTrue]: `countTrue${capitalize(field.name)}`, [AggregateOperations.COUNT_TRUE]: `countTrue${capitalize(field.name)}`,
[AGGREGATE_OPERATIONS.countFalse]: `countFalse${capitalize(field.name)}`, [AggregateOperations.COUNT_FALSE]: `countFalse${capitalize(field.name)}`,
}; };
} }
if (isFieldMetadataDateKind(field.type) === true) { if (isFieldMetadataDateKind(field.type) === true) {
acc[field.name] = { acc[field.name] = {
...acc[field.name], ...acc[field.name],
[DATE_AGGREGATE_OPERATIONS.earliest]: `min${capitalize(field.name)}`, [DateAggregateOperations.EARLIEST]: `min${capitalize(field.name)}`,
[DATE_AGGREGATE_OPERATIONS.latest]: `max${capitalize(field.name)}`, [DateAggregateOperations.LATEST]: `max${capitalize(field.name)}`,
}; };
} }
@ -83,7 +83,7 @@ export const getAvailableAggregationsFromObjectFields = (
}, },
{ {
[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: { [FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: {
[AGGREGATE_OPERATIONS.count]: 'totalCount', [AggregateOperations.COUNT]: 'totalCount',
}, },
}, },
); );

View File

@ -4,11 +4,11 @@ import { FeatureFlagKey } from '~/generated/graphql';
const getFeatureKey = (databaseKey: string): FeatureFlagKey | null => { const getFeatureKey = (databaseKey: string): FeatureFlagKey | null => {
switch (databaseKey) { switch (databaseKey) {
case 'airtable': case 'airtable':
return FeatureFlagKey.IsAirtableIntegrationEnabled; return FeatureFlagKey.IS_AIRTABLE_INTEGRATION_ENABLED;
case 'postgresql': case 'postgresql':
return FeatureFlagKey.IsPostgreSQLIntegrationEnabled; return FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED;
case 'stripe': case 'stripe':
return FeatureFlagKey.IsStripeIntegrationEnabled; return FeatureFlagKey.IS_STRIPE_INTEGRATION_ENABLED;
default: default:
return null; return null;
} }

View File

@ -10,21 +10,21 @@ import { FeatureFlagKey } from '~/generated/graphql';
export const useSettingsIntegrationCategories = export const useSettingsIntegrationCategories =
(): SettingsIntegrationCategory[] => { (): SettingsIntegrationCategory[] => {
const isAirtableIntegrationEnabled = useIsFeatureEnabled( const isAirtableIntegrationEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsAirtableIntegrationEnabled, FeatureFlagKey.IS_AIRTABLE_INTEGRATION_ENABLED,
); );
const isAirtableIntegrationActive = !!MOCK_REMOTE_DATABASES.find( const isAirtableIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
({ name }) => name === 'airtable', ({ name }) => name === 'airtable',
)?.isActive; )?.isActive;
const isPostgresqlIntegrationEnabled = useIsFeatureEnabled( const isPostgresqlIntegrationEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsPostgreSQLIntegrationEnabled, FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED,
); );
const isPostgresqlIntegrationActive = !!MOCK_REMOTE_DATABASES.find( const isPostgresqlIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
({ name }) => name === 'postgresql', ({ name }) => name === 'postgresql',
)?.isActive; )?.isActive;
const isStripeIntegrationEnabled = useIsFeatureEnabled( const isStripeIntegrationEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsStripeIntegrationEnabled, FeatureFlagKey.IS_STRIPE_INTEGRATION_ENABLED,
); );
const isStripeIntegrationActive = !!MOCK_REMOTE_DATABASES.find( const isStripeIntegrationActive = !!MOCK_REMOTE_DATABASES.find(
({ name }) => name === 'stripe', ({ name }) => name === 'stripe',

View File

@ -36,7 +36,7 @@ const StyledNoRoles = styled(TableCell)`
export const SettingsRolesList = () => { export const SettingsRolesList = () => {
const navigateSettings = useNavigateSettings(); const navigateSettings = useNavigateSettings();
const isPermissionsV2Enabled = useIsFeatureEnabled( const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled, FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
); );
const settingsAllRoles = useRecoilValue(settingsAllRolesSelector); const settingsAllRoles = useRecoilValue(settingsAllRolesSelector);

View File

@ -23,7 +23,7 @@ export const SettingsRolePermissions = ({
isCreateMode, isCreateMode,
}: SettingsRolePermissionsProps) => { }: SettingsRolePermissionsProps) => {
const isPermissionsV2Enabled = useIsFeatureEnabled( const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled, FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
); );
return ( return (

View File

@ -45,7 +45,7 @@ export const SettingsRolePermissionsSettingsSection = ({
isEditable, isEditable,
}: SettingsRolePermissionsSettingsSectionProps) => { }: SettingsRolePermissionsSettingsSectionProps) => {
const isPermissionsV2Enabled = useIsFeatureEnabled( const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled, FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
); );
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(

View File

@ -54,7 +54,7 @@ export const SettingsRolePermissionsSettingsTableRow = ({
settingsDraftRoleFamilyState(roleId), settingsDraftRoleFamilyState(roleId),
); );
const isPermissionsV2Enabled = useIsFeatureEnabled( const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled, FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
); );
const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings; const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings;

View File

@ -60,7 +60,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
); );
const isPermissionsV2Enabled = useIsFeatureEnabled( const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled, FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
); );
const navigateSettings = useNavigateSettings(); const navigateSettings = useNavigateSettings();

View File

@ -10,7 +10,6 @@ import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/reco
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext'; import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext'; import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
@ -21,6 +20,7 @@ import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId'; import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { within } from '@storybook/test'; import { within } from '@storybook/test';
@ -67,7 +67,7 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
viewGroups: [], viewGroups: [],
viewSorts: [], viewSorts: [],
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '', icon: '',
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,

View File

@ -3,7 +3,7 @@ import { renderHook } from '@testing-library/react';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups'; import { useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups } from '@/views/hooks/useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups';
@ -14,9 +14,9 @@ import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { mapViewFilterGroupLogicalOperatorToRecordFilterGroupLogicalOperator } from '@/views/utils/mapViewFilterGroupLogicalOperatorToRecordFilterGroupLogicalOperator'; import { mapViewFilterGroupLogicalOperatorToRecordFilterGroupLogicalOperator } from '@/views/utils/mapViewFilterGroupLogicalOperatorToRecordFilterGroupLogicalOperator';
import { act } from 'react'; import { act } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { isDefined } from 'twenty-shared/utils';
const mockObjectMetadataItemNameSingular = 'company'; const mockObjectMetadataItemNameSingular = 'company';
@ -54,7 +54,7 @@ describe('useApplyCurrentViewFilterGroupsToCurrentRecordFilterGroups', () => {
viewGroups: [], viewGroups: [],
viewSorts: [], viewSorts: [],
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '', icon: '',
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,

View File

@ -4,7 +4,7 @@ import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState'; import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
@ -13,10 +13,10 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { act } from 'react'; import { act } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper'; import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters'; import { useApplyCurrentViewFiltersToCurrentRecordFilters } from '../useApplyCurrentViewFiltersToCurrentRecordFilters';
import { isDefined } from 'twenty-shared/utils';
const mockObjectMetadataItemNameSingular = 'company'; const mockObjectMetadataItemNameSingular = 'company';
@ -57,7 +57,7 @@ describe('useApplyCurrentViewFiltersToCurrentRecordFilters', () => {
viewGroups: [], viewGroups: [],
viewSorts: [], viewSorts: [],
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '', icon: '',
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations'; import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useUpdateView } from '@/views/hooks/useUpdateView'; import { useUpdateView } from '@/views/hooks/useUpdateView';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
@ -34,21 +34,21 @@ describe('useUpdateViewAggregate', () => {
result.current.updateViewAggregate({ result.current.updateViewAggregate({
kanbanAggregateOperationFieldMetadataId: 'test-field-id', kanbanAggregateOperationFieldMetadataId: 'test-field-id',
kanbanAggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest, kanbanAggregateOperation: DateAggregateOperations.EARLIEST,
}); });
// updateView is called with 'EARLIEST' converted to 'MIN' // updateView is called with 'EARLIEST' converted to 'MIN'
expect(mockUpdateView).toHaveBeenCalledWith({ expect(mockUpdateView).toHaveBeenCalledWith({
id: mockCurrentViewId, id: mockCurrentViewId,
kanbanAggregateOperationFieldMetadataId: 'test-field-id', kanbanAggregateOperationFieldMetadataId: 'test-field-id',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.min, kanbanAggregateOperation: AggregateOperations.MIN,
}); });
// setAggregateOperation is called with 'EARLIEST' // setAggregateOperation is called with 'EARLIEST'
expect( expect(
mockSetRecordIndexKanbanAggregateOperationState, mockSetRecordIndexKanbanAggregateOperationState,
).toHaveBeenCalledWith({ ).toHaveBeenCalledWith({
operation: DATE_AGGREGATE_OPERATIONS.earliest, operation: DateAggregateOperations.EARLIEST,
fieldMetadataId: 'test-field-id', fieldMetadataId: 'test-field-id',
}); });
}); });

View File

@ -4,7 +4,7 @@ import { currentRecordFilterGroupsComponentState } from '@/object-record/record-
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter'; import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters'; import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
@ -36,7 +36,7 @@ export const useGetRecordIndexTotalCount = () => {
objectNameSingular: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
filter, filter,
recordGqlFieldsAggregate: { recordGqlFieldsAggregate: {
id: [AGGREGATE_OPERATIONS.count], id: [AggregateOperations.COUNT],
}, },
}); });

View File

@ -1,4 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { ViewField } from '@/views/types/ViewField'; import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@ -17,7 +17,7 @@ export type GraphQLView = {
* @deprecated Use `viewGroups.fieldMetadataId` instead. * @deprecated Use `viewGroups.fieldMetadataId` instead.
*/ */
kanbanFieldMetadataId: string; kanbanFieldMetadataId: string;
kanbanAggregateOperation?: AGGREGATE_OPERATIONS | null; kanbanAggregateOperation?: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId?: string | null; kanbanAggregateOperationFieldMetadataId?: string | null;
objectMetadataId: string; objectMetadataId: string;
isCompact: boolean; isCompact: boolean;

View File

@ -1,4 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { ViewField } from '@/views/types/ViewField'; import { ViewField } from '@/views/types/ViewField';
import { ViewFilter } from '@/views/types/ViewFilter'; import { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@ -24,7 +24,7 @@ export type View = {
* @deprecated Use `viewGroups.fieldMetadataId` instead. * @deprecated Use `viewGroups.fieldMetadataId` instead.
*/ */
kanbanFieldMetadataId: string; kanbanFieldMetadataId: string;
kanbanAggregateOperation: AGGREGATE_OPERATIONS | null; kanbanAggregateOperation: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId: string | null; kanbanAggregateOperationFieldMetadataId: string | null;
position: number; position: number;
icon: string; icon: string;

View File

@ -1,6 +1,6 @@
import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition'; import { RecordBoardFieldDefinition } from '@/object-record/record-board/types/RecordBoardFieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
export type ViewField = { export type ViewField = {
@ -10,7 +10,7 @@ export type ViewField = {
position: number; position: number;
isVisible: boolean; isVisible: boolean;
size: number; size: number;
aggregateOperation?: AGGREGATE_OPERATIONS | null; aggregateOperation?: AggregateOperations | null;
definition: definition:
| ColumnDefinition<FieldMetadata> | ColumnDefinition<FieldMetadata>
| RecordBoardFieldDefinition<FieldMetadata>; | RecordBoardFieldDefinition<FieldMetadata>;

View File

@ -1,14 +1,14 @@
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup'; import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator'; import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup'; import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator'; import { ViewFilterGroupLogicalOperator } from '@/views/types/ViewFilterGroupLogicalOperator';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup'; import { mapRecordFilterGroupToViewFilterGroup } from '@/views/utils/mapRecordFilterGroupToViewFilterGroup';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const mockObjectMetadataItemNameSingular = 'company'; const mockObjectMetadataItemNameSingular = 'company';
@ -37,7 +37,7 @@ describe('mapRecordFilterGroupToViewFilterGroup', () => {
viewGroups: [], viewGroups: [],
viewSorts: [], viewSorts: [],
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '', icon: '',
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,

View File

@ -20,13 +20,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
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 { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { FeatureFlagKey } from '~/generated/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { SETTINGS_OBJECT_DETAIL_TABS } from '~/pages/settings/data-model/constants/SettingsObjectDetailTabs';
import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { Button } from 'twenty-ui/input';
import { import {
H3Title, H3Title,
IconCodeCircle, IconCodeCircle,
@ -35,8 +29,14 @@ import {
IconPoint, IconPoint,
IconSettings, IconSettings,
} from 'twenty-ui/display'; } from 'twenty-ui/display';
import { MAIN_COLORS } from 'twenty-ui/theme'; import { Button } from 'twenty-ui/input';
import { UndecoratedLink } from 'twenty-ui/navigation'; import { UndecoratedLink } from 'twenty-ui/navigation';
import { MAIN_COLORS } from 'twenty-ui/theme';
import { FeatureFlagKey } from '~/generated/graphql';
import { useNavigateApp } from '~/hooks/useNavigateApp';
import { SETTINGS_OBJECT_DETAIL_TABS } from '~/pages/settings/data-model/constants/SettingsObjectDetailTabs';
import { updatedObjectNamePluralState } from '~/pages/settings/data-model/states/updatedObjectNamePluralState';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledContentContainer = styled.div` const StyledContentContainer = styled.div`
flex: 1; flex: 1;
@ -76,7 +76,7 @@ export const SettingsObjectDetailPage = () => {
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState); const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const isUniqueIndexesEnabled = useIsFeatureEnabled( const isUniqueIndexesEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsUniqueIndexesEnabled, FeatureFlagKey.IS_UNIQUE_INDEXES_ENABLED,
); );
useEffect(() => { useEffect(() => {

View File

@ -11,7 +11,6 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
import { useModal } from '@/ui/layout/modal/hooks/useModal'; import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { ApolloError } from '@apollo/client'; import { ApolloError } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro'; import { Trans, useLingui } from '@lingui/react/macro';
@ -19,10 +18,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { z } from 'zod'; import { z } from 'zod';
import { import { useUpdateWorkspaceMutation } from '~/generated/graphql';
FeatureFlagKey,
useUpdateWorkspaceMutation,
} from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { SettingsCustomDomain } from '~/pages/settings/workspace/SettingsCustomDomain'; import { SettingsCustomDomain } from '~/pages/settings/workspace/SettingsCustomDomain';
import { SettingsSubdomain } from '~/pages/settings/workspace/SettingsSubdomain'; import { SettingsSubdomain } from '~/pages/settings/workspace/SettingsSubdomain';
@ -68,10 +64,6 @@ export const SettingsDomain = () => {
const [updateWorkspace] = useUpdateWorkspaceMutation(); const [updateWorkspace] = useUpdateWorkspaceMutation();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain(); const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const isCustomDomainEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsCustomDomainEnabled,
);
const [currentWorkspace, setCurrentWorkspace] = useRecoilState( const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
currentWorkspaceState, currentWorkspaceState,
); );
@ -236,7 +228,7 @@ export const SettingsDomain = () => {
> >
<SettingsPageContainer> <SettingsPageContainer>
<SettingsSubdomain /> <SettingsSubdomain />
{isCustomDomainEnabled && <SettingsCustomDomain />} <SettingsCustomDomain />
</SettingsPageContainer> </SettingsPageContainer>
</SubMenuTopBarContainer> </SubMenuTopBarContainer>
<ConfirmationModal <ConfirmationModal

View File

@ -40,7 +40,7 @@ export const mockedClientConfig: ClientConfig = {
], ],
}, },
captcha: { captcha: {
provider: CaptchaDriverType.GoogleRecaptcha, provider: CaptchaDriverType.GOOGLE_RECAPTCHA,
siteKey: 'MOCKED_SITE_KEY', siteKey: 'MOCKED_SITE_KEY',
}, },
api: { mutationMaximumAffectedRecords: 100 }, api: { mutationMaximumAffectedRecords: 100 },

View File

@ -59,15 +59,15 @@ export const mockCurrentWorkspace: Workspace = {
isMicrosoftAuthEnabled: false, isMicrosoftAuthEnabled: false,
featureFlags: [ featureFlags: [
{ {
key: FeatureFlagKey.IsAirtableIntegrationEnabled, key: FeatureFlagKey.IS_AIRTABLE_INTEGRATION_ENABLED,
value: true, value: true,
}, },
{ {
key: FeatureFlagKey.IsPostgreSQLIntegrationEnabled, key: FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED,
value: true, value: true,
}, },
{ {
key: FeatureFlagKey.IsWorkflowEnabled, key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
value: true, value: true,
}, },
], ],

View File

@ -1,4 +1,4 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations'; import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { ViewKey } from '@/views/types/ViewKey'; import { ViewKey } from '@/views/types/ViewKey';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@ -26,7 +26,7 @@ export const mockedViewsData: View[] = [
icon: 'IconSkyline', icon: 'IconSkyline',
key: ViewKey.Index, key: ViewKey.Index,
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,
isCompact: false, isCompact: false,
@ -46,7 +46,7 @@ export const mockedViewsData: View[] = [
icon: 'IconPerson', icon: 'IconPerson',
key: ViewKey.Index, key: ViewKey.Index,
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,
isCompact: false, isCompact: false,
@ -66,7 +66,7 @@ export const mockedViewsData: View[] = [
icon: 'IconOpportunity', icon: 'IconOpportunity',
key: ViewKey.Index, key: ViewKey.Index,
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,
isCompact: false, isCompact: false,
@ -86,7 +86,7 @@ export const mockedViewsData: View[] = [
icon: 'IconSkyline', icon: 'IconSkyline',
key: null, key: null,
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count, kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '', kanbanAggregateOperationFieldMetadataId: '',
position: 0, position: 0,
isCompact: false, isCompact: false,

View File

@ -37,7 +37,7 @@ FRONTEND_URL=http://localhost:3001
# SUPPORT_DRIVER=front # SUPPORT_DRIVER=front
# SUPPORT_FRONT_HMAC_KEY=replace_me_with_front_chat_verification_secret # SUPPORT_FRONT_HMAC_KEY=replace_me_with_front_chat_verification_secret
# SUPPORT_FRONT_CHAT_ID=replace_me_with_front_chat_id # SUPPORT_FRONT_CHAT_ID=replace_me_with_front_chat_id
# LOGGER_DRIVER=console # LOGGER_DRIVER=CONSOLE
# LOGGER_IS_BUFFER_ENABLED=true # LOGGER_IS_BUFFER_ENABLED=true
# EXCEPTION_HANDLER_DRIVER=sentry # EXCEPTION_HANDLER_DRIVER=sentry
# METER_DRIVER=opentelemetry,console # METER_DRIVER=opentelemetry,console
@ -55,7 +55,7 @@ FRONTEND_URL=http://localhost:3001
# EMAIL_FROM_ADDRESS=contact@yourdomain.com # EMAIL_FROM_ADDRESS=contact@yourdomain.com
# EMAIL_SYSTEM_ADDRESS=system@yourdomain.com # EMAIL_SYSTEM_ADDRESS=system@yourdomain.com
# EMAIL_FROM_NAME='John from YourDomain' # EMAIL_FROM_NAME='John from YourDomain'
# EMAIL_DRIVER=logger # EMAIL_DRIVER=LOGGER
# EMAIL_SMTP_HOST= # EMAIL_SMTP_HOST=
# EMAIL_SMTP_PORT= # EMAIL_SMTP_PORT=
# EMAIL_SMTP_USER= # EMAIL_SMTP_USER=

View File

@ -3,7 +3,7 @@ REDIS_URL=redis://localhost:6379
APP_SECRET=replace_me_with_a_random_string APP_SECRET=replace_me_with_a_random_string
SIGN_IN_PREFILLED=true SIGN_IN_PREFILLED=true
EXCEPTION_HANDLER_DRIVER=console EXCEPTION_HANDLER_DRIVER=CONSOLE
SENTRY_DSN=https://ba869cb8fd72d5faeb6643560939cee0@o4505516959793152.ingest.sentry.io/4506660900306944 SENTRY_DSN=https://ba869cb8fd72d5faeb6643560939cee0@o4505516959793152.ingest.sentry.io/4506660900306944
MUTATION_MAXIMUM_RECORD_AFFECTED=100 MUTATION_MAXIMUM_RECORD_AFFECTED=100

View File

@ -65,7 +65,7 @@ const jestConfig: JestConfigWithTsJest = {
}, },
globals: { globals: {
APP_PORT: 4000, APP_PORT: 4000,
NODE_ENV: NodeEnvironment.test, NODE_ENV: NodeEnvironment.TEST,
ADMIN_ACCESS_TOKEN: ADMIN_ACCESS_TOKEN:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwiaWF0IjoxNzM5NTQ3NjYxLCJleHAiOjMzMjk3MTQ3NjYxfQ.fbOM9yhr3jWDicPZ1n771usUURiPGmNdeFApsgrbxOw', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC05ZTNiLTQ2ZDQtYTU1Ni04OGI5ZGRjMmIwMzQiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtMDY4Ny00YzQxLWI3MDctZWQxYmZjYTk3MmE3IiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtOWUzYi00NmQ0LWE1NTYtODhiOWRkYzJiMDM1IiwiaWF0IjoxNzM5NTQ3NjYxLCJleHAiOjMzMjk3MTQ3NjYxfQ.fbOM9yhr3jWDicPZ1n771usUURiPGmNdeFApsgrbxOw',
EXPIRED_ACCESS_TOKEN: EXPIRED_ACCESS_TOKEN:

View File

@ -8,7 +8,7 @@ import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner, ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs, RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@ -28,73 +28,73 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync
const AGGREGATE_OPERATION_OPTIONS = [ const AGGREGATE_OPERATION_OPTIONS = [
{ {
value: AGGREGATE_OPERATIONS.avg, value: AggregateOperations.AVG,
label: 'Average', label: 'Average',
position: 0, position: 0,
color: 'red', color: 'red',
}, },
{ {
value: AGGREGATE_OPERATIONS.count, value: AggregateOperations.COUNT,
label: 'Count', label: 'Count',
position: 1, position: 1,
color: 'purple', color: 'purple',
}, },
{ {
value: AGGREGATE_OPERATIONS.max, value: AggregateOperations.MAX,
label: 'Maximum', label: 'Maximum',
position: 2, position: 2,
color: 'sky', color: 'sky',
}, },
{ {
value: AGGREGATE_OPERATIONS.min, value: AggregateOperations.MIN,
label: 'Minimum', label: 'Minimum',
position: 3, position: 3,
color: 'turquoise', color: 'turquoise',
}, },
{ {
value: AGGREGATE_OPERATIONS.sum, value: AggregateOperations.SUM,
label: 'Sum', label: 'Sum',
position: 4, position: 4,
color: 'yellow', color: 'yellow',
}, },
{ {
value: AGGREGATE_OPERATIONS.countEmpty, value: AggregateOperations.COUNT_EMPTY,
label: 'Count empty', label: 'Count empty',
position: 5, position: 5,
color: 'red', color: 'red',
}, },
{ {
value: AGGREGATE_OPERATIONS.countNotEmpty, value: AggregateOperations.COUNT_NOT_EMPTY,
label: 'Count not empty', label: 'Count not empty',
position: 6, position: 6,
color: 'purple', color: 'purple',
}, },
{ {
value: AGGREGATE_OPERATIONS.countUniqueValues, value: AggregateOperations.COUNT_UNIQUE_VALUES,
label: 'Count unique values', label: 'Count unique values',
position: 7, position: 7,
color: 'sky', color: 'sky',
}, },
{ {
value: AGGREGATE_OPERATIONS.percentageEmpty, value: AggregateOperations.PERCENTAGE_EMPTY,
label: 'Percent empty', label: 'Percent empty',
position: 8, position: 8,
color: 'turquoise', color: 'turquoise',
}, },
{ {
value: AGGREGATE_OPERATIONS.percentageNotEmpty, value: AggregateOperations.PERCENTAGE_NOT_EMPTY,
label: 'Percent not empty', label: 'Percent not empty',
position: 9, position: 9,
color: 'yellow', color: 'yellow',
}, },
{ {
value: AGGREGATE_OPERATIONS.countTrue, value: AggregateOperations.COUNT_TRUE,
label: 'Count true', label: 'Count true',
position: 10, position: 10,
color: 'red', color: 'red',
}, },
{ {
value: AGGREGATE_OPERATIONS.countFalse, value: AggregateOperations.COUNT_FALSE,
label: 'Count false', label: 'Count false',
position: 11, position: 11,
color: 'purple', color: 'purple',

View File

@ -16,32 +16,27 @@ export const seedFeatureFlags = async (
.orIgnore() .orIgnore()
.values([ .values([
{ {
key: FeatureFlagKey.IsAirtableIntegrationEnabled, key: FeatureFlagKey.IS_AIRTABLE_INTEGRATION_ENABLED,
workspaceId: workspaceId, workspaceId: workspaceId,
value: true, value: true,
}, },
{ {
key: FeatureFlagKey.IsPostgreSQLIntegrationEnabled, key: FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED,
workspaceId: workspaceId, workspaceId: workspaceId,
value: true, value: true,
}, },
{ {
key: FeatureFlagKey.IsStripeIntegrationEnabled, key: FeatureFlagKey.IS_STRIPE_INTEGRATION_ENABLED,
workspaceId: workspaceId, workspaceId: workspaceId,
value: true, value: true,
}, },
{ {
key: FeatureFlagKey.IsWorkflowEnabled, key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
workspaceId: workspaceId, workspaceId: workspaceId,
value: true, value: true,
}, },
{ {
key: FeatureFlagKey.IsCustomDomainEnabled, key: FeatureFlagKey.IS_UNIQUE_INDEXES_ENABLED,
workspaceId: workspaceId,
value: false,
},
{
key: FeatureFlagKey.IsUniqueIndexesEnabled,
workspaceId: workspaceId, workspaceId: workspaceId,
value: false, value: false,
}, },

View File

@ -44,7 +44,7 @@ export class GraphQLConfigService
createGqlOptions(): YogaDriverConfig { createGqlOptions(): YogaDriverConfig {
const isDebugMode = const isDebugMode =
this.twentyConfigService.get('NODE_ENV') === NodeEnvironment.development; this.twentyConfigService.get('NODE_ENV') === NodeEnvironment.DEVELOPMENT;
const plugins = [ const plugins = [
useThrottler({ useThrottler({
ttl: this.twentyConfigService.get('API_RATE_LIMITING_TTL'), ttl: this.twentyConfigService.get('API_RATE_LIMITING_TTL'),

View File

@ -1,14 +1,14 @@
export enum AGGREGATE_OPERATIONS { export enum AggregateOperations {
min = 'MIN', MIN = 'MIN',
max = 'MAX', MAX = 'MAX',
avg = 'AVG', AVG = 'AVG',
sum = 'SUM', SUM = 'SUM',
count = 'COUNT', COUNT = 'COUNT',
countUniqueValues = 'COUNT_UNIQUE_VALUES', COUNT_UNIQUE_VALUES = 'COUNT_UNIQUE_VALUES',
countEmpty = 'COUNT_EMPTY', COUNT_EMPTY = 'COUNT_EMPTY',
countNotEmpty = 'COUNT_NOT_EMPTY', COUNT_NOT_EMPTY = 'COUNT_NOT_EMPTY',
countTrue = 'COUNT_TRUE', COUNT_TRUE = 'COUNT_TRUE',
countFalse = 'COUNT_FALSE', COUNT_FALSE = 'COUNT_FALSE',
percentageEmpty = 'PERCENTAGE_EMPTY', PERCENTAGE_EMPTY = 'PERCENTAGE_EMPTY',
percentageNotEmpty = 'PERCENTAGE_NOT_EMPTY', PERCENTAGE_NOT_EMPTY = 'PERCENTAGE_NOT_EMPTY',
} }

View File

@ -1,7 +1,7 @@
export enum DatabaseEventAction { export enum DatabaseEventAction {
CREATED = 'created', CREATED = 'CREATED',
UPDATED = 'updated', UPDATED = 'UPDATED',
DELETED = 'deleted', DELETED = 'DELETED',
DESTROYED = 'destroyed', DESTROYED = 'DESTROYED',
RESTORED = 'restored', RESTORED = 'RESTORED',
} }

View File

@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SelectQueryBuilder } from 'typeorm';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { SelectQueryBuilder } from 'typeorm';
import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
import { formatColumnNamesFromCompositeFieldAndSubfields } from 'src/engine/twenty-orm/utils/format-column-names-from-composite-field-and-subfield.util'; import { formatColumnNamesFromCompositeFieldAndSubfields } from 'src/engine/twenty-orm/utils/format-column-names-from-composite-field-and-subfield.util';
@ -44,7 +44,7 @@ export class ProcessAggregateHelper {
: columnNames[0]; : columnNames[0];
if ( if (
!Object.values(AGGREGATE_OPERATIONS).includes( !Object.values(AggregateOperations).includes(
aggregatedField.aggregateOperation, aggregatedField.aggregateOperation,
) )
) { ) {
@ -58,44 +58,44 @@ export class ProcessAggregateHelper {
const columnExpression = `NULLIF(CONCAT(${concatenatedColumns}), '')`; const columnExpression = `NULLIF(CONCAT(${concatenatedColumns}), '')`;
switch (aggregatedField.aggregateOperation) { switch (aggregatedField.aggregateOperation) {
case AGGREGATE_OPERATIONS.countEmpty: case AggregateOperations.COUNT_EMPTY:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(*) - COUNT(${columnExpression}) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(*) - COUNT(${columnExpression}) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,
); );
break; break;
case AGGREGATE_OPERATIONS.countNotEmpty: case AggregateOperations.COUNT_NOT_EMPTY:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(${columnExpression}) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(${columnExpression}) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,
); );
break; break;
case AGGREGATE_OPERATIONS.countUniqueValues: case AggregateOperations.COUNT_UNIQUE_VALUES:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(DISTINCT ${columnExpression}) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(DISTINCT ${columnExpression}) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,
); );
break; break;
case AGGREGATE_OPERATIONS.percentageEmpty: case AggregateOperations.PERCENTAGE_EMPTY:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST(((COUNT(*) - COUNT(${columnExpression})::decimal) / COUNT(*)) AS DECIMAL) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST(((COUNT(*) - COUNT(${columnExpression})::decimal) / COUNT(*)) AS DECIMAL) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,
); );
break; break;
case AGGREGATE_OPERATIONS.percentageNotEmpty: case AggregateOperations.PERCENTAGE_NOT_EMPTY:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST((COUNT(${columnExpression})::decimal / COUNT(*)) AS DECIMAL) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST((COUNT(${columnExpression})::decimal / COUNT(*)) AS DECIMAL) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,
); );
break; break;
case AGGREGATE_OPERATIONS.countTrue: case AggregateOperations.COUNT_TRUE:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(CASE WHEN ${columnExpression}::boolean = TRUE THEN 1 ELSE NULL END) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(CASE WHEN ${columnExpression}::boolean = TRUE THEN 1 ELSE NULL END) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,
); );
break; break;
case AGGREGATE_OPERATIONS.countFalse: case AggregateOperations.COUNT_FALSE:
queryBuilder.addSelect( queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(CASE WHEN ${columnExpression}::boolean = FALSE THEN 1 ELSE NULL END) END`, `CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(CASE WHEN ${columnExpression}::boolean = FALSE THEN 1 ELSE NULL END) END`,
`${aggregatedFieldName}`, `${aggregatedFieldName}`,

View File

@ -94,7 +94,7 @@ export abstract class GraphqlQueryBaseResolverService<
const featureFlagsMap = workspaceDataSource.featureFlagMap; const featureFlagsMap = workspaceDataSource.featureFlagMap;
const isPermissionsV2Enabled = const isPermissionsV2Enabled =
featureFlagsMap[FeatureFlagKey.IsPermissionsV2Enabled]; featureFlagsMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
if (objectMetadataItemWithFieldMaps.isSystem === true) { if (objectMetadataItemWithFieldMaps.isSystem === true) {
await this.validateSystemObjectPermissionsOrThrow(options); await this.validateSystemObjectPermissionsOrThrow(options);

View File

@ -49,7 +49,7 @@ export const metadataModuleFactory = async (
}), }),
}; };
if (twentyConfigService.get('NODE_ENV') === NodeEnvironment.development) { if (twentyConfigService.get('NODE_ENV') === NodeEnvironment.DEVELOPMENT) {
config.renderGraphiQL = () => { config.renderGraphiQL = () => {
return renderApolloPlayground({ path: 'metadata' }); return renderApolloPlayground({ path: 'metadata' });
}; };

View File

@ -29,7 +29,7 @@ export function WorkspaceQueryHook(
// Default to PreHook // Default to PreHook
if (!options.type) { if (!options.type) {
options.type = WorkspaceQueryHookType.PreHook; options.type = WorkspaceQueryHookType.PRE_HOOK;
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types

View File

@ -14,8 +14,8 @@ import {
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
export enum WorkspaceQueryHookType { export enum WorkspaceQueryHookType {
PreHook = 'PreHook', PRE_HOOK = 'PRE_HOOK',
PostHook = 'PostHook', POST_HOOK = 'POST_HOOK',
} }
export type WorkspacePreQueryHookPayload<T> = T extends 'createMany' export type WorkspacePreQueryHookPayload<T> = T extends 'createMany'

View File

@ -201,7 +201,7 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
isRequestScoped: boolean, isRequestScoped: boolean,
) { ) {
switch (type) { switch (type) {
case WorkspaceQueryHookType.PreHook: case WorkspaceQueryHookType.PRE_HOOK:
this.workspaceQueryHookStorage.registerWorkspaceQueryPreHookInstance( this.workspaceQueryHookStorage.registerWorkspaceQueryPreHookInstance(
key, key,
{ {
@ -211,7 +211,7 @@ export class WorkspaceQueryHookExplorer implements OnModuleInit {
}, },
); );
break; break;
case WorkspaceQueryHookType.PostHook: case WorkspaceQueryHookType.POST_HOOK:
this.workspaceQueryHookStorage.registerWorkspacePostQueryHookInstance( this.workspaceQueryHookStorage.registerWorkspacePostQueryHookInstance(
key, key,
{ {

View File

@ -1,13 +1,13 @@
import { GraphQLISODateTime } from '@nestjs/graphql'; import { GraphQLISODateTime } from '@nestjs/graphql';
import { GraphQLFloat, GraphQLInt, GraphQLScalarType } from 'graphql'; import { GraphQLFloat, GraphQLInt, GraphQLScalarType } from 'graphql';
import { capitalize, isFieldMetadataDateKind } from 'twenty-shared/utils';
import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants'; import { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from 'twenty-shared/constants';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { capitalize, isFieldMetadataDateKind } from 'twenty-shared/utils';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant'; import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { getSubfieldsForAggregateOperation } from 'src/engine/twenty-orm/utils/get-subfields-for-aggregate-operation.util'; import { getSubfieldsForAggregateOperation } from 'src/engine/twenty-orm/utils/get-subfields-for-aggregate-operation.util';
export type AggregationField = { export type AggregationField = {
@ -17,7 +17,7 @@ export type AggregationField = {
fromFieldType: FieldMetadataType; fromFieldType: FieldMetadataType;
fromSubFields?: string[]; fromSubFields?: string[];
subFieldForNumericOperation?: string; subFieldForNumericOperation?: string;
aggregateOperation: AGGREGATE_OPERATIONS; aggregateOperation: AggregateOperations;
}; };
export const getAvailableAggregationsFromObjectFields = ( export const getAvailableAggregationsFromObjectFields = (
@ -37,7 +37,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
fromSubFields, fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countUniqueValues, aggregateOperation: AggregateOperations.COUNT_UNIQUE_VALUES,
}; };
acc[`countEmpty${capitalize(field.name)}`] = { acc[`countEmpty${capitalize(field.name)}`] = {
@ -46,7 +46,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
fromSubFields, fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countEmpty, aggregateOperation: AggregateOperations.COUNT_EMPTY,
}; };
acc[`countNotEmpty${capitalize(field.name)}`] = { acc[`countNotEmpty${capitalize(field.name)}`] = {
@ -55,7 +55,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
fromSubFields, fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countNotEmpty, aggregateOperation: AggregateOperations.COUNT_NOT_EMPTY,
}; };
acc[`percentageEmpty${capitalize(field.name)}`] = { acc[`percentageEmpty${capitalize(field.name)}`] = {
@ -64,7 +64,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
fromSubFields, fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.percentageEmpty, aggregateOperation: AggregateOperations.PERCENTAGE_EMPTY,
}; };
acc[`percentageNotEmpty${capitalize(field.name)}`] = { acc[`percentageNotEmpty${capitalize(field.name)}`] = {
@ -73,7 +73,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
fromSubFields, fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.percentageNotEmpty, aggregateOperation: AggregateOperations.PERCENTAGE_NOT_EMPTY,
}; };
if (isFieldMetadataDateKind(field.type)) { if (isFieldMetadataDateKind(field.type)) {
@ -82,7 +82,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Earliest date contained in the field ${field.name}`, description: `Earliest date contained in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min, aggregateOperation: AggregateOperations.MIN,
}; };
acc[`max${capitalize(field.name)}`] = { acc[`max${capitalize(field.name)}`] = {
@ -90,7 +90,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Latest date contained in the field ${field.name}`, description: `Latest date contained in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.max, aggregateOperation: AggregateOperations.MAX,
}; };
} }
@ -101,7 +101,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Count of true values in the field ${field.name}`, description: `Count of true values in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.countTrue, aggregateOperation: AggregateOperations.COUNT_TRUE,
}; };
acc[`countFalse${capitalize(field.name)}`] = { acc[`countFalse${capitalize(field.name)}`] = {
@ -109,7 +109,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Count of false values in the field ${field.name}`, description: `Count of false values in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.countFalse, aggregateOperation: AggregateOperations.COUNT_FALSE,
}; };
break; break;
@ -119,7 +119,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Minimum amount contained in the field ${field.name}`, description: `Minimum amount contained in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min, aggregateOperation: AggregateOperations.MIN,
}; };
acc[`max${capitalize(field.name)}`] = { acc[`max${capitalize(field.name)}`] = {
@ -127,7 +127,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Maximum amount contained in the field ${field.name}`, description: `Maximum amount contained in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.max, aggregateOperation: AggregateOperations.MAX,
}; };
acc[`avg${capitalize(field.name)}`] = { acc[`avg${capitalize(field.name)}`] = {
@ -135,7 +135,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Average amount contained in the field ${field.name}`, description: `Average amount contained in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.avg, aggregateOperation: AggregateOperations.AVG,
}; };
acc[`sum${capitalize(field.name)}`] = { acc[`sum${capitalize(field.name)}`] = {
@ -143,7 +143,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Sum of amounts contained in the field ${field.name}`, description: `Sum of amounts contained in the field ${field.name}`,
fromField: field.name, fromField: field.name,
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.sum, aggregateOperation: AggregateOperations.SUM,
}; };
break; break;
case FieldMetadataType.CURRENCY: case FieldMetadataType.CURRENCY:
@ -154,7 +154,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromSubFields: getSubfieldsForAggregateOperation(field.type), fromSubFields: getSubfieldsForAggregateOperation(field.type),
subFieldForNumericOperation: 'amountMicros', subFieldForNumericOperation: 'amountMicros',
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min, aggregateOperation: AggregateOperations.MIN,
}; };
acc[`max${capitalize(field.name)}AmountMicros`] = { acc[`max${capitalize(field.name)}AmountMicros`] = {
@ -163,7 +163,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromSubFields: getSubfieldsForAggregateOperation(field.type), fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.max, aggregateOperation: AggregateOperations.MAX,
}; };
acc[`sum${capitalize(field.name)}AmountMicros`] = { acc[`sum${capitalize(field.name)}AmountMicros`] = {
@ -172,7 +172,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromSubFields: getSubfieldsForAggregateOperation(field.type), fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.sum, aggregateOperation: AggregateOperations.SUM,
}; };
acc[`avg${capitalize(field.name)}AmountMicros`] = { acc[`avg${capitalize(field.name)}AmountMicros`] = {
@ -181,7 +181,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name, fromField: field.name,
fromSubFields: getSubfieldsForAggregateOperation(field.type), fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type, fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.avg, aggregateOperation: AggregateOperations.AVG,
}; };
break; break;
} }
@ -194,7 +194,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Total number of records in the connection`, description: `Total number of records in the connection`,
fromField: FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION, fromField: FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION,
fromFieldType: FieldMetadataType.UUID, fromFieldType: FieldMetadataType.UUID,
aggregateOperation: AGGREGATE_OPERATIONS.count, aggregateOperation: AggregateOperations.COUNT,
}, },
}, },
); );

View File

@ -324,7 +324,7 @@ export class AuthService {
name: 'Chrome Extension', name: 'Chrome Extension',
redirectUrl: redirectUrl:
this.twentyConfigService.get('NODE_ENV') === this.twentyConfigService.get('NODE_ENV') ===
NodeEnvironment.development NodeEnvironment.DEVELOPMENT
? authorizeAppInput.redirectUrl ? authorizeAppInput.redirectUrl
: `https://${this.twentyConfigService.get( : `https://${this.twentyConfigService.get(
'CHROME_EXTENSION_ID', 'CHROME_EXTENSION_ID',

View File

@ -1,7 +1,7 @@
import { DynamicModule, Global } from '@nestjs/common'; import { DynamicModule, Global } from '@nestjs/common';
import { CAPTCHA_DRIVER } from 'src/engine/core-modules/captcha/constants/captcha-driver.constants';
import { CaptchaService } from 'src/engine/core-modules/captcha/captcha.service'; import { CaptchaService } from 'src/engine/core-modules/captcha/captcha.service';
import { CAPTCHA_DRIVER } from 'src/engine/core-modules/captcha/constants/captcha-driver.constants';
import { GoogleRecaptchaDriver } from 'src/engine/core-modules/captcha/drivers/google-recaptcha.driver'; import { GoogleRecaptchaDriver } from 'src/engine/core-modules/captcha/drivers/google-recaptcha.driver';
import { TurnstileDriver } from 'src/engine/core-modules/captcha/drivers/turnstile.driver'; import { TurnstileDriver } from 'src/engine/core-modules/captcha/drivers/turnstile.driver';
import { import {
@ -23,9 +23,9 @@ export class CaptchaModule {
} }
switch (config.type) { switch (config.type) {
case CaptchaDriverType.GoogleRecaptcha: case CaptchaDriverType.GOOGLE_RECAPTCHA:
return new GoogleRecaptchaDriver(config.options); return new GoogleRecaptchaDriver(config.options);
case CaptchaDriverType.Turnstile: case CaptchaDriverType.TURNSTILE:
return new TurnstileDriver(config.options); return new TurnstileDriver(config.options);
default: default:
return; return;

View File

@ -2,8 +2,8 @@ import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
import { registerEnumType } from '@nestjs/graphql'; import { registerEnumType } from '@nestjs/graphql';
export enum CaptchaDriverType { export enum CaptchaDriverType {
GoogleRecaptcha = 'google-recaptcha', GOOGLE_RECAPTCHA = 'GOOGLE_RECAPTCHA',
Turnstile = 'turnstile', TURNSTILE = 'TURNSTILE',
} }
registerEnumType(CaptchaDriverType, { registerEnumType(CaptchaDriverType, {
@ -16,12 +16,12 @@ export type CaptchaDriverOptions = {
}; };
export interface GoogleRecaptchaDriverFactoryOptions { export interface GoogleRecaptchaDriverFactoryOptions {
type: CaptchaDriverType.GoogleRecaptcha; type: CaptchaDriverType.GOOGLE_RECAPTCHA;
options: CaptchaDriverOptions; options: CaptchaDriverOptions;
} }
export interface TurnstileDriverFactoryOptions { export interface TurnstileDriverFactoryOptions {
type: CaptchaDriverType.Turnstile; type: CaptchaDriverType.TURNSTILE;
options: CaptchaDriverOptions; options: CaptchaDriverOptions;
} }

View File

@ -61,13 +61,13 @@ describe('ClientConfigService', () => {
IS_MULTIWORKSPACE_ENABLED: true, IS_MULTIWORKSPACE_ENABLED: true,
IS_EMAIL_VERIFICATION_REQUIRED: true, IS_EMAIL_VERIFICATION_REQUIRED: true,
DEFAULT_SUBDOMAIN: 'app', DEFAULT_SUBDOMAIN: 'app',
NODE_ENV: NodeEnvironment.development, NODE_ENV: NodeEnvironment.DEVELOPMENT,
SUPPORT_DRIVER: SupportDriver.Front, SUPPORT_DRIVER: SupportDriver.FRONT,
SUPPORT_FRONT_CHAT_ID: 'chat-123', SUPPORT_FRONT_CHAT_ID: 'chat-123',
SENTRY_ENVIRONMENT: 'development', SENTRY_ENVIRONMENT: 'development',
APP_VERSION: '1.0.0', APP_VERSION: '1.0.0',
SENTRY_FRONT_DSN: 'https://sentry.example.com', SENTRY_FRONT_DSN: 'https://sentry.example.com',
CAPTCHA_DRIVER: CaptchaDriverType.GoogleRecaptcha, CAPTCHA_DRIVER: CaptchaDriverType.GOOGLE_RECAPTCHA,
CAPTCHA_SITE_KEY: 'site-key-123', CAPTCHA_SITE_KEY: 'site-key-123',
CHROME_EXTENSION_ID: 'extension-123', CHROME_EXTENSION_ID: 'extension-123',
MUTATION_MAXIMUM_AFFECTED_RECORDS: 1000, MUTATION_MAXIMUM_AFFECTED_RECORDS: 1000,
@ -120,7 +120,7 @@ describe('ClientConfigService', () => {
frontDomain: 'app.twenty.com', frontDomain: 'app.twenty.com',
debugMode: true, debugMode: true,
support: { support: {
supportDriver: 'Front', supportDriver: 'FRONT',
supportFrontChatId: 'chat-123', supportFrontChatId: 'chat-123',
}, },
sentry: { sentry: {
@ -129,7 +129,7 @@ describe('ClientConfigService', () => {
dsn: 'https://sentry.example.com', dsn: 'https://sentry.example.com',
}, },
captcha: { captcha: {
provider: 'GoogleRecaptcha', provider: 'GOOGLE_RECAPTCHA',
siteKey: 'site-key-123', siteKey: 'site-key-123',
}, },
chromeExtensionId: 'extension-123', chromeExtensionId: 'extension-123',
@ -152,7 +152,7 @@ describe('ClientConfigService', () => {
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => { .mockImplementation((key: string) => {
if (key === 'NODE_ENV') return NodeEnvironment.production; if (key === 'NODE_ENV') return NodeEnvironment.PRODUCTION;
if (key === 'IS_BILLING_ENABLED') return false; if (key === 'IS_BILLING_ENABLED') return false;
return undefined; return undefined;
@ -191,14 +191,14 @@ describe('ClientConfigService', () => {
const result = await service.getClientConfig(); const result = await service.getClientConfig();
expect(result.support.supportDriver).toBe(SupportDriver.None); expect(result.support.supportDriver).toBe(SupportDriver.NONE);
}); });
it('should handle billing enabled with feature flags', async () => { it('should handle billing enabled with feature flags', async () => {
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => { .mockImplementation((key: string) => {
if (key === 'NODE_ENV') return NodeEnvironment.production; if (key === 'NODE_ENV') return NodeEnvironment.PRODUCTION;
if (key === 'IS_BILLING_ENABLED') return true; if (key === 'IS_BILLING_ENABLED') return true;
return undefined; return undefined;
@ -209,44 +209,4 @@ describe('ClientConfigService', () => {
expect(result.canManageFeatureFlags).toBe(true); expect(result.canManageFeatureFlags).toBe(true);
}); });
}); });
describe('transformEnum', () => {
it('should transform enum by direct key match', () => {
const result = (service as any).transformEnum(
'GoogleRecaptcha',
CaptchaDriverType,
);
expect(result).toBe(CaptchaDriverType.GoogleRecaptcha);
});
it('should transform enum by value match', () => {
const result = (service as any).transformEnum(
'google-recaptcha',
CaptchaDriverType,
);
expect(result).toBe('GoogleRecaptcha');
});
it('should transform SupportDriver enum correctly', () => {
const result = (service as any).transformEnum('front', SupportDriver);
expect(result).toBe('Front');
});
it('should throw error for unknown enum value', () => {
expect(() => {
(service as any).transformEnum('unknown-value', CaptchaDriverType);
}).toThrow(
'Unknown enum value: unknown-value. Available keys: GoogleRecaptcha, Turnstile. Available values: google-recaptcha, turnstile',
);
});
it('should handle direct key match for SupportDriver', () => {
const result = (service as any).transformEnum('Front', SupportDriver);
expect(result).toBe(SupportDriver.Front);
});
});
}); });

View File

@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface'; import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/support.interface'; import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/support.interface';
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
import { ClientConfig } from 'src/engine/core-modules/client-config/client-config.entity'; import { ClientConfig } from 'src/engine/core-modules/client-config/client-config.entity';
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const'; import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const';
@ -57,11 +56,9 @@ export class ClientConfigService {
frontDomain: this.domainManagerService.getFrontUrl().hostname, frontDomain: this.domainManagerService.getFrontUrl().hostname,
debugMode: debugMode:
this.twentyConfigService.get('NODE_ENV') === this.twentyConfigService.get('NODE_ENV') ===
NodeEnvironment.development, NodeEnvironment.DEVELOPMENT,
support: { support: {
supportDriver: supportDriver supportDriver: supportDriver ? supportDriver : SupportDriver.NONE,
? this.transformEnum(supportDriver, SupportDriver)
: SupportDriver.None,
supportFrontChatId: this.twentyConfigService.get( supportFrontChatId: this.twentyConfigService.get(
'SUPPORT_FRONT_CHAT_ID', 'SUPPORT_FRONT_CHAT_ID',
), ),
@ -72,9 +69,7 @@ export class ClientConfigService {
dsn: this.twentyConfigService.get('SENTRY_FRONT_DSN'), dsn: this.twentyConfigService.get('SENTRY_FRONT_DSN'),
}, },
captcha: { captcha: {
provider: captchaProvider provider: captchaProvider ? captchaProvider : undefined,
? this.transformEnum(captchaProvider, CaptchaDriverType)
: undefined,
siteKey: this.twentyConfigService.get('CAPTCHA_SITE_KEY'), siteKey: this.twentyConfigService.get('CAPTCHA_SITE_KEY'),
}, },
chromeExtensionId: this.twentyConfigService.get('CHROME_EXTENSION_ID'), chromeExtensionId: this.twentyConfigService.get('CHROME_EXTENSION_ID'),
@ -89,7 +84,7 @@ export class ClientConfigService {
analyticsEnabled: this.twentyConfigService.get('ANALYTICS_ENABLED'), analyticsEnabled: this.twentyConfigService.get('ANALYTICS_ENABLED'),
canManageFeatureFlags: canManageFeatureFlags:
this.twentyConfigService.get('NODE_ENV') === this.twentyConfigService.get('NODE_ENV') ===
NodeEnvironment.development || NodeEnvironment.DEVELOPMENT ||
this.twentyConfigService.get('IS_BILLING_ENABLED'), this.twentyConfigService.get('IS_BILLING_ENABLED'),
publicFeatureFlags: PUBLIC_FEATURE_FLAGS, publicFeatureFlags: PUBLIC_FEATURE_FLAGS,
isMicrosoftMessagingEnabled: this.twentyConfigService.get( isMicrosoftMessagingEnabled: this.twentyConfigService.get(
@ -111,34 +106,4 @@ export class ClientConfigService {
return clientConfig; return clientConfig;
} }
// GraphQL enum values are in PascalCase, but the config values are in kebab-case
// This function transforms the config values, the same way GraphQL does
private transformEnum<T extends Record<string, string>>(
value: string,
enumObject: T,
): T[keyof T] {
const directMatch = Object.keys(enumObject).find(
(key) => key === value,
) as keyof T;
if (directMatch) {
return enumObject[directMatch];
}
const valueMatch = Object.entries(enumObject).find(
([, enumValue]) => enumValue === value,
);
if (valueMatch) {
return valueMatch[0] as T[keyof T];
}
const availableKeys = Object.keys(enumObject);
const availableValues = Object.values(enumObject);
throw new Error(
`Unknown enum value: ${value}. Available keys: ${availableKeys.join(', ')}. Available values: ${availableValues.join(', ')}`,
);
}
} }

View File

@ -33,7 +33,7 @@ describe('EmailDriverFactory', () => {
it('should return "logger" for logger driver', () => { it('should return "logger" for logger driver', () => {
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger); .mockReturnValue(EmailDriver.LOGGER);
const result = factory['buildConfigKey'](); const result = factory['buildConfigKey']();
@ -42,7 +42,7 @@ describe('EmailDriverFactory', () => {
}); });
it('should return smtp config key for smtp driver', () => { it('should return smtp config key for smtp driver', () => {
jest.spyOn(twentyConfigService, 'get').mockReturnValue(EmailDriver.Smtp); jest.spyOn(twentyConfigService, 'get').mockReturnValue(EmailDriver.SMTP);
jest jest
.spyOn(factory as any, 'getConfigGroupHash') .spyOn(factory as any, 'getConfigGroupHash')
.mockReturnValue('smtp-hash-123'); .mockReturnValue('smtp-hash-123');
@ -66,7 +66,7 @@ describe('EmailDriverFactory', () => {
it('should create logger driver', () => { it('should create logger driver', () => {
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger); .mockReturnValue(EmailDriver.LOGGER);
const driver = factory['createDriver'](); const driver = factory['createDriver']();
@ -80,7 +80,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => { .mockImplementation((key: string) => {
switch (key) { switch (key) {
case 'EMAIL_DRIVER': case 'EMAIL_DRIVER':
return EmailDriver.Smtp; return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST': case 'EMAIL_SMTP_HOST':
return 'smtp.example.com'; return 'smtp.example.com';
case 'EMAIL_SMTP_PORT': case 'EMAIL_SMTP_PORT':
@ -108,7 +108,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => { .mockImplementation((key: string) => {
switch (key) { switch (key) {
case 'EMAIL_DRIVER': case 'EMAIL_DRIVER':
return EmailDriver.Smtp; return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST': case 'EMAIL_SMTP_HOST':
return undefined; return undefined;
case 'EMAIL_SMTP_PORT': case 'EMAIL_SMTP_PORT':
@ -136,7 +136,7 @@ describe('EmailDriverFactory', () => {
it('should return current driver for logger', () => { it('should return current driver for logger', () => {
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger); .mockReturnValue(EmailDriver.LOGGER);
const driver = factory.getCurrentDriver(); const driver = factory.getCurrentDriver();
@ -147,7 +147,7 @@ describe('EmailDriverFactory', () => {
it('should reuse driver when config key unchanged', () => { it('should reuse driver when config key unchanged', () => {
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger); .mockReturnValue(EmailDriver.LOGGER);
const driver1 = factory.getCurrentDriver(); const driver1 = factory.getCurrentDriver();
const driver2 = factory.getCurrentDriver(); const driver2 = factory.getCurrentDriver();
@ -159,7 +159,7 @@ describe('EmailDriverFactory', () => {
// First call with logger // First call with logger
jest jest
.spyOn(twentyConfigService, 'get') .spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger); .mockReturnValue(EmailDriver.LOGGER);
const driver1 = factory.getCurrentDriver(); const driver1 = factory.getCurrentDriver();
@ -169,7 +169,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => { .mockImplementation((key: string) => {
switch (key) { switch (key) {
case 'EMAIL_DRIVER': case 'EMAIL_DRIVER':
return EmailDriver.Smtp; return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST': case 'EMAIL_SMTP_HOST':
return 'smtp.example.com'; return 'smtp.example.com';
case 'EMAIL_SMTP_PORT': case 'EMAIL_SMTP_PORT':
@ -203,7 +203,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => { .mockImplementation((key: string) => {
switch (key) { switch (key) {
case 'EMAIL_DRIVER': case 'EMAIL_DRIVER':
return EmailDriver.Smtp; return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST': case 'EMAIL_SMTP_HOST':
return 'smtp.example.com'; return 'smtp.example.com';
case 'EMAIL_SMTP_PORT': case 'EMAIL_SMTP_PORT':

View File

@ -18,11 +18,11 @@ export class EmailDriverFactory extends DriverFactoryBase<EmailDriverInterface>
protected buildConfigKey(): string { protected buildConfigKey(): string {
const driver = this.twentyConfigService.get('EMAIL_DRIVER'); const driver = this.twentyConfigService.get('EMAIL_DRIVER');
if (driver === EmailDriver.Logger) { if (driver === EmailDriver.LOGGER) {
return 'logger'; return 'logger';
} }
if (driver === EmailDriver.Smtp) { if (driver === EmailDriver.SMTP) {
const emailConfigHash = this.getConfigGroupHash( const emailConfigHash = this.getConfigGroupHash(
ConfigVariablesGroup.EmailSettings, ConfigVariablesGroup.EmailSettings,
); );
@ -37,10 +37,10 @@ export class EmailDriverFactory extends DriverFactoryBase<EmailDriverInterface>
const driver = this.twentyConfigService.get('EMAIL_DRIVER'); const driver = this.twentyConfigService.get('EMAIL_DRIVER');
switch (driver) { switch (driver) {
case EmailDriver.Logger: case EmailDriver.LOGGER:
return new LoggerDriver(); return new LoggerDriver();
case EmailDriver.Smtp: { case EmailDriver.SMTP: {
const host = this.twentyConfigService.get('EMAIL_SMTP_HOST'); const host = this.twentyConfigService.get('EMAIL_SMTP_HOST');
const port = this.twentyConfigService.get('EMAIL_SMTP_PORT'); const port = this.twentyConfigService.get('EMAIL_SMTP_PORT');
const user = this.twentyConfigService.get('EMAIL_SMTP_USER'); const user = this.twentyConfigService.get('EMAIL_SMTP_USER');

View File

@ -1,4 +1,4 @@
export enum EmailDriver { export enum EmailDriver {
Logger = 'logger', LOGGER = 'LOGGER',
Smtp = 'smtp', SMTP = 'SMTP',
} }

View File

@ -19,21 +19,21 @@ export const exceptionHandlerModuleFactory = async (
const driverType = twentyConfigService.get('EXCEPTION_HANDLER_DRIVER'); const driverType = twentyConfigService.get('EXCEPTION_HANDLER_DRIVER');
switch (driverType) { switch (driverType) {
case ExceptionHandlerDriver.Console: { case ExceptionHandlerDriver.CONSOLE: {
return { return {
type: ExceptionHandlerDriver.Console, type: ExceptionHandlerDriver.CONSOLE,
}; };
} }
case ExceptionHandlerDriver.Sentry: { case ExceptionHandlerDriver.SENTRY: {
return { return {
type: ExceptionHandlerDriver.Sentry, type: ExceptionHandlerDriver.SENTRY,
options: { options: {
environment: twentyConfigService.get('SENTRY_ENVIRONMENT'), environment: twentyConfigService.get('SENTRY_ENVIRONMENT'),
release: twentyConfigService.get('APP_VERSION'), release: twentyConfigService.get('APP_VERSION'),
dsn: twentyConfigService.get('SENTRY_DSN') ?? '', dsn: twentyConfigService.get('SENTRY_DSN') ?? '',
serverInstance: adapterHost.httpAdapter?.getInstance(), serverInstance: adapterHost.httpAdapter?.getInstance(),
debug: debug:
twentyConfigService.get('NODE_ENV') === NodeEnvironment.development, twentyConfigService.get('NODE_ENV') === NodeEnvironment.DEVELOPMENT,
}, },
}; };
} }

View File

@ -22,7 +22,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass {
const provider = { const provider = {
provide: EXCEPTION_HANDLER_DRIVER, provide: EXCEPTION_HANDLER_DRIVER,
useValue: useValue:
options.type === ExceptionHandlerDriver.Console options.type === ExceptionHandlerDriver.CONSOLE
? new ExceptionHandlerConsoleDriver() ? new ExceptionHandlerConsoleDriver()
: new ExceptionHandlerSentryDriver(), : new ExceptionHandlerSentryDriver(),
}; };
@ -45,7 +45,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass {
return null; return null;
} }
return config.type === ExceptionHandlerDriver.Console return config.type === ExceptionHandlerDriver.CONSOLE
? new ExceptionHandlerConsoleDriver() ? new ExceptionHandlerConsoleDriver()
: new ExceptionHandlerSentryDriver(); : new ExceptionHandlerSentryDriver();
}, },

View File

@ -1,12 +1,12 @@
import { Router } from 'express'; import { Router } from 'express';
export enum ExceptionHandlerDriver { export enum ExceptionHandlerDriver {
Sentry = 'sentry', SENTRY = 'SENTRY',
Console = 'console', CONSOLE = 'CONSOLE',
} }
export interface ExceptionHandlerSentryDriverFactoryOptions { export interface ExceptionHandlerSentryDriverFactoryOptions {
type: ExceptionHandlerDriver.Sentry; type: ExceptionHandlerDriver.SENTRY;
options: { options: {
environment?: string; environment?: string;
release?: string; release?: string;
@ -17,7 +17,7 @@ export interface ExceptionHandlerSentryDriverFactoryOptions {
} }
export interface ExceptionHandlerDriverFactoryOptions { export interface ExceptionHandlerDriverFactoryOptions {
type: ExceptionHandlerDriver.Console; type: ExceptionHandlerDriver.CONSOLE;
} }
export type ExceptionHandlerModuleOptions = export type ExceptionHandlerModuleOptions =

View File

@ -7,16 +7,13 @@ type FeatureFlagMetadata = {
}; };
export type PublicFeatureFlag = { export type PublicFeatureFlag = {
key: Extract< key: Extract<FeatureFlagKey, FeatureFlagKey.IS_WORKFLOW_ENABLED>;
FeatureFlagKey,
FeatureFlagKey.IsWorkflowEnabled | FeatureFlagKey.IsCustomDomainEnabled
>;
metadata: FeatureFlagMetadata; metadata: FeatureFlagMetadata;
}; };
export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
{ {
key: FeatureFlagKey.IsWorkflowEnabled, key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
metadata: { metadata: {
label: 'Workflows', label: 'Workflows',
description: 'Create custom workflows to automate your work.', description: 'Create custom workflows to automate your work.',
@ -25,15 +22,9 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
}, },
...(process.env.CLOUDFLARE_API_KEY ...(process.env.CLOUDFLARE_API_KEY
? [ ? [
{ // {
key: FeatureFlagKey.IsCustomDomainEnabled as PublicFeatureFlag['key'], // Here you can add cloud only feature flags
metadata: { // },
label: 'Custom Domain',
description: 'Customize your workspace URL with your own domain.',
imagePath:
'https://twenty.com/images/lab/is-custom-domain-enabled.png',
},
},
] ]
: []), : []),
]; ];

View File

@ -1,11 +1,10 @@
export enum FeatureFlagKey { export enum FeatureFlagKey {
IsAirtableIntegrationEnabled = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED',
IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED',
IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED',
IsCopilotEnabled = 'IS_COPILOT_ENABLED', IS_COPILOT_ENABLED = 'IS_COPILOT_ENABLED',
IsWorkflowEnabled = 'IS_WORKFLOW_ENABLED', IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED',
IsUniqueIndexesEnabled = 'IS_UNIQUE_INDEXES_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED',
IsCustomDomainEnabled = 'IS_CUSTOM_DOMAIN_ENABLED', IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED',
IsPermissionsV2Enabled = 'IS_PERMISSIONS_V2_ENABLED',
} }

View File

@ -36,7 +36,7 @@ describe('FeatureFlagService', () => {
}; };
const workspaceId = 'workspace-id'; const workspaceId = 'workspace-id';
const featureFlag = FeatureFlagKey.IsWorkflowEnabled; const featureFlag = FeatureFlagKey.IS_WORKFLOW_ENABLED;
beforeEach(async () => { beforeEach(async () => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -121,13 +121,13 @@ describe('FeatureFlagService', () => {
// Prepare // Prepare
mockWorkspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMap.mockResolvedValue( mockWorkspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMap.mockResolvedValue(
{ {
[FeatureFlagKey.IsWorkflowEnabled]: true, [FeatureFlagKey.IS_WORKFLOW_ENABLED]: true,
[FeatureFlagKey.IsCopilotEnabled]: false, [FeatureFlagKey.IS_COPILOT_ENABLED]: false,
}, },
); );
const mockFeatureFlags = [ const mockFeatureFlags = [
{ key: FeatureFlagKey.IsWorkflowEnabled, value: true }, { key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true },
{ key: FeatureFlagKey.IsCopilotEnabled, value: false }, { key: FeatureFlagKey.IS_COPILOT_ENABLED, value: false },
]; ];
// Act // Act
@ -145,8 +145,8 @@ describe('FeatureFlagService', () => {
it('should return a map of feature flags for a workspace', async () => { it('should return a map of feature flags for a workspace', async () => {
// Prepare // Prepare
const mockFeatureFlags = [ const mockFeatureFlags = [
{ key: FeatureFlagKey.IsWorkflowEnabled, value: true, workspaceId }, { key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true, workspaceId },
{ key: FeatureFlagKey.IsCopilotEnabled, value: false, workspaceId }, { key: FeatureFlagKey.IS_COPILOT_ENABLED, value: false, workspaceId },
]; ];
mockFeatureFlagRepository.find.mockResolvedValue(mockFeatureFlags); mockFeatureFlagRepository.find.mockResolvedValue(mockFeatureFlags);
@ -156,8 +156,8 @@ describe('FeatureFlagService', () => {
// Assert // Assert
expect(result).toEqual({ expect(result).toEqual({
[FeatureFlagKey.IsWorkflowEnabled]: true, [FeatureFlagKey.IS_WORKFLOW_ENABLED]: true,
[FeatureFlagKey.IsCopilotEnabled]: false, [FeatureFlagKey.IS_COPILOT_ENABLED]: false,
}); });
}); });
}); });
@ -166,8 +166,8 @@ describe('FeatureFlagService', () => {
it('should enable multiple feature flags for a workspace', async () => { it('should enable multiple feature flags for a workspace', async () => {
// Prepare // Prepare
const keys = [ const keys = [
FeatureFlagKey.IsWorkflowEnabled, FeatureFlagKey.IS_WORKFLOW_ENABLED,
FeatureFlagKey.IsCopilotEnabled, FeatureFlagKey.IS_COPILOT_ENABLED,
]; ];
mockFeatureFlagRepository.upsert.mockResolvedValue({}); mockFeatureFlagRepository.upsert.mockResolvedValue({});
@ -212,7 +212,6 @@ describe('FeatureFlagService', () => {
// Assert // Assert
expect(result).toEqual(mockFeatureFlag); expect(result).toEqual(mockFeatureFlag);
expect(mockFeatureFlagRepository.save).toHaveBeenCalledWith({ expect(mockFeatureFlagRepository.save).toHaveBeenCalledWith({
// @ts-expect-error legacy noImplicitAny
key: FeatureFlagKey[featureFlag], key: FeatureFlagKey[featureFlag],
value, value,
workspaceId, workspaceId,

Some files were not shown because too many files have changed in this diff Show More