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:
@ -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']>;
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -20,7 +20,7 @@ export const ActionMenuContextProvider = ({
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const isWorkflowEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
);
|
||||
|
||||
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
|
||||
|
||||
@ -59,7 +59,7 @@ export const useShouldActionBeRegisteredParams = ({
|
||||
ContextStoreViewType.ShowPage;
|
||||
|
||||
const isWorkflowEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
);
|
||||
|
||||
const numberOfSelectedRecords = useRecoilComponentValueV2(
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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 <></>;
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ export const useFilteredObjectMetadataItems = () => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const isWorkflowEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
);
|
||||
|
||||
const isWorkflowToBeFiltered = useCallback(
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
}),
|
||||
|
||||
@ -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],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -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,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}`);
|
||||
|
||||
@ -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}`);
|
||||
|
||||
@ -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: [],
|
||||
},
|
||||
|
||||
@ -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],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum DATE_AGGREGATE_OPERATIONS {
|
||||
earliest = 'EARLIEST',
|
||||
latest = 'LATEST',
|
||||
export enum DateAggregateOperations {
|
||||
EARLIEST = 'EARLIEST',
|
||||
LATEST = 'LATEST',
|
||||
}
|
||||
|
||||
@ -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,
|
||||
],
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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],
|
||||
};
|
||||
};
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
>;
|
||||
|
||||
@ -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',
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -23,7 +23,7 @@ export const SettingsRolePermissions = ({
|
||||
isCreateMode,
|
||||
}: SettingsRolePermissionsProps) => {
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -45,7 +45,7 @@ export const SettingsRolePermissionsSettingsSection = ({
|
||||
isEditable,
|
||||
}: SettingsRolePermissionsSettingsSectionProps) => {
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
);
|
||||
|
||||
const [settingsDraftRole, setSettingsDraftRole] = useRecoilState(
|
||||
|
||||
@ -54,7 +54,7 @@ export const SettingsRolePermissionsSettingsTableRow = ({
|
||||
settingsDraftRoleFamilyState(roleId),
|
||||
);
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
);
|
||||
const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings;
|
||||
|
||||
|
||||
@ -60,7 +60,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => {
|
||||
);
|
||||
|
||||
const isPermissionsV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
);
|
||||
|
||||
const navigateSettings = useNavigateSettings();
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
});
|
||||
});
|
||||
|
||||
@ -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],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -40,7 +40,7 @@ export const mockedClientConfig: ClientConfig = {
|
||||
],
|
||||
},
|
||||
captcha: {
|
||||
provider: CaptchaDriverType.GoogleRecaptcha,
|
||||
provider: CaptchaDriverType.GOOGLE_RECAPTCHA,
|
||||
siteKey: 'MOCKED_SITE_KEY',
|
||||
},
|
||||
api: { mutationMaximumAffectedRecords: 100 },
|
||||
|
||||
@ -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,
|
||||
},
|
||||
],
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
@ -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}`,
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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' });
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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,
|
||||
{
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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(', ')}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum EmailDriver {
|
||||
Logger = 'logger',
|
||||
Smtp = 'smtp',
|
||||
LOGGER = 'LOGGER',
|
||||
SMTP = 'SMTP',
|
||||
}
|
||||
|
||||
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -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();
|
||||
},
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -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
|
||||
// },
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
@ -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
Reference in New Issue
Block a user