1825 extensibility v1 see serverless logs using subscriptions in twnty cli or settings serverkess section (#16321)
- Adds a log section to settings serverless functions test tab <img width="1303" height="827" alt="image" src="https://github.com/user-attachments/assets/2ce70558-91bc-4cfc-aced-42ac9e0226bf" /> - Adds a new subscription endpoint to graphql api `serverlessFunctionLogs` that that emit function logs - Adds a new command `twenty app logs` to `twenty-sdk` to watch logs in terminal <img width="1109" height="182" alt="image" src="https://github.com/user-attachments/assets/2e874060-e99d-4978-ab9c-c91e52cb7478" /> <img width="1061" height="176" alt="image" src="https://github.com/user-attachments/assets/1f533e32-7435-4d7b-89c6-d1154816a8ab" /> - add new version for create-twenty-app and twenty-sdk `0.1.3`
This commit is contained in:
+1
@@ -0,0 +1 @@
|
||||
export const SERVERLESS_FUNCTION_LOGS_TRIGGER = 'serverlessFunctionLogs';
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('ServerlessFunctionLogs')
|
||||
export class ServerlessFunctionLogsDTO {
|
||||
@Field({ description: 'Execution Logs' })
|
||||
logs: string;
|
||||
|
||||
@HideField()
|
||||
applicationUniversalIdentifier?: string;
|
||||
|
||||
@HideField()
|
||||
applicationId?: string;
|
||||
|
||||
@HideField()
|
||||
name?: string;
|
||||
|
||||
@HideField()
|
||||
id?: string;
|
||||
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType('ServerlessFunctionLogsInput')
|
||||
export class ServerlessFunctionLogsInput {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
applicationId?: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
applicationUniversalIdentifier?: string;
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
id?: string;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
universalIdentifier?: string;
|
||||
}
|
||||
+2
@@ -23,6 +23,7 @@ import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverles
|
||||
import { ServerlessFunctionV2Service } from 'src/engine/metadata-modules/serverless-function/services/serverless-function-v2.service';
|
||||
import { WorkspaceFlatServerlessFunctionMapCacheService } from 'src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -43,6 +44,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationV2Module,
|
||||
ServerlessFunctionLayerModule,
|
||||
SubscriptionsModule,
|
||||
],
|
||||
providers: [
|
||||
ServerlessFunctionService,
|
||||
|
||||
+47
-2
@@ -1,9 +1,11 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { Inject, UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
import { Repository } from 'typeorm';
|
||||
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
@@ -24,6 +26,9 @@ import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serve
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
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';
|
||||
import { ServerlessFunctionLogsDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-logs.dto';
|
||||
import { ServerlessFunctionLogsInput } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function-logs.input';
|
||||
import { SERVERLESS_FUNCTION_LOGS_TRIGGER } from 'src/engine/metadata-modules/serverless-function/constants/serverless-function-logs-trigger';
|
||||
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
@@ -38,6 +43,8 @@ export class ServerlessFunctionResolver {
|
||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||
@InjectRepository(ServerlessFunctionEntity)
|
||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
||||
@Inject('PUB_SUB')
|
||||
private readonly pubSub: RedisPubSub,
|
||||
) {}
|
||||
|
||||
@Query(() => ServerlessFunctionDTO)
|
||||
@@ -184,4 +191,42 @@ export class ServerlessFunctionResolver {
|
||||
serverlessFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscription(() => ServerlessFunctionLogsDTO, {
|
||||
filter: (
|
||||
payload: { serverlessFunctionLogs: ServerlessFunctionLogsDTO },
|
||||
variables: { input: ServerlessFunctionLogsInput },
|
||||
) => {
|
||||
const { serverlessFunctionLogs } = payload;
|
||||
const {
|
||||
id,
|
||||
universalIdentifier,
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
name,
|
||||
} = serverlessFunctionLogs;
|
||||
const {
|
||||
id: inputId,
|
||||
universalIdentifier: inputUniversalIdentifier,
|
||||
name: inputName,
|
||||
applicationId: inputApplicationId,
|
||||
applicationUniversalIdentifier: inputApplicationUniversalIdentifier,
|
||||
} = variables.input;
|
||||
|
||||
return (
|
||||
(!isDefined(inputId) || inputId === id) &&
|
||||
(!isDefined(inputUniversalIdentifier) ||
|
||||
inputUniversalIdentifier === universalIdentifier) &&
|
||||
(!isDefined(inputName) || inputName === name) &&
|
||||
(!isDefined(inputApplicationId) ||
|
||||
inputApplicationId === applicationId) &&
|
||||
(!isDefined(inputApplicationUniversalIdentifier) ||
|
||||
inputApplicationUniversalIdentifier ===
|
||||
applicationUniversalIdentifier)
|
||||
);
|
||||
},
|
||||
})
|
||||
serverlessFunctionLogs(@Args('input') _: ServerlessFunctionLogsInput) {
|
||||
return this.pubSub.asyncIterator(SERVERLESS_FUNCTION_LOGS_TRIGGER);
|
||||
}
|
||||
}
|
||||
|
||||
+18
-1
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { join } from 'path';
|
||||
@@ -6,6 +6,7 @@ import { join } from 'path';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
||||
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { type ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface';
|
||||
@@ -31,6 +32,7 @@ import {
|
||||
WorkflowVersionStepException,
|
||||
WorkflowVersionStepExceptionCode,
|
||||
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
|
||||
import { SERVERLESS_FUNCTION_LOGS_TRIGGER } from 'src/engine/metadata-modules/serverless-function/constants/serverless-function-logs-trigger';
|
||||
|
||||
@Injectable()
|
||||
export class ServerlessFunctionService {
|
||||
@@ -43,6 +45,8 @@ export class ServerlessFunctionService {
|
||||
private readonly throttlerService: ThrottlerService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly auditService: AuditService,
|
||||
@Inject('PUB_SUB')
|
||||
private readonly pubSub: RedisPubSub,
|
||||
) {}
|
||||
|
||||
async hasServerlessFunctionPublishedVersion(serverlessFunctionId: string) {
|
||||
@@ -98,6 +102,7 @@ export class ServerlessFunctionService {
|
||||
},
|
||||
relations: [
|
||||
'serverlessFunctionLayer',
|
||||
'application',
|
||||
'application.applicationVariables',
|
||||
],
|
||||
});
|
||||
@@ -113,6 +118,18 @@ export class ServerlessFunctionService {
|
||||
console.log(resultServerlessFunction.logs);
|
||||
}
|
||||
|
||||
await this.pubSub.publish(SERVERLESS_FUNCTION_LOGS_TRIGGER, {
|
||||
serverlessFunctionLogs: {
|
||||
logs: resultServerlessFunction.logs,
|
||||
id: functionToExecute.id,
|
||||
name: functionToExecute.name,
|
||||
universalIdentifier: functionToExecute.universalIdentifier,
|
||||
applicationId: functionToExecute.applicationId,
|
||||
applicationUniversalIdentifier:
|
||||
functionToExecute.application?.universalIdentifier,
|
||||
},
|
||||
});
|
||||
|
||||
this.auditService
|
||||
.createContext({
|
||||
workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user