Infer function input in workflow step (#8308)

- add `inputSchema` column in serverless function. This is an array of
parameters, with their name and type
- on serverless function id update, get the `inputSchema` + store empty
settings in step
- from step settings, build the form 

TODO in next PR:
- use field type to decide what kind of form should be printed
- have a strategy to handle object as input



https://github.com/user-attachments/assets/ed96f919-24b5-4baf-a051-31f76f45e575
This commit is contained in:
Thomas Trompette
2024-11-05 14:57:06 +01:00
committed by GitHub
parent d1531aa1b6
commit be8141ce5e
29 changed files with 334 additions and 90 deletions

View File

@ -0,0 +1 @@
export const SERVERLESS_FUNCTION_PUBLISHED = 'serverlessFunction.published';

View File

@ -0,0 +1,14 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { IsString } from 'class-validator';
@ObjectType()
export class FunctionParameter {
@IsString()
@Field(() => String)
name: string;
@IsString()
@Field(() => String)
type: string;
}

View File

@ -20,6 +20,7 @@ import {
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { FunctionParameter } from 'src/engine/metadata-modules/serverless-function/dtos/function-parameter.dto';
import { ServerlessFunctionSyncStatus } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
registerEnumType(ServerlessFunctionSyncStatus, {
@ -64,6 +65,10 @@ export class ServerlessFunctionDTO {
@Field(() => [String], { nullable: false })
publishedVersions: string[];
@IsArray()
@Field(() => [FunctionParameter], { nullable: true })
latestVersionInputSchema: FunctionParameter[] | null;
@IsEnum(ServerlessFunctionSyncStatus)
@IsNotEmpty()
@Field(() => ServerlessFunctionSyncStatus)

View File

@ -0,0 +1,59 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { InjectRepository } from '@nestjs/typeorm';
import { join } from 'path';
import { Repository } from 'typeorm';
import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name';
import { SERVERLESS_FUNCTION_PUBLISHED } from 'src/engine/metadata-modules/serverless-function/constants/serverless-function-published';
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 { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/workspace-event.type';
import { CodeIntrospectionService } from 'src/modules/code-introspection/code-introspection.service';
@Injectable()
export class ServerlessFunctionPublicationListener {
constructor(
private readonly serverlessFunctionService: ServerlessFunctionService,
private readonly codeIntrospectionService: CodeIntrospectionService,
@InjectRepository(ServerlessFunctionEntity, 'metadata')
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
) {}
@OnEvent(SERVERLESS_FUNCTION_PUBLISHED)
async handle(
payload: WorkspaceEventBatch<{
serverlessFunctionId: string;
serverlessFunctionVersion: string;
}>,
): Promise<void> {
payload.events.forEach(async (event) => {
const sourceCode =
await this.serverlessFunctionService.getServerlessFunctionSourceCode(
payload.workspaceId,
event.serverlessFunctionId,
event.serverlessFunctionVersion,
);
if (!sourceCode) {
return;
}
const indexCode = sourceCode[join('src', INDEX_FILE_NAME)];
if (!indexCode) {
return;
}
const latestVersionInputSchema =
await this.codeIntrospectionService.getFunctionInputSchema(indexCode);
await this.serverlessFunctionRepository.update(
{ id: event.serverlessFunctionId },
{ latestVersionInputSchema },
);
});
}
}

View File

@ -6,6 +6,8 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { FunctionParameter } from 'src/engine/metadata-modules/serverless-function/dtos/function-parameter.dto';
export enum ServerlessFunctionSyncStatus {
NOT_READY = 'NOT_READY',
READY = 'READY',
@ -32,6 +34,9 @@ export class ServerlessFunctionEntity {
@Column({ nullable: false, type: 'jsonb', default: [] })
publishedVersions: string[];
@Column({ nullable: true, type: 'jsonb' })
latestVersionInputSchema: FunctionParameter[];
@Column({ nullable: false, default: ServerlessFunctionRuntime.NODE18 })
runtime: ServerlessFunctionRuntime;

View File

@ -8,9 +8,11 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
import { ServerlessFunctionPublicationListener } from 'src/engine/metadata-modules/serverless-function/listeners/serverless-function-publication.listener';
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver';
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
import { CodeIntrospectionModule } from 'src/modules/code-introspection/code-introspection.module';
@Module({
imports: [
@ -20,8 +22,13 @@ import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverles
FileModule,
ThrottlerModule,
AnalyticsModule,
CodeIntrospectionModule,
],
providers: [
ServerlessFunctionService,
ServerlessFunctionResolver,
ServerlessFunctionPublicationListener,
],
providers: [ServerlessFunctionService, ServerlessFunctionResolver],
exports: [ServerlessFunctionService],
})
export class ServerlessFunctionModule {}

View File

@ -21,6 +21,7 @@ import { getLastLayerDependencies } from 'src/engine/core-modules/serverless/dri
import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service';
import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils';
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
import { SERVERLESS_FUNCTION_PUBLISHED } from 'src/engine/metadata-modules/serverless-function/constants/serverless-function-published';
import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input';
import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input';
import {
@ -31,6 +32,7 @@ import {
ServerlessFunctionException,
ServerlessFunctionExceptionCode,
} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { isDefined } from 'src/utils/is-defined';
@Injectable()
@ -43,6 +45,7 @@ export class ServerlessFunctionService {
private readonly throttlerService: ThrottlerService,
private readonly environmentService: EnvironmentService,
private readonly analyticsService: AnalyticsService,
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
) {}
async findManyServerlessFunctions(where) {
@ -191,6 +194,17 @@ export class ServerlessFunctionService {
},
);
this.workspaceEventEmitter.emit(
SERVERLESS_FUNCTION_PUBLISHED,
[
{
serverlessFunctionId: existingServerlessFunction.id,
serverlessFunctionVersion: newVersion,
},
],
workspaceId,
);
return this.serverlessFunctionRepository.findOneBy({
id: existingServerlessFunction.id,
});