@ -7,19 +7,11 @@ type FeatureFlagMetadata = {
|
||||
};
|
||||
|
||||
export type PublicFeatureFlag = {
|
||||
key: Extract<FeatureFlagKey, FeatureFlagKey.IS_WORKFLOW_ENABLED>;
|
||||
key: FeatureFlagKey;
|
||||
metadata: FeatureFlagMetadata;
|
||||
};
|
||||
|
||||
export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
|
||||
{
|
||||
key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
metadata: {
|
||||
label: 'Workflows',
|
||||
description: 'Create custom workflows to automate your work.',
|
||||
imagePath: 'https://twenty.com/images/lab/is-workflow-enabled.png',
|
||||
},
|
||||
},
|
||||
...(process.env.CLOUDFLARE_API_KEY
|
||||
? [
|
||||
// {
|
||||
|
||||
@ -2,7 +2,6 @@ export enum FeatureFlagKey {
|
||||
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_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',
|
||||
|
||||
@ -41,7 +41,7 @@ describe('FeatureFlagService', () => {
|
||||
};
|
||||
|
||||
const workspaceId = 'workspace-id';
|
||||
const featureFlag = FeatureFlagKey.IS_WORKFLOW_ENABLED;
|
||||
const featureFlag = FeatureFlagKey.IS_AI_ENABLED;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
@ -130,12 +130,10 @@ describe('FeatureFlagService', () => {
|
||||
// Prepare
|
||||
mockWorkspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMap.mockResolvedValue(
|
||||
{
|
||||
[FeatureFlagKey.IS_WORKFLOW_ENABLED]: true,
|
||||
[FeatureFlagKey.IS_AI_ENABLED]: false,
|
||||
},
|
||||
);
|
||||
const mockFeatureFlags = [
|
||||
{ key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true },
|
||||
{ key: FeatureFlagKey.IS_AI_ENABLED, value: false },
|
||||
];
|
||||
|
||||
@ -154,7 +152,6 @@ describe('FeatureFlagService', () => {
|
||||
it('should return a map of feature flags for a workspace', async () => {
|
||||
// Prepare
|
||||
const mockFeatureFlags = [
|
||||
{ key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true, workspaceId },
|
||||
{ key: FeatureFlagKey.IS_AI_ENABLED, value: false, workspaceId },
|
||||
];
|
||||
|
||||
@ -165,7 +162,6 @@ describe('FeatureFlagService', () => {
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual({
|
||||
[FeatureFlagKey.IS_WORKFLOW_ENABLED]: true,
|
||||
[FeatureFlagKey.IS_AI_ENABLED]: false,
|
||||
});
|
||||
});
|
||||
@ -174,10 +170,7 @@ describe('FeatureFlagService', () => {
|
||||
describe('enableFeatureFlags', () => {
|
||||
it('should enable multiple feature flags for a workspace', async () => {
|
||||
// Prepare
|
||||
const keys = [
|
||||
FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
FeatureFlagKey.IS_AI_ENABLED,
|
||||
];
|
||||
const keys = [FeatureFlagKey.IS_AI_ENABLED];
|
||||
|
||||
mockFeatureFlagRepository.upsert.mockResolvedValue({});
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ describe('featureFlagValidator', () => {
|
||||
it('should not throw error if featureFlagKey is valid', () => {
|
||||
expect(() =>
|
||||
featureFlagValidator.assertIsFeatureFlagKey(
|
||||
'IS_WORKFLOW_ENABLED',
|
||||
'IS_AI_ENABLED',
|
||||
new CustomException('Error', 'Error'),
|
||||
),
|
||||
).not.toThrow();
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { OnModuleDestroy } from '@nestjs/common';
|
||||
|
||||
import { JobsOptions, MetricsTime, Queue, QueueOptions, Worker } from 'bullmq';
|
||||
import { v4 } from 'uuid';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
QueueCronJobOptions,
|
||||
|
||||
@ -1042,7 +1042,7 @@ export class ConfigVariables {
|
||||
type: ConfigVariableType.NUMBER,
|
||||
})
|
||||
@CastToPositiveNumber()
|
||||
WORKFLOW_EXEC_THROTTLE_LIMIT = 100;
|
||||
WORKFLOW_EXEC_THROTTLE_LIMIT = 10;
|
||||
|
||||
@ConfigVariablesMetadata({
|
||||
group: ConfigVariablesGroup.RateLimiting,
|
||||
|
||||
@ -10,7 +10,6 @@ export class ServerlessFunctionException extends CustomException {
|
||||
export enum ServerlessFunctionExceptionCode {
|
||||
SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND',
|
||||
SERVERLESS_FUNCTION_VERSION_NOT_FOUND = 'SERVERLESS_FUNCTION_VERSION_NOT_FOUND',
|
||||
FEATURE_FLAG_INVALID = 'FEATURE_FLAG_INVALID',
|
||||
SERVERLESS_FUNCTION_ALREADY_EXIST = 'SERVERLESS_FUNCTION_ALREADY_EXIST',
|
||||
SERVERLESS_FUNCTION_NOT_READY = 'SERVERLESS_FUNCTION_NOT_READY',
|
||||
SERVERLESS_FUNCTION_BUILDING = 'SERVERLESS_FUNCTION_BUILDING',
|
||||
|
||||
@ -5,7 +5,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
@ -19,10 +18,6 @@ import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverles
|
||||
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 { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import {
|
||||
ServerlessFunctionException,
|
||||
ServerlessFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
|
||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||
import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils';
|
||||
|
||||
@ -37,29 +32,12 @@ export class ServerlessFunctionResolver {
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
) {}
|
||||
|
||||
async checkFeatureFlag(workspaceId: string) {
|
||||
const isWorkflowEnabled = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
value: true,
|
||||
});
|
||||
|
||||
if (!isWorkflowEnabled) {
|
||||
throw new ServerlessFunctionException(
|
||||
`IS_WORKFLOW_ENABLED feature flag is not set to true for this workspace`,
|
||||
ServerlessFunctionExceptionCode.FEATURE_FLAG_INVALID,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => ServerlessFunctionDTO)
|
||||
async findOneServerlessFunction(
|
||||
@Args('input') { id }: ServerlessFunctionIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
@ -76,8 +54,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.findManyServerlessFunctions({
|
||||
workspaceId,
|
||||
});
|
||||
@ -87,13 +63,8 @@ export class ServerlessFunctionResolver {
|
||||
}
|
||||
|
||||
@Query(() => graphqlTypeJson)
|
||||
async getAvailablePackages(
|
||||
@Args('input') { id }: ServerlessFunctionIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
async getAvailablePackages(@Args('input') { id }: ServerlessFunctionIdInput) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.getAvailablePackages(id);
|
||||
} catch (error) {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
@ -106,8 +77,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.getServerlessFunctionSourceCode(
|
||||
workspaceId,
|
||||
input.id,
|
||||
@ -124,8 +93,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.deleteOneServerlessFunction({
|
||||
id: input.id,
|
||||
workspaceId,
|
||||
@ -142,8 +109,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.updateOneServerlessFunction(
|
||||
input,
|
||||
workspaceId,
|
||||
@ -160,8 +125,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
|
||||
return await this.serverlessFunctionService.createOneServerlessFunction(
|
||||
input,
|
||||
workspaceId,
|
||||
@ -177,7 +140,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
const { id, payload, version } = input;
|
||||
|
||||
return await this.serverlessFunctionService.executeOneServerlessFunction(
|
||||
@ -197,7 +159,6 @@ export class ServerlessFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
try {
|
||||
await this.checkFeatureFlag(workspaceId);
|
||||
const { id } = input;
|
||||
|
||||
return await this.serverlessFunctionService.publishOneServerlessFunction(
|
||||
|
||||
@ -19,7 +19,6 @@ export const serverlessFunctionGraphQLApiExceptionHandler = (error: any) => {
|
||||
throw new ConflictError(error.message);
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY:
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING:
|
||||
case ServerlessFunctionExceptionCode.FEATURE_FLAG_INVALID:
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED:
|
||||
throw new ForbiddenError(error.message);
|
||||
case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED:
|
||||
|
||||
@ -30,11 +30,6 @@ export const seedFeatureFlags = async (
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_WORKFLOW_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_UNIQUE_INDEXES_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user