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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,14 +7,14 @@ export const getCaptchaUrlByProvider = (
siteKey: string,
) => {
switch (name) {
case CaptchaDriverType.GoogleRecaptcha:
case CaptchaDriverType.GOOGLE_RECAPTCHA:
if (!isNonEmptyString(siteKey)) {
throw new Error(
'SiteKey must be provided while generating url for GoogleRecaptcha provider',
);
}
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';
default:
throw new Error('Unknown captcha provider');

View File

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

View File

@ -4,7 +4,7 @@ import { RecoilRoot, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
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 { AppPath } from '@/types/AppPath';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@ -44,7 +44,7 @@ const renderHooks = ({
viewGroups: [],
viewSorts: [],
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '',
kanbanAggregateOperationFieldMetadataId: '',
position: 0,

View File

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

View File

@ -7,7 +7,7 @@ import {
import { useRefreshObjectMetadataItems } from '@/object-metadata/hooks/useRefreshObjectMetadataItem';
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 { DELETE_ONE_FIELD_METADATA_ITEM } from '../graphql/mutations';
import { useApolloMetadataClient } from './useApolloMetadataClient';
@ -37,7 +37,7 @@ export const useDeleteOneFieldMetadataItem = () => {
) => {
if (recordIndexKanbanAggregateOperation?.fieldMetadataId === idToDelete) {
setRecordIndexKanbanAggregateOperation({
operation: AGGREGATE_OPERATIONS.count,
operation: AggregateOperations.COUNT,
fieldMetadataId: null,
});
}

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
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 { renderHook } from '@testing-library/react';
import { FieldMetadataType } from '~/generated/graphql';
@ -71,7 +71,7 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({
objectNameSingular: 'company',
recordGqlFieldsAggregate: {
name: [AGGREGATE_OPERATIONS.count],
name: [AggregateOperations.COUNT],
},
}),
);
@ -92,7 +92,7 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({
objectNameSingular: 'company',
recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum],
amount: [AggregateOperations.SUM],
},
}),
);
@ -115,7 +115,7 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({
objectNameSingular: 'company',
recordGqlFieldsAggregate: {
name: [AGGREGATE_OPERATIONS.sum],
name: [AggregateOperations.SUM],
},
}),
),
@ -127,8 +127,8 @@ describe('useAggregateRecordsQuery', () => {
useAggregateRecordsQuery({
objectNameSingular: 'company',
recordGqlFieldsAggregate: {
amount: [AGGREGATE_OPERATIONS.sum],
name: [AGGREGATE_OPERATIONS.count],
amount: [AggregateOperations.SUM],
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 { RecordBoardColumnHeaderAggregateDropdownMenuContent } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderAggregateDropdownMenuContent';
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 { 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';
@ -47,10 +47,7 @@ export const AggregateDropdownContent = () => {
const datesAvailableAggregations: AvailableFieldsForAggregateOperation =
getAvailableFieldsIdsForAggregationFromObjectFields(
objectMetadataItem.fields,
[
DATE_AGGREGATE_OPERATIONS.earliest,
DATE_AGGREGATE_OPERATIONS.latest,
],
[DateAggregateOperations.EARLIEST, DateAggregateOperations.LATEST],
);
return (
<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 { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
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 { TableOptionsHotkeyScope } from '@/object-record/record-table/types/TableOptionsHotkeyScope';
import { AvailableFieldsForAggregateOperation } from '@/object-record/types/AvailableFieldsForAggregateOperation';
@ -82,7 +82,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
key={`aggregate-dropdown-menu-content-${availableAggregationOperation}`}
onContentChange={() => {
if (
availableAggregationOperation !== AGGREGATE_OPERATIONS.count
availableAggregationOperation !== AggregateOperations.COUNT
) {
setAggregateOperation(
availableAggregationOperation as ExtendedAggregateOperations,
@ -97,7 +97,7 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
kanbanAggregateOperationFieldMetadataId:
availableAggregationFieldsIdsForOperation[0],
kanbanAggregateOperation:
availableAggregationOperation as AGGREGATE_OPERATIONS,
availableAggregationOperation as AggregateOperations,
});
closeDropdown();
}
@ -106,15 +106,14 @@ export const RecordBoardColumnHeaderAggregateDropdownOptionsContent = ({
availableAggregationOperation as ExtendedAggregateOperations,
)}
hasSubMenu={
availableAggregationOperation === AGGREGATE_OPERATIONS.count
availableAggregationOperation === AggregateOperations.COUNT
? false
: true
}
RightIcon={
availableAggregationOperation ===
AGGREGATE_OPERATIONS.count &&
availableAggregationOperation === AggregateOperations.COUNT &&
recordIndexKanbanAggregateOperation?.operation ===
AGGREGATE_OPERATIONS.count
AggregateOperations.COUNT
? IconCheck
: undefined
}

View File

@ -2,7 +2,7 @@ import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { buildRecordGqlFieldsAggregateForView } from '@/object-record/record-board/record-board-column/utils/buildRecordGqlFieldsAggregateForView';
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';
const MOCK_FIELD_ID = '7d2d7b5e-7b3e-4b4a-8b0a-7b3e4b4a8b0a';
@ -47,7 +47,7 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
it('should build fields for numeric aggregate', () => {
const kanbanAggregateOperation: KanbanAggregateOperation = {
fieldMetadataId: MOCK_FIELD_ID,
operation: AGGREGATE_OPERATIONS.sum,
operation: AggregateOperations.SUM,
};
const result = buildRecordGqlFieldsAggregateForView({
@ -56,14 +56,14 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
});
expect(result).toEqual({
amount: [AGGREGATE_OPERATIONS.sum],
amount: [AggregateOperations.SUM],
});
});
it('should default to count when no field is found', () => {
const operation: KanbanAggregateOperation = {
fieldMetadataId: 'non-existent-id',
operation: AGGREGATE_OPERATIONS.count,
operation: AggregateOperations.COUNT,
};
const result = buildRecordGqlFieldsAggregateForView({
@ -72,14 +72,14 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
});
expect(result).toEqual({
id: [AGGREGATE_OPERATIONS.count],
id: [AggregateOperations.COUNT],
});
});
it('should throw error for non-count operation with invalid field', () => {
const operation: KanbanAggregateOperation = {
fieldMetadataId: 'non-existent-id',
operation: AGGREGATE_OPERATIONS.sum,
operation: AggregateOperations.SUM,
};
expect(() =>
@ -88,7 +88,7 @@ describe('buildRecordGqlFieldsAggregateForView', () => {
recordIndexKanbanAggregateOperation: operation,
}),
).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 { AggregateRecordsData } from '@/object-record/hooks/useAggregateRecords';
import { computeAggregateValueAndLabel } from '@/object-record/record-board/record-board-column/utils/computeAggregateValueAndLabel';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { enUS } from 'date-fns/locale';
import { FieldMetadataType } from '~/generated/graphql';
@ -35,7 +35,7 @@ describe('computeAggregateValueAndLabel', () => {
data: {} as AggregateRecordsData,
objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS,
...defaultParams,
});
@ -46,7 +46,7 @@ describe('computeAggregateValueAndLabel', () => {
it('should handle currency field with division by 1M', () => {
const mockData = {
amount: {
[AGGREGATE_OPERATIONS.sum]: 2000000,
[AggregateOperations.SUM]: 2000000,
},
} as AggregateRecordsData;
@ -54,7 +54,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData,
objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS,
...defaultParams,
});
@ -84,7 +84,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockData = {
percentage: {
[AGGREGATE_OPERATIONS.avg]: 0.3,
[AggregateOperations.AVG]: 0.3,
},
} as AggregateRecordsData;
@ -92,7 +92,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData,
objectMetadataItem: mockObjectMetadataWithPercentageField,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.avg,
aggregateOperation: AggregateOperations.AVG,
localeCatalog: enUS,
...defaultParams,
});
@ -122,7 +122,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockData = {
decimals: {
[AGGREGATE_OPERATIONS.sum]: 0.009,
[AggregateOperations.SUM]: 0.009,
},
} as AggregateRecordsData;
@ -130,7 +130,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData,
objectMetadataItem: mockObjectMetadataWithDecimalsField,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS,
...defaultParams,
});
@ -157,7 +157,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockFormattedData = {
createdAt: {
[DATE_AGGREGATE_OPERATIONS.earliest]: '2023-01-01T12:00:00Z',
[DateAggregateOperations.EARLIEST]: '2023-01-01T12:00:00Z',
},
} as AggregateRecordsData;
@ -165,7 +165,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockFormattedData,
objectMetadataItem: mockObjectMetadataWithDatetimeField,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest,
aggregateOperation: DateAggregateOperations.EARLIEST,
localeCatalog: enUS,
...defaultParams,
});
@ -192,7 +192,7 @@ describe('computeAggregateValueAndLabel', () => {
const mockFormattedData = {
updatedAt: {
[DATE_AGGREGATE_OPERATIONS.latest]: '2023-12-31T23:59:59Z',
[DateAggregateOperations.LATEST]: '2023-12-31T23:59:59Z',
},
} as AggregateRecordsData;
@ -200,7 +200,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockFormattedData,
objectMetadataItem: mockObjectMetadataWithDatetimeField,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: DATE_AGGREGATE_OPERATIONS.latest,
aggregateOperation: DateAggregateOperations.LATEST,
localeCatalog: enUS,
...defaultParams,
});
@ -215,7 +215,7 @@ describe('computeAggregateValueAndLabel', () => {
it('should default to count when field not found', () => {
const mockData = {
id: {
[AGGREGATE_OPERATIONS.count]: 42,
[AggregateOperations.COUNT]: 42,
},
} as AggregateRecordsData;
@ -236,7 +236,7 @@ describe('computeAggregateValueAndLabel', () => {
it('should handle undefined aggregate value', () => {
const mockData = {
amount: {
[AGGREGATE_OPERATIONS.sum]: undefined,
[AggregateOperations.SUM]: undefined,
},
} as AggregateRecordsData;
@ -244,7 +244,7 @@ describe('computeAggregateValueAndLabel', () => {
data: mockData,
objectMetadataItem: mockObjectMetadata,
fieldMetadataId: MOCK_FIELD_ID,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
aggregateOperation: AggregateOperations.SUM,
localeCatalog: enUS,
...defaultParams,
});

View File

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

View File

@ -1,7 +1,7 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
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 { isDefined } from 'twenty-shared/utils';
@ -23,7 +23,7 @@ export const buildRecordGqlFieldsAggregateForView = ({
if (
isDefined(recordIndexKanbanAggregateOperation?.operation) &&
recordIndexKanbanAggregateOperation.operation !==
AGGREGATE_OPERATIONS.count
AggregateOperations.COUNT
) {
throw new Error(
`No field found to compute aggregate operation ${recordIndexKanbanAggregateOperation.operation} on object ${objectMetadataItem.nameSingular}`,
@ -31,7 +31,7 @@ export const buildRecordGqlFieldsAggregateForView = ({
} else {
recordGqlFieldsAggregate = {
[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]: [
AGGREGATE_OPERATIONS.count,
AggregateOperations.COUNT,
],
};
}
@ -39,7 +39,7 @@ export const buildRecordGqlFieldsAggregateForView = ({
recordGqlFieldsAggregate = {
[kanbanAggregateOperationFieldName]: [
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 { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
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 { PERCENT_AGGREGATE_OPERATION_OPTIONS } from '@/object-record/record-table/record-table-footer/constants/percentAggregateOperationOptions';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
@ -48,12 +48,10 @@ export const computeAggregateValueAndLabel = ({
return {
value:
data?.[FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION]?.[
AGGREGATE_OPERATIONS.count
AggregateOperations.COUNT
],
label: getAggregateOperationLabel(AGGREGATE_OPERATIONS.count),
labelWithFieldName: getAggregateOperationLabel(
AGGREGATE_OPERATIONS.count,
),
label: getAggregateOperationLabel(AggregateOperations.COUNT),
labelWithFieldName: getAggregateOperationLabel(AggregateOperations.COUNT),
};
}
@ -69,7 +67,7 @@ export const computeAggregateValueAndLabel = ({
if (
COUNT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS,
aggregateOperation as AggregateOperations,
)
) {
value = aggregateValue;
@ -77,7 +75,7 @@ export const computeAggregateValueAndLabel = ({
value = '-';
} else if (
PERCENT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS,
aggregateOperation as AggregateOperations,
)
) {
value = `${formatNumber(Number(aggregateValue) * 100)}%`;
@ -128,8 +126,8 @@ export const computeAggregateValueAndLabel = ({
const aggregateLabel = t(getAggregateOperationShortLabel(aggregateOperation));
const fieldLabel = field.label;
const labelWithFieldName =
aggregateOperation === AGGREGATE_OPERATIONS.count
? `${getAggregateOperationLabel(AGGREGATE_OPERATIONS.count)}`
aggregateOperation === AggregateOperations.COUNT
? `${getAggregateOperationLabel(AggregateOperations.COUNT)}`
: t`${aggregateLabel} of ${fieldLabel}`;
return {

View File

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

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations';
import { msg } from '@lingui/core/macro';
@ -7,31 +7,31 @@ export const getAggregateOperationShortLabel = (
operation: ExtendedAggregateOperations,
) => {
switch (operation) {
case AGGREGATE_OPERATIONS.min:
case AggregateOperations.MIN:
return msg`Min`;
case AGGREGATE_OPERATIONS.max:
case AggregateOperations.MAX:
return msg`Max`;
case AGGREGATE_OPERATIONS.avg:
case AggregateOperations.AVG:
return msg`Average`;
case AGGREGATE_OPERATIONS.sum:
case AggregateOperations.SUM:
return msg`Sum`;
case AGGREGATE_OPERATIONS.count:
case AggregateOperations.COUNT:
return msg`All`;
case AGGREGATE_OPERATIONS.countEmpty:
case AGGREGATE_OPERATIONS.percentageEmpty:
case AggregateOperations.COUNT_EMPTY:
case AggregateOperations.PERCENTAGE_EMPTY:
return msg`Empty`;
case AGGREGATE_OPERATIONS.countNotEmpty:
case AGGREGATE_OPERATIONS.percentageNotEmpty:
case AggregateOperations.COUNT_NOT_EMPTY:
case AggregateOperations.PERCENTAGE_NOT_EMPTY:
return msg`Not empty`;
case AGGREGATE_OPERATIONS.countUniqueValues:
case AggregateOperations.COUNT_UNIQUE_VALUES:
return msg`Unique`;
case DATE_AGGREGATE_OPERATIONS.earliest:
case DateAggregateOperations.EARLIEST:
return msg`Earliest`;
case DATE_AGGREGATE_OPERATIONS.latest:
case DateAggregateOperations.LATEST:
return msg`Latest`;
case AGGREGATE_OPERATIONS.countTrue:
case AggregateOperations.COUNT_TRUE:
return msg`True`;
case AGGREGATE_OPERATIONS.countFalse:
case AggregateOperations.COUNT_FALSE:
return msg`False`;
default:
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 { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated/graphql';
import {
IconCalendarEvent,
IconHome,
@ -19,6 +17,8 @@ import {
IconNotes,
IconSettings,
} from 'twenty-ui/display';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated/graphql';
export const useRecordShowContainerTabs = (
loading: boolean,
@ -152,7 +152,7 @@ export const useRecordShowContainerTabs = (
ifMobile: false,
ifDesktop: false,
ifInRightDrawer: false,
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED],
ifRequiredObjectsInactive: [],
ifRelationsMissing: [],
},
@ -175,7 +175,7 @@ export const useRecordShowContainerTabs = (
ifMobile: false,
ifDesktop: false,
ifInRightDrawer: false,
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED],
ifRequiredObjectsInactive: [],
ifRelationsMissing: [],
},
@ -197,7 +197,7 @@ export const useRecordShowContainerTabs = (
ifMobile: false,
ifDesktop: false,
ifInRightDrawer: false,
ifFeaturesDisabled: [FeatureFlagKey.IsWorkflowEnabled],
ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED],
ifRequiredObjectsInactive: [],
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 { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
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 { prefetchIndexViewIdFromObjectMetadataItemFamilySelector } from '@/prefetch/states/selector/prefetchIndexViewIdFromObjectMetadataItemFamilySelector';
import { AppPath } from '@/types/AppPath';
@ -109,7 +109,7 @@ export const RecordDetailRelationSection = ({
filter: filtersForAggregate,
skip: !isToManyObjects,
recordGqlFieldsAggregate: {
id: [AGGREGATE_OPERATIONS.count],
id: [AggregateOperations.COUNT],
},
});

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { useDropdown } from '@/dropdown/hooks/useDropdown';
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
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 { RecordTableColumnAggregateFooterMenuContent } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterMenuContent';
@ -28,7 +28,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) =>
!STANDARD_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS,
aggregateOperation as AggregateOperations,
),
);
@ -43,7 +43,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) =>
COUNT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS,
aggregateOperation as AggregateOperations,
),
);
return (
@ -57,7 +57,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) =>
PERCENT_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as AGGREGATE_OPERATIONS,
aggregateOperation as AggregateOperations,
),
);
return (
@ -71,7 +71,7 @@ export const RecordTableColumnAggregateFooterDropdownContent = () => {
const aggregateOperations = availableAggregateOperations.filter(
(aggregateOperation) =>
DATE_AGGREGATE_OPERATION_OPTIONS.includes(
aggregateOperation as DATE_AGGREGATE_OPERATIONS,
aggregateOperation as DateAggregateOperations,
),
);
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 { 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';
@ -51,7 +51,7 @@ export const RecordTableColumnAggregateFooterMenuContent = () => {
const nonStandardAvailableAggregateOperation =
availableAggregateOperation.filter((aggregateOperation) =>
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 = [
AGGREGATE_OPERATIONS.count,
AGGREGATE_OPERATIONS.countEmpty,
AGGREGATE_OPERATIONS.countNotEmpty,
AGGREGATE_OPERATIONS.countUniqueValues,
AGGREGATE_OPERATIONS.countTrue,
AGGREGATE_OPERATIONS.countFalse,
AggregateOperations.COUNT,
AggregateOperations.COUNT_EMPTY,
AggregateOperations.COUNT_NOT_EMPTY,
AggregateOperations.COUNT_UNIQUE_VALUES,
AggregateOperations.COUNT_TRUE,
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 = [
DATE_AGGREGATE_OPERATIONS.earliest,
DATE_AGGREGATE_OPERATIONS.latest,
DateAggregateOperations.EARLIEST,
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 = [
AGGREGATE_OPERATIONS.min,
AGGREGATE_OPERATIONS.max,
AGGREGATE_OPERATIONS.avg,
AGGREGATE_OPERATIONS.sum,
AggregateOperations.MIN,
AggregateOperations.MAX,
AggregateOperations.AVG,
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 = [
AGGREGATE_OPERATIONS.percentageEmpty,
AGGREGATE_OPERATIONS.percentageNotEmpty,
AggregateOperations.PERCENTAGE_EMPTY,
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 { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
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 { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
@ -62,9 +62,9 @@ export const useAggregateRecordsForRecordTableColumnFooter = (
isFieldMetadataDateKind(fieldMetadataItem.type) &&
isDefined(aggregateOperationForViewFieldWithProbableImpossibleValues) &&
(aggregateOperationForViewFieldWithProbableImpossibleValues ===
AGGREGATE_OPERATIONS.min ||
AggregateOperations.MIN ||
aggregateOperationForViewFieldWithProbableImpossibleValues ===
AGGREGATE_OPERATIONS.max);
AggregateOperations.MAX);
const aggregateOperationForViewField:
| ExtendedAggregateOperations

View File

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

View File

@ -1,6 +1,6 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
export type ExtendedAggregateOperations =
| AGGREGATE_OPERATIONS
| DATE_AGGREGATE_OPERATIONS;
| AggregateOperations
| 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<
AGGREGATE_OPERATIONS,
| AGGREGATE_OPERATIONS.count
| AGGREGATE_OPERATIONS.countEmpty
| AGGREGATE_OPERATIONS.countNotEmpty
| AGGREGATE_OPERATIONS.countUniqueValues
| AGGREGATE_OPERATIONS.percentageEmpty
| AGGREGATE_OPERATIONS.percentageNotEmpty
AggregateOperations,
| AggregateOperations.COUNT
| AggregateOperations.COUNT_EMPTY
| AggregateOperations.COUNT_NOT_EMPTY
| AggregateOperations.COUNT_UNIQUE_VALUES
| AggregateOperations.PERCENTAGE_EMPTY
| AggregateOperations.PERCENTAGE_NOT_EMPTY
>;

View File

@ -1,5 +1,5 @@
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 { 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';
@ -23,43 +23,43 @@ jest.mock(
() => ({
getAvailableAggregationsFromObjectFields: jest.fn().mockReturnValue({
active: {
[AGGREGATE_OPERATIONS.countTrue]: 'countTrueActive',
[AGGREGATE_OPERATIONS.countFalse]: 'CountFalseActive',
[AggregateOperations.COUNT_TRUE]: 'countTrueActive',
[AggregateOperations.COUNT_FALSE]: 'CountFalseActive',
},
amount: {
[AGGREGATE_OPERATIONS.sum]: 'sumAmount',
[AGGREGATE_OPERATIONS.avg]: 'avgAmount',
[AGGREGATE_OPERATIONS.min]: 'minAmount',
[AGGREGATE_OPERATIONS.max]: 'maxAmount',
[AGGREGATE_OPERATIONS.count]: 'totalCount',
[AGGREGATE_OPERATIONS.countUniqueValues]: 'countUniqueValuesAmount',
[AGGREGATE_OPERATIONS.countEmpty]: 'countEmptyAmount',
[AGGREGATE_OPERATIONS.countNotEmpty]: 'countNotEmptyAmount',
[AGGREGATE_OPERATIONS.percentageEmpty]: 'percentageEmptyAmount',
[AGGREGATE_OPERATIONS.percentageNotEmpty]: 'percentageNotEmptyAmount',
[AggregateOperations.SUM]: 'sumAmount',
[AggregateOperations.AVG]: 'avgAmount',
[AggregateOperations.MIN]: 'minAmount',
[AggregateOperations.MAX]: 'maxAmount',
[AggregateOperations.COUNT]: 'totalCount',
[AggregateOperations.COUNT_UNIQUE_VALUES]: 'countUniqueValuesAmount',
[AggregateOperations.COUNT_EMPTY]: 'countEmptyAmount',
[AggregateOperations.COUNT_NOT_EMPTY]: 'countNotEmptyAmount',
[AggregateOperations.PERCENTAGE_EMPTY]: 'percentageEmptyAmount',
[AggregateOperations.PERCENTAGE_NOT_EMPTY]: 'percentageNotEmptyAmount',
},
price: {
[AGGREGATE_OPERATIONS.sum]: 'sumPriceAmountMicros',
[AGGREGATE_OPERATIONS.avg]: 'avgPriceAmountMicros',
[AGGREGATE_OPERATIONS.min]: 'minPriceAmountMicros',
[AGGREGATE_OPERATIONS.max]: 'maxPriceAmountMicros',
[AGGREGATE_OPERATIONS.count]: 'totalCount',
[AGGREGATE_OPERATIONS.countUniqueValues]:
[AggregateOperations.SUM]: 'sumPriceAmountMicros',
[AggregateOperations.AVG]: 'avgPriceAmountMicros',
[AggregateOperations.MIN]: 'minPriceAmountMicros',
[AggregateOperations.MAX]: 'maxPriceAmountMicros',
[AggregateOperations.COUNT]: 'totalCount',
[AggregateOperations.COUNT_UNIQUE_VALUES]:
'countUniqueValuesPriceAmountMicros',
[AGGREGATE_OPERATIONS.countEmpty]: 'countEmptyPriceAmountMicros',
[AGGREGATE_OPERATIONS.countNotEmpty]: 'countNotEmptyPriceAmountMicros',
[AGGREGATE_OPERATIONS.percentageEmpty]:
[AggregateOperations.COUNT_EMPTY]: 'countEmptyPriceAmountMicros',
[AggregateOperations.COUNT_NOT_EMPTY]: 'countNotEmptyPriceAmountMicros',
[AggregateOperations.PERCENTAGE_EMPTY]:
'percentageEmptyPriceAmountMicros',
[AGGREGATE_OPERATIONS.percentageNotEmpty]:
[AggregateOperations.PERCENTAGE_NOT_EMPTY]:
'percentageNotEmptyPriceAmountMicros',
},
name: {
[AGGREGATE_OPERATIONS.count]: 'totalCount',
[AGGREGATE_OPERATIONS.countUniqueValues]: 'countUniqueValuesName',
[AGGREGATE_OPERATIONS.countEmpty]: 'countEmptyName',
[AGGREGATE_OPERATIONS.countNotEmpty]: 'countNotEmptyName',
[AGGREGATE_OPERATIONS.percentageEmpty]: 'percentageEmptyName',
[AGGREGATE_OPERATIONS.percentageNotEmpty]: 'percentageNotEmptyName',
[AggregateOperations.COUNT]: 'totalCount',
[AggregateOperations.COUNT_UNIQUE_VALUES]: 'countUniqueValuesName',
[AggregateOperations.COUNT_EMPTY]: 'countEmptyName',
[AggregateOperations.COUNT_NOT_EMPTY]: 'countNotEmptyName',
[AggregateOperations.PERCENTAGE_EMPTY]: 'percentageEmptyName',
[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 { 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';
@ -10,7 +10,7 @@ describe('initializeAvailableFieldsForAggregateOperationMap', () => {
const result = initializeAvailableFieldsForAggregateOperationMap(
Object.keys(
FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION,
) as AGGREGATE_OPERATIONS[],
) as AggregateOperations[],
);
expect(Object.keys(result)).toEqual(
@ -25,11 +25,11 @@ describe('initializeAvailableFieldsForAggregateOperationMap', () => {
const result = initializeAvailableFieldsForAggregateOperationMap(
Object.keys(
FIELD_TYPES_AVAILABLE_FOR_NON_STANDARD_AGGREGATE_OPERATION,
) as AGGREGATE_OPERATIONS[],
) as AggregateOperations[],
);
expect(
result[
AGGREGATE_OPERATIONS.count as AggregateOperationsOmittingStandardOperations
AggregateOperations.COUNT as AggregateOperationsOmittingStandardOperations
],
).toBeUndefined();
});
@ -38,21 +38,21 @@ describe('initializeAvailableFieldsForAggregateOperationMap', () => {
const result = initializeAvailableFieldsForAggregateOperationMap(
COUNT_AGGREGATE_OPERATION_OPTIONS,
);
expect(result[AGGREGATE_OPERATIONS.count]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.countEmpty]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.countNotEmpty]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.countUniqueValues]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.min]).toBeUndefined();
expect(result[AGGREGATE_OPERATIONS.percentageEmpty]).toBeUndefined();
expect(result[AggregateOperations.COUNT]).toEqual([]);
expect(result[AggregateOperations.COUNT_EMPTY]).toEqual([]);
expect(result[AggregateOperations.COUNT_NOT_EMPTY]).toEqual([]);
expect(result[AggregateOperations.COUNT_UNIQUE_VALUES]).toEqual([]);
expect(result[AggregateOperations.MIN]).toBeUndefined();
expect(result[AggregateOperations.PERCENTAGE_EMPTY]).toBeUndefined();
});
it('should include percent operation when called with count aggregate operations', () => {
const result = initializeAvailableFieldsForAggregateOperationMap(
PERCENT_AGGREGATE_OPERATION_OPTIONS,
);
expect(result[AGGREGATE_OPERATIONS.percentageEmpty]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.percentageNotEmpty]).toEqual([]);
expect(result[AGGREGATE_OPERATIONS.count]).toBeUndefined();
expect(result[AGGREGATE_OPERATIONS.min]).toBeUndefined();
expect(result[AggregateOperations.PERCENTAGE_EMPTY]).toEqual([]);
expect(result[AggregateOperations.PERCENTAGE_NOT_EMPTY]).toEqual([]);
expect(result[AggregateOperations.COUNT]).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 { isFieldTypeValidForAggregateOperation } from '@/object-record/utils/isFieldTypeValidForAggregateOperation';
import { FieldMetadataType } from '~/generated/graphql';
@ -8,14 +8,14 @@ describe('isFieldTypeValidForAggregateOperation', () => {
expect(
isFieldTypeValidForAggregateOperation(
FieldMetadataType.NUMBER,
AGGREGATE_OPERATIONS.sum,
AggregateOperations.SUM,
),
).toBe(true);
expect(
isFieldTypeValidForAggregateOperation(
FieldMetadataType.CURRENCY,
AGGREGATE_OPERATIONS.min,
AggregateOperations.MIN,
),
).toBe(true);
});
@ -24,14 +24,14 @@ describe('isFieldTypeValidForAggregateOperation', () => {
expect(
isFieldTypeValidForAggregateOperation(
FieldMetadataType.TEXT,
AGGREGATE_OPERATIONS.avg,
AggregateOperations.AVG,
),
).toBe(false);
expect(
isFieldTypeValidForAggregateOperation(
FieldMetadataType.BOOLEAN,
AGGREGATE_OPERATIONS.max,
AggregateOperations.MAX,
),
).toBe(false);
});
@ -39,10 +39,10 @@ describe('isFieldTypeValidForAggregateOperation', () => {
it('should handle all aggregate operations', () => {
const numericField = FieldMetadataType.NUMBER;
const operations = [
AGGREGATE_OPERATIONS.min,
AGGREGATE_OPERATIONS.max,
AGGREGATE_OPERATIONS.avg,
AGGREGATE_OPERATIONS.sum,
AggregateOperations.MIN,
AggregateOperations.MAX,
AggregateOperations.AVG,
AggregateOperations.SUM,
];
operations.forEach((operation) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,7 +60,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
);
const isPermissionsV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled,
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
);
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 { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
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 { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
@ -21,6 +20,7 @@ import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ViewType } from '@/views/types/ViewType';
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 } from '@/views/types/View';
import { within } from '@storybook/test';
@ -67,7 +67,7 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
viewGroups: [],
viewSorts: [],
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
kanbanAggregateOperation: AggregateOperations.COUNT,
icon: '',
kanbanAggregateOperationFieldMetadataId: '',
position: 0,

View File

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

View File

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

View File

@ -1,5 +1,5 @@
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
import { DATE_AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/DateAggregateOperations';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useUpdateView } from '@/views/hooks/useUpdateView';
import { renderHook } from '@testing-library/react';
@ -34,21 +34,21 @@ describe('useUpdateViewAggregate', () => {
result.current.updateViewAggregate({
kanbanAggregateOperationFieldMetadataId: 'test-field-id',
kanbanAggregateOperation: DATE_AGGREGATE_OPERATIONS.earliest,
kanbanAggregateOperation: DateAggregateOperations.EARLIEST,
});
// updateView is called with 'EARLIEST' converted to 'MIN'
expect(mockUpdateView).toHaveBeenCalledWith({
id: mockCurrentViewId,
kanbanAggregateOperationFieldMetadataId: 'test-field-id',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.min,
kanbanAggregateOperation: AggregateOperations.MIN,
});
// setAggregateOperation is called with 'EARLIEST'
expect(
mockSetRecordIndexKanbanAggregateOperationState,
).toHaveBeenCalledWith({
operation: DATE_AGGREGATE_OPERATIONS.earliest,
operation: DateAggregateOperations.EARLIEST,
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 { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
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 { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
@ -36,7 +36,7 @@ export const useGetRecordIndexTotalCount = () => {
objectNameSingular: objectMetadataItem.nameSingular,
filter,
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 { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@ -17,7 +17,7 @@ export type GraphQLView = {
* @deprecated Use `viewGroups.fieldMetadataId` instead.
*/
kanbanFieldMetadataId: string;
kanbanAggregateOperation?: AGGREGATE_OPERATIONS | null;
kanbanAggregateOperation?: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId?: string | null;
objectMetadataId: string;
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 { ViewFilter } from '@/views/types/ViewFilter';
import { ViewFilterGroup } from '@/views/types/ViewFilterGroup';
@ -24,7 +24,7 @@ export type View = {
* @deprecated Use `viewGroups.fieldMetadataId` instead.
*/
kanbanFieldMetadataId: string;
kanbanAggregateOperation: AGGREGATE_OPERATIONS | null;
kanbanAggregateOperation: AggregateOperations | null;
kanbanAggregateOperationFieldMetadataId: string | null;
position: number;
icon: string;

View File

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

View File

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

View File

@ -20,13 +20,7 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
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 { Button } from 'twenty-ui/input';
import {
H3Title,
IconCodeCircle,
@ -35,8 +29,14 @@ import {
IconPoint,
IconSettings,
} from 'twenty-ui/display';
import { MAIN_COLORS } from 'twenty-ui/theme';
import { Button } from 'twenty-ui/input';
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`
flex: 1;
@ -76,7 +76,7 @@ export const SettingsObjectDetailPage = () => {
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
const isUniqueIndexesEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsUniqueIndexesEnabled,
FeatureFlagKey.IS_UNIQUE_INDEXES_ENABLED,
);
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 { useModal } from '@/ui/layout/modal/hooks/useModal';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { ApolloError } from '@apollo/client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
@ -19,10 +18,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { z } from 'zod';
import {
FeatureFlagKey,
useUpdateWorkspaceMutation,
} from '~/generated/graphql';
import { useUpdateWorkspaceMutation } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { SettingsCustomDomain } from '~/pages/settings/workspace/SettingsCustomDomain';
import { SettingsSubdomain } from '~/pages/settings/workspace/SettingsSubdomain';
@ -68,10 +64,6 @@ export const SettingsDomain = () => {
const [updateWorkspace] = useUpdateWorkspaceMutation();
const { redirectToWorkspaceDomain } = useRedirectToWorkspaceDomain();
const isCustomDomainEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsCustomDomainEnabled,
);
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
currentWorkspaceState,
);
@ -236,7 +228,7 @@ export const SettingsDomain = () => {
>
<SettingsPageContainer>
<SettingsSubdomain />
{isCustomDomainEnabled && <SettingsCustomDomain />}
<SettingsCustomDomain />
</SettingsPageContainer>
</SubMenuTopBarContainer>
<ConfirmationModal

View File

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

View File

@ -59,15 +59,15 @@ export const mockCurrentWorkspace: Workspace = {
isMicrosoftAuthEnabled: false,
featureFlags: [
{
key: FeatureFlagKey.IsAirtableIntegrationEnabled,
key: FeatureFlagKey.IS_AIRTABLE_INTEGRATION_ENABLED,
value: true,
},
{
key: FeatureFlagKey.IsPostgreSQLIntegrationEnabled,
key: FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED,
value: true,
},
{
key: FeatureFlagKey.IsWorkflowEnabled,
key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
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 { ViewKey } from '@/views/types/ViewKey';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@ -26,7 +26,7 @@ export const mockedViewsData: View[] = [
icon: 'IconSkyline',
key: ViewKey.Index,
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
isCompact: false,
@ -46,7 +46,7 @@ export const mockedViewsData: View[] = [
icon: 'IconPerson',
key: ViewKey.Index,
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
isCompact: false,
@ -66,7 +66,7 @@ export const mockedViewsData: View[] = [
icon: 'IconOpportunity',
key: ViewKey.Index,
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
isCompact: false,
@ -86,7 +86,7 @@ export const mockedViewsData: View[] = [
icon: 'IconSkyline',
key: null,
kanbanFieldMetadataId: '',
kanbanAggregateOperation: AGGREGATE_OPERATIONS.count,
kanbanAggregateOperation: AggregateOperations.COUNT,
kanbanAggregateOperationFieldMetadataId: '',
position: 0,
isCompact: false,

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} 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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-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 = [
{
value: AGGREGATE_OPERATIONS.avg,
value: AggregateOperations.AVG,
label: 'Average',
position: 0,
color: 'red',
},
{
value: AGGREGATE_OPERATIONS.count,
value: AggregateOperations.COUNT,
label: 'Count',
position: 1,
color: 'purple',
},
{
value: AGGREGATE_OPERATIONS.max,
value: AggregateOperations.MAX,
label: 'Maximum',
position: 2,
color: 'sky',
},
{
value: AGGREGATE_OPERATIONS.min,
value: AggregateOperations.MIN,
label: 'Minimum',
position: 3,
color: 'turquoise',
},
{
value: AGGREGATE_OPERATIONS.sum,
value: AggregateOperations.SUM,
label: 'Sum',
position: 4,
color: 'yellow',
},
{
value: AGGREGATE_OPERATIONS.countEmpty,
value: AggregateOperations.COUNT_EMPTY,
label: 'Count empty',
position: 5,
color: 'red',
},
{
value: AGGREGATE_OPERATIONS.countNotEmpty,
value: AggregateOperations.COUNT_NOT_EMPTY,
label: 'Count not empty',
position: 6,
color: 'purple',
},
{
value: AGGREGATE_OPERATIONS.countUniqueValues,
value: AggregateOperations.COUNT_UNIQUE_VALUES,
label: 'Count unique values',
position: 7,
color: 'sky',
},
{
value: AGGREGATE_OPERATIONS.percentageEmpty,
value: AggregateOperations.PERCENTAGE_EMPTY,
label: 'Percent empty',
position: 8,
color: 'turquoise',
},
{
value: AGGREGATE_OPERATIONS.percentageNotEmpty,
value: AggregateOperations.PERCENTAGE_NOT_EMPTY,
label: 'Percent not empty',
position: 9,
color: 'yellow',
},
{
value: AGGREGATE_OPERATIONS.countTrue,
value: AggregateOperations.COUNT_TRUE,
label: 'Count true',
position: 10,
color: 'red',
},
{
value: AGGREGATE_OPERATIONS.countFalse,
value: AggregateOperations.COUNT_FALSE,
label: 'Count false',
position: 11,
color: 'purple',

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common';
import { SelectQueryBuilder } from 'typeorm';
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 { 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];
if (
!Object.values(AGGREGATE_OPERATIONS).includes(
!Object.values(AggregateOperations).includes(
aggregatedField.aggregateOperation,
)
) {
@ -58,44 +58,44 @@ export class ProcessAggregateHelper {
const columnExpression = `NULLIF(CONCAT(${concatenatedColumns}), '')`;
switch (aggregatedField.aggregateOperation) {
case AGGREGATE_OPERATIONS.countEmpty:
case AggregateOperations.COUNT_EMPTY:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(*) - COUNT(${columnExpression}) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.countNotEmpty:
case AggregateOperations.COUNT_NOT_EMPTY:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(${columnExpression}) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.countUniqueValues:
case AggregateOperations.COUNT_UNIQUE_VALUES:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(DISTINCT ${columnExpression}) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.percentageEmpty:
case AggregateOperations.PERCENTAGE_EMPTY:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST(((COUNT(*) - COUNT(${columnExpression})::decimal) / COUNT(*)) AS DECIMAL) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.percentageNotEmpty:
case AggregateOperations.PERCENTAGE_NOT_EMPTY:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE CAST((COUNT(${columnExpression})::decimal / COUNT(*)) AS DECIMAL) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.countTrue:
case AggregateOperations.COUNT_TRUE:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(CASE WHEN ${columnExpression}::boolean = TRUE THEN 1 ELSE NULL END) END`,
`${aggregatedFieldName}`,
);
break;
case AGGREGATE_OPERATIONS.countFalse:
case AggregateOperations.COUNT_FALSE:
queryBuilder.addSelect(
`CASE WHEN COUNT(*) = 0 THEN NULL ELSE COUNT(CASE WHEN ${columnExpression}::boolean = FALSE THEN 1 ELSE NULL END) END`,
`${aggregatedFieldName}`,

View File

@ -94,7 +94,7 @@ export abstract class GraphqlQueryBaseResolverService<
const featureFlagsMap = workspaceDataSource.featureFlagMap;
const isPermissionsV2Enabled =
featureFlagsMap[FeatureFlagKey.IsPermissionsV2Enabled];
featureFlagsMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
if (objectMetadataItemWithFieldMaps.isSystem === true) {
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 = () => {
return renderApolloPlayground({ path: 'metadata' });
};

View File

@ -29,7 +29,7 @@ export function WorkspaceQueryHook(
// Default to PreHook
if (!options.type) {
options.type = WorkspaceQueryHookType.PreHook;
options.type = WorkspaceQueryHookType.PRE_HOOK;
}
// 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';
export enum WorkspaceQueryHookType {
PreHook = 'PreHook',
PostHook = 'PostHook',
PRE_HOOK = 'PRE_HOOK',
POST_HOOK = 'POST_HOOK',
}
export type WorkspacePreQueryHookPayload<T> = T extends 'createMany'

View File

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

View File

@ -1,13 +1,13 @@
import { GraphQLISODateTime } from '@nestjs/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 { 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 { 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';
export type AggregationField = {
@ -17,7 +17,7 @@ export type AggregationField = {
fromFieldType: FieldMetadataType;
fromSubFields?: string[];
subFieldForNumericOperation?: string;
aggregateOperation: AGGREGATE_OPERATIONS;
aggregateOperation: AggregateOperations;
};
export const getAvailableAggregationsFromObjectFields = (
@ -37,7 +37,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromFieldType: field.type,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countUniqueValues,
aggregateOperation: AggregateOperations.COUNT_UNIQUE_VALUES,
};
acc[`countEmpty${capitalize(field.name)}`] = {
@ -46,7 +46,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromFieldType: field.type,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countEmpty,
aggregateOperation: AggregateOperations.COUNT_EMPTY,
};
acc[`countNotEmpty${capitalize(field.name)}`] = {
@ -55,7 +55,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromFieldType: field.type,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.countNotEmpty,
aggregateOperation: AggregateOperations.COUNT_NOT_EMPTY,
};
acc[`percentageEmpty${capitalize(field.name)}`] = {
@ -64,7 +64,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromFieldType: field.type,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.percentageEmpty,
aggregateOperation: AggregateOperations.PERCENTAGE_EMPTY,
};
acc[`percentageNotEmpty${capitalize(field.name)}`] = {
@ -73,7 +73,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromFieldType: field.type,
fromSubFields,
aggregateOperation: AGGREGATE_OPERATIONS.percentageNotEmpty,
aggregateOperation: AggregateOperations.PERCENTAGE_NOT_EMPTY,
};
if (isFieldMetadataDateKind(field.type)) {
@ -82,7 +82,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Earliest date contained in the field ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min,
aggregateOperation: AggregateOperations.MIN,
};
acc[`max${capitalize(field.name)}`] = {
@ -90,7 +90,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Latest date contained in the field ${field.name}`,
fromField: field.name,
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}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.countTrue,
aggregateOperation: AggregateOperations.COUNT_TRUE,
};
acc[`countFalse${capitalize(field.name)}`] = {
@ -109,7 +109,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Count of false values in the field ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.countFalse,
aggregateOperation: AggregateOperations.COUNT_FALSE,
};
break;
@ -119,7 +119,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Minimum amount contained in the field ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min,
aggregateOperation: AggregateOperations.MIN,
};
acc[`max${capitalize(field.name)}`] = {
@ -127,7 +127,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Maximum amount contained in the field ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.max,
aggregateOperation: AggregateOperations.MAX,
};
acc[`avg${capitalize(field.name)}`] = {
@ -135,7 +135,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Average amount contained in the field ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.avg,
aggregateOperation: AggregateOperations.AVG,
};
acc[`sum${capitalize(field.name)}`] = {
@ -143,7 +143,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Sum of amounts contained in the field ${field.name}`,
fromField: field.name,
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
aggregateOperation: AggregateOperations.SUM,
};
break;
case FieldMetadataType.CURRENCY:
@ -154,7 +154,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromSubFields: getSubfieldsForAggregateOperation(field.type),
subFieldForNumericOperation: 'amountMicros',
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.min,
aggregateOperation: AggregateOperations.MIN,
};
acc[`max${capitalize(field.name)}AmountMicros`] = {
@ -163,7 +163,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.max,
aggregateOperation: AggregateOperations.MAX,
};
acc[`sum${capitalize(field.name)}AmountMicros`] = {
@ -172,7 +172,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.sum,
aggregateOperation: AggregateOperations.SUM,
};
acc[`avg${capitalize(field.name)}AmountMicros`] = {
@ -181,7 +181,7 @@ export const getAvailableAggregationsFromObjectFields = (
fromField: field.name,
fromSubFields: getSubfieldsForAggregateOperation(field.type),
fromFieldType: field.type,
aggregateOperation: AGGREGATE_OPERATIONS.avg,
aggregateOperation: AggregateOperations.AVG,
};
break;
}
@ -194,7 +194,7 @@ export const getAvailableAggregationsFromObjectFields = (
description: `Total number of records in the connection`,
fromField: FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION,
fromFieldType: FieldMetadataType.UUID,
aggregateOperation: AGGREGATE_OPERATIONS.count,
aggregateOperation: AggregateOperations.COUNT,
},
},
);

View File

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

View File

@ -1,7 +1,7 @@
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 { 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 { TurnstileDriver } from 'src/engine/core-modules/captcha/drivers/turnstile.driver';
import {
@ -23,9 +23,9 @@ export class CaptchaModule {
}
switch (config.type) {
case CaptchaDriverType.GoogleRecaptcha:
case CaptchaDriverType.GOOGLE_RECAPTCHA:
return new GoogleRecaptchaDriver(config.options);
case CaptchaDriverType.Turnstile:
case CaptchaDriverType.TURNSTILE:
return new TurnstileDriver(config.options);
default:
return;

View File

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

View File

@ -61,13 +61,13 @@ describe('ClientConfigService', () => {
IS_MULTIWORKSPACE_ENABLED: true,
IS_EMAIL_VERIFICATION_REQUIRED: true,
DEFAULT_SUBDOMAIN: 'app',
NODE_ENV: NodeEnvironment.development,
SUPPORT_DRIVER: SupportDriver.Front,
NODE_ENV: NodeEnvironment.DEVELOPMENT,
SUPPORT_DRIVER: SupportDriver.FRONT,
SUPPORT_FRONT_CHAT_ID: 'chat-123',
SENTRY_ENVIRONMENT: 'development',
APP_VERSION: '1.0.0',
SENTRY_FRONT_DSN: 'https://sentry.example.com',
CAPTCHA_DRIVER: CaptchaDriverType.GoogleRecaptcha,
CAPTCHA_DRIVER: CaptchaDriverType.GOOGLE_RECAPTCHA,
CAPTCHA_SITE_KEY: 'site-key-123',
CHROME_EXTENSION_ID: 'extension-123',
MUTATION_MAXIMUM_AFFECTED_RECORDS: 1000,
@ -120,7 +120,7 @@ describe('ClientConfigService', () => {
frontDomain: 'app.twenty.com',
debugMode: true,
support: {
supportDriver: 'Front',
supportDriver: 'FRONT',
supportFrontChatId: 'chat-123',
},
sentry: {
@ -129,7 +129,7 @@ describe('ClientConfigService', () => {
dsn: 'https://sentry.example.com',
},
captcha: {
provider: 'GoogleRecaptcha',
provider: 'GOOGLE_RECAPTCHA',
siteKey: 'site-key-123',
},
chromeExtensionId: 'extension-123',
@ -152,7 +152,7 @@ describe('ClientConfigService', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => {
if (key === 'NODE_ENV') return NodeEnvironment.production;
if (key === 'NODE_ENV') return NodeEnvironment.PRODUCTION;
if (key === 'IS_BILLING_ENABLED') return false;
return undefined;
@ -191,14 +191,14 @@ describe('ClientConfigService', () => {
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 () => {
jest
.spyOn(twentyConfigService, 'get')
.mockImplementation((key: string) => {
if (key === 'NODE_ENV') return NodeEnvironment.production;
if (key === 'NODE_ENV') return NodeEnvironment.PRODUCTION;
if (key === 'IS_BILLING_ENABLED') return true;
return undefined;
@ -209,44 +209,4 @@ describe('ClientConfigService', () => {
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 { 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 { 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';
@ -57,11 +56,9 @@ export class ClientConfigService {
frontDomain: this.domainManagerService.getFrontUrl().hostname,
debugMode:
this.twentyConfigService.get('NODE_ENV') ===
NodeEnvironment.development,
NodeEnvironment.DEVELOPMENT,
support: {
supportDriver: supportDriver
? this.transformEnum(supportDriver, SupportDriver)
: SupportDriver.None,
supportDriver: supportDriver ? supportDriver : SupportDriver.NONE,
supportFrontChatId: this.twentyConfigService.get(
'SUPPORT_FRONT_CHAT_ID',
),
@ -72,9 +69,7 @@ export class ClientConfigService {
dsn: this.twentyConfigService.get('SENTRY_FRONT_DSN'),
},
captcha: {
provider: captchaProvider
? this.transformEnum(captchaProvider, CaptchaDriverType)
: undefined,
provider: captchaProvider ? captchaProvider : undefined,
siteKey: this.twentyConfigService.get('CAPTCHA_SITE_KEY'),
},
chromeExtensionId: this.twentyConfigService.get('CHROME_EXTENSION_ID'),
@ -89,7 +84,7 @@ export class ClientConfigService {
analyticsEnabled: this.twentyConfigService.get('ANALYTICS_ENABLED'),
canManageFeatureFlags:
this.twentyConfigService.get('NODE_ENV') ===
NodeEnvironment.development ||
NodeEnvironment.DEVELOPMENT ||
this.twentyConfigService.get('IS_BILLING_ENABLED'),
publicFeatureFlags: PUBLIC_FEATURE_FLAGS,
isMicrosoftMessagingEnabled: this.twentyConfigService.get(
@ -111,34 +106,4 @@ export class ClientConfigService {
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', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger);
.mockReturnValue(EmailDriver.LOGGER);
const result = factory['buildConfigKey']();
@ -42,7 +42,7 @@ describe('EmailDriverFactory', () => {
});
it('should return smtp config key for smtp driver', () => {
jest.spyOn(twentyConfigService, 'get').mockReturnValue(EmailDriver.Smtp);
jest.spyOn(twentyConfigService, 'get').mockReturnValue(EmailDriver.SMTP);
jest
.spyOn(factory as any, 'getConfigGroupHash')
.mockReturnValue('smtp-hash-123');
@ -66,7 +66,7 @@ describe('EmailDriverFactory', () => {
it('should create logger driver', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger);
.mockReturnValue(EmailDriver.LOGGER);
const driver = factory['createDriver']();
@ -80,7 +80,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => {
switch (key) {
case 'EMAIL_DRIVER':
return EmailDriver.Smtp;
return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST':
return 'smtp.example.com';
case 'EMAIL_SMTP_PORT':
@ -108,7 +108,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => {
switch (key) {
case 'EMAIL_DRIVER':
return EmailDriver.Smtp;
return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST':
return undefined;
case 'EMAIL_SMTP_PORT':
@ -136,7 +136,7 @@ describe('EmailDriverFactory', () => {
it('should return current driver for logger', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger);
.mockReturnValue(EmailDriver.LOGGER);
const driver = factory.getCurrentDriver();
@ -147,7 +147,7 @@ describe('EmailDriverFactory', () => {
it('should reuse driver when config key unchanged', () => {
jest
.spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger);
.mockReturnValue(EmailDriver.LOGGER);
const driver1 = factory.getCurrentDriver();
const driver2 = factory.getCurrentDriver();
@ -159,7 +159,7 @@ describe('EmailDriverFactory', () => {
// First call with logger
jest
.spyOn(twentyConfigService, 'get')
.mockReturnValue(EmailDriver.Logger);
.mockReturnValue(EmailDriver.LOGGER);
const driver1 = factory.getCurrentDriver();
@ -169,7 +169,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => {
switch (key) {
case 'EMAIL_DRIVER':
return EmailDriver.Smtp;
return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST':
return 'smtp.example.com';
case 'EMAIL_SMTP_PORT':
@ -203,7 +203,7 @@ describe('EmailDriverFactory', () => {
.mockImplementation((key: string) => {
switch (key) {
case 'EMAIL_DRIVER':
return EmailDriver.Smtp;
return EmailDriver.SMTP;
case 'EMAIL_SMTP_HOST':
return 'smtp.example.com';
case 'EMAIL_SMTP_PORT':

View File

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

View File

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

View File

@ -19,21 +19,21 @@ export const exceptionHandlerModuleFactory = async (
const driverType = twentyConfigService.get('EXCEPTION_HANDLER_DRIVER');
switch (driverType) {
case ExceptionHandlerDriver.Console: {
case ExceptionHandlerDriver.CONSOLE: {
return {
type: ExceptionHandlerDriver.Console,
type: ExceptionHandlerDriver.CONSOLE,
};
}
case ExceptionHandlerDriver.Sentry: {
case ExceptionHandlerDriver.SENTRY: {
return {
type: ExceptionHandlerDriver.Sentry,
type: ExceptionHandlerDriver.SENTRY,
options: {
environment: twentyConfigService.get('SENTRY_ENVIRONMENT'),
release: twentyConfigService.get('APP_VERSION'),
dsn: twentyConfigService.get('SENTRY_DSN') ?? '',
serverInstance: adapterHost.httpAdapter?.getInstance(),
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 = {
provide: EXCEPTION_HANDLER_DRIVER,
useValue:
options.type === ExceptionHandlerDriver.Console
options.type === ExceptionHandlerDriver.CONSOLE
? new ExceptionHandlerConsoleDriver()
: new ExceptionHandlerSentryDriver(),
};
@ -45,7 +45,7 @@ export class ExceptionHandlerModule extends ConfigurableModuleClass {
return null;
}
return config.type === ExceptionHandlerDriver.Console
return config.type === ExceptionHandlerDriver.CONSOLE
? new ExceptionHandlerConsoleDriver()
: new ExceptionHandlerSentryDriver();
},

View File

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

View File

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

View File

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

View File

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

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