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:
@ -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,
|
||||
|
||||
@ -99,12 +99,9 @@ export class FeatureFlagService {
|
||||
),
|
||||
);
|
||||
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
const featureFlagKey = FeatureFlagKey[featureFlag];
|
||||
|
||||
if (shouldBePublic) {
|
||||
publicFeatureFlagValidator.assertIsPublicFeatureFlag(
|
||||
featureFlagKey,
|
||||
featureFlag,
|
||||
new FeatureFlagException(
|
||||
'Invalid feature flag key, flag is not public',
|
||||
FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY,
|
||||
@ -114,7 +111,7 @@ export class FeatureFlagService {
|
||||
|
||||
const existingFeatureFlag = await this.featureFlagRepository.findOne({
|
||||
where: {
|
||||
key: featureFlagKey,
|
||||
key: featureFlag,
|
||||
workspaceId: workspaceId,
|
||||
},
|
||||
});
|
||||
@ -125,7 +122,7 @@ export class FeatureFlagService {
|
||||
value,
|
||||
}
|
||||
: {
|
||||
key: featureFlagKey,
|
||||
key: featureFlag,
|
||||
value,
|
||||
workspaceId: workspaceId,
|
||||
};
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate';
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
describe('featureFlagValidator', () => {
|
||||
describe('assertIsFeatureFlagKey', () => {
|
||||
it('should not throw error if featureFlagKey is valid', () => {
|
||||
expect(() =>
|
||||
featureFlagValidator.assertIsFeatureFlagKey(
|
||||
'IsWorkflowEnabled',
|
||||
'IS_WORKFLOW_ENABLED',
|
||||
new CustomException('Error', 'Error'),
|
||||
),
|
||||
).not.toThrow();
|
||||
|
||||
@ -37,7 +37,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -55,7 +55,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
it('should build config key for S3 storage', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockReturnValue(StorageDriverType.S3);
|
||||
.mockReturnValue(StorageDriverType.S_3);
|
||||
jest
|
||||
.spyOn(factory as any, 'getConfigGroupHash')
|
||||
.mockReturnValue('s3-hash-123');
|
||||
@ -84,7 +84,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -102,7 +102,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'STORAGE_TYPE':
|
||||
return StorageDriverType.S3;
|
||||
return StorageDriverType.S_3;
|
||||
case 'STORAGE_S3_NAME':
|
||||
return 'test-bucket';
|
||||
case 'STORAGE_S3_ENDPOINT':
|
||||
@ -130,7 +130,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'STORAGE_TYPE':
|
||||
return StorageDriverType.S3;
|
||||
return StorageDriverType.S_3;
|
||||
case 'STORAGE_S3_NAME':
|
||||
return 'test-bucket';
|
||||
case 'STORAGE_S3_ENDPOINT':
|
||||
@ -168,7 +168,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -186,7 +186,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
@ -203,7 +203,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return '/tmp/storage1';
|
||||
|
||||
return undefined;
|
||||
@ -215,7 +215,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return '/tmp/storage2';
|
||||
|
||||
return undefined;
|
||||
@ -233,7 +233,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return '/tmp/storage';
|
||||
|
||||
return undefined;
|
||||
@ -247,7 +247,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
.mockImplementation((key: string) => {
|
||||
switch (key) {
|
||||
case 'STORAGE_TYPE':
|
||||
return StorageDriverType.S3;
|
||||
return StorageDriverType.S_3;
|
||||
case 'STORAGE_S3_NAME':
|
||||
return 'test-bucket';
|
||||
case 'STORAGE_S3_ENDPOINT':
|
||||
@ -285,7 +285,7 @@ describe('FileStorageDriverFactory', () => {
|
||||
jest
|
||||
.spyOn(twentyConfigService, 'get')
|
||||
.mockImplementation((key: string) => {
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.Local;
|
||||
if (key === 'STORAGE_TYPE') return StorageDriverType.LOCAL;
|
||||
if (key === 'STORAGE_LOCAL_PATH') return storagePath;
|
||||
|
||||
return undefined;
|
||||
|
||||
@ -21,13 +21,13 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
|
||||
protected buildConfigKey(): string {
|
||||
const storageType = this.twentyConfigService.get('STORAGE_TYPE');
|
||||
|
||||
if (storageType === StorageDriverType.Local) {
|
||||
if (storageType === StorageDriverType.LOCAL) {
|
||||
const storagePath = this.twentyConfigService.get('STORAGE_LOCAL_PATH');
|
||||
|
||||
return `local|${storagePath}`;
|
||||
}
|
||||
|
||||
if (storageType === StorageDriverType.S3) {
|
||||
if (storageType === StorageDriverType.S_3) {
|
||||
const storageConfigHash = this.getConfigGroupHash(
|
||||
ConfigVariablesGroup.StorageConfig,
|
||||
);
|
||||
@ -42,7 +42,7 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
|
||||
const storageType = this.twentyConfigService.get('STORAGE_TYPE');
|
||||
|
||||
switch (storageType) {
|
||||
case StorageDriverType.Local: {
|
||||
case StorageDriverType.LOCAL: {
|
||||
const storagePath = this.twentyConfigService.get('STORAGE_LOCAL_PATH');
|
||||
|
||||
return new LocalDriver({
|
||||
@ -50,7 +50,7 @@ export class FileStorageDriverFactory extends DriverFactoryBase<StorageDriver> {
|
||||
});
|
||||
}
|
||||
|
||||
case StorageDriverType.S3: {
|
||||
case StorageDriverType.S_3: {
|
||||
const bucketName = this.twentyConfigService.get('STORAGE_S3_NAME');
|
||||
const endpoint = this.twentyConfigService.get('STORAGE_S3_ENDPOINT');
|
||||
const region = this.twentyConfigService.get('STORAGE_S3_REGION');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum StorageDriverType {
|
||||
S3 = 's3',
|
||||
Local = 'local',
|
||||
S_3 = 'S_3',
|
||||
LOCAL = 'LOCAL',
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { ModuleMetadata, FactoryProvider } from '@nestjs/common';
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
export enum LLMChatModelDriver {
|
||||
OpenAI = 'openai',
|
||||
OPENAI = 'OPENAI',
|
||||
}
|
||||
|
||||
export interface LLMChatModelModuleOptions {
|
||||
|
||||
@ -8,8 +8,8 @@ export const llmChatModelModuleFactory = (
|
||||
const driver = twentyConfigService.get('LLM_CHAT_MODEL_DRIVER');
|
||||
|
||||
switch (driver) {
|
||||
case LLMChatModelDriver.OpenAI: {
|
||||
return { type: LLMChatModelDriver.OpenAI };
|
||||
case LLMChatModelDriver.OPENAI: {
|
||||
return { type: LLMChatModelDriver.OPENAI };
|
||||
}
|
||||
default:
|
||||
// `No LLM chat model driver (${driver})`);
|
||||
|
||||
@ -5,8 +5,8 @@ import {
|
||||
LLMChatModelModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/llm-chat-model/interfaces/llm-chat-model.interface';
|
||||
|
||||
import { LLM_CHAT_MODEL_DRIVER } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.constants';
|
||||
import { OpenAIDriver } from 'src/engine/core-modules/llm-chat-model/drivers/openai.driver';
|
||||
import { LLM_CHAT_MODEL_DRIVER } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.constants';
|
||||
import { LLMChatModelService } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.service';
|
||||
|
||||
@Global()
|
||||
@ -19,7 +19,7 @@ export class LLMChatModelModule {
|
||||
const config = options.useFactory(...args);
|
||||
|
||||
switch (config?.type) {
|
||||
case LLMChatModelDriver.OpenAI: {
|
||||
case LLMChatModelDriver.OPENAI: {
|
||||
return new OpenAIDriver();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,19 @@
|
||||
import { ModuleMetadata, FactoryProvider } from '@nestjs/common';
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
import { LangfuseDriverOptions } from 'src/engine/core-modules/llm-tracing/drivers/langfuse.driver';
|
||||
|
||||
export enum LLMTracingDriver {
|
||||
Langfuse = 'langfuse',
|
||||
Console = 'console',
|
||||
LANGFUSE = 'LANGFUSE',
|
||||
CONSOLE = 'CONSOLE',
|
||||
}
|
||||
|
||||
export interface LangfuseDriverFactoryOptions {
|
||||
type: LLMTracingDriver.Langfuse;
|
||||
type: LLMTracingDriver.LANGFUSE;
|
||||
options: LangfuseDriverOptions;
|
||||
}
|
||||
|
||||
export interface ConsoleDriverFactoryOptions {
|
||||
type: LLMTracingDriver.Console;
|
||||
type: LLMTracingDriver.CONSOLE;
|
||||
}
|
||||
|
||||
export type LLMTracingModuleOptions =
|
||||
|
||||
@ -8,10 +8,10 @@ export const llmTracingModuleFactory = (
|
||||
const driver = twentyConfigService.get('LLM_TRACING_DRIVER');
|
||||
|
||||
switch (driver) {
|
||||
case LLMTracingDriver.Console: {
|
||||
return { type: LLMTracingDriver.Console as const };
|
||||
case LLMTracingDriver.CONSOLE: {
|
||||
return { type: LLMTracingDriver.CONSOLE as const };
|
||||
}
|
||||
case LLMTracingDriver.Langfuse: {
|
||||
case LLMTracingDriver.LANGFUSE: {
|
||||
const secretKey = twentyConfigService.get('LANGFUSE_SECRET_KEY');
|
||||
const publicKey = twentyConfigService.get('LANGFUSE_PUBLIC_KEY');
|
||||
|
||||
@ -22,7 +22,7 @@ export const llmTracingModuleFactory = (
|
||||
}
|
||||
|
||||
return {
|
||||
type: LLMTracingDriver.Langfuse as const,
|
||||
type: LLMTracingDriver.LANGFUSE as const,
|
||||
options: { secretKey, publicKey },
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { Global, DynamicModule } from '@nestjs/common';
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
LLMTracingModuleAsyncOptions,
|
||||
LLMTracingDriver,
|
||||
LLMTracingModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/llm-tracing/interfaces/llm-tracing.interface';
|
||||
|
||||
import { LangfuseDriver } from 'src/engine/core-modules/llm-tracing/drivers/langfuse.driver';
|
||||
import { ConsoleDriver } from 'src/engine/core-modules/llm-tracing/drivers/console.driver';
|
||||
import { LLMTracingService } from 'src/engine/core-modules/llm-tracing/llm-tracing.service';
|
||||
import { LangfuseDriver } from 'src/engine/core-modules/llm-tracing/drivers/langfuse.driver';
|
||||
import { LLM_TRACING_DRIVER } from 'src/engine/core-modules/llm-tracing/llm-tracing.constants';
|
||||
import { LLMTracingService } from 'src/engine/core-modules/llm-tracing/llm-tracing.service';
|
||||
|
||||
@Global()
|
||||
export class LLMTracingModule {
|
||||
@ -20,10 +20,10 @@ export class LLMTracingModule {
|
||||
const config = options.useFactory(...args);
|
||||
|
||||
switch (config.type) {
|
||||
case LLMTracingDriver.Langfuse: {
|
||||
case LLMTracingDriver.LANGFUSE: {
|
||||
return new LangfuseDriver(config.options);
|
||||
}
|
||||
case LLMTracingDriver.Console: {
|
||||
case LLMTracingDriver.CONSOLE: {
|
||||
return new ConsoleDriver();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { LogLevel } from '@nestjs/common';
|
||||
|
||||
export enum LoggerDriverType {
|
||||
Console = 'console',
|
||||
CONSOLE = 'CONSOLE',
|
||||
}
|
||||
|
||||
export interface ConsoleDriverFactoryOptions {
|
||||
type: LoggerDriverType.Console;
|
||||
type: LoggerDriverType.CONSOLE;
|
||||
logLevels?: LogLevel[];
|
||||
}
|
||||
|
||||
|
||||
@ -16,9 +16,9 @@ export const loggerModuleFactory = async (
|
||||
const logLevels = twentyConfigService.get('LOG_LEVELS');
|
||||
|
||||
switch (driverType) {
|
||||
case LoggerDriverType.Console: {
|
||||
case LoggerDriverType.CONSOLE: {
|
||||
return {
|
||||
type: LoggerDriverType.Console,
|
||||
type: LoggerDriverType.CONSOLE,
|
||||
logLevels: logLevels,
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export class LoggerModule extends ConfigurableModuleClass {
|
||||
const provider = {
|
||||
provide: LOGGER_DRIVER,
|
||||
useValue:
|
||||
options.type === LoggerDriverType.Console
|
||||
options.type === LoggerDriverType.CONSOLE
|
||||
? new ConsoleLogger()
|
||||
: undefined,
|
||||
};
|
||||
@ -45,7 +45,7 @@ export class LoggerModule extends ConfigurableModuleClass {
|
||||
const logLevels = config.logLevels ?? [];
|
||||
|
||||
const logger =
|
||||
config?.type === LoggerDriverType.Console
|
||||
config?.type === LoggerDriverType.CONSOLE
|
||||
? new ConsoleLogger()
|
||||
: undefined;
|
||||
|
||||
|
||||
@ -15,13 +15,13 @@ export const serverlessModuleFactory = async (
|
||||
const options = { fileStorageService };
|
||||
|
||||
switch (driverType) {
|
||||
case ServerlessDriverType.Local: {
|
||||
case ServerlessDriverType.LOCAL: {
|
||||
return {
|
||||
type: ServerlessDriverType.Local,
|
||||
type: ServerlessDriverType.LOCAL,
|
||||
options,
|
||||
};
|
||||
}
|
||||
case ServerlessDriverType.Lambda: {
|
||||
case ServerlessDriverType.LAMBDA: {
|
||||
const region = twentyConfigService.get('SERVERLESS_LAMBDA_REGION');
|
||||
const accessKeyId = twentyConfigService.get(
|
||||
'SERVERLESS_LAMBDA_ACCESS_KEY_ID',
|
||||
@ -36,7 +36,7 @@ export const serverlessModuleFactory = async (
|
||||
);
|
||||
|
||||
return {
|
||||
type: ServerlessDriverType.Lambda,
|
||||
type: ServerlessDriverType.LAMBDA,
|
||||
options: {
|
||||
...options,
|
||||
credentials: accessKeyId
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { FactoryProvider, ModuleMetadata } from '@nestjs/common';
|
||||
|
||||
import { LocalDriverOptions } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
import { LambdaDriverOptions } from 'src/engine/core-modules/serverless/drivers/lambda.driver';
|
||||
import { LocalDriverOptions } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
|
||||
export enum ServerlessDriverType {
|
||||
Lambda = 'lambda',
|
||||
Local = 'local',
|
||||
LAMBDA = 'LAMBDA',
|
||||
LOCAL = 'LOCAL',
|
||||
}
|
||||
|
||||
export interface LocalDriverFactoryOptions {
|
||||
type: ServerlessDriverType.Local;
|
||||
type: ServerlessDriverType.LOCAL;
|
||||
options: LocalDriverOptions;
|
||||
}
|
||||
|
||||
export interface LambdaDriverFactoryOptions {
|
||||
type: ServerlessDriverType.Lambda;
|
||||
type: ServerlessDriverType.LAMBDA;
|
||||
options: LambdaDriverOptions;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { DynamicModule, Global } from '@nestjs/common';
|
||||
|
||||
import { AddPackagesCommand } from 'src/engine/core-modules/serverless/commands/add-packages.command';
|
||||
import { LambdaDriver } from 'src/engine/core-modules/serverless/drivers/lambda.driver';
|
||||
import { LocalDriver } from 'src/engine/core-modules/serverless/drivers/local.driver';
|
||||
import { SERVERLESS_DRIVER } from 'src/engine/core-modules/serverless/serverless.constants';
|
||||
@ -8,7 +9,6 @@ import {
|
||||
ServerlessModuleAsyncOptions,
|
||||
} from 'src/engine/core-modules/serverless/serverless.interface';
|
||||
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
|
||||
import { AddPackagesCommand } from 'src/engine/core-modules/serverless/commands/add-packages.command';
|
||||
|
||||
@Global()
|
||||
export class ServerlessModule {
|
||||
@ -19,7 +19,7 @@ export class ServerlessModule {
|
||||
useFactory: async (...args: any[]) => {
|
||||
const config = await options.useFactory(...args);
|
||||
|
||||
return config?.type === ServerlessDriverType.Local
|
||||
return config?.type === ServerlessDriverType.LOCAL
|
||||
? new LocalDriver(config.options)
|
||||
: new LambdaDriver(config.options);
|
||||
},
|
||||
|
||||
@ -27,6 +27,7 @@ import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverl
|
||||
import { CastToLogLevelArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-log-level-array.decorator';
|
||||
import { CastToMeterDriverArray } from 'src/engine/core-modules/twenty-config/decorators/cast-to-meter-driver.decorator';
|
||||
import { CastToPositiveNumber } from 'src/engine/core-modules/twenty-config/decorators/cast-to-positive-number.decorator';
|
||||
import { CastToUpperSnakeCase } from 'src/engine/core-modules/twenty-config/decorators/cast-to-upper-snake-case.decorator';
|
||||
import { ConfigVariablesMetadata } from 'src/engine/core-modules/twenty-config/decorators/config-variables-metadata.decorator';
|
||||
import { IsAWSRegion } from 'src/engine/core-modules/twenty-config/decorators/is-aws-region.decorator';
|
||||
import { IsDuration } from 'src/engine/core-modules/twenty-config/decorators/is-duration.decorator';
|
||||
@ -301,7 +302,8 @@ export class ConfigVariables {
|
||||
type: ConfigVariableType.ENUM,
|
||||
options: Object.values(EmailDriver),
|
||||
})
|
||||
EMAIL_DRIVER: EmailDriver = EmailDriver.Logger;
|
||||
@CastToUpperSnakeCase()
|
||||
EMAIL_DRIVER: EmailDriver = EmailDriver.LOGGER;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.EmailSettings,
|
||||
@ -349,14 +351,15 @@ export class ConfigVariables {
|
||||
options: Object.values(StorageDriverType),
|
||||
})
|
||||
@IsOptional()
|
||||
STORAGE_TYPE: StorageDriverType = StorageDriverType.Local;
|
||||
@CastToUpperSnakeCase()
|
||||
STORAGE_TYPE: StorageDriverType = StorageDriverType.LOCAL;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.StorageConfig,
|
||||
description: 'Local path for storage when using local storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.Local)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.LOCAL)
|
||||
STORAGE_LOCAL_PATH = '.local-storage';
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -364,7 +367,7 @@ export class ConfigVariables {
|
||||
description: 'S3 region for storage when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsAWSRegion()
|
||||
STORAGE_S3_REGION: AwsRegion;
|
||||
|
||||
@ -373,7 +376,7 @@ export class ConfigVariables {
|
||||
description: 'S3 bucket name for storage when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
STORAGE_S3_NAME: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -381,7 +384,7 @@ export class ConfigVariables {
|
||||
description: 'S3 endpoint for storage when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsOptional()
|
||||
STORAGE_S3_ENDPOINT: string;
|
||||
|
||||
@ -392,7 +395,7 @@ export class ConfigVariables {
|
||||
'S3 access key ID for authentication when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsOptional()
|
||||
STORAGE_S3_ACCESS_KEY_ID: string;
|
||||
|
||||
@ -403,7 +406,7 @@ export class ConfigVariables {
|
||||
'S3 secret access key for authentication when using S3 storage type',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S3)
|
||||
@ValidateIf((env) => env.STORAGE_TYPE === StorageDriverType.S_3)
|
||||
@IsOptional()
|
||||
STORAGE_S3_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ -415,7 +418,8 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
SERVERLESS_TYPE: ServerlessDriverType = ServerlessDriverType.Local;
|
||||
@CastToUpperSnakeCase()
|
||||
SERVERLESS_TYPE: ServerlessDriverType = ServerlessDriverType.LOCAL;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ServerlessConfig,
|
||||
@ -439,7 +443,7 @@ export class ConfigVariables {
|
||||
description: 'Region for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsAWSRegion()
|
||||
SERVERLESS_LAMBDA_REGION: AwsRegion;
|
||||
|
||||
@ -448,7 +452,7 @@ export class ConfigVariables {
|
||||
description: 'IAM role for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
SERVERLESS_LAMBDA_ROLE: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -456,7 +460,7 @@ export class ConfigVariables {
|
||||
description: 'Role to assume when hosting lambdas in dedicated AWS account',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SUBHOSTING_ROLE?: string;
|
||||
|
||||
@ -466,7 +470,7 @@ export class ConfigVariables {
|
||||
description: 'Access key ID for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_ACCESS_KEY_ID: string;
|
||||
|
||||
@ -476,7 +480,7 @@ export class ConfigVariables {
|
||||
description: 'Secret access key for AWS Lambda functions',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.Lambda)
|
||||
@ValidateIf((env) => env.SERVERLESS_TYPE === ServerlessDriverType.LAMBDA)
|
||||
@IsOptional()
|
||||
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY: string;
|
||||
|
||||
@ -634,8 +638,9 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@CastToUpperSnakeCase()
|
||||
EXCEPTION_HANDLER_DRIVER: ExceptionHandlerDriver =
|
||||
ExceptionHandlerDriver.Console;
|
||||
ExceptionHandlerDriver.CONSOLE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.Logging,
|
||||
@ -676,7 +681,8 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
LOGGER_DRIVER: LoggerDriverType = LoggerDriverType.Console;
|
||||
@CastToUpperSnakeCase()
|
||||
LOGGER_DRIVER: LoggerDriverType = LoggerDriverType.CONSOLE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ExceptionHandler,
|
||||
@ -685,7 +691,7 @@ export class ConfigVariables {
|
||||
isSensitive: true,
|
||||
})
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY,
|
||||
)
|
||||
SENTRY_DSN: string;
|
||||
|
||||
@ -696,7 +702,7 @@ export class ConfigVariables {
|
||||
isSensitive: true,
|
||||
})
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY,
|
||||
)
|
||||
SENTRY_FRONT_DSN: string;
|
||||
@ConfigVariablesMetadata({
|
||||
@ -705,7 +711,7 @@ export class ConfigVariables {
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf(
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.Sentry,
|
||||
(env) => env.EXCEPTION_HANDLER_DRIVER === ExceptionHandlerDriver.SENTRY,
|
||||
)
|
||||
@IsOptional()
|
||||
SENTRY_ENVIRONMENT: string;
|
||||
@ -717,7 +723,8 @@ export class ConfigVariables {
|
||||
options: Object.values(SupportDriver),
|
||||
})
|
||||
@IsOptional()
|
||||
SUPPORT_DRIVER: SupportDriver = SupportDriver.None;
|
||||
@CastToUpperSnakeCase()
|
||||
SUPPORT_DRIVER: SupportDriver = SupportDriver.NONE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.SupportChatConfig,
|
||||
@ -725,7 +732,7 @@ export class ConfigVariables {
|
||||
description: 'Chat ID for the support front integration',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.FRONT)
|
||||
SUPPORT_FRONT_CHAT_ID: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -734,7 +741,7 @@ export class ConfigVariables {
|
||||
description: 'HMAC key for the support front integration',
|
||||
type: ConfigVariableType.STRING,
|
||||
})
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.Front)
|
||||
@ValidateIf((env) => env.SUPPORT_DRIVER === SupportDriver.FRONT)
|
||||
SUPPORT_FRONT_HMAC_KEY: string;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -840,7 +847,8 @@ export class ConfigVariables {
|
||||
options: Object.values(NodeEnvironment),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
NODE_ENV: NodeEnvironment = NodeEnvironment.production;
|
||||
// @CastToUpperSnakeCase()
|
||||
NODE_ENV: NodeEnvironment = NodeEnvironment.PRODUCTION;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ServerConfig,
|
||||
@ -949,6 +957,7 @@ export class ConfigVariables {
|
||||
options: Object.values(LLMChatModelDriver),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@CastToUpperSnakeCase()
|
||||
LLM_CHAT_MODEL_DRIVER: LLMChatModelDriver;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
@ -981,7 +990,8 @@ export class ConfigVariables {
|
||||
options: Object.values(LLMTracingDriver),
|
||||
isEnvOnly: true,
|
||||
})
|
||||
LLM_TRACING_DRIVER: LLMTracingDriver = LLMTracingDriver.Console;
|
||||
@CastToUpperSnakeCase()
|
||||
LLM_TRACING_DRIVER: LLMTracingDriver = LLMTracingDriver.CONSOLE;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.ServerConfig,
|
||||
@ -1059,6 +1069,7 @@ export class ConfigVariables {
|
||||
isEnvOnly: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@CastToUpperSnakeCase()
|
||||
CAPTCHA_DRIVER?: CaptchaDriverType;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { plainToClass } from 'class-transformer';
|
||||
|
||||
import { CastToUpperSnakeCase } from 'src/engine/core-modules/twenty-config/decorators/cast-to-upper-snake-case.decorator';
|
||||
|
||||
class TestClass {
|
||||
@CastToUpperSnakeCase()
|
||||
value: string;
|
||||
}
|
||||
|
||||
describe('CastToUpperSnakeCase Decorator', () => {
|
||||
it('should transform lowercase string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'local' });
|
||||
|
||||
expect(result.value).toBe('LOCAL');
|
||||
});
|
||||
|
||||
it('should transform camelCase string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'camelCase' });
|
||||
|
||||
expect(result.value).toBe('CAMEL_CASE');
|
||||
});
|
||||
|
||||
it('should transform kebab-case string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'kebab-case' });
|
||||
|
||||
expect(result.value).toBe('KEBAB_CASE');
|
||||
});
|
||||
|
||||
it('should transform space-separated string to UPPER_SNAKE_CASE', () => {
|
||||
const result = plainToClass(TestClass, { value: 'space separated' });
|
||||
|
||||
expect(result.value).toBe('SPACE_SEPARATED');
|
||||
});
|
||||
|
||||
it('should handle already UPPER_SNAKE_CASE string', () => {
|
||||
const result = plainToClass(TestClass, { value: 'ALREADY_UPPER_SNAKE' });
|
||||
|
||||
expect(result.value).toBe('ALREADY_UPPER_SNAKE');
|
||||
});
|
||||
|
||||
it('should handle mixed case with numbers', () => {
|
||||
const result = plainToClass(TestClass, { value: 'test123Value' });
|
||||
|
||||
expect(result.value).toBe('TEST_123_VALUE');
|
||||
});
|
||||
|
||||
it('should trim whitespace', () => {
|
||||
const result = plainToClass(TestClass, { value: ' local ' });
|
||||
|
||||
expect(result.value).toBe('LOCAL');
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
const result = plainToClass(TestClass, { value: '' });
|
||||
|
||||
expect(result.value).toBe('');
|
||||
});
|
||||
|
||||
it('should return undefined for non-string values', () => {
|
||||
const result = plainToClass(TestClass, { value: 123 });
|
||||
|
||||
expect(result.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for null values', () => {
|
||||
const result = plainToClass(TestClass, { value: null });
|
||||
|
||||
expect(result.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for undefined values', () => {
|
||||
const result = plainToClass(TestClass, { value: undefined });
|
||||
|
||||
expect(result.value).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle complex mixed formats', () => {
|
||||
const result = plainToClass(TestClass, {
|
||||
value: 'Complex-Mixed_Format test123',
|
||||
});
|
||||
|
||||
expect(result.value).toBe('COMPLEX_MIXED_FORMAT_TEST_123');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
|
||||
export const CastToUpperSnakeCase = () =>
|
||||
Transform(({ value }: { value: string }) => toUpperSnakeCase(value));
|
||||
|
||||
const toUpperSnakeCase = (value: unknown): string | undefined => {
|
||||
if (typeof value === 'string') {
|
||||
return snakeCase(value.trim()).toUpperCase();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
export enum NodeEnvironment {
|
||||
test = 'test',
|
||||
development = 'development',
|
||||
production = 'production',
|
||||
TEST = 'test',
|
||||
DEVELOPMENT = 'development',
|
||||
PRODUCTION = 'production',
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export enum SupportDriver {
|
||||
None = 'none',
|
||||
Front = 'front',
|
||||
NONE = 'NONE',
|
||||
FRONT = 'FRONT',
|
||||
}
|
||||
|
||||
@ -201,9 +201,9 @@ export class TwentyConfigService {
|
||||
|
||||
getLoggingConfig(): LoggerOptions {
|
||||
switch (this.get('NODE_ENV')) {
|
||||
case NodeEnvironment.development:
|
||||
case NodeEnvironment.DEVELOPMENT:
|
||||
return ['query', 'error'];
|
||||
case NodeEnvironment.test:
|
||||
case NodeEnvironment.TEST:
|
||||
return [];
|
||||
default:
|
||||
return ['error'];
|
||||
|
||||
@ -123,7 +123,7 @@ export class UserResolver {
|
||||
) {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
@ -334,7 +334,7 @@ export class UserResolver {
|
||||
})
|
||||
supportUserHash(@Parent() parent: User): string | null {
|
||||
if (
|
||||
this.twentyConfigService.get('SUPPORT_DRIVER') !== SupportDriver.Front
|
||||
this.twentyConfigService.get('SUPPORT_DRIVER') !== SupportDriver.FRONT
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ export class PermissionsService {
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
@ -234,7 +234,7 @@ export class PermissionsService {
|
||||
}): Promise<boolean> {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
|
||||
@ -34,9 +34,9 @@ export const validateRemoteServerType = async (
|
||||
const getFeatureFlagKey = (remoteServerType: RemoteServerType) => {
|
||||
switch (remoteServerType) {
|
||||
case RemoteServerType.POSTGRES_FDW:
|
||||
return FeatureFlagKey.IsPostgreSQLIntegrationEnabled;
|
||||
return FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED;
|
||||
case RemoteServerType.STRIPE_FDW:
|
||||
return FeatureFlagKey.IsStripeIntegrationEnabled;
|
||||
return FeatureFlagKey.IS_STRIPE_INTEGRATION_ENABLED;
|
||||
default:
|
||||
throw new RemoteServerException(
|
||||
`Type ${remoteServerType} is not supported.`,
|
||||
|
||||
@ -205,7 +205,7 @@ export class RoleResolver {
|
||||
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
|
||||
@ -11,11 +11,11 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
|
||||
import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-id.input';
|
||||
import { ExecuteServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/execute-serverless-function.input';
|
||||
import { GetServerlessFunctionSourceCodeInput } from 'src/engine/metadata-modules/serverless-function/dtos/get-serverless-function-source-code.input';
|
||||
import { PublishServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/publish-serverless-function.input';
|
||||
import { ServerlessFunctionExecutionResultDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-execution-result.dto';
|
||||
import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-id.input';
|
||||
import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto';
|
||||
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
|
||||
import {
|
||||
@ -37,7 +37,7 @@ export class ServerlessFunctionResolver {
|
||||
async checkFeatureFlag(workspaceId: string) {
|
||||
const isWorkflowEnabled = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKey.IsWorkflowEnabled,
|
||||
key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
value: true,
|
||||
});
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ export class WorkspacePermissionsCacheService {
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
|
||||
@ -42,13 +42,13 @@ describe('WorkspaceEntityManager', () => {
|
||||
idByNameSingular: {},
|
||||
},
|
||||
featureFlagsMap: {
|
||||
[FeatureFlagKey.IsPermissionsV2Enabled]: true,
|
||||
[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]: true,
|
||||
},
|
||||
} as WorkspaceInternalContext;
|
||||
|
||||
mockDataSource = {
|
||||
featureFlagMap: {
|
||||
[FeatureFlagKey.IsPermissionsV2Enabled]: true,
|
||||
[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]: true,
|
||||
},
|
||||
permissionsPerRoleId: {},
|
||||
} as WorkspaceDataSource;
|
||||
@ -118,7 +118,7 @@ describe('WorkspaceEntityManager', () => {
|
||||
|
||||
// Mock getFeatureFlagMap
|
||||
jest.spyOn(entityManager as any, 'getFeatureFlagMap').mockReturnValue({
|
||||
[FeatureFlagKey.IsPermissionsV2Enabled]: true,
|
||||
[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]: true,
|
||||
});
|
||||
|
||||
// Mock typeORM's EntityManager methods
|
||||
|
||||
@ -94,7 +94,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
const featureFlagMap = this.getFeatureFlagMap();
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
|
||||
|
||||
if (permissionOptions?.roleId) {
|
||||
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
||||
@ -159,7 +159,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
const featureFlagMap = this.getFeatureFlagMap();
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return queryBuilder;
|
||||
@ -371,7 +371,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
const featureFlagMap = this.getFeatureFlagMap();
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return;
|
||||
|
||||
@ -67,7 +67,7 @@ export class WorkspaceRepository<
|
||||
queryRunner,
|
||||
) as unknown as WorkspaceSelectQueryBuilder<U>;
|
||||
const isPermissionsV2Enabled =
|
||||
this.featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
this.featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED];
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return queryBuilder;
|
||||
|
||||
@ -137,7 +137,7 @@ const convertHttpExceptionToGraphql = (exception: HttpException) => {
|
||||
}
|
||||
|
||||
// Only show the stack trace in development mode
|
||||
if (process.env.NODE_ENV === NodeEnvironment.development) {
|
||||
if (process.env.NODE_ENV === NodeEnvironment.DEVELOPMENT) {
|
||||
error.stack = exception.stack;
|
||||
error.extensions['response'] = exception.getResponse();
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
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 { ViewOpenRecordInType } from 'src/modules/view/standard-objects/view.workspace-entity';
|
||||
|
||||
export interface ViewDefinition {
|
||||
@ -11,14 +11,14 @@ export interface ViewDefinition {
|
||||
icon?: string;
|
||||
openRecordIn?: ViewOpenRecordInType;
|
||||
kanbanFieldMetadataId?: string;
|
||||
kanbanAggregateOperation?: AGGREGATE_OPERATIONS;
|
||||
kanbanAggregateOperation?: AggregateOperations;
|
||||
kanbanAggregateOperationFieldMetadataId?: string;
|
||||
fields?: {
|
||||
fieldMetadataId: string;
|
||||
position: number;
|
||||
isVisible: boolean;
|
||||
size: number;
|
||||
aggregateOperation?: AGGREGATE_OPERATIONS;
|
||||
aggregateOperation?: AggregateOperations;
|
||||
}[];
|
||||
filters?: {
|
||||
fieldMetadataId: string;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
||||
|
||||
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 {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
COMPANY_STANDARD_FIELD_IDS,
|
||||
@ -38,7 +38,7 @@ export const companiesAllView = (
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.count,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -75,7 +75,7 @@ export const companiesAllView = (
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.max,
|
||||
aggregateOperation: AggregateOperations.MAX,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -85,7 +85,7 @@ export const companiesAllView = (
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.percentageEmpty,
|
||||
aggregateOperation: AggregateOperations.PERCENTAGE_EMPTY,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -95,7 +95,7 @@ export const companiesAllView = (
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.countNotEmpty,
|
||||
aggregateOperation: AggregateOperations.COUNT_NOT_EMPTY,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
||||
|
||||
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 { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
|
||||
@ -33,7 +33,7 @@ export const opportunitiesAllView = (
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.avg,
|
||||
aggregateOperation: AggregateOperations.AVG,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -50,7 +50,7 @@ export const opportunitiesAllView = (
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.min,
|
||||
aggregateOperation: AggregateOperations.MIN,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
||||
|
||||
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 { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
|
||||
@ -19,7 +19,7 @@ export const opportunitiesByStageView = (
|
||||
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.stage
|
||||
],
|
||||
kanbanAggregateOperation: AGGREGATE_OPERATIONS.min,
|
||||
kanbanAggregateOperation: AggregateOperations.MIN,
|
||||
kanbanAggregateOperationFieldMetadataId:
|
||||
objectMetadataStandardIdToIdMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.amount
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
||||
|
||||
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 { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
|
||||
@ -36,7 +36,7 @@ export const opportunitiesTableByStageView = (
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.avg,
|
||||
aggregateOperation: AggregateOperations.AVG,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -53,7 +53,7 @@ export const opportunitiesTableByStageView = (
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.max,
|
||||
aggregateOperation: AggregateOperations.MAX,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -62,7 +62,7 @@ export const opportunitiesTableByStageView = (
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.count,
|
||||
aggregateOperation: AggregateOperations.COUNT,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
|
||||
|
||||
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 {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
PERSON_STANDARD_FIELD_IDS,
|
||||
@ -38,7 +38,7 @@ export const peopleAllView = (
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.countUniqueValues,
|
||||
aggregateOperation: AggregateOperations.COUNT_UNIQUE_VALUES,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -66,7 +66,7 @@ export const peopleAllView = (
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.percentageEmpty,
|
||||
aggregateOperation: AggregateOperations.PERCENTAGE_EMPTY,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
@ -76,7 +76,7 @@ export const peopleAllView = (
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
aggregateOperation: AGGREGATE_OPERATIONS.min,
|
||||
aggregateOperation: AggregateOperations.MIN,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
|
||||
Reference in New Issue
Block a user