diff --git a/.all-contributorsrc b/.all-contributorsrc index a15a8bd05e..5fd0978202 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -845,6 +845,15 @@ "contributions": [ "plugin" ] + }, + { + "login": "Verlich", + "name": "Ahmad(Ed)", + "avatar_url": "https://avatars.githubusercontent.com/u/30838131?v=4", + "profile": "https://github.com/Verlich", + "contributions": [ + "plugin" + ] } ], "commitType": "docs" diff --git a/.gitignore b/.gitignore index 2222a0f23f..de7d319436 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ tmp firebase-admin-sdk.json dev cache +.env # SDK Build builds @@ -23,7 +24,7 @@ node_modules *.launch .settings/ *.sublime-workspace - +cache # IDE - VSCode .vscode/* !.vscode/settings.json @@ -60,3 +61,4 @@ activepieces-engine.js scratch.md # environment variables +.env diff --git a/.husky/commit-msg b/.husky/commit-msg index 5098393f9d..1f85f6c6a7 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -7,4 +7,4 @@ if git diff --cached --name-only -- packages/server/api/.env | grep -q '^package exit 1 fi -npx --no -- commitlint --edit ${1} +npx --no -- commitlint --edit ${1} \ No newline at end of file diff --git a/README.md b/README.md index 5778364136..441dce82a4 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,7 @@ Not into coding but still interested in contributing? Come join our [Discord](ht Olivier Sambourg
Olivier Sambourg

🔌 + Ahmad(Ed)
Ahmad(Ed)

🔌 diff --git a/docker-compose.yml b/docker-compose.yml index d74fa83867..2f9e1c3c0f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3.0' services: activepieces: - image: ghcr.io/activepieces/activepieces:0.20.0 + image: ghcr.io/activepieces/activepieces:0.20.2 container_name: activepieces restart: unless-stopped ## Enable the following line if you already use AP_EXECUTION_MODE with SANDBOXED or old activepieces, checking the breaking change documentation for more info. diff --git a/docs/developers/piece-reference/flow-control.mdx b/docs/developers/piece-reference/flow-control.mdx index c0ab8c2bee..71b2d02c21 100644 --- a/docs/developers/piece-reference/flow-control.mdx +++ b/docs/developers/piece-reference/flow-control.mdx @@ -1,14 +1,14 @@ --- title: 'Flow Control' -icon: 'joystick' -description: 'Learn How to control flow from inside the piece' +icon: 'Joystick' +description: 'Learn How to Control Flow from Inside the Piece' --- Flow Controls provide the ability to control the flow of execution from inside a piece. By using the `ctx` parameter in the `run` method of actions, you can perform various operations to control the flow. ## Stop Flow -You can stop the flow and provide a response to the webhook trigger, This can be useful when you want to terminate the execution of the piece and send a specific response back. +You can stop the flow and provide a response to the webhook trigger. This can be useful when you want to terminate the execution of the piece and send a specific response back. **Example with Response:** @@ -28,7 +28,26 @@ context.run.stop({ context.run.stop(); ``` -## Pause / Delay Flow +## Pause Flow and Wait for Webhook + +You can pause flow and return HTTP response, also provide a callback to URL that you can call with certain payload and continue the flow. + +**Example:** + +```typescript +ctx.run.pause({ + pauseMetadata: { + type: PauseType.WEBHOOK, + response: { + callbackUrl: context.generateResumeUrl({ + queryParams: {}, + }), + }, + }, +}); +``` + +## Pause Flow and Delay You can pause or delay the flow until a specific timestamp. Currently, the only supported type of pause is a delay based on a future timestamp. diff --git a/package-lock.json b/package-lock.json index eb45cdf5ca..367ddb0b8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "activepieces", - "version": "0.20.0", + "version": "0.20.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "activepieces", - "version": "0.20.0", + "version": "0.20.2", "dependencies": { "@angular/animations": "16.2.6", "@angular/cdk": "16.2.6", @@ -22710,9 +22710,9 @@ } }, "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "devOptional": true }, "node_modules/ipaddr.js": { diff --git a/package.json b/package.json index e29e5bf761..1defee48fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "activepieces", - "version": "0.20.0", + "version": "0.20.2", "rcVersion": "0.21.0-rc.0", "scripts": { "prepare": "husky install", diff --git a/packages/ee/components/src/lib/share-flow-template-dialog/share-flow-template-dialog.component.ts b/packages/ee/components/src/lib/share-flow-template-dialog/share-flow-template-dialog.component.ts index 7d12b7b218..39e1023376 100644 --- a/packages/ee/components/src/lib/share-flow-template-dialog/share-flow-template-dialog.component.ts +++ b/packages/ee/components/src/lib/share-flow-template-dialog/share-flow-template-dialog.component.ts @@ -73,7 +73,7 @@ export class ShareFlowTemplateDialogComponent { ) { this.show$ = this.flagsService .getEdition() - .pipe(map((ed) => ed === ApEdition.CLOUD)); + .pipe(map((ed) => ed === ApEdition.ENTERPRISE || ed === ApEdition.CLOUD)); this.isPublicTemplatesProject$ = combineLatest({ templateProjectId: this.flagsService.getAllFlags().pipe( map((flags) => { @@ -96,11 +96,11 @@ export class ShareFlowTemplateDialogComponent { if (!this.loading) { this.loading = true; const request: CreateFlowTemplateRequest = { - description: this.form.value.description, template: this.flow.version, type: TemplateType.PROJECT, blogUrl: this.form.value.blogUrl, tags: this.form.value.tags, + description: this.form.value.description, }; this.telemetryService.capture({ name: TelemetryEventName.FLOW_SHARED, @@ -145,7 +145,6 @@ export class ShareFlowTemplateDialogComponent { tap((template) => { if (template) { this.form.patchValue({ - description: template.description, blogUrl: template.blogUrl, tags: template.tags, }); diff --git a/packages/ee/shared/src/lib/audit-events/index.ts b/packages/ee/shared/src/lib/audit-events/index.ts index c02ed7e3f2..ab40ba70a1 100644 --- a/packages/ee/shared/src/lib/audit-events/index.ts +++ b/packages/ee/shared/src/lib/audit-events/index.ts @@ -1,7 +1,7 @@ import { Static, Type } from "@sinclair/typebox"; -import { BaseModelSchema } from "@activepieces/shared"; +import { BaseModelSchema, FlowOperationRequest, FlowOperationType } from "@activepieces/shared"; export const ListAuditEventsRequest = Type.Object({ - limit: Type.Optional( Type.Number()), + limit: Type.Optional(Type.Number()), cursor: Type.Optional(Type.String()), }) @@ -13,6 +13,7 @@ export enum ApplicationEventName { CREATED_FOLDER = 'CREATED_FOLDER', UPDATED_FOLDER = 'UPDATED_FOLDER', DELETED_FOLDER = 'DELETED_FOLDER', + UPDATED_FLOW = 'UPDATED_FLOW', UPSERTED_CONNECTION = 'UPSERTED_CONNECTION', DELETED_CONNECTION = 'DELETED_CONNECTION', SIGNED_IN = 'SIGNED_IN', @@ -57,7 +58,10 @@ export type FolderEvent = Static export const FlowEvent = Type.Object({ ...BaseAuditEventProps, - action: Type.Union([Type.Literal(ApplicationEventName.CREATED_FLOW), Type.Literal(ApplicationEventName.DELETED_FLOW)]), + action: Type.Union([ + Type.Literal(ApplicationEventName.CREATED_FLOW), + Type.Literal(ApplicationEventName.DELETED_FLOW), + ]), data: Type.Object({ flowId: Type.String(), flowName: Type.String(), @@ -66,6 +70,18 @@ export const FlowEvent = Type.Object({ export type FlowEvent = Static +export const UpdatedFlowEvent = Type.Object({ + ...BaseAuditEventProps, + action: Type.Literal(ApplicationEventName.UPDATED_FLOW), + data: Type.Object({ + flowId: Type.String(), + flowName: Type.String(), + request: FlowOperationRequest + }), +}) + +export type UpdatedFlowEvent = Static + export const AuthenticationEvent = Type.Object({ ...BaseAuditEventProps, action: Type.Union([Type.Literal(ApplicationEventName.SIGNED_UP), Type.Literal(ApplicationEventName.SIGNED_IN), Type.Literal(ApplicationEventName.RESET_PASSWORD), Type.Literal(ApplicationEventName.VERIFIED_EMAIL)]), @@ -79,7 +95,71 @@ export const ApplicationEvent = Type.Union([ FlowEvent, AuthenticationEvent, FolderEvent, + UpdatedFlowEvent, ]) export type ApplicationEvent = Static + +export function summarizeApplicationEvent(event: ApplicationEvent) { + switch (event.action) { + case ApplicationEventName.UPDATED_FLOW: { + return convertUpdateActionToDetails(event); + } + case ApplicationEventName.CREATED_FLOW: + return `${event.data.flowName} is created`; + case ApplicationEventName.DELETED_FLOW: + return `${event.data.flowName} is deleted`; + case ApplicationEventName.CREATED_FOLDER: + return `${event.data.folderName} is created`; + case ApplicationEventName.UPDATED_FOLDER: + return `${event.data.folderName} is updated`; + case ApplicationEventName.DELETED_FOLDER: + return `${event.data.folderName} is deleted`; + case ApplicationEventName.UPSERTED_CONNECTION: + return `${event.data.connectionName} is updated`; + case ApplicationEventName.DELETED_CONNECTION: + return `${event.data.connectionName} is deleted`; + case ApplicationEventName.SIGNED_UP: + return `User ${event.userEmail} signed up`; + case ApplicationEventName.SIGNED_IN: + return `User ${event.userEmail} signed in`; + case ApplicationEventName.RESET_PASSWORD: + return `User ${event.userEmail} reset password`; + case ApplicationEventName.VERIFIED_EMAIL: + return `User ${event.userEmail} verified email`; + } +} + +function convertUpdateActionToDetails(event: UpdatedFlowEvent) { + switch (event.data.request.type) { + case FlowOperationType.ADD_ACTION: + return `Added action "${event.data.request.request.action.displayName}" to "${event.data.flowName}" Flow.`; + case FlowOperationType.UPDATE_ACTION: + return `Updated action "${event.data.request.request.displayName}" in "${event.data.flowName}" Flow.`; + case FlowOperationType.DELETE_ACTION: + return `Deleted action "${event.data.request.request.name}" from "${event.data.flowName}" Flow.`; + case FlowOperationType.CHANGE_NAME: + return `Renamed flow "${event.data.flowName}" to "${event.data.request.request.displayName}".`; + case FlowOperationType.LOCK_AND_PUBLISH: + return `Locked and published flow "${event.data.flowName}" Flow.`; + case FlowOperationType.USE_AS_DRAFT: + return `Unlocked and unpublished flow "${event.data.flowName}" Flow.`; + case FlowOperationType.MOVE_ACTION: + return `Moved action "${event.data.request.request.name}" to after "${event.data.request.request.newParentStep}".`; + case FlowOperationType.LOCK_FLOW: + return `Locked flow "${event.data.flowName}" Flow.`; + case FlowOperationType.CHANGE_STATUS: + return `Changed status of flow "${event.data.flowName}" Flow to "${event.data.request.request.status}".`; + case FlowOperationType.DUPLICATE_ACTION: + return `Duplicated action "${event.data.request.request.stepName}" in "${event.data.flowName}" Flow.`; + case FlowOperationType.IMPORT_FLOW: + return `Imported flow in "${event.data.request.request.displayName}" Flow.`; + case FlowOperationType.UPDATE_TRIGGER: + return `Updated trigger in "${event.data.flowName}" Flow to "${event.data.request.request.displayName}".`; + case FlowOperationType.CHANGE_FOLDER: + return `Moved flow "${event.data.flowName}" to folder id ${event.data.request.request.folderId}.`; + } + + +} \ No newline at end of file diff --git a/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.component.ts b/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.component.ts index c8b04b4d21..b56db01396 100644 --- a/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.component.ts +++ b/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.component.ts @@ -17,7 +17,9 @@ import { ApplicationEvent, ApplicationEventName, Platform, + summarizeApplicationEvent, } from '@activepieces/ee-shared'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-audit-event-table', @@ -45,7 +47,10 @@ export class AuditEventTableComponent dialogClosed$?: Observable; featureDisabledTooltip = featureDisabledTooltip; - constructor(private auditEventService: AuditEventService) { + constructor( + private auditEventService: AuditEventService, + private activatedRoute: ActivatedRoute + ) { super(); } ngOnInit(): void { @@ -60,7 +65,8 @@ export class AuditEventTableComponent this.refresh$.asObservable().pipe(startWith(false)), this.auditEventService, this.paginator, - this.isEnabled$ + this.isEnabled$, + this.activatedRoute.queryParams ); } @@ -76,6 +82,7 @@ export class AuditEventTableComponent case ApplicationEventName.CREATED_FLOW: case ApplicationEventName.DELETED_FLOW: case ApplicationEventName.CREATED_FOLDER: + case ApplicationEventName.UPDATED_FLOW: return { icon: 'assets/img/custom/dashboard/flows.svg', tooltip: 'Flow', @@ -104,29 +111,6 @@ export class AuditEventTableComponent } convertToDetails(event: ApplicationEvent) { - switch (event.action) { - case ApplicationEventName.CREATED_FLOW: - return `${event.data.flowName} is created`; - case ApplicationEventName.DELETED_FLOW: - return `${event.data.flowName} is deleted`; - case ApplicationEventName.CREATED_FOLDER: - return `${event.data.folderName} is created`; - case ApplicationEventName.UPDATED_FOLDER: - return `${event.data.folderName} is updated`; - case ApplicationEventName.DELETED_FOLDER: - return `${event.data.folderName} is deleted`; - case ApplicationEventName.UPSERTED_CONNECTION: - return `${event.data.connectionName} is updated`; - case ApplicationEventName.DELETED_CONNECTION: - return `${event.data.connectionName} is deleted`; - case ApplicationEventName.SIGNED_UP: - return `User ${event.userEmail} signed up`; - case ApplicationEventName.SIGNED_IN: - return `User ${event.userEmail} signed in`; - case ApplicationEventName.RESET_PASSWORD: - return `User ${event.userEmail} reset password`; - case ApplicationEventName.VERIFIED_EMAIL: - return `User ${event.userEmail} verified email`; - } + return summarizeApplicationEvent(event); } } diff --git a/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.datasource.ts b/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.datasource.ts index 1ab4e08aca..d4347ea515 100644 --- a/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.datasource.ts +++ b/packages/ee/ui-platform/src/lib/components/audit-event-table/audit-event-table.datasource.ts @@ -11,7 +11,12 @@ import { import { combineLatest } from 'rxjs'; import { AuditEventService } from '../../service/audit-event-service'; import { ApplicationEvent } from '@activepieces/ee-shared'; -import { ApPaginatorComponent } from '@activepieces/ui/common'; +import { + ApPaginatorComponent, + CURSOR_QUERY_PARAM, + LIMIT_QUERY_PARAM, +} from '@activepieces/ui/common'; +import { Params } from '@angular/router'; /** * Data source for the LogsTable view. This class should @@ -25,7 +30,8 @@ export class AuditEventDataSource extends DataSource { private refresh$: Observable, private auditEventService: AuditEventService, private paginator: ApPaginatorComponent, - private isEnabled$: Observable + private isEnabled$: Observable, + private queryParams$: Observable ) { super(); } @@ -37,11 +43,15 @@ export class AuditEventDataSource extends DataSource { */ connect(): Observable { - return combineLatest([this.refresh$, this.isEnabled$]).pipe( + return combineLatest([ + this.refresh$, + this.isEnabled$, + this.queryParams$, + ]).pipe( tap(() => { this.isLoading$.next(true); }), - switchMap(([refresh, isEnabled]) => { + switchMap(([refresh, isEnabled, queryParams]) => { if (!isEnabled) { return of({ data: [], @@ -50,7 +60,8 @@ export class AuditEventDataSource extends DataSource { }).pipe(delay(100)); } return this.auditEventService.list({ - limit: 10, + cursor: queryParams[CURSOR_QUERY_PARAM] ?? null, + limit: queryParams[LIMIT_QUERY_PARAM] ?? 10, }); }), tap((res) => { diff --git a/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.html b/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.html index 2cb3d732cc..f71d053d64 100644 --- a/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.html +++ b/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.html @@ -11,12 +11,6 @@ Name is required - - Description - - Description is required - - Flow template diff --git a/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.ts b/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.ts index 83f28f989a..7b6ce9bc04 100644 --- a/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.ts +++ b/packages/ee/ui-platform/src/lib/components/dialogs/create-or-update-template-dialogue/create-or-update-template-dialogue.component.ts @@ -38,7 +38,7 @@ export class CreateOrUpdateTemplateDialogueComponent { form: FormGroup<{ file: FormControl; name: FormControl; - description: FormControl; + blogUrl: FormControl; tags: FormControl; }>; @@ -55,10 +55,7 @@ export class CreateOrUpdateTemplateDialogueComponent { file: new FormControl(null, { validators: Validators.required, }), - description: new FormControl('', { - nonNullable: true, - validators: Validators.required, - }), + name: new FormControl('', { nonNullable: true, validators: Validators.required, @@ -69,7 +66,6 @@ export class CreateOrUpdateTemplateDialogueComponent { if (data?.template) { this.form.patchValue({ name: data.template.name, - description: data.template.description, blogUrl: data.template.blogUrl, tags: data.template.tags, }); @@ -98,7 +94,6 @@ export class CreateOrUpdateTemplateDialogueComponent { this.createTemplate$ = this.templateService .create({ ...template, - description: this.form.value.description, type: TemplateType.PLATFORM, blogUrl: this.form.value.blogUrl, tags: this.form.value.tags, diff --git a/packages/ee/ui-platform/src/lib/pages/pieces-table/pieces-table.datasource.ts b/packages/ee/ui-platform/src/lib/pages/pieces-table/pieces-table.datasource.ts index 37e3c29f7f..aa9d153312 100644 --- a/packages/ee/ui-platform/src/lib/pages/pieces-table/pieces-table.datasource.ts +++ b/packages/ee/ui-platform/src/lib/pages/pieces-table/pieces-table.datasource.ts @@ -36,7 +36,7 @@ export class PiecesTableDataSource extends DataSource> currentState: Record + /** + * Execution duration in milliseconds + */ duration: number pauseRequestId: string verdict: ExecutionVerdict @@ -58,10 +61,10 @@ export class FlowExecutorContext { if (isNil(stepOutput)) { return undefined } - assertEqual(stepOutput.type, ActionType.LOOP_ON_ITEMS, 'stepout', 'LoopStepOutput') + assertEqual(stepOutput.type, ActionType.LOOP_ON_ITEMS, 'stepOutput.type', 'LOOP_ON_ITEMS') return stepOutput as LoopStepOutput } - + public isCompleted({ stepName }: { stepName: string }): boolean { const stateAtPath = getStateAtPath({ currentPath: this.currentPath, steps: this.steps }) const stepOutput = stateAtPath[stepName] @@ -122,6 +125,31 @@ export class FlowExecutorContext { }) } + public setStepDuration({ stepName, duration }: SetStepDurationParams): FlowExecutorContext { + const steps = { + ...this.steps, + } + + const targetMap = getStateAtPath({ + steps, + currentPath: this.currentPath, + }) + + const stepOutput = targetMap[stepName] + + if (isNil(stepOutput)) { + console.error(`[FlowExecutorContext#setStepDuration] Step ${stepName} not found in current path`) + return this + } + + targetMap[stepName].duration = duration + + return new FlowExecutorContext({ + ...this, + steps, + }) + } + public setCurrentPath(currentStatePath: StepExecutionPath): FlowExecutorContext { return new FlowExecutorContext({ ...this, @@ -155,7 +183,7 @@ export class FlowExecutorContext { case ExecutionVerdict.PAUSED: { const verdictResponse = this.verdictResponse if (verdictResponse?.reason !== ExecutionOutputStatus.PAUSED) { - throw new Error('Veridct Response should have pause metadata response') + throw new Error('Verdict Response should have pause metadata response') } return { ...baseExecutionOutput, @@ -182,7 +210,6 @@ export class FlowExecutorContext { } } - function getStateAtPath({ currentPath, steps }: { currentPath: StepExecutionPath, steps: Record }): Record { let targetMap = steps currentPath.path.forEach(([stepName, iteration]) => { @@ -194,3 +221,8 @@ function getStateAtPath({ currentPath, steps }: { currentPath: StepExecutionPath }) return targetMap } + +type SetStepDurationParams = { + stepName: string + duration: number +} diff --git a/packages/engine/src/lib/handler/flow-executor.ts b/packages/engine/src/lib/handler/flow-executor.ts index 8bcd5b3de6..f9c6699070 100644 --- a/packages/engine/src/lib/handler/flow-executor.ts +++ b/packages/engine/src/lib/handler/flow-executor.ts @@ -1,3 +1,4 @@ +import { performance } from 'node:perf_hooks' import { Action, ActionType, isNil } from '@activepieces/shared' import { codeExecutor } from './code-executor' import { ExecutionVerdict, FlowExecutorContext } from './context/flow-execution-context' @@ -27,21 +28,34 @@ export const flowExecutor = { executionState: FlowExecutorContext constants: EngineConstants }): Promise { - const startTime = new Date().getMilliseconds() + const flowStartTime = performance.now() let flowExecutionContext = executionState let currentAction: Action | undefined = action + while (!isNil(currentAction)) { const handler = this.getExecutorForAction(currentAction.type) + + const stepStartTime = performance.now() flowExecutionContext = await handler.handle({ action: currentAction, executionState: flowExecutionContext, constants, }) + const stepEndTime = performance.now() + + flowExecutionContext = flowExecutionContext.setStepDuration({ + stepName: currentAction.name, + duration: stepEndTime - stepStartTime, + }) + if (flowExecutionContext.verdict !== ExecutionVerdict.RUNNING) { - return flowExecutionContext + break } + currentAction = currentAction.nextAction } - return flowExecutionContext.setDuration(new Date().getMilliseconds() - startTime) + + const flowEndTime = performance.now() + return flowExecutionContext.setDuration(flowEndTime - flowStartTime) }, } diff --git a/packages/engine/src/lib/handler/loop-executor.ts b/packages/engine/src/lib/handler/loop-executor.ts index 80c7f7c057..396bdf7810 100644 --- a/packages/engine/src/lib/handler/loop-executor.ts +++ b/packages/engine/src/lib/handler/loop-executor.ts @@ -50,11 +50,12 @@ export const loopExecutor: BaseExecutor = { }) } + newExecutionContext = newExecutionContext.setCurrentPath(newExecutionContext.currentPath.removeLast()) + if (newExecutionContext.verdict !== ExecutionVerdict.RUNNING) { return newExecutionContext } - newExecutionContext = newExecutionContext.setCurrentPath(newExecutionContext.currentPath.removeLast()) if (constants.testSingleStepMode) { break } diff --git a/packages/engine/src/lib/handler/piece-executor.ts b/packages/engine/src/lib/handler/piece-executor.ts index 85df1fc0ed..7d7ec5a90a 100644 --- a/packages/engine/src/lib/handler/piece-executor.ts +++ b/packages/engine/src/lib/handler/piece-executor.ts @@ -1,7 +1,6 @@ import { AUTHENTICATION_PROPERTY_NAME, GenericStepOutput, ActionType, ExecutionOutputStatus, PieceAction, StepOutputStatus, assertNotNullOrUndefined, isNil, ExecutionType, PauseType } from '@activepieces/shared' import { ActionHandler, BaseExecutor } from './base-executor' import { ExecutionVerdict, FlowExecutorContext } from './context/flow-execution-context' -import { variableService } from '../services/variable-service' import { ActionContext, ConnectionsManager, PauseHook, PauseHookParams, PiecePropertyMap, StaticPropsValue, StopHook, StopHookParams, TagsManager } from '@activepieces/pieces-framework' import { createContextStore } from '../services/storage.service' import { createFilesService } from '../services/files.service' @@ -33,22 +32,12 @@ export const pieceExecutor: BaseExecutor = { } const executeAction: ActionHandler = async ({ action, executionState, constants }) => { - const { - censoredInput, - resolvedInput, - } = await variableService({ - projectId: constants.projectId, - workerToken: constants.workerToken, - }).resolve>({ - unresolvedInput: action.settings.input, - executionState, - }) - const stepOutput = GenericStepOutput.create({ - input: censoredInput, + input: {}, type: ActionType.PIECE, status: StepOutputStatus.SUCCEEDED, }) + try { assertNotNullOrUndefined(action.settings.actionName, 'actionName') const { pieceAction, piece } = await pieceLoader.getPieceAndActionOrThrow({ @@ -58,6 +47,13 @@ const executeAction: ActionHandler = async ({ action, executionStat piecesSource: constants.piecesSource, }) + const { resolvedInput, censoredInput } = await constants.variableService.resolve>({ + unresolvedInput: action.settings.input, + executionState, + }) + + stepOutput.input = censoredInput + const { processedInput, errors } = await constants.variableService.applyProcessorsAndValidators(resolvedInput, pieceAction.props, piece.auth) if (Object.keys(errors).length > 0) { throw new Error(JSON.stringify(errors)) @@ -109,13 +105,10 @@ const executeAction: ActionHandler = async ({ action, executionStat externalId: constants.externalProjectId, }, generateResumeUrl: (params) => { - const url = new URL(`${constants.serverUrl}v1/flow-runs/${constants.flowRunId}/resume`) - url.search = new URLSearchParams({ - ...params.queryParams, - requestId: executionState.pauseRequestId, - }).toString() + const url = new URL(`${constants.serverUrl}v1/flow-runs/${constants.flowRunId}/requests/${executionState.pauseRequestId}`) + url.search = new URLSearchParams(params.queryParams).toString() return url.toString() - }, + }, } const runMethodToExecute = (constants.testSingleStepMode && !isNil(pieceAction.test)) ? pieceAction.test : pieceAction.run const output = await runMethodToExecute(context) diff --git a/packages/engine/src/lib/helper/execution-errors.ts b/packages/engine/src/lib/helper/execution-errors.ts new file mode 100644 index 0000000000..70cfcaf404 --- /dev/null +++ b/packages/engine/src/lib/helper/execution-errors.ts @@ -0,0 +1,13 @@ +export class ConnectionNotFoundError extends Error { + constructor(connectionName: string) { + super(`connection (${connectionName}) not found`) + this.name = 'ConnectionNotFound' + } +} + +export class ConnectionLoadingFailureError extends Error { + constructor(connectionName: string, url: string) { + super(`Failed to load connection (${connectionName}) from ${url}`) + this.name = 'ConnectionLoadingFailure' + } +} diff --git a/packages/engine/src/lib/services/connections.service.ts b/packages/engine/src/lib/services/connections.service.ts index 59c7d2ff18..a5ec33f32c 100644 --- a/packages/engine/src/lib/services/connections.service.ts +++ b/packages/engine/src/lib/services/connections.service.ts @@ -1,35 +1,94 @@ +import { StatusCodes } from 'http-status-codes' +import { AppConnection, AppConnectionType, CloudOAuth2ConnectionValue, BasicAuthConnectionValue, OAuth2ConnectionValueWithApp, isNil } from '@activepieces/shared' import { EngineConstants } from '../handler/context/engine-constants' -import { AppConnection, AppConnectionType, CloudOAuth2ConnectionValue, BasicAuthConnectionValue, OAuth2ConnectionValueWithApp } from '@activepieces/shared' +import { ConnectionLoadingFailureError, ConnectionNotFoundError } from '../helper/execution-errors' -export const createConnectionService = ({ projectId, workerToken }: { projectId: string, workerToken: string }) => { +export const createConnectionService = ({ projectId, workerToken }: CreateConnectionServiceParams): ConnectionService => { return { - async obtain(connectionName: string): Promise> { + async obtain(connectionName: string): Promise { const url = `${EngineConstants.API_URL}v1/worker/app-connections/${encodeURIComponent(connectionName)}?projectId=${projectId}` + try { const response = await fetch(url, { method: 'GET', headers: { - Authorization: 'Bearer ' + workerToken, + Authorization: `Bearer ${workerToken}`, }, }) + if (!response.ok) { - throw new Error('Connection information failed to load. URL: ' + url) - } - const result: AppConnection | null = await response.json() - if (result === null) { - return null - } - if (result.value.type === AppConnectionType.SECRET_TEXT) { - return result.value.secret_text + return handleErrors({ + httpStatus: response.status, + connectionName, + url, + }) } - if (result.value.type === AppConnectionType.CUSTOM_AUTH) { - return result.value.props + + const connection: AppConnection | null = await response.json() + + if (isNil(connection)) { + return handleNotFoundError(connectionName) } - return result.value + + return getConnectionValue(connection) } catch (e) { - throw new Error('Connection information failed to load. URL: ' + url + ' Error: ' + e) + if (e instanceof ConnectionNotFoundError) { + throw e + } + + return handleErrors({ + connectionName, + url, + }) } }, } } + +const handleErrors = ({ httpStatus, connectionName, url }: HandleErrorsParams): never => { + if (httpStatus === StatusCodes.NOT_FOUND.valueOf()) { + handleNotFoundError(connectionName) + } + + throw new ConnectionLoadingFailureError(connectionName, url) +} + +const handleNotFoundError = (connectionName: string): never => { + throw new ConnectionNotFoundError(connectionName) +} + +const getConnectionValue = (connection: AppConnection): ConnectionValue => { + switch (connection.value.type) { + case AppConnectionType.SECRET_TEXT: + return connection.value.secret_text + + case AppConnectionType.CUSTOM_AUTH: + return connection.value.props + + default: + return connection.value + } +} + +type ConnectionValue = + | OAuth2ConnectionValueWithApp + | CloudOAuth2ConnectionValue + | BasicAuthConnectionValue + | Record + | string + +type ConnectionService = { + obtain(connectionName: string): Promise +} + +type CreateConnectionServiceParams = { + projectId: string + workerToken: string +} + +type HandleErrorsParams = { + httpStatus?: number + connectionName: string + url: string +} diff --git a/packages/pieces/community/airtable/package.json b/packages/pieces/community/airtable/package.json index dc9d24105f..45edad8f8c 100644 --- a/packages/pieces/community/airtable/package.json +++ b/packages/pieces/community/airtable/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-airtable", - "version": "0.4.11" + "version": "0.4.12" } diff --git a/packages/pieces/community/airtable/src/lib/common/index.ts b/packages/pieces/community/airtable/src/lib/common/index.ts index 5ff87298b6..44654fd3a8 100644 --- a/packages/pieces/community/airtable/src/lib/common/index.ts +++ b/packages/pieces/community/airtable/src/lib/common/index.ts @@ -1,11 +1,12 @@ -import Airtable from 'airtable'; -import { Property, DynamicPropsValue } from '@activepieces/pieces-framework'; import { - HttpMethod, AuthenticationType, - httpClient, + HttpMethod, HttpRequest, + httpClient, } from '@activepieces/pieces-common'; +import { DynamicPropsValue, Property } from '@activepieces/pieces-framework'; +import Airtable from 'airtable'; +import { isNil } from 'lodash'; import { AirtableBase, AirtableEnterpriseFields, @@ -15,7 +16,6 @@ import { AirtableTable, AirtableView, } from './models'; -import { isNil } from 'lodash'; export const airtableCommon = { base: Property.Dropdown({ @@ -318,7 +318,11 @@ export const airtableCommon = { }); const airtable = new Airtable(); const currentTablleSnapshot = ( - await airtable.base(params.baseId).table(params.tableId).select().all() + await airtable + .base(params.baseId) + .table(params.tableId) + .select(params.limitToView ? { view: params.limitToView } : {}) + .all() ) .map((r) => r._rawJson) .sort( @@ -373,19 +377,19 @@ export const airtableCommon = { baseId: string; tableId: string; }) { - const response = await httpClient.sendRequest<{ views: AirtableView[] }>({ + const response = await httpClient.sendRequest<{ tables: AirtableTable[] }>({ method: HttpMethod.GET, - url: `https://api.airtable.com/v0/${baseId}/${tableId}/views`, + url: `https://api.airtable.com/v0/meta/bases/${baseId}/tables`, authentication: { type: AuthenticationType.BEARER_TOKEN, token, }, }); - if (response.status === 200) { - return response.body.views; + const table = response.body.tables.find((table) => table.id === tableId); + if (table) { + return table.views; } - return []; }, diff --git a/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts b/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts index 51fbeb8fae..1e5a7b0814 100644 --- a/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts +++ b/packages/pieces/community/airtable/src/lib/trigger/new-record.trigger.ts @@ -1,19 +1,20 @@ -import { - StaticPropsValue, - TriggerStrategy, - createTrigger, -} from '@activepieces/pieces-framework'; -import { airtableCommon } from '../common'; import { DedupeStrategy, Polling, pollingHelper, } from '@activepieces/pieces-common'; +import { + StaticPropsValue, + TriggerStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; import { airtableAuth } from '../../'; +import { airtableCommon } from '../common'; const props = { base: airtableCommon.base, tableId: airtableCommon.tableId, + viewId: airtableCommon.views, }; const polling: Polling> = { @@ -23,6 +24,7 @@ const polling: Polling> = { personalToken: auth, baseId: propsValue.base, tableId: propsValue.tableId!, + limitToView: propsValue.viewId, }); return records.map((record) => ({ epochMilliSeconds: Date.parse(record.createdTime), diff --git a/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts b/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts index 87d5658d49..4139d1409a 100644 --- a/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts +++ b/packages/pieces/community/airtable/src/lib/trigger/update-record.trigger.ts @@ -1,19 +1,19 @@ +import { + DedupeStrategy, + Polling, + pollingHelper, +} from '@activepieces/pieces-common'; import { Property, StaticPropsValue, TriggerStrategy, createTrigger, } from '@activepieces/pieces-framework'; -import { airtableCommon } from '../common'; -import { - DedupeStrategy, - Polling, - pollingHelper, -} from '@activepieces/pieces-common'; -import { airtableAuth } from '../../'; -import { AirtableField, AirtableTable } from '../common/models'; import Airtable from 'airtable'; import dayjs from 'dayjs'; +import { airtableAuth } from '../../'; +import { airtableCommon } from '../common'; +import { AirtableField, AirtableTable } from '../common/models'; const props = { base: airtableCommon.base, @@ -62,6 +62,7 @@ const props = { }; }, }), + viewId: airtableCommon.views, }; const polling: Polling> = { strategy: DedupeStrategy.TIMEBASED, @@ -75,6 +76,7 @@ const polling: Polling> = { .table(propsValue.tableId!) .select({ sort: [{ direction: 'desc', field: propsValue.sortFields! }], + view: propsValue.viewId ?? '', }) .all(); const records = currentValues.filter((record) => { diff --git a/packages/pieces/community/approval/package.json b/packages/pieces/community/approval/package.json index 8fbd9080cf..c4ba1cd60b 100644 --- a/packages/pieces/community/approval/package.json +++ b/packages/pieces/community/approval/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-approval", - "version": "0.1.3" + "version": "0.1.5" } diff --git a/packages/pieces/community/asana/src/lib/common/index.ts b/packages/pieces/community/asana/src/lib/common/index.ts index 941f88b9aa..dcf2c91393 100644 --- a/packages/pieces/community/asana/src/lib/common/index.ts +++ b/packages/pieces/community/asana/src/lib/common/index.ts @@ -200,7 +200,7 @@ export async function getTags( return response.data; } -export async function callAsanaApi( +export async function callAsanaApi( method: HttpMethod, apiUrl: string, accessToken: string, diff --git a/packages/pieces/community/certopus/src/lib/common/client.ts b/packages/pieces/community/certopus/src/lib/common/client.ts index 81833665d6..f9f13b9348 100644 --- a/packages/pieces/community/certopus/src/lib/common/client.ts +++ b/packages/pieces/community/certopus/src/lib/common/client.ts @@ -13,13 +13,13 @@ import { certopusCommon } from '.'; export class CertopusClient { constructor(private token: string) {} - async makeRequest( + async makeRequest( method: HttpMethod, url: string, query?: QueryParams, body?: object ): Promise { - const res = await httpClient.sendRequest({ + const res = await httpClient.sendRequest({ method, url: certopusCommon.baseUrl + url, queryParams: query, diff --git a/packages/pieces/community/clickup/src/lib/common/index.ts b/packages/pieces/community/clickup/src/lib/common/index.ts index a3aac36b98..1313e4533c 100644 --- a/packages/pieces/community/clickup/src/lib/common/index.ts +++ b/packages/pieces/community/clickup/src/lib/common/index.ts @@ -500,7 +500,7 @@ export async function callClickupGetTask(accessToken: string, taskId: string) { ).body; } -export async function callClickUpApi( +export async function callClickUpApi( method: HttpMethod, apiUrl: string, accessToken: string, diff --git a/packages/pieces/community/common/package.json b/packages/pieces/community/common/package.json index b2c2f3b647..684c792d27 100644 --- a/packages/pieces/community/common/package.json +++ b/packages/pieces/community/common/package.json @@ -1,5 +1,5 @@ { "name": "@activepieces/pieces-common", - "version": "0.2.8", + "version": "0.2.9", "type": "commonjs" } diff --git a/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts b/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts index e6ad8a7e0d..b28af3aedd 100644 --- a/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts +++ b/packages/pieces/community/common/src/lib/http/axios/axios-http-client.ts @@ -1,4 +1,4 @@ -import axios, { AxiosRequestConfig, AxiosStatic } from 'axios'; +import axios, { AxiosRequestConfig } from 'axios'; import { DelegatingAuthenticationConverter } from '../core/delegating-authentication-converter'; import { BaseHttpClient } from '../core/base-http-client'; import { HttpError } from '../core/http-error'; @@ -7,6 +7,7 @@ import { HttpMessageBody } from '../core/http-message-body'; import { HttpMethod } from '../core/http-method'; import { HttpRequest } from '../core/http-request'; import { HttpResponse } from '../core/http-response'; +import { HttpRequestBody } from '../core/http-request-body'; export class AxiosHttpClient extends BaseHttpClient { constructor( @@ -16,8 +17,8 @@ export class AxiosHttpClient extends BaseHttpClient { super(baseUrl, authenticationConverter); } - async sendRequest( - request: HttpRequest + async sendRequest( + request: HttpRequest ): Promise> { try { process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; diff --git a/packages/pieces/community/common/src/lib/http/core/base-http-client.ts b/packages/pieces/community/common/src/lib/http/core/base-http-client.ts index 245db361f9..3d80782f02 100644 --- a/packages/pieces/community/common/src/lib/http/core/base-http-client.ts +++ b/packages/pieces/community/common/src/lib/http/core/base-http-client.ts @@ -5,6 +5,7 @@ import { HttpHeader } from './http-header'; import type { HttpHeaders } from './http-headers'; import type { HttpMessageBody } from './http-message-body'; import type { HttpRequest } from './http-request'; +import { HttpRequestBody } from './http-request-body'; import { HttpResponse } from './http-response'; import { MediaType } from './media-type'; import { URL } from 'node:url'; @@ -39,7 +40,7 @@ export abstract class BaseHttpClient implements HttpClient { }; } - protected getHeaders( + protected getHeaders( request: HttpRequest ): HttpHeaders { let requestHeaders: HttpHeaders = { diff --git a/packages/pieces/community/common/src/lib/http/core/http-client.ts b/packages/pieces/community/common/src/lib/http/core/http-client.ts index 8062fd8ca4..8be15d2e65 100644 --- a/packages/pieces/community/common/src/lib/http/core/http-client.ts +++ b/packages/pieces/community/common/src/lib/http/core/http-client.ts @@ -1,11 +1,12 @@ import { AxiosHttpClient } from '../axios/axios-http-client'; import type { HttpMessageBody } from './http-message-body'; import type { HttpRequest } from './http-request'; +import { HttpRequestBody } from './http-request-body'; import { HttpResponse } from './http-response'; export type HttpClient = { sendRequest< - RequestBody extends HttpMessageBody, + RequestBody extends HttpRequestBody, ResponseBody extends HttpMessageBody >( request: HttpRequest diff --git a/packages/pieces/community/common/src/lib/http/core/http-message-body.ts b/packages/pieces/community/common/src/lib/http/core/http-message-body.ts index 3ca19bcff3..0ba1f99771 100644 --- a/packages/pieces/community/common/src/lib/http/core/http-message-body.ts +++ b/packages/pieces/community/common/src/lib/http/core/http-message-body.ts @@ -1 +1 @@ -export type HttpMessageBody = Record; +export type HttpMessageBody = any diff --git a/packages/pieces/community/common/src/lib/http/core/http-request-body.ts b/packages/pieces/community/common/src/lib/http/core/http-request-body.ts new file mode 100644 index 0000000000..da413ed807 --- /dev/null +++ b/packages/pieces/community/common/src/lib/http/core/http-request-body.ts @@ -0,0 +1,3 @@ +import { HttpMessageBody } from "./http-message-body"; + +export type HttpRequestBody = HttpMessageBody | string; \ No newline at end of file diff --git a/packages/pieces/community/common/src/lib/http/core/http-request.ts b/packages/pieces/community/common/src/lib/http/core/http-request.ts index b37d8e5c55..66fb5a297e 100644 --- a/packages/pieces/community/common/src/lib/http/core/http-request.ts +++ b/packages/pieces/community/common/src/lib/http/core/http-request.ts @@ -1,10 +1,10 @@ -import type { HttpMessageBody } from './http-message-body'; import type { HttpMethod } from './http-method'; import type { QueryParams } from './query-params'; import { HttpHeaders } from './http-headers'; import { Authentication } from '../../authentication'; +import { HttpRequestBody } from './http-request-body'; -export type HttpRequest = { +export type HttpRequest = { method: HttpMethod; url: string; body?: RequestBody | undefined; diff --git a/packages/pieces/community/csv/package.json b/packages/pieces/community/csv/package.json index 0321a006a8..54fb5025f3 100644 --- a/packages/pieces/community/csv/package.json +++ b/packages/pieces/community/csv/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-csv", - "version": "0.3.2" + "version": "0.3.3" } diff --git a/packages/pieces/community/data-mapper/package.json b/packages/pieces/community/data-mapper/package.json index d262ad7d09..73b9ace040 100644 --- a/packages/pieces/community/data-mapper/package.json +++ b/packages/pieces/community/data-mapper/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-data-mapper", - "version": "0.3.2" + "version": "0.3.3" } diff --git a/packages/pieces/community/date-helper/package.json b/packages/pieces/community/date-helper/package.json index b0f748eacb..71ce9291fc 100644 --- a/packages/pieces/community/date-helper/package.json +++ b/packages/pieces/community/date-helper/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-date-helper", - "version": "0.0.4" + "version": "0.0.5" } diff --git a/packages/pieces/community/delay/package.json b/packages/pieces/community/delay/package.json index 9697578ea9..c982617c01 100644 --- a/packages/pieces/community/delay/package.json +++ b/packages/pieces/community/delay/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-delay", - "version": "0.3.3" + "version": "0.3.4" } diff --git a/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts b/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts index a3c14ff5e6..28d3a1107c 100644 --- a/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts +++ b/packages/pieces/community/discord/src/lib/actions/send-approval-message.ts @@ -22,23 +22,28 @@ export const discordSendApprovalMessage = createAction({ }), channel: discordCommon.channel, }, - async run(configValue) { - if (configValue.executionType === ExecutionType.BEGIN) { - configValue.run.pause({ + async run(ctx) { + if (ctx.executionType === ExecutionType.BEGIN) { + ctx.run.pause({ pauseMetadata: { type: PauseType.WEBHOOK, response: {}, }, }); - const approvalLink = `${configValue.serverUrl}v1/flow-runs/${configValue.run.id}/resume?action=approve`; - const disapprovalLink = `${configValue.serverUrl}v1/flow-runs/${configValue.run.id}/resume?action=disapprove`; + const approvalLink = ctx.generateResumeUrl({ + queryParams: { action: 'approve' }, + }) + const disapprovalLink = ctx.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }) + const request: HttpRequest = { method: HttpMethod.POST, - url: `https://discord.com/api/v9/channels/${configValue.propsValue.channel}/messages`, + url: `https://discord.com/api/v9/channels/${ctx.propsValue.channel}/messages`, body: { - content: configValue.propsValue.content, + content: ctx.propsValue.content, components: [ { type: 1, @@ -60,7 +65,7 @@ export const discordSendApprovalMessage = createAction({ ], }, headers: { - authorization: `Bot ${configValue.auth}`, + authorization: `Bot ${ctx.auth}`, 'Content-Type': 'application/json', }, }; @@ -70,7 +75,7 @@ export const discordSendApprovalMessage = createAction({ } else { return { - approved: configValue.resumePayload.queryParams['action'] === 'approve', + approved: ctx.resumePayload.queryParams['action'] === 'approve', }; } }, diff --git a/packages/pieces/community/dust/.eslintrc.json b/packages/pieces/community/dust/.eslintrc.json new file mode 100644 index 0000000000..d7449bb038 --- /dev/null +++ b/packages/pieces/community/dust/.eslintrc.json @@ -0,0 +1,37 @@ +{ + "extends": [ + "../../../../.eslintrc.json" + ], + "ignorePatterns": [ + "!**/*" + ], + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx", + "*.js", + "*.jsx" + ], + "rules": {}, + "extends": [ + "plugin:prettier/recommended" + ], + "plugins": ["prettier"] + }, + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": {} + }, + { + "files": [ + "*.js", + "*.jsx" + ], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/dust/README.md b/packages/pieces/community/dust/README.md new file mode 100644 index 0000000000..70fd912f9d --- /dev/null +++ b/packages/pieces/community/dust/README.md @@ -0,0 +1,7 @@ +# pieces-dust + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-dust` to build the library. diff --git a/packages/pieces/community/dust/package.json b/packages/pieces/community/dust/package.json new file mode 100644 index 0000000000..1317d7a8e7 --- /dev/null +++ b/packages/pieces/community/dust/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-dust", + "version": "0.0.1" +} diff --git a/packages/pieces/community/dust/project.json b/packages/pieces/community/dust/project.json new file mode 100644 index 0000000000..60cb49f5fc --- /dev/null +++ b/packages/pieces/community/dust/project.json @@ -0,0 +1,43 @@ +{ + "name": "pieces-dust", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/dust/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": [ + "{options.outputPath}" + ], + "options": { + "outputPath": "dist/packages/pieces/community/dust", + "tsConfig": "packages/pieces/community/dust/tsconfig.lib.json", + "packageJson": "packages/pieces/community/dust/package.json", + "main": "packages/pieces/community/dust/src/index.ts", + "assets": [ + "packages/pieces/community/dust/*.md" + ], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-dust {args.ver} {args.tag}", + "dependsOn": [ + "build" + ] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": [ + "{options.outputFile}" + ], + "options": { + "lintFilePatterns": [ + "packages/pieces/community/dust/**/*.ts" + ] + } + } + }, + "tags": [] +} \ No newline at end of file diff --git a/packages/pieces/community/dust/src/index.ts b/packages/pieces/community/dust/src/index.ts new file mode 100644 index 0000000000..37264c0207 --- /dev/null +++ b/packages/pieces/community/dust/src/index.ts @@ -0,0 +1,37 @@ +import { + createPiece, + PieceAuth, + Property, +} from '@activepieces/pieces-framework'; +import { createConversation } from './lib/actions/create-conversation'; + +export const dustAuth = PieceAuth.CustomAuth({ + description: 'Dust authentication requires an API key.', + required: true, + props: { + apiKey: PieceAuth.SecretText({ + displayName: 'API key', + required: true, + }), + workspaceId: Property.ShortText({ + displayName: 'Dust workspace ID', + required: true, + description: "Can be found in any of the workspace's URL", + }), + }, +}); + +export type DustAuthType = { + apiKey: string; + workspaceId: string; +}; + +export const dust = createPiece({ + displayName: 'Dust', + auth: dustAuth, + minimumSupportedRelease: '0.9.0', + logoUrl: 'https://cdn.activepieces.com/pieces/dust.png', + authors: ['AdamSelene'], + actions: [createConversation], + triggers: [], +}); diff --git a/packages/pieces/community/dust/src/lib/actions/create-conversation.ts b/packages/pieces/community/dust/src/lib/actions/create-conversation.ts new file mode 100644 index 0000000000..be32f18015 --- /dev/null +++ b/packages/pieces/community/dust/src/lib/actions/create-conversation.ts @@ -0,0 +1,146 @@ +import { + createAction, + CustomAuthProperty, + CustomAuthProps, + Property, +} from '@activepieces/pieces-framework'; +import { dustAuth, DustAuthType } from '../..'; +import { + httpClient, + HttpMessageBody, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; + +const DUST_BASE_URL = 'https://dust.tt/api/v1/w'; +export const createConversation = createAction({ + // auth: check https://www.activepieces.com/docs/developers/piece-reference/authentication, + name: 'createConversation', + displayName: 'Create conversation', + description: '', + auth: dustAuth, + props: { + assistant: Property.Dropdown({ + displayName: 'Agent', + required: true, + refreshers: ['auth'], + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please authenticate first', + }; + } + const { workspaceId, apiKey } = auth as DustAuthType; + const request: HttpRequest = { + method: HttpMethod.GET, + url: `${DUST_BASE_URL}/${workspaceId}/assistant/agent_configurations`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + }; + const response = await httpClient.sendRequest(request); + const options = response.body['agentConfigurations'] + ?.filter( + (agentConfiguration: { status: string }) => + agentConfiguration.status === 'active' + ) + ?.map( + (agentConfiguration: { + name: string; + sId: string; + scope: string; + }) => { + return { + label: `[${agentConfiguration['scope']}] ${agentConfiguration['name']}`, + value: agentConfiguration['sId'], + }; + } + ) + ?.sort((a: { label: string }, b: { label: string }) => + a['label'].localeCompare(b['label']) + ); + return { + options: options, + }; + }, + }), + query: Property.LongText({ displayName: 'Query', required: true }), + username: Property.ShortText({ + displayName: 'Username', + required: true, + }), + timeZone: Property.ShortText({ + displayName: 'Time zone', + required: true, + defaultValue: 'Europe/Paris', + }), + }, + async run({ auth, propsValue }) { + const request: HttpRequest = { + method: HttpMethod.POST, + url: `${DUST_BASE_URL}/${auth.workspaceId}/assistant/conversations`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + body: JSON.stringify( + { + visibility: 'unlisted', + title: null, + message: { + content: propsValue.query, + mentions: [{ configurationId: propsValue.assistant }], + context: { + timezone: 'Europe/Paris', + username: propsValue.username, + email: null, + fullName: null, + profilePictureUrl: null, + }, + }, + }, + (key, value) => (typeof value === 'undefined' ? null : value) + ), + }; + const body = (await httpClient.sendRequest(request)).body; + const conversationId = body['conversation']['sId']; + const getConversation = async (conversationId: string) => { + return httpClient.sendRequest({ + method: HttpMethod.GET, + url: `${DUST_BASE_URL}/${auth.workspaceId}/assistant/conversations/${conversationId}`, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${auth.apiKey}`, + }, + }); + }; + + let conversation = await getConversation(conversationId); + + let retries = 0; + while ( + !['succeeded', 'errored'].includes( + getConversationStatus(conversation.body) + ) && + retries < 12 // 2mn + ) { + await new Promise((f) => setTimeout(f, 10000)); + + conversation = await getConversation(conversationId); + retries += 1; + } + + if (getConversationStatus(conversation.body) != 'succeeded') { + throw new Error('Could not load conversation'); + } + + return conversation.body; + }, +}); + +function getConversationStatus(conversation: HttpMessageBody): string { + return conversation['conversation']['content']?.at(-1)?.at(0)?.status; +} diff --git a/packages/pieces/community/dust/tsconfig.json b/packages/pieces/community/dust/tsconfig.json new file mode 100644 index 0000000000..059cd81661 --- /dev/null +++ b/packages/pieces/community/dust/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/dust/tsconfig.lib.json b/packages/pieces/community/dust/tsconfig.lib.json new file mode 100644 index 0000000000..28369ef762 --- /dev/null +++ b/packages/pieces/community/dust/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/file-helper/package.json b/packages/pieces/community/file-helper/package.json index e2551d500a..98992b09ba 100644 --- a/packages/pieces/community/file-helper/package.json +++ b/packages/pieces/community/file-helper/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-file-helper", - "version": "0.0.3", + "version": "0.0.4", "dependencies": { "@activepieces/pieces-framework": "*", "tslib": "2.6.2", diff --git a/packages/pieces/community/framework/package.json b/packages/pieces/community/framework/package.json index b42206aec6..b16863de88 100644 --- a/packages/pieces/community/framework/package.json +++ b/packages/pieces/community/framework/package.json @@ -1,5 +1,5 @@ { "name": "@activepieces/pieces-framework", - "version": "0.7.18", + "version": "0.7.19", "type": "commonjs" -} +} \ No newline at end of file diff --git a/packages/pieces/community/framework/src/lib/piece-metadata.ts b/packages/pieces/community/framework/src/lib/piece-metadata.ts index 34c82adbf3..8963a747c9 100644 --- a/packages/pieces/community/framework/src/lib/piece-metadata.ts +++ b/packages/pieces/community/framework/src/lib/piece-metadata.ts @@ -91,9 +91,19 @@ export const PieceMetadataSummary = Type.Composite([ Type.Object({ actions: Type.Number(), triggers: Type.Number(), + suggestedActions: Type.Optional(Type.Array(Type.Object({ + name: Type.String(), + displayName: Type.String(), + }))), + suggestedTriggers: Type.Optional(Type.Array(Type.Object({ + name: Type.String(), + displayName: Type.String(), + }))), }) ]) export type PieceMetadataSummary = Omit & { actions: number; triggers: number; + suggestedActions?: { name: string, displayName: string }[]; + suggestedTriggers?: { name: string, displayName: string }[]; } diff --git a/packages/pieces/community/generatebanners/package.json b/packages/pieces/community/generatebanners/package.json index eb311ee1c9..f43cd375f0 100644 --- a/packages/pieces/community/generatebanners/package.json +++ b/packages/pieces/community/generatebanners/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-generatebanners", - "version": "0.3.1" + "version": "0.3.2" } diff --git a/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts b/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts index e68fd7a8f8..0a8b55d5e8 100644 --- a/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts +++ b/packages/pieces/community/generatebanners/src/lib/actions/renderTemplate.action.ts @@ -78,18 +78,9 @@ export const renderTemplate = createAction({ label: 'Document (pdf)', value: 'pdf', }, - { - label: 'Video (mp4)', - value: 'mp4', - }, ], }, }), - audio_url: Property.ShortText({ - displayName: 'Audio url', - description: 'Link to an audio file. Only used for videos.', - required: false, - }), variables: Property.DynamicProperties({ displayName: 'Variables', required: true, @@ -155,8 +146,6 @@ export const renderTemplate = createAction({ propsValue.template_id }/sign-url?filetype=${encodeURIComponent( propsValue.filetype - )}&audioUrl=${encodeURIComponent( - propsValue.audio_url || '' )}&${query.join('&')}`, authentication: { type: AuthenticationType.BEARER_TOKEN, diff --git a/packages/pieces/community/http/package.json b/packages/pieces/community/http/package.json index 5e0026c37b..0fdcca63b8 100644 --- a/packages/pieces/community/http/package.json +++ b/packages/pieces/community/http/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-http", - "version": "0.3.10" + "version": "0.4.0" } diff --git a/packages/pieces/community/http/src/index.ts b/packages/pieces/community/http/src/index.ts index af29a67025..f35ec1e175 100644 --- a/packages/pieces/community/http/src/index.ts +++ b/packages/pieces/community/http/src/index.ts @@ -9,7 +9,7 @@ export const http = createPiece({ logoUrl: 'https://cdn.activepieces.com/pieces/http.png', categories: [PieceCategory.CORE], auth: PieceAuth.None(), - minimumSupportedRelease: '0.5.0', + minimumSupportedRelease: '0.20.3', actions: [httpSendRequestAction, httpReturnResponse], authors: ['khaledmashaly', 'bibhuty-did-this', 'AbdulTheActivePiecer'], triggers: [], diff --git a/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts b/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts index 7c524a356e..c7e92b2542 100644 --- a/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts +++ b/packages/pieces/community/http/src/lib/actions/send-http-request-action.ts @@ -1,13 +1,17 @@ -import { createAction, Property } from '@activepieces/pieces-framework'; import { - HttpRequest, + httpClient, HttpHeaders, + HttpRequest, QueryParams, - httpClient, - HttpError, } from '@activepieces/pieces-common'; -import { httpMethodDropdown } from '../common/props'; +import { + createAction, + DynamicPropsValue, + Property, +} from '@activepieces/pieces-framework'; import { assertNotNullOrUndefined } from '@activepieces/shared'; +import FormData from 'form-data'; +import { httpMethodDropdown } from '../common/props'; export const httpSendRequestAction = createAction({ name: 'send_request', @@ -27,28 +31,79 @@ export const httpSendRequestAction = createAction({ displayName: 'Query params', required: true, }), - body: Property.Json({ - displayName: 'Body', + body_type: Property.StaticDropdown({ + displayName: 'Body Type', required: false, + defaultValue: 'json', + options: { + disabled: false, + options: [ + { + label: 'Form Data', + value: 'form_data', + }, + { + label: 'JSON', + value: 'json', + }, + { + label: 'Raw', + value: 'raw', + }, + ], + }, }), - failsafe: Property.Checkbox({ - displayName: 'No Error On Failure', + body: Property.DynamicProperties({ + displayName: 'Body', + refreshers: ['body_type'], required: false, + props: async ({ body_type }) => { + if (!body_type) return {}; + + const bodyTypeInput = body_type as unknown as string; + + const fields: DynamicPropsValue = {}; + + switch (bodyTypeInput) { + case 'json': + fields['data'] = Property.Json({ + displayName: 'JSON Body', + required: true, + }); + break; + case 'raw': + fields['data'] = Property.LongText({ + displayName: 'Raw Body', + required: true, + }); + break; + case 'form_data': + fields['data'] = Property.Object({ + displayName: 'Form Data', + required: true, + }); + break; + } + return fields; + }, }), timeout: Property.Number({ displayName: 'Timeout(in seconds)', required: false, }), }, - + errorHandlingOptions: { + continueOnFailure: { hide: true }, + retryOnFailure: { defaultValue: true }, + }, async run(context) { - const { method, url, headers, queryParams, body, failsafe, timeout } = + const { method, url, headers, queryParams, body, body_type, timeout } = context.propsValue; assertNotNullOrUndefined(method, 'Method'); assertNotNullOrUndefined(url, 'URL'); - const request: HttpRequest> = { + const request: HttpRequest = { method, url, headers: headers as HttpHeaders, @@ -56,15 +111,18 @@ export const httpSendRequestAction = createAction({ timeout: timeout ? timeout * 1000 : 0, }; if (body) { - request.body = body; - } - try { - return await httpClient.sendRequest(request); - } catch (error) { - if (failsafe) { - return (error as HttpError).errorMessage(); + const bodyInput = body['data']; + if (body_type === 'form_data') { + const formData = new FormData(); + for (const key in bodyInput) { + formData.append(key, bodyInput[key]); + } + request.body = formData; + request.headers = { ...request.headers, ...formData.getHeaders() }; + } else { + request.body = bodyInput; } - throw error; } + return await httpClient.sendRequest(request); }, }); diff --git a/packages/pieces/community/math-helper/package.json b/packages/pieces/community/math-helper/package.json index cb6f697b9e..a37e1f46c4 100644 --- a/packages/pieces/community/math-helper/package.json +++ b/packages/pieces/community/math-helper/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-math-helper", - "version": "0.0.3" + "version": "0.0.4" } diff --git a/packages/pieces/community/quickzu/.eslintrc.json b/packages/pieces/community/quickzu/.eslintrc.json new file mode 100644 index 0000000000..632e9b0e22 --- /dev/null +++ b/packages/pieces/community/quickzu/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/quickzu/README.md b/packages/pieces/community/quickzu/README.md new file mode 100644 index 0000000000..a9c722d76e --- /dev/null +++ b/packages/pieces/community/quickzu/README.md @@ -0,0 +1,7 @@ +# pieces-quickzu + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-quickzu` to build the library. diff --git a/packages/pieces/community/quickzu/package.json b/packages/pieces/community/quickzu/package.json new file mode 100644 index 0000000000..03889fa278 --- /dev/null +++ b/packages/pieces/community/quickzu/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/piece-quickzu", + "version": "0.0.1" +} diff --git a/packages/pieces/community/quickzu/project.json b/packages/pieces/community/quickzu/project.json new file mode 100644 index 0000000000..e9f44063e8 --- /dev/null +++ b/packages/pieces/community/quickzu/project.json @@ -0,0 +1,33 @@ +{ + "name": "pieces-quickzu", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/quickzu/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/quickzu", + "tsConfig": "packages/pieces/community/quickzu/tsconfig.lib.json", + "packageJson": "packages/pieces/community/quickzu/package.json", + "main": "packages/pieces/community/quickzu/src/index.ts", + "assets": ["packages/pieces/community/quickzu/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-quickzu {args.ver} {args.tag}", + "dependsOn": ["build"] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/pieces/community/quickzu/**/*.ts"] + } + } + }, + "tags": [] +} diff --git a/packages/pieces/community/quickzu/src/index.ts b/packages/pieces/community/quickzu/src/index.ts new file mode 100644 index 0000000000..f7d6f17e8d --- /dev/null +++ b/packages/pieces/community/quickzu/src/index.ts @@ -0,0 +1,54 @@ +import { createPiece, PieceAuth } from '@activepieces/pieces-framework'; +import { updateBusinessTimeAction } from './lib/actions/business-settings/update-business-time'; +import { createCategoryAction } from './lib/actions/categories/create-category'; +import { deleteCategoryAction } from './lib/actions/categories/delete-category'; +import { listCategoriesAction } from './lib/actions/categories/list-categories'; +import { updateCategoryAction } from './lib/actions/categories/update-category'; +import { createProductDiscountAction } from './lib/actions/discounts/create-product-discount'; +import { createPromoCodeAction } from './lib/actions/discounts/create-promo-code'; +import { getOrderDetailsAction } from './lib/actions/orders/get-order-details'; +import { listLiveOrdersAction } from './lib/actions/orders/list-live-orders'; +import { listOrdersAction } from './lib/actions/orders/list-orders'; +import { updateOrderStatusAction } from './lib/actions/orders/update-order-status'; +import { addProductAction } from './lib/actions/products/create-product'; +import { deleteProductAction } from './lib/actions/products/delete-product'; +import { listProductsAction } from './lib/actions/products/list-products'; +import { updateProductAction } from './lib/actions/products/update-product'; +import { orderCreatedTrigger } from './lib/triggers/order-created'; + +const authHelpDescription = ` +1. Login to your Quickzu Dashboard. +2. Go to **https://app.quickzu.com/dash/settings/api-webhooks**. +3. Copy **API Token** to the clipboard and paste it.`; + +export const quickzuAuth = PieceAuth.SecretText({ + displayName: 'API Token', + description: authHelpDescription, + required: true, +}); + +export const quickzu = createPiece({ + displayName: 'Quickzu', + auth: quickzuAuth, + minimumSupportedRelease: '0.20.0', + logoUrl: 'https://cdn.activepieces.com/pieces/quickzu.png', + authors: ['kishanprmr'], + actions: [ + addProductAction, + updateProductAction, + deleteProductAction, + listProductsAction, + createCategoryAction, + updateCategoryAction, + deleteCategoryAction, + listCategoriesAction, + getOrderDetailsAction, + listOrdersAction, + listLiveOrdersAction, + updateOrderStatusAction, + createProductDiscountAction, + createPromoCodeAction, + updateBusinessTimeAction, + ], + triggers: [orderCreatedTrigger], +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/business-settings/update-business-time.ts b/packages/pieces/community/quickzu/src/lib/actions/business-settings/update-business-time.ts new file mode 100644 index 0000000000..d19bfe1304 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/business-settings/update-business-time.ts @@ -0,0 +1,73 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; +import { BusinessTimingInput } from '../../common/types'; + +export const updateBusinessTimeAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_business_time', + displayName: 'Update Business Time', + description: 'Updates business hours.', + props: { + items: Property.Array({ + displayName: 'Business Hours', + required: true, + properties: { + weekday: Property.StaticDropdown({ + displayName: 'Day', + required: true, + options: { + disabled: false, + options: [ + { label: 'Sunday', value: '0' }, + { label: 'Monday', value: '1' }, + { label: 'Tuesday', value: '2' }, + { label: 'Wednesday', value: '3' }, + { label: 'Thursday', value: '4' }, + { label: 'Friday', value: '5' }, + { label: 'Saturday', value: '6' }, + ], + }, + }), + start: Property.ShortText({ + displayName: 'Start Time', + description: 'Please use 24:00 hour format', + required: true, + }), + end: Property.ShortText({ + displayName: 'Start Time', + description: 'Please use 24:00 hour format', + required: true, + }), + status: Property.Checkbox({ + displayName: 'Status', + required: true, + defaultValue: true, + }), + }, + }), + }, + async run(context) { + const items = context.propsValue.items as BusinessDayHour[]; + const input: BusinessTimingInput = { + timing: {}, + }; + for (const day of items) { + input.timing[day.weekday] = { + start: day.start, + end: day.end, + status: day.status, + }; + } + + const client = makeClient(context.auth); + return await client.updateBusinessTime(input); + }, +}); + +type BusinessDayHour = { + weekday: string; + start: string; + end: string; + status: boolean; +}; diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/create-category.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/create-category.ts new file mode 100644 index 0000000000..76b8565894 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/create-category.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; + +export const createCategoryAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_create_category', + displayName: 'Create Category', + description: 'Creates a new category in store.', + props: { + name: Property.ShortText({ + displayName: 'Category Name', + required: true, + }), + status: Property.Checkbox({ + displayName: 'Category Status', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { name, status } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.createCategory({ name, status }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/delete-category.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/delete-category.ts new file mode 100644 index 0000000000..19b793d1ce --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/delete-category.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const deleteCategoryAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_delete_category', + displayName: 'Delete Category', + description: 'Deletes an existing category from store.', + props: { + categoryId: quickzuCommon.categoryId(true), + }, + async run(context) { + const { categoryId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.deleteCategory(categoryId!); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/list-categories.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/list-categories.ts new file mode 100644 index 0000000000..261db64c48 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/list-categories.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../../'; +import { makeClient } from '../../common'; + +export const listCategoriesAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_categories', + displayName: 'List Categories', + description: 'Retrives all categories from store.', + props: { + term: Property.ShortText({ + displayName: 'Search Term', + description: 'Category name that need to be search.', + required: false, + }), + }, + async run(context) { + const { term } = context.propsValue; + + const client = makeClient(context.auth); + return client.listCategories(term); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/categories/update-category.ts b/packages/pieces/community/quickzu/src/lib/actions/categories/update-category.ts new file mode 100644 index 0000000000..363288a0df --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/categories/update-category.ts @@ -0,0 +1,29 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const updateCategoryAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_category', + displayName: 'Update Category', + description: 'Updates an existing category in store.', + props: { + categoryId: quickzuCommon.categoryId(true), + name: Property.ShortText({ + displayName: 'Category Name', + required: false, + }), + status: Property.Checkbox({ + displayName: 'Category Status', + required: true, + defaultValue: false, + }), + }, + async run(context) { + const { categoryId, name, status } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.updateCategory(categoryId!, { name, status }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/discounts/create-product-discount.ts b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-product-discount.ts new file mode 100644 index 0000000000..82266a68d5 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-product-discount.ts @@ -0,0 +1,168 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; +import { + DiscountFilterType, + DiscountMethod, + DiscountValueType, +} from '../../common/constants'; +import { ProductDiscountInput } from '../../common/types'; + +export const createProductDiscountAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_create_product_discount', + displayName: 'Create Product Discount', + description: 'Creates a new discount for category or product level.', + props: { + title: Property.ShortText({ + displayName: 'Promotion / Discount Title', + required: true, + }), + start_date: Property.DateTime({ + displayName: 'Valid From', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + end_date: Property.DateTime({ + displayName: 'Valid To', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + filter_type: Property.StaticDropdown({ + displayName: 'Filter', + description: 'Choose what gets discount (products/categories).', + required: true, + options: { + disabled: false, + options: Object.values(DiscountFilterType).map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + selectedFilterValues: Property.DynamicProperties({ + displayName: 'Select Option', + refreshers: ['filter_type'], + required: true, + props: async ({ auth, filter_type }) => { + if (!auth) return {}; + if (!filter_type) return {}; + + const fields: DynamicPropsValue = {}; + const discountFilterType = filter_type as unknown as DiscountFilterType; + + const client = makeClient(auth as unknown as string); + + switch (discountFilterType) { + case DiscountFilterType.CATEGORIES: { + const res = await client.listCategories(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Categories', + required: true, + description: 'Categories eligible for a discount.', + options: { + disabled: false, + options: res.data.map((category) => { + return { + label: category.name, + value: category._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.PRODUCTS: { + const res = await client.listProducts(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Products', + required: true, + description: 'Products eligible for a discount.', + options: { + disabled: false, + options: res.data.map((product) => { + return { + label: product.name, + value: product._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.ALL_PRODUCTS: { + fields['values'] = []; + break; + } + } + return fields; + }, + }), + type: Property.StaticDropdown({ + displayName: 'Discount', + required: true, + options: { + disabled: false, + options: Object.values(DiscountValueType).map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + value: Property.ShortText({ + displayName: 'Discount Value', + required: true, + }), + is_enabled: Property.Checkbox({ + displayName: 'Enabled ?', + required: true, + defaultValue: true, + }), + is_visible: Property.Checkbox({ + displayName: 'Visibility', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { + title, + start_date, + end_date, + filter_type, + selectedFilterValues, + type, + value, + is_enabled, + is_visible, + } = context.propsValue; + + const input: ProductDiscountInput = { + discount_method: DiscountMethod.ITEM_LEVEL, + title, + start_date, + end_date, + filter_type, + type, + value, + is_enabled, + is_visible, + }; + if (filter_type === DiscountFilterType.CATEGORIES) { + input.selectedCategories = selectedFilterValues['values']; + } else if (filter_type === DiscountFilterType.PRODUCTS) { + input.selectedProducts = selectedFilterValues['values']; + } + + const client = makeClient(context.auth); + return await client.createProductDiscount(input); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/discounts/create-promo-code.ts b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-promo-code.ts new file mode 100644 index 0000000000..9601118ce4 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/discounts/create-promo-code.ts @@ -0,0 +1,188 @@ +import { + DynamicPropsValue, + Property, + createAction, +} from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; +import { + DiscountFilterType, + DiscountMethod, + DiscountValueType, +} from '../../common/constants'; +import { ProductDiscountInput } from '../../common/types'; + +export const createPromoCodeAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_create_promo_code', + displayName: 'Create Promo/Coupon Code', + description: 'Creates a new promo code for category or product level.', + props: { + title: Property.ShortText({ + displayName: 'Promotion / Discount Title', + required: true, + }), + start_date: Property.DateTime({ + displayName: 'Valid From', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + end_date: Property.DateTime({ + displayName: 'Valid To', + description: 'Please use YYYY-MM-DD format.', + required: true, + }), + filter_type: Property.StaticDropdown({ + displayName: 'Filter', + description: 'Choose what gets discount (products/categories).', + required: true, + options: { + disabled: false, + options: Object.values(DiscountFilterType).map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + selectedFilterValues: Property.DynamicProperties({ + displayName: 'Select Option', + refreshers: ['filter_type'], + required: true, + props: async ({ auth, filter_type }) => { + if (!auth) return {}; + if (!filter_type) return {}; + + const fields: DynamicPropsValue = {}; + const discountFilterType = filter_type as unknown as DiscountFilterType; + + const client = makeClient(auth as unknown as string); + + switch (discountFilterType) { + case DiscountFilterType.CATEGORIES: { + const res = await client.listCategories(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Categories', + required: true, + description: 'Categories eligible for a discount.', + options: { + disabled: false, + options: res.data.map((category) => { + return { + label: category.name, + value: category._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.PRODUCTS: { + const res = await client.listProducts(); + fields['values'] = Property.StaticMultiSelectDropdown({ + displayName: 'Products', + required: true, + description: 'Products eligible for a discount.', + options: { + disabled: false, + options: res.data.map((product) => { + return { + label: product.name, + value: product._id, + }; + }), + }, + }); + break; + } + case DiscountFilterType.ALL_PRODUCTS: { + fields['values'] = []; + break; + } + } + return fields; + }, + }), + promo_code: Property.ShortText({ + displayName: 'Promo Code', + required: true, + }), + minimum_cart_value: Property.Number({ + displayName: 'Minium Order Amount', + required: true, + }), + max_users_limit: Property.Number({ + displayName: 'Maximum Promo Code Limit', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Discount', + required: true, + options: { + disabled: false, + options: Object.values(DiscountValueType) + .filter((value) => value !== DiscountValueType.FIXED_PRICE) + .map((value) => { + return { + label: value.toLowerCase(), + value: value, + }; + }), + }, + }), + value: Property.ShortText({ + displayName: 'Discount Value', + required: true, + }), + is_enabled: Property.Checkbox({ + displayName: 'Enabled ?', + required: true, + defaultValue: true, + }), + is_visible: Property.Checkbox({ + displayName: 'Visibility', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { + title, + start_date, + end_date, + filter_type, + selectedFilterValues, + type, + promo_code, + max_users_limit, + minimum_cart_value, + value, + is_enabled, + is_visible, + } = context.propsValue; + + const input: ProductDiscountInput = { + discount_method: DiscountMethod.SUB_TOTAL, + title, + start_date, + end_date, + filter_type, + max_users_limit, + minimum_cart_value, + promo_code, + type, + value, + is_enabled, + is_visible, + }; + if (filter_type === DiscountFilterType.CATEGORIES) { + input.selectedCategories = selectedFilterValues['values']; + } else if (filter_type === DiscountFilterType.PRODUCTS) { + input.selectedProducts = selectedFilterValues['values']; + } + + const client = makeClient(context.auth); + return await client.createProductDiscount(input); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/get-order-details.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/get-order-details.ts new file mode 100644 index 0000000000..173dd8353f --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/get-order-details.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const getOrderDetailsAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_get_order_details', + displayName: 'Get Order Details', + description: 'Retrives order details from store.', + props: { + orderId: quickzuCommon.orderId(true), + }, + async run(context) { + const { orderId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.getOrderDetails(orderId!); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/list-live-orders.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/list-live-orders.ts new file mode 100644 index 0000000000..269256d0b0 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/list-live-orders.ts @@ -0,0 +1,24 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; + +export const listLiveOrdersAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_live_orders', + displayName: 'List Live Orders', + description: 'Retrives live orders of store.', + props: { + limit: Property.Number({ + displayName: + 'Number of live orders that need to be fetched per order status', + required: true, + defaultValue: 15, + }), + }, + async run(context) { + const { limit } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listLiveOrders(limit); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/list-orders.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/list-orders.ts new file mode 100644 index 0000000000..c9b9d7b466 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/list-orders.ts @@ -0,0 +1,28 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient } from '../../common'; + +export const listOrdersAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_orders', + displayName: 'List Orders', + description: 'Retrives orders of store.', + props: { + page: Property.Number({ + displayName: 'Current page number', + required: true, + defaultValue: 1, + }), + limit: Property.Number({ + displayName: 'Number of orders per page', + required: true, + defaultValue: 20, + }), + }, + async run(context) { + const { page, limit } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listOrders(page, limit); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/orders/update-order-status.ts b/packages/pieces/community/quickzu/src/lib/actions/orders/update-order-status.ts new file mode 100644 index 0000000000..f2cd7b5753 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/orders/update-order-status.ts @@ -0,0 +1,33 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; +import { OrderStatus } from '../../common/constants'; + +export const updateOrderStatusAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_order_status', + displayName: 'Update Order Status', + description: 'Updates status of order in store.', + props: { + orderId: quickzuCommon.orderId(true), + status: Property.StaticDropdown({ + displayName: 'Order Status', + required: true, + options: { + disabled: false, + options: Object.values(OrderStatus).map((value) => { + return { + label: value, + value: value, + }; + }), + }, + }), + }, + async run(context) { + const { orderId, status } = context.propsValue; + + const client = makeClient(context.auth); + return await client.updateOrderStatus(orderId!, status); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/create-product.ts b/packages/pieces/community/quickzu/src/lib/actions/products/create-product.ts new file mode 100644 index 0000000000..7fdddd2036 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/create-product.ts @@ -0,0 +1,104 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; +import { ProductUnit } from '../../common/constants'; + +export const addProductAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_add_product', + displayName: 'Add Product', + description: 'Adds new product to store.', + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + desc: Property.LongText({ + displayName: 'Description', + required: false, + }), + categoryId: quickzuCommon.categoryId(true), + mrp: Property.Number({ + displayName: 'MRP Price', + required: true, + }), + price: Property.Number({ + displayName: 'Selling Price', + required: false, + description: 'Selling price should be equal or less than MRP.', + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: true, + defaultValue: 'kilogram', + options: { + disabled: false, + options: Object.values(ProductUnit).map((val) => { + return { + label: val, + value: val, + }; + }), + }, + }), + value_per_unit: Property.Number({ + displayName: 'Unit Value', + required: true, + defaultValue: 1, + }), + availability: Property.Checkbox({ + displayName: 'Availability', + required: true, + defaultValue: true, + }), + exclude_tax: Property.Checkbox({ + displayName: 'Exclude Tax', + required: true, + defaultValue: false, + }), + enable_variants: Property.Checkbox({ + displayName: 'Enable Variants', + required: true, + defaultValue: false, + }), + status: Property.Checkbox({ + displayName: 'Status', + required: true, + defaultValue: true, + }), + }, + async run(context) { + const { + name, + desc, + categoryId, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.createProduct({ + name, + desc, + category: categoryId!, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + meta: { + nonveg: false, + }, + }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/delete-product.ts b/packages/pieces/community/quickzu/src/lib/actions/products/delete-product.ts new file mode 100644 index 0000000000..d9a05135ec --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/delete-product.ts @@ -0,0 +1,19 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const deleteProductAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_delete_product', + displayName: 'Delete Product', + description: 'Deletes an existing product from store.', + props: { + productId: quickzuCommon.productId(true), + }, + async run(context) { + const { productId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.deleteProduct(productId!); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/list-products.ts b/packages/pieces/community/quickzu/src/lib/actions/products/list-products.ts new file mode 100644 index 0000000000..20cbe11ec6 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/list-products.ts @@ -0,0 +1,23 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; + +export const listProductsAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_list_products', + displayName: 'List Products', + description: 'Retrives all or single product details from store.', + props: { + term: Property.ShortText({ + displayName: 'Product name that need to be search.', + required: false, + }), + categoryId: quickzuCommon.categoryId(false), + }, + async run(context) { + const { term, categoryId } = context.propsValue; + + const client = makeClient(context.auth); + return await client.listProducts(term, categoryId); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/actions/products/update-product.ts b/packages/pieces/community/quickzu/src/lib/actions/products/update-product.ts new file mode 100644 index 0000000000..b9529289c5 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/actions/products/update-product.ts @@ -0,0 +1,100 @@ +import { Property, createAction } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../..'; +import { makeClient, quickzuCommon } from '../../common'; +import { ProductUnit } from '../../common/constants'; + +export const updateProductAction = createAction({ + auth: quickzuAuth, + name: 'quickzu_update_product', + displayName: 'Update Product', + description: 'Updates an existing product in store.', + props: { + productId: quickzuCommon.productId(true), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + desc: Property.LongText({ + displayName: 'Description', + required: false, + }), + categoryId: quickzuCommon.categoryId(false), + mrp: Property.Number({ + displayName: 'MRP Price', + required: false, + }), + price: Property.Number({ + displayName: 'Selling Price', + required: false, + description: 'Selling price should be equal or less than MRP.', + }), + unit: Property.StaticDropdown({ + displayName: 'Unit', + required: false, + options: { + disabled: false, + options: Object.values(ProductUnit).map((val) => { + return { + label: val, + value: val, + }; + }), + }, + }), + value_per_unit: Property.Number({ + displayName: 'Unit Value', + required: false, + }), + availability: Property.Checkbox({ + displayName: 'Availability', + required: false, + }), + exclude_tax: Property.Checkbox({ + displayName: 'Exclude Tax', + required: false, + }), + enable_variants: Property.Checkbox({ + displayName: 'Enable Variants', + required: false, + }), + status: Property.Checkbox({ + displayName: 'Status', + required: false, + }), + }, + async run(context) { + const { + name, + productId, + desc, + categoryId, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + } = context.propsValue; + + const client = makeClient(context.auth); + + return await client.updateProduct(productId!, { + name, + desc, + category: categoryId!, + mrp, + price, + unit, + value_per_unit, + availability, + exclude_tax, + enable_variants, + status, + meta: { + nonveg: false, + }, + }); + }, +}); diff --git a/packages/pieces/community/quickzu/src/lib/common/client.ts b/packages/pieces/community/quickzu/src/lib/common/client.ts new file mode 100644 index 0000000000..57842b5b4c --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/client.ts @@ -0,0 +1,182 @@ +import { + HttpMessageBody, + HttpMethod, + QueryParams, + httpClient, +} from '@activepieces/pieces-common'; +import { OrderStatus } from './constants'; +import { + BusinessTimingInput, + Category, + CategoryInput, + ListAPIResponse, + Product, + ProductDiscountInput, + ProductInput, +} from './types'; + +function emptyValueFilter( + accessor: (key: string) => any +): (key: string) => boolean { + return (key: string) => { + const val = accessor(key); + return ( + val !== null && + val !== undefined && + (typeof val != 'string' || val.length > 0) + ); + }; +} + +export function prepareQuery(request?: Record): QueryParams { + const params: QueryParams = {}; + if (!request) return params; + Object.keys(request) + .filter(emptyValueFilter((k) => request[k])) + .forEach((k: string) => { + params[k] = (request as Record)[k].toString(); + }); + return params; +} + +export class QuickzuAPIClient { + constructor(private apiToken: string) {} + async makeRequest( + method: HttpMethod, + resourceUri: string, + query?: QueryParams, + body: any | undefined = undefined + ): Promise { + // const baseUrl = this.apiTableUrl.replace(/\/$/, ''); + const res = await httpClient.sendRequest({ + method: method, + url: `https://app.quickzu.com/api` + resourceUri, + headers: { + Authorization: this.apiToken, + }, + queryParams: query, + body: body, + }); + return res.body; + } + async createCategory(categoryInput: CategoryInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/categories/', + undefined, + categoryInput + ); + } + async updateCategory( + categoryId: string, + categoryInput: Partial + ) { + return await this.makeRequest( + HttpMethod.PUT, + `/seller/categories/${categoryId}`, + undefined, + categoryInput + ); + } + async deleteCategory(categoryId: string) { + return await this.makeRequest( + HttpMethod.DELETE, + `/seller/categories/${categoryId}` + ); + } + async listCategories(term?: string): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/seller/categories', + prepareQuery({ + term: term, + }) + ); + } + async createProduct(productInput: ProductInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/products', + undefined, + productInput + ); + } + async updateProduct(productId: string, productInput: Partial) { + return await this.makeRequest( + HttpMethod.PUT, + `/seller/products/${productId}`, + undefined, + productInput + ); + } + async deleteProduct(productId: string) { + return await this.makeRequest( + HttpMethod.DELETE, + `/seller/products/${productId}` + ); + } + async listProducts( + term?: string, + categoryId?: string + ): Promise> { + return await this.makeRequest>( + HttpMethod.GET, + '/seller/products', + prepareQuery({ + term: term, + category: categoryId, + }) + ); + } + async getOrderDetails(orderId: string) { + return await this.makeRequest(HttpMethod.GET, `/seller/orders/${orderId}`); + } + async updateOrderStatus(orderId: string, status: OrderStatus) { + return await this.makeRequest( + HttpMethod.PUT, + `/seller/orders/${orderId}`, + undefined, + { + status: status, + } + ); + } + async listOrders(page: number, limit: number) { + return await this.makeRequest( + HttpMethod.GET, + '/seller/orders/', + prepareQuery({ page: page, limit: limit }) + ); + } + async listLiveOrders(limit: number) { + return await this.makeRequest( + HttpMethod.GET, + '/seller/orders/live', + prepareQuery({ limit: limit }) + ); + } + async createProductDiscount(discountInput: ProductDiscountInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/discounts', + undefined, + discountInput + ); + } + async createPromoCode(promocodeInput: ProductDiscountInput) { + return await this.makeRequest( + HttpMethod.POST, + '/seller/discounts', + undefined, + promocodeInput + ); + } + async updateBusinessTime(timingInput: BusinessTimingInput) { + return await this.makeRequest( + HttpMethod.PUT, + '/seller/settings', + undefined, + timingInput + ); + } +} diff --git a/packages/pieces/community/quickzu/src/lib/common/constants.ts b/packages/pieces/community/quickzu/src/lib/common/constants.ts new file mode 100644 index 0000000000..73875fbc97 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/constants.ts @@ -0,0 +1,48 @@ +export enum OrderStatus { + PENDING = 'pending', + PAID = 'paid', + ACCEPTED = 'accepted', + CANCELLED = 'cancelled', + DONE = 'done', +} +export enum DiscountFilterType { + PRODUCTS = 'PRODUCTS', + CATEGORIES = 'CATEGORIES', + ALL_PRODUCTS = 'ALL_PRODUCTS', +} + +export enum DiscountValueType { + PERCENTAGE = 'PERCENTAGE', + FLAT = 'FLAT', + FIXED_PRICE = 'FIXED_PRICE', +} + +export enum DiscountMethod { + ITEM_LEVEL = 'ITEM_LEVEL', + SUB_TOTAL = 'SUB_TOTAL', +} + +export enum ProductUnit { + PIECE = 'piece', + KILOGRAM = 'kilogram', + GRAM = 'Gram', + POUND = 'pound', + LITRE = 'litre', + MILI_LITRE = 'mili litre', + DOZEN = 'dozen', + FEET = 'feet', + METER = 'meter', + SQUARE_FEET = 'square feet', + SQUARE_METER = 'square meter', + SET = 'set', + HOUR = 'hour', + DAY = 'day', + SERVICE = 'service', + COMBO = 'combo', + BOX = 'box', + PACK = 'pack', + BOTTLE = 'bottle', + WHOLE = 'whole', + SLICE = 'slice', + BULK = 'bulk', +} diff --git a/packages/pieces/community/quickzu/src/lib/common/index.ts b/packages/pieces/community/quickzu/src/lib/common/index.ts new file mode 100644 index 0000000000..a06a09c7d0 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/index.ts @@ -0,0 +1,94 @@ +import { PiecePropValueSchema, Property } from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../'; +import { QuickzuAPIClient } from './client'; + +export function makeClient(auth: PiecePropValueSchema) { + const client = new QuickzuAPIClient(auth); + return client; +} + +export const quickzuCommon = { + categoryId: (required = false) => + Property.Dropdown({ + displayName: 'Category', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listCategories(); + + return { + disabled: false, + options: res.data.map((category) => { + return { + label: category.name, + value: category._id, + }; + }), + }; + }, + }), + productId: (required = false) => + Property.Dropdown({ + displayName: 'Product', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listProducts(); + + return { + disabled: false, + options: res.data.map((product) => { + return { + label: product.name, + value: product._id, + }; + }), + }; + }, + }), + orderId: (required = false) => + Property.Dropdown({ + displayName: 'Order', + refreshers: [], + required, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account first.', + }; + } + const client = makeClient(auth as string); + const res = await client.listOrders(1, 20); + + return { + disabled: false, + options: res['data'].map( + (order: { _id: string; order_id: number }) => { + return { + label: order.order_id.toString(), + value: order._id, + }; + } + ), + }; + }, + }), +}; diff --git a/packages/pieces/community/quickzu/src/lib/common/types.ts b/packages/pieces/community/quickzu/src/lib/common/types.ts new file mode 100644 index 0000000000..9f507195ec --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/common/types.ts @@ -0,0 +1,92 @@ +import { + DiscountFilterType, + DiscountMethod, + DiscountValueType, +} from './constants'; + +export type ListAPIResponse = { + data: Array; + total: number; + success: boolean; +}; + +export type Category = { + status: boolean; + index: number; + _id: string; + name: string; + shop: string; + __v: number; +}; + +export type CategoryInput = { + name: string; + status: boolean; +}; + +export type Product = { + desc: string; + value_per_unit: number; + status: boolean; + availability: boolean; + enable_variants: boolean; + stock_enabled: boolean; + available_stock: number; + sku: string; + exclude_tax: boolean; + index: number; + _id: string; + name: string; + mrp: number; + price: number; + category: Pick; + unit: string; + meta: { nonveg: boolean }; + picture: string; + shop: string; + __v: number; + picture_thumb: string; + id: string; +}; + +export type ProductInput = { + name: string; + desc?: string; + value_per_unit: number; + status: boolean; + availability: boolean; + enable_variants: boolean; + exclude_tax: boolean; + mrp: number; + price?: number; + category: string; + unit: string; + meta: { nonveg: boolean }; +}; + +export type ProductDiscountInput = { + discount_method: DiscountMethod; + filter_type: DiscountFilterType; + is_enabled: boolean; + is_visible: boolean; + start_date: string; + end_date: string; + max_users_limit?: number; + minimum_cart_value?: number; + selectedCategories?: string[]; + selectedProducts?: string[]; + promo_code?: string; + title: string; + type: DiscountValueType; + value: string; +}; + +export type BusinessTimingInput = { + timing: { + [key: string]: { + status: boolean; + start: string; + end: string; + }; + }; +}; diff --git a/packages/pieces/community/quickzu/src/lib/triggers/order-created.ts b/packages/pieces/community/quickzu/src/lib/triggers/order-created.ts new file mode 100644 index 0000000000..0b754fd744 --- /dev/null +++ b/packages/pieces/community/quickzu/src/lib/triggers/order-created.ts @@ -0,0 +1,293 @@ +import { + Property, + TriggerStrategy, + WebhookHandshakeStrategy, + createTrigger, +} from '@activepieces/pieces-framework'; +import { quickzuAuth } from '../../'; + +const markdown = ` +- Go to the **Settings->API and Webhooks** section. +- In the webhook settings, paste this URL: + \`{{webhookUrl}}\` +- Click on **Save**. +`; +const sampleData = { + payload: { + id: '65cc96cfcf7028f638e20b0c', + data: { + id: '65cc96cfcf7028f638e20b0c', + __v: 0, + _id: '65cc96cfcf7028f638e20b0c', + area: '63862620938a5552acce9e6b', + shop: '5fbe833cef26b83b8f53b7c3', + status: 'pending', + currency: 'INR', + customer: { + phone: '9039101337', + address: 'okay mumbai', + full_name: 'Mohit', + }, + order_id: 233312, + products: [ + { + id: '65cc96cfcf70280fbde20b0d', + _id: '65cc96cfcf70280fbde20b0d', + qty: 1, + addon: [], + amount: 10, + product: { + id: '5fbe8677ef26b83b8f53b7cf', + __v: 0, + _id: '5fbe8677ef26b83b8f53b7cf', + mrp: 10, + sku: '', + desc: '', + meta: { nonveg: false }, + name: 'Shrewsbury Cookies', + shop: '5fbe833cef26b83b8f53b7c3', + unit: 'gram', + index: 0, + price: 10, + addons: [], + status: true, + options: [], + picture: + 'https://d1mzjggyz5012h.cloudfront.net/quickzu.com/products/308790a53b3ca742_scookies.jpeg', + category: '5fbe83acef26b83b8f53b7c7', + variants: [], + exclude_tax: false, + availability: true, + picture_thumb: + 'https://d1mzjggyz5012h.cloudfront.net/quickzu.com/products/308790a53b3ca742_scookies.jpeg?width=300', + stock_enabled: false, + value_per_unit: 200, + available_stock: -1, + enable_variants: true, + }, + variant: [], + createdAt: '2024-02-14T10:32:47.953Z', + updatedAt: '2024-02-14T10:32:47.953Z', + }, + ], + coupon_id: '', + createdAt: '2024-02-14T10:32:47.953Z', + sub_total: 29, + updatedAt: '2024-02-14T10:32:47.953Z', + order_type: 'delivery', + coupon_code: '', + instruction: 'spicy food', + payment_mode: 'paylater', + taxes_amount: 2.9, + total_amount: '31.90', + currency_data: { + code: 'INR', + name: 'Indian Rupee', + symbol: 'Rs', + rounding: 0, + name_plural: 'Indian rupees', + symbol_native: '₹', + decimal_digits: 2, + }, + payment_status: 'unpaid', + transaction_id: '', + delivery_charges: 0, + stripe_session_id: '', + subtotal_discount: 0, + item_level_discount: 0, + razorpay_session_id: '', + }, + shop: { + id: '5fbe833cef26b83b8f53b7c3', + __v: 0, + _id: '5fbe833cef26b83b8f53b7c3', + css: '', + apps: { analytics: '' }, + desc: 'pastries, sandwiches, cake', + link: 'ccake.quickzu.com', + logo: 'https://d1mzjggyz5012h.cloudfront.net/quickzu.com/logos/a74e93e49cc56f69_cakelogo.jpg', + name: 'Classic Cakes', + alias: 'ccake', + cover: + 'https://d1mzjggyz5012h.cloudfront.net/quickzu.com/covers/4b92ef0c7b90189b_pngtree-nutritional-food-top-view-simple-gray-banner-image_176015.jpg', + phone: '916260494878', + footer: + '\r\n', + header: '', + timing: { + '0': { end: '23:59', start: '00:00', status: true }, + '1': { end: '23:59', start: '00:00', status: false }, + '2': { end: '23:59', start: '00:00', status: true }, + '3': { end: '23:59', start: '00:00', status: true }, + '4': { end: '23:59', start: '00:00', status: true }, + '5': { end: '23:59', start: '00:00', status: true }, + '6': { end: '23:59', start: '00:00', status: true }, + createdAt: '2024-01-13T17:54:46.861Z', + updatedAt: '2024-01-13T17:54:46.861Z', + }, + address: '78 Washington St, Hoboken', + country: 'IN', + dine_in: true, + domains: [], + message: { + dine_in: + "Hi, I'd like to place an order 👇\n\n✅🏃🏽\n {{ORDER_TYPE}} *Order No: {{ORDER_NUMBER}}*\nfrom {{STORE_LINK}}\n--\n{{PRODUCTS}}\n Notes: {{ORDER_INSTRUCTION}}\n\n--\nItems Total: {{SUB_TOTAL}}\n \nCoupon Discount: {{SUBTOTAL_DISCOUNT}}\n \nItem Discount: {{ITEM_LEVEL_DISCOUNT}}\n \nTaxes: {{TAXES}}\n \nDelivery: {{DELIVERY}}\n \n*Total: {{TOTAL}}*\n \n\nCustomer Details🏃🏽\n\nName: {{CUSTOMER_NAME}}\nContact: {{CUSTOMER_PHONE}}\n \nTable Number *{{TABLE_NUMBER}}*\n \n\n---‐---------------------------------------\n\n{{STORE_NAME}} will confirm your order upon receiving the message. Here below are the payment options available 👇🏼.\n\nPayment Options 💳\n {{PAYMENT_INSTRUCTION}}\n\nPayment Method 💳\n {{PAYMENT_METHOD}}", + pick_up: + "Hi, I'd like to place an order 👇\n\n✅🏃🏽\n {{ORDER_TYPE}} *Order No: {{ORDER_NUMBER}}*\nfrom {{STORE_LINK}}\n--\n{{PRODUCTS}}\n Notes: {{ORDER_INSTRUCTION}}\n\n--\nItems Total: {{SUB_TOTAL}}\n \nCoupon Discount: {{SUBTOTAL_DISCOUNT}}\n \nItem Discount: {{ITEM_LEVEL_DISCOUNT}}\n \nTaxes: {{TAXES}}\n \nDelivery: {{DELIVERY}}\n \n*Total: {{TOTAL}}*\n \n\nCustomer Details🏃🏽\n\nName: {{CUSTOMER_NAME}}\nContact: {{CUSTOMER_PHONE}}\n \nI will pick up this order at {{PICKUP_TIME}}\n \n\n---‐---------------------------------------\n\n{{STORE_NAME}} will confirm your order upon receiving the message. Here below are the payment options available 👇🏼.\n\nPayment Options 💳\n {{PAYMENT_INSTRUCTION}}\n\nPayment Method 💳\n {{PAYMENT_METHOD}}", + delivery: + "Hi, I'd like to place an order 👇\n\n✅🏃🏽\n {{ORDER_TYPE}} *Order No: {{ORDER_NUMBER}}*\nfrom {{STORE_LINK}}\n--\n{{PRODUCTS}}\n Notes: {{ORDER_INSTRUCTION}}\n\n--\nItems Total: {{SUB_TOTAL}}\n \nCoupon Discount: {{SUBTOTAL_DISCOUNT}}\n \nItem Discount: {{ITEM_LEVEL_DISCOUNT}}\n \nTaxes: {{TAXES}}\n \nDelivery: {{DELIVERY}}\n \n*Total: {{TOTAL}}*\n \n\nCustomer Details🏃🏽\n\nName: {{CUSTOMER_NAME}}\nContact: {{CUSTOMER_PHONE}}\n \nPlease deliver it to my address {{CUSTOMER_ADDRESS}}\n \n\n---‐---------------------------------------\n\n{{STORE_NAME}} will confirm your order upon receiving the message. Here below are the payment options available 👇🏼.\n\nPayment Options 💳\n {{PAYMENT_INSTRUCTION}}\n\nPayment Method 💳\n {{PAYMENT_METHOD}}", + appointment: + "Hi, I'd like to make an appointment 👇\n\n✅🏃🏽\n *Apointment No: {{ORDER_NUMBER}}*\nfrom {{STORE_LINK}}\n--\n{{PRODUCTS}}\n Notes: {{ORDER_INSTRUCTION}}\n\n--\nItems Total: {{SUB_TOTAL}}\n \nCoupon Discount: {{SUBTOTAL_DISCOUNT}}\n \nItem Discount: {{ITEM_LEVEL_DISCOUNT}}\n \nTaxes: {{TAXES}}\n \nDelivery: {{DELIVERY}}\n \n*Total: {{TOTAL}}*\n \n\nCustomer Details🏃🏽\n\nName: {{CUSTOMER_NAME}}\nContact: {{CUSTOMER_PHONE}}\n \nI will be there at {{APPOINTMENT_TIME}}\n \n\n---‐---------------------------------------\n\n{{STORE_NAME}} will confirm your appointment upon receiving the message. Here below are the payment options available 👇🏼.\n\nPayment Options 💳\n {{PAYMENT_INSTRUCTION}}.\n\nPayment Method 💳\n {{PAYMENT_METHOD}}", + }, + pick_up: true, + category: 'restaurant', + currency: 'INR', + delivery: { + cost: 0, + free: 0, + status: true, + min_order: 0, + is_free_delivery: true, + }, + language: 'en', + template: 'five', + createdAt: '2020-11-25T16:15:56.049Z', + is_closed: false, + seller_id: { + id: '5fbe8101ef26b83b8f53b7c1', + __v: 0, + _id: '5fbe8101ef26b83b8f53b7c1', + hash: 'ffa2cd4a438e23809531052bd7accf45669be99982eed59baffff963fd2cb48367042071da26c4d0dc865735c2fdf64876c69897c08dbd4d220ad65679810cd6ae4f6a7ec71b8c46720da7e2de4420d1f5f5ee6440c34af36e1689414381e7d191df4ccdfd1c88bf1bb4004bfbbde7cf3cf094f25bf907bb275cdb11be89afe5ce2cf66a9a633d185401336784c471edf43f89c5f53e4461e9c0703186204167b7d622261738bc964d3be975fa45ab245e1ca0680cdcefd5cc25b079ac1416a9854c5a1c31cc567085c90144f542aa734d5a26c972d4dfe6fd9326841d9cd4c90d0db904eea5a8d19365f05c26082e362bb7d126022e1653a56f6fd33a4bccb576a84101b0167d20e95db019d6945d5ca12e96c339ca8cab22c18aa780c626ce32c18a0029ce5e7320e59dc3751c48f2e3be394b4f6c22e961bed035dae5cbea7d5f93582ce08d38e9182950725d4743f6f293faa3a4d9a6b0725195da1b21f48079da2a878c66b797d46d3bc7c44728e59f2dcc297ea118926f0355fe4c805541da5675b802d0022e76b334a461f4f2df6fc0673dac432fa27bf27b570c7005cd58dcaf7e62e217b00bf0872dbafb00d8b8c324b14d7f4d8ad879545870ecb3e546f8bb9112236862368d10cf794ced73a7397747d1df220ee5ec20626bb9eee2c271b1435c3e25ac61dda946678d4950a9361bd2d11132bb22e2a43bc44d91', + salt: '4aa078be319a316ba11dfae6b8117f80', + email: 'focix@getnada.com', + language: 'en-us', + password: + '$2a$13$NwC..fYJpEmwtcmertejlu6E/I/8E8ryC2gKv8S0XJ5EN1rQ8TSHy', + createdAt: '2020-11-25T16:06:25.546Z', + full_name: 'focix', + updatedAt: '2023-07-01T14:02:27.589Z', + account_type: 'seller', + is_email_verified: true, + }, + updatedAt: '2024-01-29T10:22:46.775Z', + is_blocked: false, + logo_thumb: + 'https://d1mzjggyz5012h.cloudfront.net/quickzu.com/logos/a74e93e49cc56f69_cakelogo.jpg?width=300', + receive_on: 'whatsapp', + appointment: false, + is_onlymenu: false, + payment_inst: 'Pay Offline via UPI/CASH etc', + currency_data: { + code: 'INR', + name: 'Indian Rupee', + symbol: 'Rs', + rounding: 0, + name_plural: 'Indian rupees', + symbol_native: '₹', + decimal_digits: 2, + }, + payment_modes: { + stripe: { + logo: '/public/assets/imgs/stripe.svg', + name: 'stripe', + enabled: true, + test_mode: true, + secret_key: + 'sk_test_51HK1rfLxC9RJsx0f53ARD7EdFRGBK2HH26uFoXNZ66tlwmGWkUaZxju2UfaIUnAd7jOnKogZXNrQZY3OvfbwjaL100IBjyBOYx', + description: '', + display_name: 'Stripe', + publishable_key: + 'pk_test_51HK1rfLxC9RJsx0fH8vzV7BHKeem4wZw3i00K3NGSfn8cFN3fpDOaLaXntVMwt7URfTdOmzqjYsi40erLuRqpJ4D00ftojur6v', + test_secret_key: + 'sk_test_51HK1rfLxC9RJsx0f53ARD7EdFRGBK2HH26uFoXNZ66tlwmGWkUaZxju2UfaIUnAd7jOnKogZXNrQZY3OvfbwjaL100IBjyBOYx', + test_publishable_key: + 'pk_test_51HK1rfLxC9RJsx0fH8vzV7BHKeem4wZw3i00K3NGSfn8cFN3fpDOaLaXntVMwt7URfTdOmzqjYsi40erLuRqpJ4D00ftojur6v', + }, + razorpay: { + logo: '/public/assets/imgs/razorpay.svg', + name: 'razorpay', + key_id: 'as', + enabled: false, + test_mode: false, + secret_key: 'da', + description: '', + test_key_id: '', + display_name: 'Razorpay', + test_secret_key: '', + }, + }, + use_area_list: true, + is_maintenance: false, + manual_payments: true, + payment_inst_title: 'Pay Later', + term_condition_text: 'Bhgghhhbsbsbbsbnzja bhbb', + condition_optin_enabled: true, + invoice_footer_thankyou_msg: '', + }, + text: "Hi, I'd like to place an order 👇\n\n✅🏃🏽\n Delivery *Order No: 233312*\nfrom ccake.quickzu.com\n--\n▪ 1 ⨯ Shrewsbury Cookies 🟩 INR 10\n▪ 1 ⨯ Choco Chips Cookies 🟩 INR 9\n▪ 1 ⨯ Cokin 🟩 INR 10\n\n Notes: spicy food\n\n--\nItems Total: INR 29.00\n \nCoupon Discount: INR 0.00\n \nItem Discount: INR 0.00\n \nTaxes: INR 2.90\n \nDelivery: INR 0.00\n \n*Total: INR 31.90*\n \n\nCustomer Details🏃🏽\n\nName: Mohit\nContact: 9039101337\n \nPlease deliver it to my address okay mumbai\n \n\n---‐---------------------------------------\n\nClassic Cakes will confirm your order upon receiving the message. Here below are the payment options available 👇🏼.\n\nPayment Options 💳\n \nPay Offline via UPI/CASH etc\n\nPayment Method 💳\n PAYLATER", + overiew: { + TAXES: 'INR 2.90', + TOTAL: 'INR 31.90', + DELIVERY: 'INR 0.00', + PRODUCTS: + '▪ 1 ⨯ Shrewsbury Cookies 🟩 INR 10\n▪ 1 ⨯ Choco Chips Cookies 🟩 INR 9\n▪ 1 ⨯ Cokin 🟩 INR 10\n', + SUB_TOTAL: 'INR 29.00', + ORDER_TYPE: 'Delivery', + STORE_LINK: 'ccake.quickzu.com', + STORE_NAME: 'Classic Cakes', + PICKUP_TIME: '', + INVOICE_LINK: + 'https://ccake.quickzu.com/paylater/success?orderId=65cc96cfcf7028f638e20b0c', + ORDER_NUMBER: 233312, + TABLE_NUMBER: '', + CUSTOMER_NAME: 'Mohit', + CUSTOMER_PHONE: '9039101337', + PAYMENT_METHOD: 'PAYLATER', + APPOINTMENT_TIME: '', + CUSTOMER_ADDRESS: 'okay mumbai', + ORDER_INSTRUCTION: 'spicy food', + SUBTOTAL_DISCOUNT: 'INR 0.00', + ITEM_LEVEL_DISCOUNT: 'INR 0.00', + PAYMENT_INSTRUCTION: '\nPay Offline via UPI/CASH etc', + }, + hyperlink: + "https://wa.me/916260494878?text=Hi%2C%20I'd%20like%20to%20place%20an%20order%20%F0%9F%91%87%0A%0A%E2%9C%85%F0%9F%8F%83%F0%9F%8F%BD%0A%20Delivery%20*Order%20No%3A%20233312*%0Afrom%20ccake.quickzu.com%0A--%0A%E2%96%AA%201%20%E2%A8%AF%20Shrewsbury%20Cookies%20%20%F0%9F%9F%A9%20%20%20%20%20%20INR%2010%0A%E2%96%AA%201%20%E2%A8%AF%20Choco%20Chips%20Cookies%20%20%F0%9F%9F%A9%20%20%20%20%20%20INR%209%0A%E2%96%AA%201%20%E2%A8%AF%20Cokin%20%20%F0%9F%9F%A9%20%20%20%20%20%20INR%2010%0A%0A%20Notes%3A%20spicy%20food%0A%0A--%0AItems%20Total%3A%20INR%2029.00%0A%20%20%20%20%0ACoupon%20Discount%3A%20INR%200.00%0A%20%20%20%20%0AItem%20Discount%3A%20INR%200.00%0A%20%20%20%20%0ATaxes%3A%20INR%202.90%0A%20%20%20%20%0ADelivery%3A%20INR%200.00%0A%20%20%20%20%0A*Total%3A%20INR%2031.90*%0A%20%20%20%20%0A%0ACustomer%20Details%F0%9F%8F%83%F0%9F%8F%BD%0A%0AName%3A%20Mohit%0AContact%3A%209039101337%0A%20%20%20%20%0APlease%20deliver%20it%20to%20my%20address%20okay%20mumbai%0A%20%20%20%20%0A%0A---%E2%80%90---------------------------------------%0A%0AClassic%20Cakes%20will%20confirm%20your%20order%20upon%20receiving%20the%20message.%20Here%20below%20are%20the%20payment%20options%20available%20%F0%9F%91%87%F0%9F%8F%BC.%0A%0APayment%20Options%20%F0%9F%92%B3%0A%20%0APay%20Offline%20via%20UPI%2FCASH%20etc%0A%0APayment%20Method%20%F0%9F%92%B3%0A%20PAYLATER", + total_amount: '31.90', + }, + resource: 'order', + operation: 'create', +}; + +export const orderCreatedTrigger = createTrigger({ + auth: quickzuAuth, + name: 'quickzu_order_created_trigger', + displayName: 'Order Created/Updated', + description: + 'Triggers when a new order is created or a order status is changed in store.', + type: TriggerStrategy.WEBHOOK, + sampleData: sampleData, + props: { + md: Property.MarkDown({ + value: markdown, + }), + }, + async onEnable(context) { + // Empty + }, + async onDisable(context) { + // Empty + }, + async run(context) { + return [context.payload]; + }, + handshakeConfiguration: { + strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT, + paramName: 'test', + }, + async onHandshake(context) { + return { + status: 200, + body: {}, + }; + }, +}); diff --git a/packages/pieces/community/quickzu/tsconfig.json b/packages/pieces/community/quickzu/tsconfig.json new file mode 100644 index 0000000000..059cd81661 --- /dev/null +++ b/packages/pieces/community/quickzu/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/quickzu/tsconfig.lib.json b/packages/pieces/community/quickzu/tsconfig.lib.json new file mode 100644 index 0000000000..28369ef762 --- /dev/null +++ b/packages/pieces/community/quickzu/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/pieces/community/salesforce/src/lib/common/index.ts b/packages/pieces/community/salesforce/src/lib/common/index.ts index e30d990d08..3c0cd1f658 100644 --- a/packages/pieces/community/salesforce/src/lib/common/index.ts +++ b/packages/pieces/community/salesforce/src/lib/common/index.ts @@ -103,7 +103,7 @@ export async function querySalesforceApi( }); } -export async function createBulkJob( +export async function createBulkJob( method: HttpMethod, authentication: OAuth2PropertyValue, jobDetails: HttpMessageBody diff --git a/packages/pieces/community/slack/package.json b/packages/pieces/community/slack/package.json index 555fe38cef..72fe90eff3 100644 --- a/packages/pieces/community/slack/package.json +++ b/packages/pieces/community/slack/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-slack", - "version": "0.3.15" + "version": "0.3.16" } diff --git a/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts b/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts index 655dadd2a4..36ea6ada50 100644 --- a/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts +++ b/packages/pieces/community/slack/src/lib/actions/request-approval-direct-message.ts @@ -34,8 +34,12 @@ export const requestApprovalDirectMessageAction = createAction({ assertNotNullOrUndefined(token, 'token'); assertNotNullOrUndefined(text, 'text'); assertNotNullOrUndefined(userId, 'userId'); - const approvalLink = `${context.serverUrl}v1/flow-runs/${context.run.id}/resume?action=approve`; - const disapprovalLink = `${context.serverUrl}v1/flow-runs/${context.run.id}/resume?action=disapprove`; + const approvalLink = context.generateResumeUrl({ + queryParams: { action: 'approve' }, + }) + const disapprovalLink = context.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }) return await slackSendMessage({ token, diff --git a/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts b/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts index 6708c14ff1..764236ac27 100644 --- a/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts +++ b/packages/pieces/community/slack/src/lib/actions/request-approval-message.ts @@ -34,8 +34,12 @@ export const requestSendApprovalMessageAction = createAction({ assertNotNullOrUndefined(token, 'token'); assertNotNullOrUndefined(text, 'text'); assertNotNullOrUndefined(channel, 'channel'); - const approvalLink = `${context.serverUrl}v1/flow-runs/${context.run.id}/resume?action=approve`; - const disapprovalLink = `${context.serverUrl}v1/flow-runs/${context.run.id}/resume?action=disapprove`; + const approvalLink = context.generateResumeUrl({ + queryParams: { action: 'approve' }, + }) + const disapprovalLink = context.generateResumeUrl({ + queryParams: { action: 'disapprove' }, + }) return await slackSendMessage({ token, diff --git a/packages/pieces/community/slack/src/lib/common/request-action.ts b/packages/pieces/community/slack/src/lib/common/request-action.ts index 1e0ac8b001..279b074fcb 100644 --- a/packages/pieces/community/slack/src/lib/common/request-action.ts +++ b/packages/pieces/community/slack/src/lib/common/request-action.ts @@ -39,7 +39,9 @@ export const requestAction = async (conversationId: string, context: any) => { assertNotNullOrUndefined(text, 'text'); const actionElements = actionTextToIds.map((action: any) => { - const actionLink = `${context.serverUrl}v1/flow-runs/${context.run.id}/resume?action=${action.actionId}`; + const actionLink = context.generateResumeUrl({ + queryParams: { action: action.actionId }, + }) return { type: 'button', diff --git a/packages/pieces/community/store/package.json b/packages/pieces/community/store/package.json index 48408f5db3..154411e11a 100644 --- a/packages/pieces/community/store/package.json +++ b/packages/pieces/community/store/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-store", - "version": "0.4.3" + "version": "0.4.4" } diff --git a/packages/pieces/community/tags/package.json b/packages/pieces/community/tags/package.json index 70b35cd6bf..84660bc0fc 100644 --- a/packages/pieces/community/tags/package.json +++ b/packages/pieces/community/tags/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-tags", - "version": "0.0.3" + "version": "0.0.4" } diff --git a/packages/pieces/community/text-helper/package.json b/packages/pieces/community/text-helper/package.json index 2eb92d2a55..9774a212d5 100644 --- a/packages/pieces/community/text-helper/package.json +++ b/packages/pieces/community/text-helper/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-text-helper", - "version": "0.1.5" + "version": "0.1.6" } diff --git a/packages/pieces/community/wootric/.eslintrc.json b/packages/pieces/community/wootric/.eslintrc.json new file mode 100644 index 0000000000..632e9b0e22 --- /dev/null +++ b/packages/pieces/community/wootric/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/packages/pieces/community/wootric/README.md b/packages/pieces/community/wootric/README.md new file mode 100644 index 0000000000..1eae3c28f9 --- /dev/null +++ b/packages/pieces/community/wootric/README.md @@ -0,0 +1,7 @@ +# pieces-wootric + +This library was generated with [Nx](https://nx.dev). + +## Building + +Run `nx build pieces-wootric` to build the library. diff --git a/packages/pieces/community/wootric/package.json b/packages/pieces/community/wootric/package.json new file mode 100644 index 0000000000..cc673959bd --- /dev/null +++ b/packages/pieces/community/wootric/package.json @@ -0,0 +1,4 @@ +{ + "name": "@activepieces/pieces-wootric", + "version": "0.0.1" +} diff --git a/packages/pieces/community/wootric/project.json b/packages/pieces/community/wootric/project.json new file mode 100644 index 0000000000..5facf22edb --- /dev/null +++ b/packages/pieces/community/wootric/project.json @@ -0,0 +1,33 @@ +{ + "name": "pieces-wootric", + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/pieces/community/wootric/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/packages/pieces/community/wootric", + "tsConfig": "packages/pieces/community/wootric/tsconfig.lib.json", + "packageJson": "packages/pieces/community/wootric/package.json", + "main": "packages/pieces/community/wootric/src/index.ts", + "assets": ["packages/pieces/community/wootric/*.md"], + "buildableProjectDepsInPackageJsonType": "dependencies", + "updateBuildableProjectDepsInPackageJson": true + } + }, + "publish": { + "command": "node tools/scripts/publish.mjs pieces-community-wootric {args.ver} {args.tag}", + "dependsOn": ["build"] + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["packages/pieces/community/wootric/**/*.ts"] + } + } + }, + "tags": [] +} diff --git a/packages/pieces/community/wootric/src/index.ts b/packages/pieces/community/wootric/src/index.ts new file mode 100644 index 0000000000..0e9a4a5b42 --- /dev/null +++ b/packages/pieces/community/wootric/src/index.ts @@ -0,0 +1,28 @@ +import { + createPiece, + PieceAuth, +} from '@activepieces/pieces-framework'; +import { createWootricSurvey } from './lib/actions/create-survey'; +import { OAuth2GrantType } from '@activepieces/shared'; + +export const WOOTRIC_API_URL = 'https://api.wootric.com'; +export const WOOTRIC_IMAGE_URL = + 'https://assets-production.wootric.com/assets/wootric-is-now-inmoment-250x108-85cb4900c62ff4d33200abafee7d63372d410abc5bf0cab90e80a07d4f4e5a31.png'; + +export const wootricAuth = PieceAuth.OAuth2({ + required: true, + grantType: OAuth2GrantType.CLIENT_CREDENTIALS, + authUrl: '', + tokenUrl: `${WOOTRIC_API_URL}/oauth/token`, + scope: [], +}); + +export const wootric = createPiece({ + displayName: 'Wootric', + auth: wootricAuth, + minimumSupportedRelease: '0.9.0', + logoUrl: WOOTRIC_IMAGE_URL, + authors: [], + actions: [createWootricSurvey], + triggers: [], +}); diff --git a/packages/pieces/community/wootric/src/lib/actions/create-survey.ts b/packages/pieces/community/wootric/src/lib/actions/create-survey.ts new file mode 100644 index 0000000000..25ca9509cd --- /dev/null +++ b/packages/pieces/community/wootric/src/lib/actions/create-survey.ts @@ -0,0 +1,50 @@ +import { + createAction, + Property, +} from '@activepieces/pieces-framework'; +import { wootricAuth, WOOTRIC_API_URL } from '../../'; +import { httpClient, HttpMethod } from '@activepieces/pieces-common'; + +export const sendSurvey = async (surveyRequestPayload: object) => { + const EMAIL_SURVEY = `${WOOTRIC_API_URL}/v1/email_survey`; + return await httpClient.sendRequest({ + method: HttpMethod.POST, + url: EMAIL_SURVEY, + body: surveyRequestPayload, + }); +}; + +export const createWootricSurvey = createAction({ + name: 'trigger_wootric_survey', + auth: wootricAuth, + displayName: 'Trigger Wootric Survey', + description: 'Trigger a survey from Wootric', + props: { + emails: Property.Array({ + displayName: 'Emails', + description: 'End user emails, where you want the survey to be recieved', + required: true, + defaultValue: [], + }), + surveyImmediately: Property.Checkbox({ + displayName: 'Survey Immediately', + description: + 'Enter "true" to survey immediately to bypass checks, otherwise "false"', + required: true, + }), + }, + async run(context) { + const { surveyImmediately, emails } = context.propsValue; + const { access_token } = context.auth; + + const surveyRequestPayload = { + emails: emails, + survey_immediately: surveyImmediately, + access_token: access_token, + }; + + const surveyResponse = await sendSurvey(surveyRequestPayload); + + return surveyResponse.body; + }, +}); diff --git a/packages/pieces/community/wootric/tsconfig.json b/packages/pieces/community/wootric/tsconfig.json new file mode 100644 index 0000000000..059cd81661 --- /dev/null +++ b/packages/pieces/community/wootric/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/pieces/community/wootric/tsconfig.lib.json b/packages/pieces/community/wootric/tsconfig.lib.json new file mode 100644 index 0000000000..28369ef762 --- /dev/null +++ b/packages/pieces/community/wootric/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts b/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts index 90e3b4e568..c02ea4799a 100644 --- a/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts +++ b/packages/server/api/src/app/app-connection/app-connection-service/app-connection-service.ts @@ -105,9 +105,10 @@ export const appConnectionService = { }) if (isNil(connectionById)) { throw new ActivepiecesError({ - code: ErrorCode.APP_CONNECTION_NOT_FOUND, + code: ErrorCode.ENTITY_NOT_FOUND, params: { - id: params.id, + entityType: 'AppConnection', + entityId: params.id, }, }) } diff --git a/packages/server/api/src/app/app-connection/app-connection-worker-controller.ts b/packages/server/api/src/app/app-connection/app-connection-worker-controller.ts index 23291691ad..6223e8b4d4 100644 --- a/packages/server/api/src/app/app-connection/app-connection-worker-controller.ts +++ b/packages/server/api/src/app/app-connection/app-connection-worker-controller.ts @@ -29,9 +29,10 @@ export const appConnectionWorkerController: FastifyPluginCallbackTypebox = ( if (isNil(appConnection)) { throw new ActivepiecesError({ - code: ErrorCode.APP_CONNECTION_NOT_FOUND, + code: ErrorCode.ENTITY_NOT_FOUND, params: { - id: request.params.connectionName, + entityId: `connectionName=${request.params.connectionName}`, + entityType: 'AppConnection', }, }) } diff --git a/packages/server/api/src/app/database/migration/common/1707087022764-add-trigger-test-strategy.ts b/packages/server/api/src/app/database/migration/common/1707087022764-add-trigger-test-strategy.ts index 6d13534cfe..45dc65567b 100644 --- a/packages/server/api/src/app/database/migration/common/1707087022764-add-trigger-test-strategy.ts +++ b/packages/server/api/src/app/database/migration/common/1707087022764-add-trigger-test-strategy.ts @@ -56,12 +56,16 @@ const addTestStrategyToTriggers = (pieceMetadata: PieceMetadata): void => { APP_WEBHOOK: 'TEST_FUNCTION', } + pieceMetadata.triggers = parseTriggers(pieceMetadata.triggers) + for (const trigger of Object.values(pieceMetadata.triggers)) { trigger.testStrategy = testStrategyMap[trigger.type] } } const removeTestStrategyFromTriggers = (pieceMetadata: PieceMetadata): void => { + pieceMetadata.triggers = parseTriggers(pieceMetadata.triggers) + for (const trigger of Object.values(pieceMetadata.triggers)) { delete trigger.testStrategy } @@ -73,10 +77,18 @@ const updatePieceMetadata = async ( ): Promise => { await queryRunner.query( 'UPDATE piece_metadata SET triggers = $1 WHERE id = $2', - [pieceMetadata.triggers, pieceMetadata.id], + [JSON.stringify(pieceMetadata.triggers), pieceMetadata.id], ) } +const parseTriggers = (triggers: string | Record): Record => { + if (typeof triggers === 'string') { + return JSON.parse(triggers) + } + + return triggers +} + type TriggerType = 'POLLING' | 'WEBHOOK' | 'APP_WEBHOOK' type TriggerTestStrategy = 'SIMULATION' | 'TEST_FUNCTION' @@ -88,5 +100,5 @@ type Trigger = { type PieceMetadata = { id: string - triggers: Record + triggers: Record | string } diff --git a/packages/server/api/src/app/ee/audit-logs/audit-event-service.ts b/packages/server/api/src/app/ee/audit-logs/audit-event-service.ts index fdc83f716d..48973651b2 100644 --- a/packages/server/api/src/app/ee/audit-logs/audit-event-service.ts +++ b/packages/server/api/src/app/ee/audit-logs/audit-event-service.ts @@ -54,7 +54,7 @@ export const auditLogService: AuditLogService = { entity: AuditEventEntity, query: { limit, - order: 'ASC', + order: 'DESC', afterCursor: decodedCursor.nextCursor, beforeCursor: decodedCursor.previousCursor, }, @@ -113,6 +113,18 @@ const saveEvent = async ( } break } + case ApplicationEventName.UPDATED_FLOW: { + eventToSave = { + ...baseProps, + action: rawEvent.action, + data: { + flowId: rawEvent.flow.id, + flowName: rawEvent.flow.version.displayName, + request: rawEvent.request, + }, + } + break + } case ApplicationEventName.CREATED_FLOW: case ApplicationEventName.DELETED_FLOW: { eventToSave = { diff --git a/packages/server/api/src/app/ee/pieces/admin-piece-requests.ee.ts b/packages/server/api/src/app/ee/pieces/admin-piece-requests.ee.ts index a03d4771b1..cb4c550c3d 100644 --- a/packages/server/api/src/app/ee/pieces/admin-piece-requests.ee.ts +++ b/packages/server/api/src/app/ee/pieces/admin-piece-requests.ee.ts @@ -1,4 +1,5 @@ import { + ErrorHandlingOptionsParam, TriggerStrategy, WebhookHandshakeConfiguration, } from '@activepieces/pieces-framework' @@ -16,6 +17,7 @@ const Action = Type.Object({ description: Type.String(), requireAuth: Type.Boolean(), props: Type.Unknown(), + errorHandlingOptions: Type.Optional(ErrorHandlingOptionsParam), }) const Trigger = Type.Composite([ diff --git a/packages/server/api/src/app/flows/flow-run/flow-run-controller.ts b/packages/server/api/src/app/flows/flow-run/flow-run-controller.ts index 10aeaa3a9f..803334eb63 100644 --- a/packages/server/api/src/app/flows/flow-run/flow-run-controller.ts +++ b/packages/server/api/src/app/flows/flow-run/flow-run-controller.ts @@ -30,6 +30,7 @@ const ResumeFlowRunRequest = { schema: { params: Type.Object({ id: ApId, + requestId: Type.String(), }), }, } @@ -89,11 +90,12 @@ export const flowRunController: FastifyPluginCallbackTypebox = ( }, ) - app.all('/:id/resume', ResumeFlowRunRequest, async (req) => { + app.all('/:id/requests/:requestId', ResumeFlowRunRequest, async (req) => { const headers = req.headers as Record const queryParams = req.query as Record await flowRunService.addToQueue({ flowRunId: req.params.id, + requestId: req.params.requestId, resumePayload: { body: req.body, headers, @@ -102,6 +104,7 @@ export const flowRunController: FastifyPluginCallbackTypebox = ( executionType: ExecutionType.RESUME, }) }) + app.post('/:id/retry', RetryFlowRequest, async (req) => { await flowRunService.retry({ diff --git a/packages/server/api/src/app/flows/flow-run/flow-run-service.ts b/packages/server/api/src/app/flows/flow-run/flow-run-service.ts index 25b957614a..1394685994 100644 --- a/packages/server/api/src/app/flows/flow-run/flow-run-service.ts +++ b/packages/server/api/src/app/flows/flow-run/flow-run-service.ts @@ -40,13 +40,13 @@ import { flowRunHooks } from './flow-run-hooks' import { flowResponseWatcher } from './flow-response-watcher' export const flowRunRepo = - databaseConnection.getRepository(FlowRunEntity) + databaseConnection.getRepository(FlowRunEntity) const getFlowRunOrCreate = async ( params: GetOrCreateParams, ): Promise> => { const { id, projectId, flowId, flowVersionId, flowDisplayName, environment } = - params + params if (id) { return flowRunService.getOneOrThrow({ @@ -143,9 +143,11 @@ export const flowRunService = { async addToQueue({ flowRunId, resumePayload, + requestId, executionType, }: { flowRunId: FlowRunId + requestId?: string resumePayload?: ResumePayload executionType: ExecutionType }): Promise { @@ -164,11 +166,7 @@ export const flowRunService = { }) } const pauseMetadata = flowRunToResume.pauseMetadata - const matchRequestId = - !isNil(resumePayload) && - pauseMetadata && - pauseMetadata.type === PauseType.WEBHOOK && - resumePayload.queryParams.requestId === pauseMetadata.requestId + const matchRequestId = pauseMetadata?.type === PauseType.WEBHOOK && requestId === pauseMetadata.requestId if (matchRequestId) { await flowRunService.start({ payload: resumePayload, @@ -276,7 +274,7 @@ export const flowRunService = { const flowVersion = await flowVersionService.getOneOrThrow(flowVersionId) const payload = - flowVersion.trigger.settings.inputUiInfo.currentSelectedData + flowVersion.trigger.settings.inputUiInfo.currentSelectedData return this.start({ projectId, diff --git a/packages/server/api/src/app/flows/flow-version/flow-version.service.ts b/packages/server/api/src/app/flows/flow-version/flow-version.service.ts index c58881fefb..268c30ce5c 100644 --- a/packages/server/api/src/app/flows/flow-version/flow-version.service.ts +++ b/packages/server/api/src/app/flows/flow-version/flow-version.service.ts @@ -84,7 +84,7 @@ export const flowVersionService = { }: ApplyOperationParams): Promise { let operations: FlowOperationRequest[] = [] let mutatedFlowVersion: FlowVersion = flowVersion - + switch (userOperation.type) { case FlowOperationType.USE_AS_DRAFT: { const previousVersion = await flowVersionService.getFlowVersionOrThrow({ @@ -142,7 +142,6 @@ export const flowVersionService = { mutatedFlowVersion.updated = dayjs().toISOString() mutatedFlowVersion.updatedBy = userId - return flowVersionRepo(entityManager).save(mutatedFlowVersion) }, diff --git a/packages/server/api/src/app/flows/flow/flow.controller.ts b/packages/server/api/src/app/flows/flow/flow.controller.ts index 8db1ff0e64..f056acb455 100644 --- a/packages/server/api/src/app/flows/flow/flow.controller.ts +++ b/packages/server/api/src/app/flows/flow/flow.controller.ts @@ -1,6 +1,8 @@ import { + ActivepiecesError, ApId, CreateFlowRequest, + ErrorCode, FlowOperationRequest, FlowTemplateWithoutProjectInformation, GetFlowQueryParamsRequest, @@ -44,23 +46,20 @@ export const flowController: FastifyPluginAsyncTypebox = async (app) => { return reply.status(StatusCodes.CREATED).send(newFlow) }) - app.post('/:id', UpdateFlowRequestOptions, async (request, reply) => { + app.post('/:id', UpdateFlowRequestOptions, async (request) => { const flow = await flowService.getOnePopulatedOrThrow({ id: request.params.id, projectId: request.principal.projectId, }) - // BEGIN EE - const currentTime = dayjs() - const userId = await extractUserIdFromPrincipal(request.principal) - if ( - !isNil(flow.version.updatedBy) && - flow.version.updatedBy !== userId && - currentTime.diff(dayjs(flow.version.updated), 'minute') <= 1 - ) { - return reply.status(StatusCodes.CONFLICT).send() - } - // END EE + const userId = await extractUserIdFromPrincipal(request.principal) + await assertThatFlowIsNotBeingUsed(flow, userId) + eventsHooks.get().send(request, { + action: ApplicationEventName.UPDATED_FLOW, + request: request.body, + flow, + userId: request.principal.id, + }) const updatedFlow = await flowService.update({ id: request.params.id, @@ -122,6 +121,26 @@ export const flowController: FastifyPluginAsyncTypebox = async (app) => { }) } +async function assertThatFlowIsNotBeingUsed( + flow: PopulatedFlow, + userId: string, +): Promise { + const currentTime = dayjs() + if ( + !isNil(flow.version.updatedBy) && + flow.version.updatedBy !== userId && + currentTime.diff(dayjs(flow.version.updated), 'minute') <= 1 + ) { + throw new ActivepiecesError({ + code: ErrorCode.FLOW_IN_USE, + params: { + flowVersionId: flow.version.id, + message: 'Flow is being used by another user in the last minute. Please try again later.', + }, + }) + } +} + async function extractUserIdFromPrincipal( principal: Principal, ): Promise { diff --git a/packages/server/api/src/app/flows/flow/flow.service.ts b/packages/server/api/src/app/flows/flow/flow.service.ts index 6e5132a971..bf0ff29410 100644 --- a/packages/server/api/src/app/flows/flow/flow.service.ts +++ b/packages/server/api/src/app/flows/flow/flow.service.ts @@ -217,10 +217,10 @@ export const flowService = { if (lastVersion.state === FlowVersionState.LOCKED) { const lastVersionWithArtifacts = - await flowVersionService.getFlowVersionOrThrow({ - flowId: id, - versionId: undefined, - }) + await flowVersionService.getFlowVersionOrThrow({ + flowId: id, + versionId: undefined, + }) lastVersion = await flowVersionService.createEmptyVersion(id, { displayName: lastVersionWithArtifacts.displayName, diff --git a/packages/server/api/src/app/helper/application-events/index.ts b/packages/server/api/src/app/helper/application-events/index.ts index f3f4035581..3f9584537c 100644 --- a/packages/server/api/src/app/helper/application-events/index.ts +++ b/packages/server/api/src/app/helper/application-events/index.ts @@ -1,5 +1,5 @@ import { ApplicationEventName } from '@activepieces/ee-shared' -import { AppConnection, Folder, PopulatedFlow } from '@activepieces/shared' +import { AppConnection, FlowOperationRequest, Folder, PopulatedFlow } from '@activepieces/shared' import { FastifyRequest } from 'fastify' export type CreateAuditEventParam = @@ -34,6 +34,12 @@ export type CreateAuditEventParam = userId: string projectId: string } + | { + action: ApplicationEventName.UPDATED_FLOW + flow: PopulatedFlow + request: FlowOperationRequest + userId: string + } let hooks: ApplicationEventHooks = { async send(_request, _params) { diff --git a/packages/server/api/src/app/helper/error-handler.ts b/packages/server/api/src/app/helper/error-handler.ts index 49ef72ca86..ab589202c2 100644 --- a/packages/server/api/src/app/helper/error-handler.ts +++ b/packages/server/api/src/app/helper/error-handler.ts @@ -18,6 +18,7 @@ export const errorHandler = async ( [ErrorCode.PERMISSION_DENIED]: StatusCodes.FORBIDDEN, [ErrorCode.ENTITY_NOT_FOUND]: StatusCodes.NOT_FOUND, [ErrorCode.EXISTING_USER]: StatusCodes.CONFLICT, + [ErrorCode.FLOW_IN_USE]: StatusCodes.CONFLICT, [ErrorCode.AUTHORIZATION]: StatusCodes.FORBIDDEN, [ErrorCode.SIGN_UP_DISABLED]: StatusCodes.FORBIDDEN, [ErrorCode.INVALID_CREDENTIALS]: StatusCodes.UNAUTHORIZED, diff --git a/packages/server/api/src/app/helper/jwt-utils.ts b/packages/server/api/src/app/helper/jwt-utils.ts index 6bfd836bd7..cecabb126d 100644 --- a/packages/server/api/src/app/helper/jwt-utils.ts +++ b/packages/server/api/src/app/helper/jwt-utils.ts @@ -13,6 +13,7 @@ import { localFileStore } from './store' import { QueueMode, SystemProp, system } from 'server-shared' import { promisify } from 'util' import { randomBytes } from 'crypto' +import dayjs from 'dayjs' export enum JwtSignAlgorithm { HS256 = 'HS256', @@ -121,6 +122,19 @@ export const jwtUtils = { return reject(err) } + // Todo - remove after old tokens has been invalidated, maybe after (April 2024), remove the hardcoded date + const decodedToken = jwtLibrary.decode(jwt, { + json: true, + }) + if (decodedToken?.exp && decodedToken.exp !== 1754223678 && decodedToken.exp > dayjs().add(2, 'weeks').unix()) { + throw new ActivepiecesError({ + code: ErrorCode.INVALID_BEARER_TOKEN, + params: { + message: 'Old token in use. Please login again.', + }, + }) + } + return resolve(payload as T) }) }) diff --git a/packages/server/api/src/app/pieces/base-piece-module.ts b/packages/server/api/src/app/pieces/base-piece-module.ts index 4d6a75bdc3..99f6f0c374 100644 --- a/packages/server/api/src/app/pieces/base-piece-module.ts +++ b/packages/server/api/src/app/pieces/base-piece-module.ts @@ -4,9 +4,7 @@ import { } from '@fastify/type-provider-typebox' import { ALL_PRINICPAL_TYPES, - ActivepiecesError, ApEdition, - ErrorCode, GetPieceRequestParams, GetPieceRequestQuery, GetPieceRequestWithScopeParams, @@ -16,7 +14,6 @@ import { PrincipalType, } from '@activepieces/shared' import { engineHelper } from '../helper/engine-helper' -import { system, SystemProp } from 'server-shared' import { getPiecePackage, pieceMetadataService, @@ -33,8 +30,6 @@ export const pieceModule: FastifyPluginAsyncTypebox = async (app) => { await app.register(basePiecesController, { prefix: '/v1/pieces' }) } -const statsEnabled = system.getBoolean(SystemProp.STATS_ENABLED) - const basePiecesController: FastifyPluginAsyncTypebox = async (app) => { app.get( '/categories', @@ -59,6 +54,7 @@ const basePiecesController: FastifyPluginAsyncTypebox = async (app) => { }, schema: { querystring: ListPiecesRequestQuery, + }, }, async (req): Promise => { @@ -75,6 +71,7 @@ const basePiecesController: FastifyPluginAsyncTypebox = async (app) => { searchQuery: req.query.searchQuery, sortBy: req.query.sortBy, orderBy: req.query.orderBy, + suggestionType: req.query.suggestionType, }) return pieceMetadataSummary }, @@ -178,27 +175,6 @@ const basePiecesController: FastifyPluginAsyncTypebox = async (app) => { }, ) - app.get( - '/stats', - { - config: { - allowedPrincipals: ALL_PRINICPAL_TYPES, - }, - }, - async () => { - if (!statsEnabled) { - throw new ActivepiecesError({ - code: ErrorCode.ENTITY_NOT_FOUND, - params: { - message: 'not found', - }, - }) - } - - return pieceMetadataService.stats() - }, - ) - app.delete( '/:id', { diff --git a/packages/server/api/src/app/pieces/piece-metadata-entity.ts b/packages/server/api/src/app/pieces/piece-metadata-entity.ts index 4e5553bd8c..2444ce2bc5 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-entity.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-entity.ts @@ -6,11 +6,9 @@ import { import { ApId, BaseModel, - FileId, PackageType, PieceType, Project, - ProjectId, } from '@activepieces/shared' import { ARRAY_COLUMN_TYPE, @@ -20,18 +18,24 @@ import { JSON_COLUMN_TYPE, isPostgres, } from '../database/database-common' +import { Static, Type } from '@sinclair/typebox' -type PiecePackageMetadata = { - projectId?: ProjectId - pieceType: PieceType - packageType: PackageType - archiveId?: FileId -} + +const PiecePackageMetadata = Type.Object({ + projectId: Type.Optional(Type.String()), + pieceType: Type.Enum(PieceType), + packageType: Type.Enum(PackageType), + archiveId: Type.Optional(Type.String()), +}) +type PiecePackageMetadata = Static export type PieceMetadataModel = PieceMetadata & PiecePackageMetadata -export type PieceMetadataModelSummary = PieceMetadataSummary & -PiecePackageMetadata +export const PieceMetadataModelSummary = Type.Union([ + PieceMetadataSummary, + PiecePackageMetadata, +]) +export type PieceMetadataModelSummary = Static export type PieceMetadataSchema = BaseModel & PieceMetadataModel diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/aggregated-metadata-service.ts b/packages/server/api/src/app/pieces/piece-metadata-service/aggregated-metadata-service.ts index fb5326d564..75468cfefa 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/aggregated-metadata-service.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/aggregated-metadata-service.ts @@ -1,5 +1,4 @@ import { PieceMetadataService } from './piece-metadata-service' -import { AllPiecesStats } from './piece-stats-service' import { CloudPieceMetadataService } from './cloud-piece-metadata-service' import { DbPieceMetadataService } from './db-piece-metadata-service' import { @@ -62,10 +61,6 @@ export const AggregatedPieceMetadataService = (): PieceMetadataService => { return dbPieceProvider.delete(params) }, - async stats(): Promise { - throw new Error('operation not supported') - }, - async getExactPieceVersion({ name, version, projectId }): Promise { const isExactVersion = EXACT_VERSION_PATTERN.test(version) diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/cloud-piece-metadata-service.ts b/packages/server/api/src/app/pieces/piece-metadata-service/cloud-piece-metadata-service.ts index 45869e99ca..27d7f16d1d 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/cloud-piece-metadata-service.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/cloud-piece-metadata-service.ts @@ -4,7 +4,6 @@ import { PieceMetadataSchema, } from '../piece-metadata-entity' import { PieceMetadataService } from './piece-metadata-service' -import { AllPiecesStats, pieceStatsService } from './piece-stats-service' import { StatusCodes } from 'http-status-codes' import { ActivepiecesError, @@ -80,10 +79,6 @@ export const CloudPieceMetadataService = (): PieceMetadataService => { throw new Error('operation not supported') }, - async stats(): Promise { - return pieceStatsService.get() - }, - async getExactPieceVersion({ name, version, projectId }): Promise { const isExactVersion = EXACT_VERSION_PATTERN.test(version) diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/db-piece-metadata-service.ts b/packages/server/api/src/app/pieces/piece-metadata-service/db-piece-metadata-service.ts index 4278b1f1c6..7fca759f59 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/db-piece-metadata-service.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/db-piece-metadata-service.ts @@ -16,10 +16,10 @@ import { import { PieceMetadataService } from './piece-metadata-service' import { EXACT_VERSION_PATTERN, PieceType, isNil } from '@activepieces/shared' import { ActivepiecesError, ErrorCode, apId } from '@activepieces/shared' -import { AllPiecesStats, pieceStatsService } from './piece-stats-service' import * as semver from 'semver' import { pieceMetadataServiceHooks as hooks } from './hooks' import { projectService } from '../../project/project-service' +import { toPieceMetadataModelSummary } from '.' const repo = repoFactory(PieceMetadataEntity) @@ -63,8 +63,9 @@ export const DbPieceMetadataService = (): PieceMetadataService => { const pieces = await hooks.get().filterPieces({ ...params, pieces: pieceMetadataEntityList, + suggestionType: params.suggestionType, }) - return toPieceMetadataModelSummary(pieces) + return toPieceMetadataModelSummary(pieces, params.suggestionType) }, async getOrThrow({ @@ -153,10 +154,6 @@ export const DbPieceMetadataService = (): PieceMetadataService => { }) }, - async stats(): Promise { - return pieceStatsService.get() - }, - async getExactPieceVersion({ name, version, projectId }): Promise { const isExactVersion = EXACT_VERSION_PATTERN.test(version) @@ -244,17 +241,7 @@ const applyVersionFilter = ( })) } -const toPieceMetadataModelSummary = ( - pieceMetadataEntityList: PieceMetadataSchema[], -): PieceMetadataModelSummary[] => { - return pieceMetadataEntityList.map((pieceMetadataEntity) => { - return { - ...pieceMetadataEntity, - actions: Object.keys(pieceMetadataEntity.actions).length, - triggers: Object.keys(pieceMetadataEntity.triggers).length, - } - }) -} + const toPieceMetadataModel = ( pieceMetadataEntity: PieceMetadataSchema, diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/file-piece-metadata-service.ts b/packages/server/api/src/app/pieces/piece-metadata-service/file-piece-metadata-service.ts index 2d71fb0dee..03e60e1b5f 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/file-piece-metadata-service.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/file-piece-metadata-service.ts @@ -13,7 +13,6 @@ import { isNil, } from '@activepieces/shared' import { PieceMetadataService } from './piece-metadata-service' -import { AllPiecesStats } from './piece-stats-service' import importFresh from 'import-fresh' import { PieceMetadataModel, @@ -22,6 +21,7 @@ import { import { pieceMetadataServiceHooks } from './hooks' import { nanoid } from 'nanoid' import { exceptionHandler } from 'server-shared' +import { toPieceMetadataModelSummary } from '.' const loadPiecesMetadata = async (): Promise => { const pieces = await findAllPieces() @@ -106,13 +106,16 @@ export const FilePieceMetadataService = (): PieceMetadataService => { updated: new Date().toISOString(), } }), + suggestionType: params.suggestionType, }) - return pieces.map((p) => - toPieceMetadataModelSummary({ + const mappedToModel = pieces.map((p) => + toPieceMetadataModel({ pieceMetadata: p, projectId, }), ) + return toPieceMetadataModelSummary(mappedToModel, params.suggestionType) + }, async getOrThrow({ @@ -147,10 +150,6 @@ export const FilePieceMetadataService = (): PieceMetadataService => { throw new Error('Creating pieces is not supported in development mode') }, - async stats(): Promise { - return {} - }, - async getExactPieceVersion({ projectId, name, version }): Promise { const isExactVersion = EXACT_VERSION_PATTERN.test(version) @@ -192,21 +191,7 @@ const toPieceMetadataModel = ({ } } -const toPieceMetadataModelSummary = ({ - pieceMetadata, - projectId, -}: ToPieceMetadataModelParams): PieceMetadataModelSummary => { - const pieceMetadataModel = toPieceMetadataModel({ - pieceMetadata, - projectId, - }) - return { - ...pieceMetadataModel, - actions: Object.keys(pieceMetadataModel.actions).length, - triggers: Object.keys(pieceMetadataModel.triggers).length, - } -} type ToPieceMetadataModelParams = { pieceMetadata: PieceMetadata diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/hooks/index.ts b/packages/server/api/src/app/pieces/piece-metadata-service/hooks/index.ts index b018828d54..6645786954 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/hooks/index.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/hooks/index.ts @@ -1,20 +1,22 @@ import { PieceMetadataSchema } from '../../piece-metadata-entity' import { PlatformId } from '@activepieces/ee-shared' -import { PieceSortBy, PieceOrderBy, PieceCategory } from '@activepieces/shared' +import { PieceSortBy, PieceOrderBy, PieceCategory, SuggestionType } from '@activepieces/shared' import { filterPiecesBasedUser } from './piece-filtering' import { sortAndOrderPieces } from './piece-sorting' export const defaultPieceHooks: PieceMetadataServiceHooks = { async filterPieces(params) { - return sortAndOrderPieces( + const sortedPieces = sortAndOrderPieces( params.sortBy, params.orderBy, - filterPiecesBasedUser({ - categories: params.categories, - searchQuery: params.searchQuery, - pieces: params.pieces, - }), + params.pieces, ) + return filterPiecesBasedUser({ + categories: params.categories, + searchQuery: params.searchQuery, + pieces: sortedPieces, + suggestionType: params.suggestionType, + }) }, } @@ -42,4 +44,5 @@ export type FilterPiecesParams = { sortBy?: PieceSortBy orderBy?: PieceOrderBy pieces: PieceMetadataSchema[] + suggestionType?: SuggestionType } diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-filtering.ts b/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-filtering.ts index 65753e72be..d0e8f06f75 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-filtering.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-filtering.ts @@ -1,51 +1,82 @@ -import { PieceCategory } from '@activepieces/shared' +import { PieceCategory, SuggestionType } from '@activepieces/shared' import { PieceMetadataSchema } from '../../piece-metadata-entity' import Fuse from 'fuse.js' +import { ActionBase, TriggerBase } from '@activepieces/pieces-framework' + +const pieceFilterKeys = [{ + name: 'displayName', + weight: 2, +}, { + name: 'description', + weight: 1, +}] + +const suggestionLimit = 3 export const filterPiecesBasedUser = ({ searchQuery, pieces, categories, + suggestionType, }: { categories: PieceCategory[] | undefined searchQuery: string | undefined pieces: PieceMetadataSchema[] + suggestionType?: SuggestionType }): PieceMetadataSchema[] => { return filterBasedOnCategories({ categories, - pieces: filterBasedOnSearchQuery({ searchQuery, pieces }), + pieces: filterBasedOnSearchQuery({ searchQuery, pieces, suggestionType }), }) } const filterBasedOnSearchQuery = ({ searchQuery, pieces, + suggestionType, }: { searchQuery: string | undefined pieces: PieceMetadataSchema[] + suggestionType?: SuggestionType }): PieceMetadataSchema[] => { if (!searchQuery) { return pieces } - const fuse = new Fuse(pieces, { + const putActionsAndTriggersInAnArray = pieces.map((piece) => { + const actions = Object.values(piece.actions) + const triggers = Object.values(piece.triggers) + return { + ...piece, + actions: suggestionType === SuggestionType.ACTION || suggestionType === SuggestionType.ACTION_AND_TRIGGER ? actions : [], + triggers: suggestionType === SuggestionType.TRIGGER || suggestionType === SuggestionType.ACTION_AND_TRIGGER ? triggers : [], + } + }) + + const pieceWithTriggersAndActionsFilterKeys = [ + ...pieceFilterKeys, + 'actions.displayName', + 'actions.description', + 'triggers.displayName', + 'triggers.description', + ] + const fuse = new Fuse(putActionsAndTriggersInAnArray, { isCaseSensitive: false, shouldSort: true, - keys: [ - { - name: 'name', - weight: 5, - }, - { - name: 'description', - weight: 5, - }, - ], - threshold: 0.3, + keys: pieceWithTriggersAndActionsFilterKeys, + threshold: 0.2, }) return fuse .search(searchQuery) - .map(({ item }) => pieces.find((p) => p.name === item.name)!) + .map(({ item }) => { + const suggestedActions = searchForSuggestion(item.actions, searchQuery) + const suggestedTriggers = searchForSuggestion(item.triggers, searchQuery) + return { + ...item, + actions: suggestedActions, + triggers: suggestedTriggers, + } + }) } const filterBasedOnCategories = ({ @@ -63,3 +94,19 @@ const filterBasedOnCategories = ({ return categories.some((item) => (p.categories ?? []).includes(item)) }) } + + +function searchForSuggestion(actions: T[], searchQuery: string): Record { + const nestedFuse = new Fuse(actions, { + isCaseSensitive: false, + shouldSort: true, + keys: ['displayName', 'description'], + threshold: 0.2, + }) + const suggestions = nestedFuse.search(searchQuery, { limit: suggestionLimit }).map(({ item }) => item) + return suggestions.reduce>((filteredSuggestions, suggestion) => { + filteredSuggestions[suggestion.name] = suggestion + return filteredSuggestions + }, {}) +} + diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-sorting.ts b/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-sorting.ts index 16a35a0fd6..f5e02314d7 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-sorting.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/hooks/piece-sorting.ts @@ -40,7 +40,7 @@ const reverseIfDesc = ( const sortByName = (pieces: PieceMetadataSchema[]): PieceMetadataSchema[] => { return pieces.sort((a, b) => - a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()), + a.displayName.toLocaleLowerCase().localeCompare(b.displayName.toLocaleLowerCase()), ) } diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/index.ts b/packages/server/api/src/app/pieces/piece-metadata-service/index.ts index 10d9609241..6645f1716c 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/index.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/index.ts @@ -8,7 +8,9 @@ import { PiecePackage, PrivatePiecePackage, PublicPiecePackage, + SuggestionType, } from '@activepieces/shared' +import { PieceMetadataModel, PieceMetadataModelSummary, PieceMetadataSchema } from '../piece-metadata-entity' const initPieceMetadataService = (): PieceMetadataService => { const source = system.getOrThrow(SystemProp.PIECES_SOURCE) @@ -48,3 +50,20 @@ export const getPiecePackage = async ( } } } + +export function toPieceMetadataModelSummary( + pieceMetadataEntityList: T[], + suggestionType?: SuggestionType, +): PieceMetadataModelSummary[] { + return pieceMetadataEntityList.map((pieceMetadataEntity) => { + return { + ...pieceMetadataEntity, + actions: Object.keys(pieceMetadataEntity.actions).length, + triggers: Object.keys(pieceMetadataEntity.triggers).length, + suggestedActions: suggestionType === SuggestionType.ACTION || suggestionType === SuggestionType.ACTION_AND_TRIGGER ? + Object.values(pieceMetadataEntity.actions) : undefined, + suggestedTriggers: suggestionType === SuggestionType.TRIGGER || suggestionType === SuggestionType.ACTION_AND_TRIGGER ? + Object.values(pieceMetadataEntity.triggers) : undefined, + } + }) +} \ No newline at end of file diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/piece-metadata-service.ts b/packages/server/api/src/app/pieces/piece-metadata-service/piece-metadata-service.ts index 4e9255d059..e947e04fcc 100644 --- a/packages/server/api/src/app/pieces/piece-metadata-service/piece-metadata-service.ts +++ b/packages/server/api/src/app/pieces/piece-metadata-service/piece-metadata-service.ts @@ -1,5 +1,4 @@ import { PieceMetadata } from '@activepieces/pieces-framework' -import { AllPiecesStats } from './piece-stats-service' import { ApEdition, PackageType, @@ -8,6 +7,7 @@ import { PieceSortBy, PieceType, ProjectId, + SuggestionType, } from '@activepieces/shared' import { PieceMetadataModel, @@ -25,6 +25,7 @@ type ListParams = { sortBy?: PieceSortBy orderBy?: PieceOrderBy searchQuery?: string + suggestionType?: SuggestionType } type GetOrThrowParams = { @@ -59,6 +60,5 @@ export type PieceMetadataService = { getOrThrow(params: GetOrThrowParams): Promise create(params: CreateParams): Promise delete(params: DeleteParams): Promise - stats(): Promise getExactPieceVersion(params: GetExactPieceVersionParams): Promise } diff --git a/packages/server/api/src/app/pieces/piece-metadata-service/piece-stats-service.ts b/packages/server/api/src/app/pieces/piece-metadata-service/piece-stats-service.ts deleted file mode 100644 index 2eb99aac31..0000000000 --- a/packages/server/api/src/app/pieces/piece-metadata-service/piece-stats-service.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { - FlowId, - ProjectId, - flowHelper, - TriggerType, - ActionType, - isNil, - ApEdition, - FlowStatus, -} from '@activepieces/shared' -import { flowRepo } from '../../flows/flow/flow.repo' -import { flowService } from '../../flows/flow/flow.service' -import { CloudPieceMetadataService } from './cloud-piece-metadata-service' -import { flagService } from '../../flags/flag.service' - -type PieceStats = { - activeSteps: number - allSteps: number - allProjects: number - activeProjects: number - allFlows: number - activeFlows: number -} - -export type AllPiecesStats = Record - -let cachedStats: AllPiecesStats = {} -let cacheTime: number -const TWENTY_FOUR_HOURS_IN_MILLISECONDS = 24 * 60 * 60 * 1000 - -export const pieceStatsService = { - async get(): Promise { - const pieceMetaService = CloudPieceMetadataService() - if ( - cachedStats && - Date.now() - cacheTime < TWENTY_FOUR_HOURS_IN_MILLISECONDS - ) { - return cachedStats - } - const flows = await flowRepo().find() - const stats: Record = {} - const uniqueStatsPerPiece: Record< - string, - { - flows: Set - projects: Set - activeprojects: Set - activeFlows: Set - } - > = {} - const defaultStats = { - activeSteps: 0, - allSteps: 0, - allProjects: 0, - activeFlows: 0, - allFlows: 0, - activeProjects: 0, - } - const pieces = await pieceMetaService.list({ - release: await flagService.getCurrentRelease(), - projectId: undefined, - edition: ApEdition.ENTERPRISE, - includeHidden: true, - }) - for (const piece of pieces) { - uniqueStatsPerPiece[piece.name] = { - flows: new Set(), - projects: new Set(), - activeprojects: new Set(), - activeFlows: new Set(), - } - stats[piece.name] = { ...defaultStats } - } - for (const flowWithoutVersion of flows) { - const flow = await flowService.getOnePopulatedOrThrow({ - id: flowWithoutVersion.id, - projectId: flowWithoutVersion.projectId, - }) - if (isNil(flow.version)) { - continue - } - const trigger = flow.version.trigger - if (isNil(trigger)) { - continue - } - const steps = flowHelper.getAllSteps(flow.version.trigger) - for (const step of steps) { - if (step.type === TriggerType.PIECE || step.type === ActionType.PIECE) { - if (!stats[step.settings.pieceName]) { - uniqueStatsPerPiece[step.settings.pieceName] = { - flows: new Set(), - projects: new Set(), - activeprojects: new Set(), - activeFlows: new Set(), - } - stats[step.settings.pieceName] = { ...defaultStats } - } - uniqueStatsPerPiece[step.settings.pieceName].projects.add( - flow.projectId, - ) - uniqueStatsPerPiece[step.settings.pieceName].flows.add(flow.id) - stats[step.settings.pieceName].allSteps++ - if (flow.status === FlowStatus.ENABLED) { - uniqueStatsPerPiece[step.settings.pieceName].activeFlows.add( - flow.id, - ) - uniqueStatsPerPiece[step.settings.pieceName].activeprojects.add( - flow.projectId, - ) - stats[step.settings.pieceName].activeSteps++ - } - } - } - } - for (const pieceName in uniqueStatsPerPiece) { - stats[pieceName].allProjects = - uniqueStatsPerPiece[pieceName].projects.size - stats[pieceName].activeProjects = - uniqueStatsPerPiece[pieceName].activeprojects.size - stats[pieceName].allFlows = uniqueStatsPerPiece[pieceName].flows.size - stats[pieceName].activeFlows = - uniqueStatsPerPiece[pieceName].activeFlows.size - } - cachedStats = Object.entries(stats) - .sort( - ([, valueA], [, valueB]) => - valueB.activeProjects - valueA.activeProjects, - ) - .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) - - cacheTime = Date.now() - return stats - }, -} diff --git a/packages/server/api/src/app/webhooks/webhook-service.ts b/packages/server/api/src/app/webhooks/webhook-service.ts index 0d7889ea86..1e289afa29 100644 --- a/packages/server/api/src/app/webhooks/webhook-service.ts +++ b/packages/server/api/src/app/webhooks/webhook-service.ts @@ -43,7 +43,6 @@ export const webhookService = { logger.info( `[WebhookService#handshake] flowInstance not found, flowId=${flow.id}`, ) - saveSampleDataForWebhookTesting(flow, payload) return null } diff --git a/packages/server/api/test/integration/ce/flows/flow-worker.test.ts b/packages/server/api/test/integration/ce/flows/flow-worker.test.ts index ace026270d..2acdd895d4 100644 --- a/packages/server/api/test/integration/ce/flows/flow-worker.test.ts +++ b/packages/server/api/test/integration/ce/flows/flow-worker.test.ts @@ -155,6 +155,7 @@ describe('flow execution', () => { output: { key: 3, }, + duration: expect.any(Number), }, datamapper: { type: 'PIECE', @@ -167,6 +168,7 @@ describe('flow execution', () => { output: { key: 3, }, + duration: expect.any(Number), }, }, }) diff --git a/packages/server/api/test/integration/cloud/piece-metadata/piece-metadata.test.ts b/packages/server/api/test/integration/cloud/piece-metadata/piece-metadata.test.ts index 4946594051..5651145115 100644 --- a/packages/server/api/test/integration/cloud/piece-metadata/piece-metadata.test.ts +++ b/packages/server/api/test/integration/cloud/piece-metadata/piece-metadata.test.ts @@ -90,6 +90,7 @@ describe('Piece Metadata API', () => { const mockPieceMetadata = createMockPieceMetadata({ name: '@activepieces/a', pieceType: PieceType.OFFICIAL, + displayName: 'a', }) await databaseConnection .getRepository('piece_metadata') @@ -168,21 +169,25 @@ describe('Piece Metadata API', () => { name: 'a', pieceType: PieceType.CUSTOM, projectId: mockProject.id, + displayName: 'a', }) const mockPieceMetadataB = createMockPieceMetadata({ name: 'b', pieceType: PieceType.OFFICIAL, + displayName: 'b', }) const mockPieceMetadataC = createMockPieceMetadata({ name: 'c', pieceType: PieceType.CUSTOM, projectId: mockProject2.id, platformId: mockPlatform.id, + displayName: 'c', }) const mockPieceMetadataD = createMockPieceMetadata({ name: 'd', pieceType: PieceType.CUSTOM, platformId: mockPlatform.id, + displayName: 'd', }) await databaseConnection .getRepository('piece_metadata') @@ -241,15 +246,18 @@ describe('Piece Metadata API', () => { name: 'a', pieceType: PieceType.CUSTOM, projectId: mockProject.id, + displayName: 'a', }) const mockPieceMetadataB = createMockPieceMetadata({ name: 'b', pieceType: PieceType.OFFICIAL, + displayName: 'b', }) const mockPieceMetadataC = createMockPieceMetadata({ name: 'c', pieceType: PieceType.CUSTOM, projectId: mockProject2.id, + displayName: 'c', }) await databaseConnection .getRepository('piece_metadata') @@ -284,10 +292,12 @@ describe('Piece Metadata API', () => { const mockPieceMetadataA = createMockPieceMetadata({ name: 'a', pieceType: PieceType.OFFICIAL, + displayName: 'a', }) const mockPieceMetadataB = createMockPieceMetadata({ name: 'b', pieceType: PieceType.OFFICIAL, + displayName: 'b', }) await databaseConnection .getRepository('piece_metadata') @@ -329,10 +339,12 @@ describe('Piece Metadata API', () => { const mockPieceMetadataA = createMockPieceMetadata({ name: 'a', pieceType: PieceType.OFFICIAL, + displayName: 'a', }) const mockPieceMetadataB = createMockPieceMetadata({ name: 'b', pieceType: PieceType.OFFICIAL, + displayName: 'b', }) await databaseConnection .getRepository('piece_metadata') @@ -379,10 +391,12 @@ describe('Piece Metadata API', () => { const mockPieceMetadataA = createMockPieceMetadata({ name: 'a', pieceType: PieceType.OFFICIAL, + displayName: 'a', }) const mockPieceMetadataB = createMockPieceMetadata({ name: 'b', pieceType: PieceType.OFFICIAL, + displayName: 'b', }) await databaseConnection .getRepository('piece_metadata') diff --git a/packages/server/api/test/integration/cloud/signing-key/signing-key.test.ts b/packages/server/api/test/integration/cloud/signing-key/signing-key.test.ts index 2e52874f54..d42029ac7c 100644 --- a/packages/server/api/test/integration/cloud/signing-key/signing-key.test.ts +++ b/packages/server/api/test/integration/cloud/signing-key/signing-key.test.ts @@ -8,7 +8,7 @@ import { } from '../../../helpers/mocks' import { StatusCodes } from 'http-status-codes' import { FastifyInstance } from 'fastify' -import { PlatformRole, PrincipalType, apId } from '@activepieces/shared' +import { PlatformRole, PrincipalType } from '@activepieces/shared' import { faker } from '@faker-js/faker' let app: FastifyInstance | null = null @@ -64,34 +64,6 @@ describe('Signing Key API', () => { expect(responseBody.algorithm).toBe('RSA') }, 10000) - it('Fails if platform is not found', async () => { - // arrange - const nonExistentPlatformId = apId() - - const testToken = await generateMockToken({ - type: PrincipalType.USER, - platform: { - id: nonExistentPlatformId, - role: PlatformRole.OWNER, - }, - }) - - const mockSigningKeyName = faker.lorem.word() - const response = await app?.inject({ - method: 'POST', - url: '/v1/signing-keys', - body: { - displayName: mockSigningKeyName, - }, - headers: { - authorization: `Bearer ${testToken}`, - }, - }) - - // assert - expect(response?.statusCode).toBe(StatusCodes.INTERNAL_SERVER_ERROR) - }) - it('Fails if user is not platform owner', async () => { // arrange const mockUser = createMockUser() diff --git a/packages/server/shared/src/lib/system/system-prop.ts b/packages/server/shared/src/lib/system/system-prop.ts index e550993af9..90e62a861d 100644 --- a/packages/server/shared/src/lib/system/system-prop.ts +++ b/packages/server/shared/src/lib/system/system-prop.ts @@ -58,7 +58,6 @@ export enum SystemProp { SMTP_SENDER_NAME = 'SMTP_SENDER_NAME', SMTP_SENDER_EMAIL = 'SMTP_SENDER_EMAIL', SMTP_USE_SSL = 'SMTP_USE_SSL', - STATS_ENABLED = 'STATS_ENABLED', TELEMETRY_ENABLED = 'TELEMETRY_ENABLED', TEMPLATES_SOURCE_URL = 'TEMPLATES_SOURCE_URL', TRIGGER_DEFAULT_POLL_INTERVAL = 'TRIGGER_DEFAULT_POLL_INTERVAL', diff --git a/packages/server/shared/src/lib/system/system.ts b/packages/server/shared/src/lib/system/system.ts index 5bbe0a0d2a..2a9716d8fb 100644 --- a/packages/server/shared/src/lib/system/system.ts +++ b/packages/server/shared/src/lib/system/system.ts @@ -46,7 +46,6 @@ const systemPropDefaultValues: Partial> = { [SystemProp.SANDBOX_MEMORY_LIMIT]: '131072', [SystemProp.SANDBOX_RUN_TIME_SECONDS]: '600', [SystemProp.SIGN_UP_ENABLED]: 'false', - [SystemProp.STATS_ENABLED]: 'false', [SystemProp.TELEMETRY_ENABLED]: 'true', [SystemProp.TEMPLATES_SOURCE_URL]: 'https://cloud.activepieces.com/api/v1/flow-templates', diff --git a/packages/shared/package.json b/packages/shared/package.json index f873429726..35b73a0370 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,5 +1,5 @@ { "name": "@activepieces/shared", - "version": "0.10.76", + "version": "0.10.81", "type": "commonjs" } \ No newline at end of file diff --git a/packages/shared/src/lib/common/activepieces-error.ts b/packages/shared/src/lib/common/activepieces-error.ts index 90dc6e9f18..7946af024f 100755 --- a/packages/shared/src/lib/common/activepieces-error.ts +++ b/packages/shared/src/lib/common/activepieces-error.ts @@ -1,4 +1,3 @@ -import { AppConnectionId } from '../app-connection/app-connection' import { FileId } from '../file/file' import { FlowRunId } from '../flow-run/flow-run' import { FlowId } from '../flows/flow' @@ -12,7 +11,6 @@ export class ActivepiecesError extends Error { } type ErrorParams = - | AppConnectionNotFoundErrorParams | AuthenticationParams | AuthorizationErrorParams | ConfigNotFoundErrorParams @@ -23,6 +21,7 @@ type ErrorParams = | ExistingUserErrorParams | FileNotFoundErrorParams | FlowNotFoundErrorParams + | FlowIsLockedErrorParams | FlowOperationErrorParams | FlowRunNotFoundErrorParams | InvalidApiKeyParams @@ -77,13 +76,6 @@ export type FileNotFoundErrorParams = BaseErrorParams> -export type AppConnectionNotFoundErrorParams = BaseErrorParams< -ErrorCode.APP_CONNECTION_NOT_FOUND, -{ - id: AppConnectionId -} -> - export type AuthorizationErrorParams = BaseErrorParams< ErrorCode.AUTHORIZATION, { @@ -226,6 +218,13 @@ ErrorCode.FLOW_OPERATION_INVALID, Record > +export type FlowIsLockedErrorParams = BaseErrorParams< +ErrorCode.FLOW_IN_USE, +{ + flowVersionId: FlowVersionId + message: string +}> + export type InvalidJwtTokenErrorParams = BaseErrorParams< ErrorCode.INVALID_OR_EXPIRED_JWT_TOKEN, { @@ -328,7 +327,6 @@ ErrorCode.AUTHENTICATION, export type InvalidOtpParams = BaseErrorParams> export enum ErrorCode { - APP_CONNECTION_NOT_FOUND = 'APP_CONNECTION_NOT_FOUND', AUTHENTICATION = 'AUTHENTICATION', AUTHORIZATION = 'AUTHORIZATION', CONFIG_NOT_FOUND = 'CONFIG_NOT_FOUND', @@ -343,6 +341,7 @@ export enum ErrorCode { FLOW_INSTANCE_NOT_FOUND = 'INSTANCE_NOT_FOUND', FLOW_NOT_FOUND = 'FLOW_NOT_FOUND', FLOW_OPERATION_INVALID = 'FLOW_OPERATION_INVALID', + FLOW_IN_USE = 'FLOW_IN_USE', FLOW_RUN_NOT_FOUND = 'FLOW_RUN_NOT_FOUND', INVALID_API_KEY = 'INVALID_API_KEY', INVALID_APP_CONNECTION = 'INVALID_APP_CONNECTION', diff --git a/packages/shared/src/lib/flow-run/execution/step-output.ts b/packages/shared/src/lib/flow-run/execution/step-output.ts index 1388b21670..63d047fab6 100755 --- a/packages/shared/src/lib/flow-run/execution/step-output.ts +++ b/packages/shared/src/lib/flow-run/execution/step-output.ts @@ -57,13 +57,6 @@ export class GenericStepOutput { }) } - setDuration(duration: number): GenericStepOutput { - return new GenericStepOutput({ - ...this, - duration, - }) - } - static create({ input, type, status, output }: { input: unknown, type: T, status: StepOutputStatus, output?: OUTPUT }): GenericStepOutput { return new GenericStepOutput({ input, @@ -124,7 +117,7 @@ export class LoopStepOutput extends GenericStepOutput diff --git a/packages/ui/canvas-utils/.eslintrc.json b/packages/ui/canvas-utils/.eslintrc.json new file mode 100644 index 0000000000..46def90f9b --- /dev/null +++ b/packages/ui/canvas-utils/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "extends": ["../.eslintrc.json"], + "overrides": [ + { + "files": ["*.ts"], + "parserOptions": { + "project": ["packages/ui/canvas-utils/tsconfig.*?.json"] + } + } + ] +} diff --git a/packages/ui/canvas-utils/README.md b/packages/ui/canvas-utils/README.md new file mode 100644 index 0000000000..8fd5764d77 --- /dev/null +++ b/packages/ui/canvas-utils/README.md @@ -0,0 +1,7 @@ +# canvas-utils + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test canvas-utils` to execute the unit tests. diff --git a/packages/ui/canvas-utils/jest.config.ts b/packages/ui/canvas-utils/jest.config.ts new file mode 100644 index 0000000000..930e707be1 --- /dev/null +++ b/packages/ui/canvas-utils/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'canvas-utils', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../coverage/packages/ui/canvas-utils', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/packages/ui/canvas-utils/ng-package.json b/packages/ui/canvas-utils/ng-package.json new file mode 100644 index 0000000000..b506be618a --- /dev/null +++ b/packages/ui/canvas-utils/ng-package.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../../node_modules/ng-packagr/ng-package.schema.json", + "dest": "../../../dist/packages/ui/canvas-utils", + "lib": { + "entryFile": "src/index.ts" + } +} diff --git a/packages/ui/canvas-utils/package.json b/packages/ui/canvas-utils/package.json new file mode 100644 index 0000000000..c08c1b5cba --- /dev/null +++ b/packages/ui/canvas-utils/package.json @@ -0,0 +1,12 @@ +{ + "name": "@activepieces/ui-canvas-utils", + "version": "0.0.1", + "peerDependencies": { + "@angular/common": "^16.2.0", + "@angular/core": "^16.2.0" + }, + "dependencies": { + "tslib": "^2.3.0" + }, + "sideEffects": false +} diff --git a/packages/ui/canvas-utils/project.json b/packages/ui/canvas-utils/project.json new file mode 100644 index 0000000000..eb76b1f973 --- /dev/null +++ b/packages/ui/canvas-utils/project.json @@ -0,0 +1,50 @@ +{ + "name": "canvas-utils", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/ui/canvas-utils/src", + "prefix": "app", + "tags": [], + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/angular:ng-packagr-lite", + "outputs": ["{workspaceRoot}/dist/{projectRoot}"], + "options": { + "project": "packages/ui/canvas-utils/ng-package.json" + }, + "configurations": { + "production": { + "tsConfig": "packages/ui/canvas-utils/tsconfig.lib.prod.json" + }, + "development": { + "tsConfig": "packages/ui/canvas-utils/tsconfig.lib.json" + } + }, + "defaultConfiguration": "production" + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "packages/ui/canvas-utils/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "packages/ui/canvas-utils/**/*.ts", + "packages/ui/canvas-utils/**/*.html" + ] + } + } + } +} diff --git a/packages/ui/canvas-utils/src/index.ts b/packages/ui/canvas-utils/src/index.ts new file mode 100644 index 0000000000..4b3844a2ad --- /dev/null +++ b/packages/ui/canvas-utils/src/index.ts @@ -0,0 +1,9 @@ +export * from './lib/canvas-utils.component'; +export * from './lib/canvas-utils.module'; +export * from './lib/panning/panner.service'; +export * from './lib/zooming/zooming.service'; +export * from './lib/panning/panner.directive'; +export * from './lib/drawing/flow-drawer'; +export * from './lib/drawing/draw-common'; +export * from './lib/drawing/step-card'; +export * from './lib/drawing/svg-drawer'; diff --git a/packages/ui/canvas-utils/src/lib/canvas-utils.component.html b/packages/ui/canvas-utils/src/lib/canvas-utils.component.html new file mode 100644 index 0000000000..a01e5b5d00 --- /dev/null +++ b/packages/ui/canvas-utils/src/lib/canvas-utils.component.html @@ -0,0 +1,36 @@ +
+ + + +
+ loop +
+
+
+ + + + +
+ +
+
+ + +
+ +
+
+ + + +
+ + + +
+ +
+
+
+
diff --git a/packages/ui/canvas-utils/src/lib/canvas-utils.component.ts b/packages/ui/canvas-utils/src/lib/canvas-utils.component.ts new file mode 100644 index 0000000000..84c316df7c --- /dev/null +++ b/packages/ui/canvas-utils/src/lib/canvas-utils.component.ts @@ -0,0 +1,45 @@ +import { AfterViewInit, Component, Input } from '@angular/core'; +import { PannerService } from './panning/panner.service'; +import { ZoomingService } from './zooming/zooming.service'; +import { FlowVersion } from '@activepieces/shared'; + +@Component({ + selector: 'app-canvas-utils', + templateUrl: './canvas-utils.component.html', +}) +export class CanvasUtilsComponent implements AfterViewInit { + @Input({ required: true }) flowVersion!: FlowVersion; + constructor( + private zoomingService: ZoomingService, + private pannerService: PannerService + ) {} + ngAfterViewInit(): void { + this.resetZoom(); + } + zoomIn() { + this.zoomingService.setZoomingScale( + Math.min( + this.zoomingService.zoomingScale + this.zoomingService.zoomingStep, + this.zoomingService.maxZoom + ) + ); + } + zoomOut() { + this.zoomingService.setZoomingScale( + Math.max( + this.zoomingService.zoomingScale - this.zoomingService.zoomingStep, + this.zoomingService.minZoom + ) + ); + } + recenter() { + if (this.flowVersion) { + this.pannerService.fitToScreen(this.flowVersion); + } + } + resetZoom() { + if (this.flowVersion) { + this.pannerService.resetZoom(this.flowVersion); + } + } +} diff --git a/packages/ui/canvas-utils/src/lib/canvas-utils.module.ts b/packages/ui/canvas-utils/src/lib/canvas-utils.module.ts new file mode 100644 index 0000000000..6700de61c0 --- /dev/null +++ b/packages/ui/canvas-utils/src/lib/canvas-utils.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CanvasUtilsComponent } from './canvas-utils.component'; +import { CanvasPannerDirective } from './panning/panner.directive'; +import { UiCommonModule } from '@activepieces/ui/common'; +@NgModule({ + imports: [CommonModule, UiCommonModule], + declarations: [CanvasUtilsComponent, CanvasPannerDirective], + exports: [CanvasUtilsComponent, CanvasPannerDirective], +}) +export class UiCanvasUtilsModule {} diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/branch-drawer.ts b/packages/ui/canvas-utils/src/lib/drawing/branch-drawer.ts similarity index 100% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/branch-drawer.ts rename to packages/ui/canvas-utils/src/lib/drawing/branch-drawer.ts diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/draw-common.ts b/packages/ui/canvas-utils/src/lib/drawing/draw-common.ts similarity index 90% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/draw-common.ts rename to packages/ui/canvas-utils/src/lib/drawing/draw-common.ts index 01e22df2c7..d77ffbaf10 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/draw-common.ts +++ b/packages/ui/canvas-utils/src/lib/drawing/draw-common.ts @@ -39,3 +39,7 @@ export const HORIZONTAL_SPACE_BETWEEN_RETURNING_LOOP_ARROW_AND_STARTING_LOOP_ARC export const VERTICAL_SPACE_BETWEEN_AFTERLOOP_LINE_AND_LOOP_BOTTOM = 67; export type ButtonType = 'big' | 'small'; export const FLOW_ITEM_ICON_SIZE = 40 + 'px'; +export const FLOW_BUILDER_HEADER_HEIGHT = 80; +export const END_WIDGET_HEIGHT_AND_SPACE = 32 + 120; +export const DEFAULT_TOP_MARGIN = 40; +export const MAX_ZOOM = 1.25; diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/flow-drawer.ts b/packages/ui/canvas-utils/src/lib/drawing/flow-drawer.ts similarity index 99% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/flow-drawer.ts rename to packages/ui/canvas-utils/src/lib/drawing/flow-drawer.ts index 6a26db40b8..d50e3f634a 100755 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/flow-drawer.ts +++ b/packages/ui/canvas-utils/src/lib/drawing/flow-drawer.ts @@ -18,8 +18,6 @@ import { PositionedStep } from './step-card'; import { BranchDrawer } from './branch-drawer'; import { LoopDrawer } from './loop-drawer'; -export const ARC_LENGTH = 15; - export class FlowDrawer { readonly steps: readonly PositionedStep[]; readonly svg: SvgDrawer; diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/loop-drawer.ts b/packages/ui/canvas-utils/src/lib/drawing/loop-drawer.ts similarity index 100% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/loop-drawer.ts rename to packages/ui/canvas-utils/src/lib/drawing/loop-drawer.ts diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/step-card.ts b/packages/ui/canvas-utils/src/lib/drawing/step-card.ts similarity index 100% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/step-card.ts rename to packages/ui/canvas-utils/src/lib/drawing/step-card.ts diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/svg-drawer.ts b/packages/ui/canvas-utils/src/lib/drawing/svg-drawer.ts similarity index 100% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/drawing/svg-drawer.ts rename to packages/ui/canvas-utils/src/lib/drawing/svg-drawer.ts diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/panning/panner.directive.ts b/packages/ui/canvas-utils/src/lib/panning/panner.directive.ts similarity index 57% rename from packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/panning/panner.directive.ts rename to packages/ui/canvas-utils/src/lib/panning/panner.directive.ts index 0e68f9ba39..76b43e345d 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/panning/panner.directive.ts +++ b/packages/ui/canvas-utils/src/lib/panning/panner.directive.ts @@ -1,6 +1,6 @@ import { Directive, HostListener } from '@angular/core'; import { PannerService } from './panner.service'; -import { FlowRendererService } from '@activepieces/ui/feature-builder-store'; +import { FlowRendererService } from '@activepieces/ui/common'; @Directive({ selector: '[appCanvasPanner]', @@ -25,42 +25,52 @@ export class CanvasPannerDirective { if ( (event.button === 0 || event.button === 1) && - !this.flowRendererService.isDraggingStateSnapshot + !this.flowRendererService.isDraggingStep ) { - this.pannerService.panningState.currentOffset.x = event.clientX; - this.pannerService.panningState.currentOffset.y = event.clientY; - this.pannerService.panningState.isDragging = true; - this.pannerService.isPanning$.next(true); + this.pannerService.setPanningState({ + isPanning: true, + currentOffset: { + x: event.clientX, + y: event.clientY, + }, + }); } } @HostListener('mouseup', ['$event']) mouseUp(ignoredEvent: unknown) { - this.pannerService.panningState.isDragging = false; - this.pannerService.isPanning$.next(false); + this.pannerService.setPanningState({ + ...this.pannerService.panningState, + isPanning: false, + }); } @HostListener('mouseleave', ['$event']) mouseleave(ignoredEvent: unknown) { - this.pannerService.panningState.isDragging = false; - this.pannerService.isPanning$.next(false); + this.pannerService.setPanningState({ + ...this.pannerService.panningState, + isPanning: false, + }); } @HostListener('mousemove', ['$event']) mouseMover(event: MouseEvent) { - if (this.pannerService.panningState.isDragging) { + if (this.pannerService.panningState.isPanning) { const delta = { x: event.pageX - this.pannerService.panningState.currentOffset.x, y: event.pageY - this.pannerService.panningState.currentOffset.y, }; - this.pannerService.lastPanningOffset = { + const lastPanningOffset = { x: this.pannerService.lastPanningOffset.x + delta.x, y: this.pannerService.lastPanningOffset.y + delta.y, }; - this.pannerService.panningState.currentOffset.x = event.clientX; - this.pannerService.panningState.currentOffset.y = event.clientY; - this.pannerService.panningOffset$.next( - this.pannerService.lastPanningOffset - ); + this.pannerService.setLastPanningOffset(lastPanningOffset); + this.pannerService.setPanningState({ + currentOffset: { + x: event.clientX, + y: event.clientY, + }, + isPanning: true, + }); } event.preventDefault(); } @@ -76,13 +86,18 @@ export class CanvasPannerDirective { } } - if (!this.flowRendererService.isDraggingStateSnapshot) { - this.pannerService.lastPanningOffset.x -= event.deltaX; - this.pannerService.lastPanningOffset.y -= event.deltaY; - this.pannerService.panningState.currentOffset.x = event.clientX; - this.pannerService.panningState.currentOffset.y = event.clientY; - this.pannerService.panningOffset$.next({ - ...this.pannerService.lastPanningOffset, + if (!this.flowRendererService.isDraggingStep) { + const lastPanningOffset = { + x: this.pannerService.lastPanningOffset.x - event.deltaX, + y: this.pannerService.lastPanningOffset.y - event.deltaY, + }; + this.pannerService.setLastPanningOffset(lastPanningOffset); + this.pannerService.setPanningState({ + currentOffset: { + x: event.clientX, + y: event.clientY, + }, + isPanning: false, }); } event.preventDefault(); diff --git a/packages/ui/canvas-utils/src/lib/panning/panner.service.ts b/packages/ui/canvas-utils/src/lib/panning/panner.service.ts new file mode 100644 index 0000000000..5a6b43b95f --- /dev/null +++ b/packages/ui/canvas-utils/src/lib/panning/panner.service.ts @@ -0,0 +1,109 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, map } from 'rxjs'; +import { ZoomingService } from '../zooming/zooming.service'; +import { FlowVersion } from '@activepieces/shared'; +import { FlowDrawer } from '../drawing/flow-drawer'; +import { + DEFAULT_TOP_MARGIN, + MAX_ZOOM, + FLOW_BUILDER_HEADER_HEIGHT, + END_WIDGET_HEIGHT_AND_SPACE, +} from '../drawing/draw-common'; +export type PanningState = { + currentOffset: { + x: number; + y: number; + }; + isPanning: boolean; +}; +@Injectable({ + providedIn: 'root', +}) +export class PannerService { + constructor(private zoomService: ZoomingService) {} + private _panningState$: BehaviorSubject = + new BehaviorSubject({ + currentOffset: { + x: 0, + y: 0, + }, + isPanning: false, + }); + + private _lastPanningOffset$: BehaviorSubject<{ x: number; y: number }> = + new BehaviorSubject({ + x: 0, + y: 0, + }); + fitToScreen(flowVersion: FlowVersion) { + this.setCanvasTransform({ + flowVersion, + resetZoom: false, + }); + } + resetZoom(flowVersion: FlowVersion) { + this.setCanvasTransform({ + flowVersion, + resetZoom: true, + }); + } + setCanvasTransform({ + flowVersion, + resetZoom, + }: { + flowVersion: FlowVersion; + resetZoom: boolean; + }) { + const flowHeight = FlowDrawer.construct(flowVersion.trigger) + .offset(0, 0) + .boundingBox().height; + const canvasHeight = window.innerHeight - FLOW_BUILDER_HEADER_HEIGHT; + const fullFlowHeightWithWidgets = + END_WIDGET_HEIGHT_AND_SPACE + flowHeight + DEFAULT_TOP_MARGIN; + let zoomScale = 1.0; + const maxPossibleViewedFlowHeight = Math.min( + fullFlowHeightWithWidgets, + resetZoom ? canvasHeight : Number.MAX_SAFE_INTEGER + ); + zoomScale = canvasHeight / maxPossibleViewedFlowHeight; + zoomScale = Math.min(zoomScale, MAX_ZOOM); + if (resetZoom) { + zoomScale = Math.max(zoomScale, 0.5); + } + const newState: PanningState = { + currentOffset: { + x: 0, + // The number of pixels are affected by the zoom scale, that is why we divide by the zoom scale + y: (canvasHeight * zoomScale - canvasHeight) / 2.0 + DEFAULT_TOP_MARGIN, + }, + isPanning: false, + }; + this._panningState$.next(newState); + this.setLastPanningOffset({ + ...newState.currentOffset, + }); + this.zoomService.setZoomingScale(zoomScale); + } + get panningState$() { + return this._panningState$.asObservable(); + } + get panningState() { + return this._panningState$.value; + } + + setPanningState(newState: PanningState) { + this._panningState$.next(newState); + } + setLastPanningOffset(offset: { x: number; y: number }) { + this._lastPanningOffset$.next(offset); + } + get lastPanningOffset() { + return this._lastPanningOffset$.value; + } + get isPanning$() { + return this._panningState$.pipe(map((state) => state.isPanning)); + } + get panningOffset$() { + return this._lastPanningOffset$.asObservable(); + } +} diff --git a/packages/ui/canvas-utils/src/lib/zooming/zooming.service.ts b/packages/ui/canvas-utils/src/lib/zooming/zooming.service.ts new file mode 100644 index 0000000000..bc3a153c65 --- /dev/null +++ b/packages/ui/canvas-utils/src/lib/zooming/zooming.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class ZoomingService { + private _zoomingScale$: BehaviorSubject = new BehaviorSubject(1); + readonly zoomingStep = 0.25; + readonly maxZoom = 1.25; + readonly minZoom = 0.5; + + get zoomingScale$() { + return this._zoomingScale$.asObservable(); + } + get zoomingScale() { + return this._zoomingScale$.value; + } + setZoomingScale(scale: number) { + this._zoomingScale$.next(scale); + } +} diff --git a/packages/ui/canvas-utils/tsconfig.json b/packages/ui/canvas-utils/tsconfig.json new file mode 100644 index 0000000000..5cf0a16564 --- /dev/null +++ b/packages/ui/canvas-utils/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/packages/ui/canvas-utils/tsconfig.lib.json b/packages/ui/canvas-utils/tsconfig.lib.json new file mode 100644 index 0000000000..9b49be758c --- /dev/null +++ b/packages/ui/canvas-utils/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "src/**/*.spec.ts", + "src/test-setup.ts", + "jest.config.ts", + "src/**/*.test.ts" + ], + "include": ["src/**/*.ts"] +} diff --git a/packages/ui/canvas-utils/tsconfig.lib.prod.json b/packages/ui/canvas-utils/tsconfig.lib.prod.json new file mode 100644 index 0000000000..61b523783f --- /dev/null +++ b/packages/ui/canvas-utils/tsconfig.lib.prod.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "declarationMap": false + }, + "angularCompilerOptions": {} +} diff --git a/packages/ui/canvas-utils/tsconfig.spec.json b/packages/ui/canvas-utils/tsconfig.spec.json new file mode 100644 index 0000000000..f858ef78c1 --- /dev/null +++ b/packages/ui/canvas-utils/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/packages/ui/common/src/lib/components/array-form-control/array-form-control.component.html b/packages/ui/common/src/lib/components/array-form-control/array-form-control.component.html index 36a12e0ef1..842911a838 100644 --- a/packages/ui/common/src/lib/components/array-form-control/array-form-control.component.html +++ b/packages/ui/common/src/lib/components/array-form-control/array-form-control.component.html @@ -9,7 +9,7 @@ (buttonClicked)="removeValue(idx)"> -
- - - - + + + + diff --git a/packages/ui/core/src/assets/scss/ng-material-override.scss b/packages/ui/core/src/assets/scss/ng-material-override.scss index e4e4305ad3..a258f6dc56 100644 --- a/packages/ui/core/src/assets/scss/ng-material-override.scss +++ b/packages/ui/core/src/assets/scss/ng-material-override.scss @@ -583,6 +583,15 @@ mat-form-field.\!ap-bg-white { .no-check-bg .mat-button-toggle-checked { background-color: $white; } +app-canvas-utils{ + mat-button-toggle-group{ + + mat-button-toggle{ + background: var(--white) !important; + } + } + +} .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { line-height: unset; } @@ -727,4 +736,14 @@ app-platform-settings { @include mat.form-field-density(-2); -@include mat.button-density(0); \ No newline at end of file +@include mat.button-density(0); + + +.suggested-actions-or-triggers +{ + .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) + { + background-color: var(--primary-palette-50) ; + color: var(--primary-palette-contrast-400); + } +} \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/index.ts b/packages/ui/feature-builder-canvas/src/index.ts index 7a84f8e6fe..c5483fe714 100644 --- a/packages/ui/feature-builder-canvas/src/index.ts +++ b/packages/ui/feature-builder-canvas/src/index.ts @@ -1,7 +1,3 @@ export * from './lib/ui-feature-builder-canvas.module'; -export * from './lib/components/canvas-utils/panning/panner.service'; -export * from './lib/components/canvas-utils/canvas-utils.component'; export * from './lib/components/flow-item-tree/flow-item-tree.component'; -export * from './lib/components/canvas-utils/panning/panner.directive'; - import '@angular/localize/init'; diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/canvas-utils.component.html b/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/canvas-utils.component.html deleted file mode 100644 index 7bca05bc64..0000000000 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/canvas-utils.component.html +++ /dev/null @@ -1,18 +0,0 @@ - - - -
- -
-
- -
- -
-
- -
- -
-
-
\ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/canvas-utils.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/canvas-utils.component.ts deleted file mode 100644 index 7aa2e4768d..0000000000 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/canvas-utils.component.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Component } from '@angular/core'; -import { PannerService } from './panning/panner.service'; -import { ZoomingService } from './zooming/zooming.service'; - -@Component({ - selector: 'app-canvas-utils', - templateUrl: './canvas-utils.component.html', -}) -export class CanvasUtilsComponent { - constructor( - private zoomingService: ZoomingService, - private pannerService: PannerService - ) {} - zoomIn() { - this.zoomingService.zoomingScale$.next( - Math.min( - this.zoomingService.zoomingScale$.value + - this.zoomingService.zoomingStep, - this.zoomingService.zoomingMax - ) - ); - } - zoomOut() { - this.zoomingService.zoomingScale$.next( - Math.max( - this.zoomingService.zoomingScale$.value - - this.zoomingService.zoomingStep, - this.zoomingService.zoomingMin - ) - ); - } - recenter() { - this.pannerService.recenter(); - this.zoomingService.zoomingScale$.next(1); - } -} diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/panning/panner.service.ts b/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/panning/panner.service.ts deleted file mode 100644 index dcce6f7556..0000000000 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/panning/panner.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs'; - -@Injectable({ - providedIn: 'root', -}) -export class PannerService { - panningOffset$: Subject<{ x: number; y: number }> = new Subject(); - isPanning$: Subject = new Subject(); - panningState = { - currentOffset: { - x: 0, - y: 0, - }, - isDragging: false, - }; - lastPanningOffset = { - x: 0, - y: 0, - }; - recenter() { - this.panningState = { - currentOffset: { - x: 0, - y: 0, - }, - isDragging: false, - }; - this.lastPanningOffset = { - x: 0, - y: 0, - }; - this.panningOffset$.next({ x: 0, y: 0 }); - } -} diff --git a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/zooming/zooming.service.ts b/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/zooming/zooming.service.ts deleted file mode 100644 index fa2583780a..0000000000 --- a/packages/ui/feature-builder-canvas/src/lib/components/canvas-utils/zooming/zooming.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Injectable } from '@angular/core'; -import { BehaviorSubject } from 'rxjs'; - -@Injectable({ - providedIn: 'root', -}) -export class ZoomingService { - zoomingScale$: BehaviorSubject = new BehaviorSubject(1); - readonly zoomingStep = 0.25; - readonly zoomingMax = 1.75; - readonly zoomingMin = 0.25; -} diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.html index 94cfd79d42..bc392eb0cb 100755 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.html @@ -1,47 +1,53 @@ + +
+
+
+ + + +
+
+
+ + + + + -
-
- - - -
-
-
- - - - - - - - - - - - - - - - - - -
{{ label.label - }}
-
+ + + + + + + + + + + + +
{{ + label.label + }}
+
+
+
+
+ +
+
-
- -
-
+ + \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.ts index 304dca2459..3288d0f66c 100755 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item-tree.component.ts @@ -1,23 +1,24 @@ import { Component, OnInit } from '@angular/core'; -import { Observable, combineLatest, map, startWith } from 'rxjs'; +import { Observable, combineLatest, map } from 'rxjs'; import { BuilderSelectors } from '@activepieces/ui/feature-builder-store'; -import { FlowDrawer } from '../canvas-utils/drawing/flow-drawer'; import { Store } from '@ngrx/store'; -import { PositionedStep } from '../canvas-utils/drawing/step-card'; import { + FLOW_ITEM_HEIGHT_WITH_BOTTOM_PADDING, FLOW_ITEM_WIDTH, + FlowDrawer, + PannerService, PositionButton, - FLOW_ITEM_HEIGHT_WITH_BOTTOM_PADDING, -} from '../canvas-utils/drawing/draw-common'; -import { ZoomingService } from '../canvas-utils/zooming/zooming.service'; -import { PannerService } from '../canvas-utils/panning/panner.service'; + ZoomingService, + PositionedStep, + DEFAULT_TOP_MARGIN, +} from '@activepieces/ui-canvas-utils'; type UiFlowDrawer = { centeringGraphTransform: string; svg: string; boundingBox: { width: number; height: number }; } & Pick; -const GRAPH_Y_OFFSET_FROM_TEST_FLOW_WIDGET = 45; + @Component({ selector: 'app-flow-item-tree', templateUrl: './flow-item-tree.component.html', @@ -27,12 +28,14 @@ export class FlowItemTreeComponent implements OnInit { flowDrawer$: Observable; transform$: Observable; readOnly$: Observable; + isPanning$: Observable; constructor( private store: Store, private pannerService: PannerService, private zoomingService: ZoomingService ) { this.transform$ = this.getTransform$(); + this.isPanning$ = this.pannerService.isPanning$; this.readOnly$ = this.store.select(BuilderSelectors.selectReadOnly); } @@ -43,7 +46,12 @@ export class FlowItemTreeComponent implements OnInit { this.flowDrawer$ = flowVersion$.pipe( map((version) => { FlowDrawer.trigger = version.trigger; - const drawer = FlowDrawer.construct(version.trigger).offset(0, 40); + return FlowDrawer.construct(version.trigger).offset( + 0, + DEFAULT_TOP_MARGIN + ); + }), + map((drawer) => { return { svg: drawer.svg.toSvg().content, boundingBox: drawer.boundingBox(), @@ -52,10 +60,7 @@ export class FlowItemTreeComponent implements OnInit { labels: drawer.labels, centeringGraphTransform: `translate(${ drawer.boundingBox().width / 2 - FLOW_ITEM_WIDTH / 2 - }px,-${ - FLOW_ITEM_HEIGHT_WITH_BOTTOM_PADDING - - GRAPH_Y_OFFSET_FROM_TEST_FLOW_WIDGET - }px)`, + }px,-${FLOW_ITEM_HEIGHT_WITH_BOTTOM_PADDING - DEFAULT_TOP_MARGIN}px)`, }; }) ); @@ -69,26 +74,24 @@ export class FlowItemTreeComponent implements OnInit { } getTransform$() { - const scale$ = this.zoomingService.zoomingScale$.asObservable().pipe( - startWith(1), + const scale$ = this.zoomingService.zoomingScale$.pipe( map((val) => { return `scale(${val})`; }) ); - const translate$ = this.pannerService.panningOffset$.asObservable().pipe( - startWith({ x: 0, y: 0 }), + const translate$ = this.pannerService.panningOffset$.pipe( map((val) => { - return `translate(${val.x}px,${val.y}px)`; + return `translate(${val.x}px, ${val.y}px)`; }) ); const transformObs$ = combineLatest({ scale: scale$, translate: translate$, - }).pipe( - map((value) => { - return `${value.scale} ${value.translate}`; - }) + }); + + // Combine the scale and translate values into transform to apply animation + return transformObs$.pipe( + map(({ scale, translate }) => `${translate} ${scale}`) ); - return transformObs$; } } diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html index 88a98d9b5a..e0fcd12f32 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.html @@ -4,7 +4,7 @@ ap-transition ap-fade-in ap-duration-500 ap-cursor-pointer ap-flex ap-items-center ap-mr-5 ap-justify-center ap-bg-white ap-border-solid ap-border-[1px] - ap-border-line ap-min-w-[50px] ap-min-h-[50px]" apTrackHover + ap-border-line ap-w-[45px] ap-h-[45px]" apTrackHover #replaceTriggerContainer="hoverTrackerDirective" *ngIf="trigger" [class.ap-opacity-100]="(stepHovered || replaceTriggerContainer.isHovered)" [class.ap-opacity-0]="(!stepHovered && !replaceTriggerContainer.isHovered)" @@ -17,7 +17,9 @@ ap-transition ap-fade-in ap-duration-500 ap-cursor-pointer ap-flex ap-items-center ap-mr-5 ap-justify-center ap-bg-white ap-border-solid ap-border-[1px] - ap-border-line ap-w-[40px] ap-h-[40px] ap-overflow-hidden" apTrackHover + ap-border-line ap-overflow-hidden" apTrackHover + [style.width]="ACTION_BUTTON_DIMENSION+ 'px'" + [style.height]="ACTION_BUTTON_DIMENSION+ 'px'" #deleteStepContainer="hoverTrackerDirective" [class.ap-opacity-100]="(stepHovered || deleteStepContainer.isHovered || duplicateStepContainer.isHovered )" [class.ap-opacity-0]="(!stepHovered && !deleteStepContainer.isHovered && !duplicateStepContainer.isHovered)" @@ -32,7 +34,10 @@ ap-transition ap-fade-in ap-duration-500 ap-cursor-pointer ap-flex ap-items-center ap-mr-5 ap-justify-center ap-bg-white ap-border-solid ap-border-[1px] - ap-border-line ap-w-[40px] ap-h-[40px] ap-overflow-hidden" apTrackHover + ap-border-line ap-overflow-hidden" apTrackHover + [style.width]="ACTION_BUTTON_DIMENSION+ 'px'" + [style.height]="ACTION_BUTTON_DIMENSION+ 'px'" + #duplicateStepContainer="hoverTrackerDirective" [class.ap-opacity-100]="(stepHovered || duplicateStepContainer.isHovered || deleteStepContainer.isHovered)" [class.ap-opacity-0]="(!stepHovered && !duplicateStepContainer.isHovered && !deleteStepContainer.isHovered )" diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.ts index 1f1a3390d0..bfcb03f563 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/actions-container/actions-container.component.ts @@ -1,11 +1,9 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { - BuilderSelectors, - Step, - FlowRendererService, -} from '@activepieces/ui/feature-builder-store'; +import { BuilderSelectors, Step } from '@activepieces/ui/feature-builder-store'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; +import { FlowRendererService } from '@activepieces/ui/common'; +import { ACTION_BUTTON_DIMENSION } from '../common'; @Component({ selector: 'app-actions-container', @@ -18,6 +16,7 @@ export class ActionsContainerComponent { @Input() stepHovered: boolean; isDragging$: Observable; readonly$: Observable; + readonly ACTION_BUTTON_DIMENSION = ACTION_BUTTON_DIMENSION; constructor( private store: Store, private flowRendererService: FlowRendererService diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/common.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/common.ts new file mode 100644 index 0000000000..3c557de213 --- /dev/null +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/common.ts @@ -0,0 +1,3 @@ +export const ACTION_BUTTON_DIMENSION = 35; +export const ACTION_BUTTON_ICON_DIMENSION = 15; +export const ACTIONS_CONTAINER_MARGIN = 8; diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html index bbec272300..8e2c0f03d1 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.html @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.ts index 5aeb3c768d..d83971b1c6 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-flow-item-action.component.ts @@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Step } from '@activepieces/ui/feature-builder-store'; import { DeleteStepDialogComponent } from './delete-step-dialog/delete-step-dialog.component'; +import { ACTION_BUTTON_ICON_DIMENSION } from '../common'; @Component({ selector: 'app-delete-flow-item-action', @@ -11,6 +12,7 @@ import { DeleteStepDialogComponent } from './delete-step-dialog/delete-step-dial export class DeleteFlowItemActionComponent { @Input() flowItem: Step; + readonly ACTION_BUTTON_ICON_DIMENSION = ACTION_BUTTON_ICON_DIMENSION; constructor(private dialogService: MatDialog) {} deleteStep() { const stepName = this.flowItem.name; diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html index b8f2640cc2..7a4dda3465 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.html @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts index 7dd6ddab3b..8d22a28500 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/duplicate-step-action/duplicate-step-action.component.ts @@ -8,6 +8,7 @@ import { Observable, forkJoin, map, switchMap, take, tap } from 'rxjs'; import { FlowVersion, flowHelper } from '@activepieces/shared'; import { Store } from '@ngrx/store'; import { FlowService } from '@activepieces/ui/common'; +import { ACTION_BUTTON_ICON_DIMENSION } from '../common'; @Component({ selector: 'app-duplicate-step-action', templateUrl: './duplicate-step-action.component.html', @@ -17,6 +18,7 @@ export class DuplicateStepActionComponent { @Input({ required: true }) flowItem: Step; duplicate$: Observable | undefined; + readonly ACTION_BUTTON_ICON_DIMENSION = ACTION_BUTTON_ICON_DIMENSION; constructor(private store: Store, private flowService: FlowService) {} duplicateStep() { const flowVersionWithArtifacts$ = this.getFlowVersionWithArtifacts(); diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/replace-trigger-action/replace-trigger-action.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/replace-trigger-action/replace-trigger-action.component.html index e7d1f6b23b..91c1535d6c 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/replace-trigger-action/replace-trigger-action.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/actions/replace-trigger-action/replace-trigger-action.component.html @@ -1,2 +1,2 @@ - \ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/add-button-core.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/add-button-core.component.ts index 59dc049a60..25d9cd70c5 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/add-button-core.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/add-button-core.component.ts @@ -2,7 +2,6 @@ import { Store } from '@ngrx/store'; import { Observable, map } from 'rxjs'; import { BuilderSelectors, - FlowRendererService, FlowsActions, RightSideBarType, canvasActions, @@ -16,6 +15,7 @@ import { } from '@activepieces/shared'; import { DropEvent } from 'angular-draggable-droppable'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { FlowRendererService } from '@activepieces/ui/common'; @Component({ template: '' }) export class AddButtonCoreComponent { readonly INVALID_DROP_MESSAGE = $localize`Can't move here`; diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/big-add-button/big-add-button.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/big-add-button/big-add-button.component.ts index bb138e09db..daeb055f52 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/big-add-button/big-add-button.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/big-add-button/big-add-button.component.ts @@ -1,15 +1,15 @@ import { Component, Input, OnInit } from '@angular/core'; import { AddButtonCoreComponent } from '../add-button-core.component'; import { Store } from '@ngrx/store'; -import { FlowRendererService } from '@activepieces/ui/feature-builder-store'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { StepLocationRelativeToParent } from '@activepieces/shared'; import { BIG_BUTTON_SIZE, FLOW_ITEM_BOTTOM_PADDING, FLOW_ITEM_HEIGHT, -} from '../../../../canvas-utils/drawing/draw-common'; -import { SvgDrawer } from '../../../../canvas-utils/drawing/svg-drawer'; -import { StepLocationRelativeToParent } from '@activepieces/shared'; + SvgDrawer, +} from '@activepieces/ui-canvas-utils'; +import { FlowRendererService } from '@activepieces/ui/common'; @Component({ selector: 'app-big-add-button', diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.html index c7687ae9c4..ee8c5b30ca 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.html @@ -1,5 +1,5 @@
+ (dragEnter)="dragEnter.emit(true)" (dragLeave)="dragLeave.emit(true)" dragOverClass="ap-z-40" [style.left]="left">
\ No newline at end of file diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.ts index 9dfa217a9e..6a64620680 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/drop-zone/drop-zone.component.ts @@ -6,14 +6,15 @@ import { OnInit, Output, } from '@angular/core'; -import { FlowRendererService } from '@activepieces/ui/feature-builder-store'; import { Observable } from 'rxjs'; import { DropEvent } from 'angular-draggable-droppable'; + +import { Action } from '@activepieces/shared'; import { DROP_ZONE_HEIGHT, DROP_ZONE_WIDTH, -} from '../../../../canvas-utils/drawing/draw-common'; -import { Action } from '@activepieces/shared'; +} from '@activepieces/ui-canvas-utils'; +import { FlowRendererService } from '@activepieces/ui/common'; @Component({ selector: 'app-drop-zone', diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/small-add-button/small-add-button.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/small-add-button/small-add-button.component.ts index 2576fa8a1a..90a5e9f0cd 100644 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/small-add-button/small-add-button.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-connection/small-add-button/small-add-button.component.ts @@ -1,9 +1,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { AddButtonCoreComponent } from '../add-button-core.component'; import { Store } from '@ngrx/store'; -import { FlowRendererService } from '@activepieces/ui/feature-builder-store'; -import { BUTTON_SIZE } from '../../../../canvas-utils/drawing/draw-common'; import { MatSnackBar } from '@angular/material/snack-bar'; +import { BUTTON_SIZE } from '@activepieces/ui-canvas-utils'; +import { FlowRendererService } from '@activepieces/ui/common'; @Component({ selector: 'app-small-add-button', diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-content/flow-item-content.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-content/flow-item-content.component.ts index 4f923b478e..dbfbdf5e0b 100755 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-content/flow-item-content.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item-content/flow-item-content.component.ts @@ -40,13 +40,13 @@ import { } from '@activepieces/ui/feature-pieces'; import { FlowItemDetails, + FlowRendererService, fadeIn400ms, isOverflown, } from '@activepieces/ui/common'; import { BuilderSelectors, Step, - FlowRendererService, canvasActions, } from '@activepieces/ui/feature-builder-store'; import { @@ -54,7 +54,7 @@ import { FLOW_ITEM_WIDTH, FLOW_ITEM_ICON_SIZE, MAX_FLOW_ITEM_NAME_WIDTH, -} from '../../../canvas-utils/drawing/draw-common'; +} from '@activepieces/ui-canvas-utils'; @Component({ selector: 'app-flow-item-content', diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.html b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.html index 138abfb3b6..777d8e8d39 100755 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.html +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.html @@ -1,9 +1,9 @@
-
-
+
-
+
+
+ [stepHovered]="itemContentHoverTracker.isHovered || spacer.isHovered">
- +
-
- - +
+ -
-
+ + +
+
+ +
+ +
+ + + + +
- +
- +
{{_flowItemData.content?.name}} diff --git a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.ts index c6701aa708..93135f5b9e 100755 --- a/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.ts +++ b/packages/ui/feature-builder-canvas/src/lib/components/flow-item-tree/flow-item/flow-item.component.ts @@ -8,17 +8,19 @@ import { import { map, Observable, of, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; -import { PositionedStep } from '../../canvas-utils/drawing/step-card'; import { + PositionedStep, FLOW_ITEM_HEIGHT, FLOW_ITEM_HEIGHT_WITH_BOTTOM_PADDING, FLOW_ITEM_WIDTH, -} from '../../canvas-utils/drawing/draw-common'; -import { - BuilderSelectors, - FlowRendererService, -} from '@activepieces/ui/feature-builder-store'; +} from '@activepieces/ui-canvas-utils'; +import { BuilderSelectors } from '@activepieces/ui/feature-builder-store'; import { flowHelper } from '@activepieces/shared'; +import { FlowRendererService } from '@activepieces/ui/common'; +import { + ACTION_BUTTON_DIMENSION, + ACTIONS_CONTAINER_MARGIN, +} from './actions/common'; @Component({ selector: 'app-flow-item', @@ -37,6 +39,10 @@ export class FlowItemComponent implements OnInit { delayTimerSet = false; touchStartLongPress = { delay: 750, delta: 10 }; snappedDraggedShadowToCursor = false; + readonly ACTION_BUTTON_DIMENSION = ACTION_BUTTON_DIMENSION; + readonly ACTION_CONTAINER_OFFSET = + ACTION_BUTTON_DIMENSION + ACTIONS_CONTAINER_MARGIN; + readonly ACTIONS_CONTAINER_MARGIN = ACTIONS_CONTAINER_MARGIN; hideDraggableSource$: Subject = new Subject(); @Input() set flowItemData(value: PositionedStep) { this._flowItemData = value; diff --git a/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts b/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts index 31cb96d6ea..662cba1952 100644 --- a/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts +++ b/packages/ui/feature-builder-canvas/src/lib/ui-feature-builder-canvas.module.ts @@ -4,8 +4,6 @@ import { UiCommonModule } from '@activepieces/ui/common'; import { FlowItemComponent } from './components/flow-item-tree/flow-item/flow-item.component'; import { FlowItemContentComponent } from './components/flow-item-tree/flow-item/flow-item-content/flow-item-content.component'; import { FlowItemTreeComponent } from './components/flow-item-tree/flow-item-tree.component'; -import { CanvasUtilsComponent } from './components/canvas-utils/canvas-utils.component'; -import { CanvasPannerDirective } from './components/canvas-utils/panning/panner.directive'; import { DeleteStepDialogComponent } from './components/flow-item-tree/flow-item/actions/delete-flow-item-action/delete-step-dialog/delete-step-dialog.component'; import { IncompleteStepsWidgetComponent } from './components/widgets/incomplete-steps-widget/incomplete-steps-widget.component'; import { DragAndDropModule } from 'angular-draggable-droppable'; @@ -34,9 +32,7 @@ import { EndfOfFlowWidgetComponent } from './components/widgets/end-of-flow-widg FlowItemComponent, FlowItemContentComponent, FlowItemTreeComponent, - CanvasUtilsComponent, DeleteStepDialogComponent, - CanvasPannerDirective, IncompleteStepsWidgetComponent, DropZoneComponent, SmallAddButtonComponent, @@ -49,6 +45,6 @@ import { EndfOfFlowWidgetComponent } from './components/widgets/end-of-flow-widg DuplicateStepActionComponent, EndfOfFlowWidgetComponent, ], - exports: [FlowItemTreeComponent, CanvasUtilsComponent, CanvasPannerDirective], + exports: [FlowItemTreeComponent], }) export class UiFeatureBuilderCanvasModule {} diff --git a/packages/ui/feature-builder-form-controls/src/lib/interpolating-text-form-control/mentions-list/mentions-list.component.ts b/packages/ui/feature-builder-form-controls/src/lib/interpolating-text-form-control/mentions-list/mentions-list.component.ts index 3347a4d61e..356f0db3ed 100644 --- a/packages/ui/feature-builder-form-controls/src/lib/interpolating-text-form-control/mentions-list/mentions-list.component.ts +++ b/packages/ui/feature-builder-form-controls/src/lib/interpolating-text-form-control/mentions-list/mentions-list.component.ts @@ -87,7 +87,7 @@ export class MentionsListComponent implements OnInit, AfterViewInit { ngAfterViewInit(): void { if (this.focusSearchInput$) { setTimeout(() => { - this.searchInput.nativeElement.focus(); + this.searchInput?.nativeElement.focus(); }, 1); } } @@ -96,7 +96,7 @@ export class MentionsListComponent implements OnInit, AfterViewInit { this.focusSearchInput$ = this.focusSearchInput$.pipe( tap((val) => { if (val && this.searchInput) { - this.searchInput.nativeElement.focus(); + this.searchInput?.nativeElement.focus(); } }) ); diff --git a/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.html b/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.html index 5f2f7e19ca..2c75c01b04 100644 --- a/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.html +++ b/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.html @@ -534,8 +534,8 @@ (mentionEmitted)="addMentionToJsonControl($event)">
-
-

+

{{property.value.displayName}} is required

diff --git a/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.ts b/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.ts index 9dfe7d7331..fa2da88d33 100644 --- a/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.ts +++ b/packages/ui/feature-builder-form-controls/src/lib/piece-properties-form/piece-properties-form.component.ts @@ -401,10 +401,6 @@ export class PiecePropertiesFormComponent implements ControlValueAccessor { ); } - getControl(configKey: string) { - return this.form.get(configKey); - } - connectionValueChanged(event: { propertyKey: string; value: `{{connections['${string}']}}`; diff --git a/packages/ui/feature-builder-header/src/lib/flow-builder-header.component.html b/packages/ui/feature-builder-header/src/lib/flow-builder-header.component.html index 508cf4e170..7ac112940d 100755 --- a/packages/ui/feature-builder-header/src/lib/flow-builder-header.component.html +++ b/packages/ui/feature-builder-header/src/lib/flow-builder-header.component.html @@ -1,5 +1,5 @@