diff --git a/packages/pieces/community/github/package.json b/packages/pieces/community/github/package.json
index 2970bc75dd..9d25f1fc13 100644
--- a/packages/pieces/community/github/package.json
+++ b/packages/pieces/community/github/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-github",
- "version": "0.3.9"
+ "version": "0.3.10"
}
diff --git a/packages/pieces/community/github/src/lib/common/index.ts b/packages/pieces/community/github/src/lib/common/index.ts
index 3cfeb9f92b..51eae540ce 100644
--- a/packages/pieces/community/github/src/lib/common/index.ts
+++ b/packages/pieces/community/github/src/lib/common/index.ts
@@ -1,170 +1,180 @@
import { Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
import {
- HttpRequest,
- HttpMethod,
- AuthenticationType,
- httpClient,
+ HttpRequest,
+ HttpMethod,
+ AuthenticationType,
+ httpClient,
} from '@activepieces/pieces-common';
export const githubCommon = {
- baseUrl: 'https://api.github.com',
- repositoryDropdown: Property.Dropdown<{ repo: string; owner: string }>({
- displayName: 'Repository',
- refreshers: [],
- required: true,
- options: async ({ auth }) => {
- if (!auth) {
- return {
- disabled: true,
- options: [],
- placeholder: 'please authenticate first',
- };
- }
- const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue;
- const repositories = await getUserRepo(authProp);
- return {
- disabled: false,
- options: repositories.map((repo) => {
- return {
- label: repo.owner.login + '/' + repo.name,
- value: {
- owner: repo.owner.login,
- repo: repo.name,
- },
- };
- }),
- };
- },
- }),
- assigneeDropDown: (required = false) =>
- Property.MultiSelectDropdown({
- displayName: 'Assignees',
- description: 'Assignees for the Issue',
- refreshers: ['repository'],
+ baseUrl: 'https://api.github.com',
+ repositoryDropdown: Property.Dropdown<{ repo: string; owner: string }>({
+ displayName: 'Repository',
+ refreshers: [],
+ required: true,
+ options: async ({ auth }) => {
+ if (!auth) {
+ return {
+ disabled: true,
+ options: [],
+ placeholder: 'please authenticate first',
+ };
+ }
+ const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue;
+ const repositories = await getUserRepo(authProp);
+ return {
+ disabled: false,
+ options: repositories.map((repo) => {
+ return {
+ label: repo.owner.login + '/' + repo.name,
+ value: {
+ owner: repo.owner.login,
+ repo: repo.name,
+ },
+ };
+ }),
+ };
+ },
+ }),
+ assigneeDropDown: (required = false) =>
+ Property.MultiSelectDropdown({
+ displayName: 'Assignees',
+ description: 'Assignees for the Issue',
+ refreshers: ['repository'],
- required,
- options: async ({ auth, repository }) => {
- if (!auth || !repository) {
- return {
- disabled: true,
- options: [],
- placeholder: 'please authenticate first and select repo',
- };
- }
- const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue;
- const { owner, repo } = repository as RepositoryProp;
- const assignees = await getAssignee(authProp, owner, repo);
- return {
- disabled: false,
- options: assignees.map((assignee) => {
- return {
- label: assignee.login,
- value: assignee.login,
- };
- }),
- };
- },
- }),
- labelDropDown: (required = false) =>
- Property.MultiSelectDropdown({
- displayName: 'Labels',
- description: 'Labels for the Issue',
- refreshers: ['repository'],
- required,
- options: async ({ auth, repository }) => {
- if (!auth || !repository) {
- return {
- disabled: true,
- options: [],
- placeholder: 'please authenticate first and select repo',
- };
- }
- const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue;
- const { owner, repo } = repository as RepositoryProp;
- const labels = await listIssueLabels(authProp, owner, repo);
- return {
- disabled: false,
- options: labels.map((label) => {
- return {
- label: label.name,
- value: label.name,
- };
- }),
- };
- },
- }),
+ required,
+ options: async ({ auth, repository }) => {
+ if (!auth || !repository) {
+ return {
+ disabled: true,
+ options: [],
+ placeholder: 'please authenticate first and select repo',
+ };
+ }
+ const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue;
+ const { owner, repo } = repository as RepositoryProp;
+ const assignees = await getAssignee(authProp, owner, repo);
+ return {
+ disabled: false,
+ options: assignees.map((assignee) => {
+ return {
+ label: assignee.login,
+ value: assignee.login,
+ };
+ }),
+ };
+ },
+ }),
+ labelDropDown: (required = false) =>
+ Property.MultiSelectDropdown({
+ displayName: 'Labels',
+ description: 'Labels for the Issue',
+ refreshers: ['repository'],
+ required,
+ options: async ({ auth, repository }) => {
+ if (!auth || !repository) {
+ return {
+ disabled: true,
+ options: [],
+ placeholder: 'please authenticate first and select repo',
+ };
+ }
+ const authProp: OAuth2PropertyValue = auth as OAuth2PropertyValue;
+ const { owner, repo } = repository as RepositoryProp;
+ const labels = await listIssueLabels(authProp, owner, repo);
+ return {
+ disabled: false,
+ options: labels.map((label) => {
+ return {
+ label: label.name,
+ value: label.name,
+ };
+ }),
+ };
+ },
+ }),
};
-async function getUserRepo(
- authProp: OAuth2PropertyValue
-): Promise
{
- const request: HttpRequest = {
- method: HttpMethod.GET,
- url: `${githubCommon.baseUrl}/user/repos`,
- queryParams: {
- per_page: '200',
- },
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token: authProp.access_token,
- },
- };
- const response = await httpClient.sendRequest(request);
- return response.body;
+async function getUserRepo(authProp: OAuth2PropertyValue): Promise {
+ let page = 1;
+ let hasNext;
+ const repos: GithubRepository[] = [];
+ do {
+ const request: HttpRequest = {
+ method: HttpMethod.GET,
+ url: `${githubCommon.baseUrl}/user/repos`,
+ queryParams: {
+ page: page.toString(),
+ per_page: '100',
+ },
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token: authProp.access_token,
+ },
+ };
+
+ const response = await httpClient.sendRequest(request);
+ repos.push(...response.body);
+
+ hasNext = response.headers?.link?.includes('rel="next"');
+ page += 1;
+ } while (hasNext);
+
+ return repos;
}
async function getAssignee(
- authProp: OAuth2PropertyValue,
- owner: string,
- repo: string
+ authProp: OAuth2PropertyValue,
+ owner: string,
+ repo: string,
): Promise {
- const request: HttpRequest = {
- method: HttpMethod.GET,
- url: `${githubCommon.baseUrl}/repos/${owner}/${repo}/assignees`,
- queryParams: {
- per_page: '30',
- },
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token: authProp.access_token,
- },
- };
- const response = await httpClient.sendRequest(request);
- return response.body;
+ const request: HttpRequest = {
+ method: HttpMethod.GET,
+ url: `${githubCommon.baseUrl}/repos/${owner}/${repo}/assignees`,
+ queryParams: {
+ per_page: '30',
+ },
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token: authProp.access_token,
+ },
+ };
+ const response = await httpClient.sendRequest(request);
+ return response.body;
}
async function listIssueLabels(
- authProp: OAuth2PropertyValue,
- owner: string,
- repo: string
+ authProp: OAuth2PropertyValue,
+ owner: string,
+ repo: string,
): Promise {
- const request: HttpRequest = {
- method: HttpMethod.GET,
- url: `${githubCommon.baseUrl}/repos/${owner}/${repo}/labels`,
- queryParams: {
- per_page: '30',
- },
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token: authProp.access_token,
- },
- };
- const response = await httpClient.sendRequest(request);
- return response.body;
+ const request: HttpRequest = {
+ method: HttpMethod.GET,
+ url: `${githubCommon.baseUrl}/repos/${owner}/${repo}/labels`,
+ queryParams: {
+ per_page: '30',
+ },
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token: authProp.access_token,
+ },
+ };
+ const response = await httpClient.sendRequest(request);
+ return response.body;
}
export interface GithubRepository {
- name: string;
- owner: {
- login: string;
- };
+ name: string;
+ owner: {
+ login: string;
+ };
}
export interface GithubAssignee {
- login: string;
+ login: string;
}
export interface GithubIssueLabel {
- id: string;
- name: string;
+ id: string;
+ name: string;
}
export interface RepositoryProp {
- repo: string;
- owner: string;
+ repo: string;
+ owner: string;
}
diff --git a/packages/pieces/community/notion/package.json b/packages/pieces/community/notion/package.json
index a55daadcf0..0fcf17e575 100644
--- a/packages/pieces/community/notion/package.json
+++ b/packages/pieces/community/notion/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-notion",
- "version": "0.2.12"
+ "version": "0.2.13"
}
\ No newline at end of file
diff --git a/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts b/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts
index ba86360b3b..72fff717f3 100644
--- a/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts
+++ b/packages/pieces/community/notion/src/lib/triggers/updated-database-item.ts
@@ -128,9 +128,9 @@ const polling: Polling<
lastFetchEpochMS === 0 ? null : dayjs(lastFetchEpochMS).toISOString()
);
return items.results.map((item) => {
- const object = item as { created_time: string };
+ const object = item as { last_edited_time: string };
return {
- epochMilliSeconds: dayjs(object.created_time).valueOf(),
+ epochMilliSeconds: dayjs(object.last_edited_time).valueOf(),
data: item,
};
});
diff --git a/packages/pieces/community/openai/package.json b/packages/pieces/community/openai/package.json
index 4f1c94d95a..f4068e4900 100644
--- a/packages/pieces/community/openai/package.json
+++ b/packages/pieces/community/openai/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-openai",
- "version": "0.3.22"
+ "version": "0.3.24"
}
\ No newline at end of file
diff --git a/packages/pieces/community/openai/src/index.ts b/packages/pieces/community/openai/src/index.ts
index 3dc5c44794..2359fc6152 100644
--- a/packages/pieces/community/openai/src/index.ts
+++ b/packages/pieces/community/openai/src/index.ts
@@ -1,13 +1,10 @@
import {
- AuthenticationType,
- HttpMethod,
- createCustomApiCallAction,
- httpClient,
+ AuthenticationType,
+ HttpMethod,
+ createCustomApiCallAction,
+ httpClient,
} from '@activepieces/pieces-common';
-import {
- PieceAuth,
- createPiece,
-} from '@activepieces/pieces-framework';
+import { PieceAuth, createPiece } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { askAssistant } from './lib/actions/ask-assistant';
import { generateImage } from './lib/actions/generate-image';
@@ -17,6 +14,7 @@ import { transcribeAction } from './lib/actions/transcriptions';
import { translateAction } from './lib/actions/translation';
import { visionPrompt } from './lib/actions/vision-prompt';
import { baseUrl } from './lib/common/common';
+import { extractStructuredDataAction } from './lib/actions/extract-structure-data.action';
const markdownDescription = `
Follow these instructions to get your OpenAI API Key:
@@ -28,58 +26,69 @@ It is strongly recommended that you add your credit card information to your Ope
`;
export const openaiAuth = PieceAuth.SecretText({
- description: markdownDescription,
- displayName: 'API Key',
- required: true,
- validate: async (auth) => {
- try {
- await httpClient.sendRequest<{
- data: { id: string }[];
- }>({
- url: `${baseUrl}/models`,
- method: HttpMethod.GET,
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token: auth.auth as string,
- },
- });
- return {
- valid: true,
- };
- } catch (e) {
- return {
- valid: false,
- error: 'Invalid API key',
- };
- }
- },
+ description: markdownDescription,
+ displayName: 'API Key',
+ required: true,
+ validate: async (auth) => {
+ try {
+ await httpClient.sendRequest<{
+ data: { id: string }[];
+ }>({
+ url: `${baseUrl}/models`,
+ method: HttpMethod.GET,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token: auth.auth as string,
+ },
+ });
+ return {
+ valid: true,
+ };
+ } catch (e) {
+ return {
+ valid: false,
+ error: 'Invalid API key',
+ };
+ }
+ },
});
export const openai = createPiece({
- displayName: 'OpenAI',
- description: 'Use the many tools ChatGPT has to offer.',
- minimumSupportedRelease: '0.5.0',
- logoUrl: 'https://cdn.activepieces.com/pieces/openai.png',
- categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE],
- auth: openaiAuth,
- actions: [
- askOpenAI,
- askAssistant,
- generateImage,
- visionPrompt,
- textToSpeech,
- transcribeAction,
- translateAction,
- createCustomApiCallAction({
- auth: openaiAuth,
- baseUrl: () => baseUrl,
- authMapping: (auth) => {
- return {
- Authorization: `Bearer ${auth}`,
- };
- },
- }),
- ],
- authors: ["aboudzein","astorozhevsky","Willianwg","Nilesh","Salem-Alaa","kishanprmr","MoShizzle","khaledmashaly","abuaboud"],
- triggers: [],
+ displayName: 'OpenAI',
+ description: 'Use the many tools ChatGPT has to offer.',
+ minimumSupportedRelease: '0.5.0',
+ logoUrl: 'https://cdn.activepieces.com/pieces/openai.png',
+ categories: [PieceCategory.ARTIFICIAL_INTELLIGENCE],
+ auth: openaiAuth,
+ actions: [
+ askOpenAI,
+ askAssistant,
+ generateImage,
+ visionPrompt,
+ textToSpeech,
+ transcribeAction,
+ translateAction,
+ extractStructuredDataAction,
+ createCustomApiCallAction({
+ auth: openaiAuth,
+ baseUrl: () => baseUrl,
+ authMapping: (auth) => {
+ return {
+ Authorization: `Bearer ${auth}`,
+ };
+ },
+ }),
+ ],
+ authors: [
+ 'aboudzein',
+ 'astorozhevsky',
+ 'Willianwg',
+ 'Nilesh',
+ 'Salem-Alaa',
+ 'kishanprmr',
+ 'MoShizzle',
+ 'khaledmashaly',
+ 'abuaboud',
+ ],
+ triggers: [],
});
diff --git a/packages/pieces/community/openai/src/lib/actions/extract-structure-data.action.ts b/packages/pieces/community/openai/src/lib/actions/extract-structure-data.action.ts
new file mode 100644
index 0000000000..4539d103f9
--- /dev/null
+++ b/packages/pieces/community/openai/src/lib/actions/extract-structure-data.action.ts
@@ -0,0 +1,146 @@
+import { openaiAuth } from '../../';
+import { createAction, Property } from '@activepieces/pieces-framework';
+import OpenAI from 'openai';
+import { notLLMs } from '../common/common';
+
+export const extractStructuredDataAction = createAction({
+ auth: openaiAuth,
+ name: 'extract-structured-data',
+ displayName: 'Extract Structured Data from Text',
+ description: 'Returns structured data from provided unstructured text.',
+ props: {
+ model: Property.Dropdown({
+ displayName: 'Model',
+ required: true,
+ refreshers: [],
+ defaultValue: 'gpt-3.5-turbo',
+ options: async ({ auth }) => {
+ if (!auth) {
+ return {
+ disabled: true,
+ placeholder: 'Enter your API key first',
+ options: [],
+ };
+ }
+ try {
+ const openai = new OpenAI({
+ apiKey: auth as string,
+ });
+ const response = await openai.models.list();
+ // We need to get only LLM models
+ const models = response.data.filter((model) => !notLLMs.includes(model.id));
+ return {
+ disabled: false,
+ options: models.map((model) => {
+ return {
+ label: model.id,
+ value: model.id,
+ };
+ }),
+ };
+ } catch (error) {
+ return {
+ disabled: true,
+ options: [],
+ placeholder: "Couldn't load models, API key is invalid",
+ };
+ }
+ },
+ }),
+ text: Property.LongText({
+ displayName: 'Unstructured Text',
+ required: true,
+ }),
+ params: Property.Array({
+ displayName: 'Data Definition',
+ required: true,
+ properties: {
+ propName: Property.ShortText({
+ displayName: 'Name',
+ description:
+ 'Provide the name of the value you want to extract from the unstructured text. The name should be unique and short. ',
+ required: true,
+ }),
+ propDescription: Property.LongText({
+ displayName: 'Description',
+ description:
+ 'Brief description of the data, this hints for the AI on what to look for',
+ required: false,
+ }),
+ propDataType: Property.StaticDropdown({
+ displayName: 'Data Type',
+ description: 'Type of parameter.',
+ required: true,
+ defaultValue: 'string',
+ options: {
+ disabled: false,
+ options: [
+ { label: 'Text', value: 'string' },
+ { label: 'Number', value: 'number' },
+ { label: 'Boolean', value: 'boolean' },
+ ],
+ },
+ }),
+ propIsRequired: Property.Checkbox({
+ displayName: 'Fail if Not present?',
+ required: true,
+ defaultValue: false,
+ }),
+ },
+ }),
+ },
+ async run(context) {
+ const { model, text } = context.propsValue;
+ const paramInputArray = context.propsValue.params as ParamInput[];
+ const functionParams: Record = {};
+ const requiredFunctionParams: string[] = [];
+ for (const param of paramInputArray) {
+ functionParams[param.propName] = {
+ type: param.propDataType,
+ description: param.propDescription ?? param.propName,
+ };
+ if (param.propIsRequired) {
+ requiredFunctionParams.push(param.propName);
+ }
+ }
+ const prompt = 'Extract the following data from the provided text'
+ const openai = new OpenAI({
+ apiKey: context.auth,
+ });
+
+ const response = await openai.chat.completions.create({
+ model: model,
+ messages: [{ role: 'user', content: text }],
+ tools: [
+ {
+ type: 'function',
+ function: {
+ name: 'extract_structured_data',
+ description: prompt,
+ parameters: {
+ type: 'object',
+ properties: functionParams,
+ required: requiredFunctionParams,
+ },
+ },
+ },
+ ],
+ });
+
+ const toolCallsResponse = response.choices[0].message.tool_calls;
+ if (toolCallsResponse) {
+ return JSON.parse(toolCallsResponse[0].function.arguments);
+ } else {
+ throw new Error(JSON.stringify({
+ message: "OpenAI couldn't extract the fields from the above text."
+ }));
+ }
+ },
+});
+
+interface ParamInput {
+ propName: string;
+ propDescription: string;
+ propDataType: string;
+ propIsRequired: boolean;
+}
diff --git a/packages/pieces/community/pdf/package.json b/packages/pieces/community/pdf/package.json
index 6c38c0d693..a6f0e7b593 100644
--- a/packages/pieces/community/pdf/package.json
+++ b/packages/pieces/community/pdf/package.json
@@ -3,7 +3,7 @@
"version": "0.0.1",
"dependencies": {
"@activepieces/pieces-framework": "*",
- "pdf-text-reader": "4.0.1",
+ "pdf-text-reader": "4.1.0",
"tslib": "2.6.2"
},
"main": "./src/index.js",
diff --git a/packages/pieces/community/poper/.eslintrc.json b/packages/pieces/community/poper/.eslintrc.json
new file mode 100644
index 0000000000..4a4e695c54
--- /dev/null
+++ b/packages/pieces/community/poper/.eslintrc.json
@@ -0,0 +1,33 @@
+{
+ "extends": [
+ "../../../../.eslintrc.base.json"
+ ],
+ "ignorePatterns": [
+ "!**/*"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "*.ts",
+ "*.tsx",
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.ts",
+ "*.tsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/pieces/community/poper/README.md b/packages/pieces/community/poper/README.md
new file mode 100644
index 0000000000..9deb0c9627
--- /dev/null
+++ b/packages/pieces/community/poper/README.md
@@ -0,0 +1,7 @@
+# pieces-poper
+
+This library was generated with [Nx](https://nx.dev).
+
+## Building
+
+Run `nx build pieces-poper` to build the library.
diff --git a/packages/pieces/community/poper/package.json b/packages/pieces/community/poper/package.json
new file mode 100644
index 0000000000..e510c65446
--- /dev/null
+++ b/packages/pieces/community/poper/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "@activepieces/piece-poper",
+ "version": "0.0.1"
+}
diff --git a/packages/pieces/community/poper/project.json b/packages/pieces/community/poper/project.json
new file mode 100644
index 0000000000..89e68c6793
--- /dev/null
+++ b/packages/pieces/community/poper/project.json
@@ -0,0 +1,38 @@
+{
+ "name": "pieces-poper",
+ "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "packages/pieces/community/poper/src",
+ "projectType": "library",
+ "targets": {
+ "build": {
+ "executor": "@nx/js:tsc",
+ "outputs": [
+ "{options.outputPath}"
+ ],
+ "options": {
+ "outputPath": "dist/packages/pieces/community/poper",
+ "tsConfig": "packages/pieces/community/poper/tsconfig.lib.json",
+ "packageJson": "packages/pieces/community/poper/package.json",
+ "main": "packages/pieces/community/poper/src/index.ts",
+ "assets": [
+ "packages/pieces/community/poper/*.md"
+ ],
+ "buildableProjectDepsInPackageJsonType": "dependencies",
+ "updateBuildableProjectDepsInPackageJson": true
+ }
+ },
+ "publish": {
+ "command": "node tools/scripts/publish.mjs pieces-poper {args.ver} {args.tag}",
+ "dependsOn": [
+ "build"
+ ]
+ },
+ "lint": {
+ "executor": "@nx/eslint:lint",
+ "outputs": [
+ "{options.outputFile}"
+ ]
+ }
+ },
+ "tags": []
+}
\ No newline at end of file
diff --git a/packages/pieces/community/poper/src/index.ts b/packages/pieces/community/poper/src/index.ts
new file mode 100644
index 0000000000..b70c9b5ee3
--- /dev/null
+++ b/packages/pieces/community/poper/src/index.ts
@@ -0,0 +1,56 @@
+import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
+import { newLead } from './lib/triggers/new-lead';
+import { HttpMethod, HttpRequest, httpClient } from '@activepieces/pieces-common';
+import { PieceCategory } from '@activepieces/shared';
+
+export const poperAuth = PieceAuth.SecretText({
+ displayName: 'API Key',
+ description:
+ 'Use the Poper API key to authenticate the piece. You can find your API key in the Poper settings.',
+ required: true,
+ validate: async ({ auth }) => {
+ const request: HttpRequest = {
+ method: HttpMethod.POST,
+ url: 'https://api.poper.ai/general/v1/ping',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: {
+ api_key: auth,
+ },
+ };
+
+ try {
+ const res = await httpClient.sendRequest(request);
+
+ if (res.status === 200) {
+ return {
+ valid: true,
+ };
+ }
+
+ return {
+ valid: false,
+ error: 'API Key is invalid',
+ };
+ } catch (e) {
+ return {
+ valid: false,
+ error: 'API Key is invalid',
+ };
+ }
+ },
+});
+
+export const poper = createPiece({
+ displayName: 'Poper',
+ auth: poperAuth,
+ minimumSupportedRelease: '0.20.0',
+ categories: [PieceCategory.MARKETING],
+ description:
+ 'AI Driven Pop-up Builder that can convert visitors into customers,increase subscriber count, and skyrocket sales.',
+ logoUrl: 'https://cdn.activepieces.com/pieces/poper.png',
+ authors: ['thirstycode'],
+ actions: [],
+ triggers: [newLead],
+});
diff --git a/packages/pieces/community/poper/src/lib/triggers/new-lead.ts b/packages/pieces/community/poper/src/lib/triggers/new-lead.ts
new file mode 100644
index 0000000000..38c6b8f9cb
--- /dev/null
+++ b/packages/pieces/community/poper/src/lib/triggers/new-lead.ts
@@ -0,0 +1,143 @@
+
+import { createTrigger, TriggerStrategy, PiecePropValueSchema, StaticPropsValue, DropdownProperty } from '@activepieces/pieces-framework';
+import { DedupeStrategy, Polling, pollingHelper, HttpMethod, HttpRequest, httpClient } from '@activepieces/pieces-common';
+import { poperAuth } from './../../';
+
+import { Property } from '@activepieces/pieces-framework';
+
+const buildEmptyList = ({ placeholder }: { placeholder: string }) => {
+ return {
+ disabled: true,
+ options: [],
+ placeholder,
+ };
+};
+
+const popupsDropdown = Property.Dropdown({
+ displayName: 'Popup Name',
+ refreshers: [],
+ description: "Select the popup name to trigger the action",
+ required: true,
+ options: async ({ auth }) => {
+ if (!auth) {
+ return buildEmptyList({
+ placeholder: 'Please select an authentication',
+ });
+ }
+
+ const request: HttpRequest = {
+ method: HttpMethod.POST,
+ url: 'https://api.poper.ai/general/v1/popup/list',
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: {
+ api_key: auth,
+ }
+ };
+
+ try{
+
+ const res = await httpClient.sendRequest(request);
+
+ if(res.status === 200){
+ const popups = res.body.popups;
+ if (popups.length === 0) {
+ return buildEmptyList({
+ placeholder: 'No popups found! Please create a popup.',
+ });
+ }
+
+ const options = popups.map((p:any) => ({
+ label: p.name,
+ value: p.id,
+ }));
+
+ return {
+ disabled: false,
+ options,
+ };
+ }
+
+ return buildEmptyList({
+ placeholder: 'Not authorized.',
+ });
+ } catch(e){
+ return buildEmptyList({
+ placeholder: 'Not authorized.',
+ });
+ }
+
+ },
+});
+
+// replace auth with piece auth variable
+const polling: Polling< PiecePropValueSchema, StaticPropsValue<{ popup_id: DropdownProperty | DropdownProperty; }> > = {
+ strategy: DedupeStrategy.LAST_ITEM,
+ items: async ({ auth, propsValue }) => {
+ // implement the logic to fetch the items
+ const request: HttpRequest = {
+ method: HttpMethod.POST,
+ url: 'https://api.poper.ai/general/v1/popup/responses',
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: {
+ api_key: auth,
+ popup_id: propsValue.popup_id,
+ }
+ };
+
+ try{
+
+ const res = await httpClient.sendRequest(request);
+
+ if(res.status === 200){
+ const responses = res.body.responses;
+ if (responses.length === 0) {
+ return [];
+ }
+
+ return responses.map((response:any) => ({
+ id: response.id,
+ data: response,
+ }));
+ }
+
+ return [];
+ } catch(e){
+ return [];
+ }
+ },
+
+}
+
+export const newLead = createTrigger({
+auth: poperAuth,
+name: 'newLead',
+displayName: 'New Lead',
+description: 'Triggers when a new lead is obtained from popup',
+props: {
+ popup_id: popupsDropdown,
+},
+sampleData: {},
+type: TriggerStrategy.POLLING,
+async test(context) {
+ const { store, auth, propsValue } = context;
+ return await pollingHelper.test(polling, { store, auth, propsValue });
+},
+async onEnable(context) {
+ const { store, auth, propsValue } = context;
+ await pollingHelper.onEnable(polling, { store, auth, propsValue });
+},
+
+async onDisable(context) {
+ const { store, auth, propsValue } = context;
+ await pollingHelper.onDisable(polling, { store, auth, propsValue });
+},
+
+async run(context) {
+ const { store, auth, propsValue } = context;
+ return await pollingHelper.poll(polling, { store, auth, propsValue });
+},
+});
\ No newline at end of file
diff --git a/packages/pieces/community/poper/tsconfig.json b/packages/pieces/community/poper/tsconfig.json
new file mode 100644
index 0000000000..059cd81661
--- /dev/null
+++ b/packages/pieces/community/poper/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/poper/tsconfig.lib.json b/packages/pieces/community/poper/tsconfig.lib.json
new file mode 100644
index 0000000000..28369ef762
--- /dev/null
+++ b/packages/pieces/community/poper/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/todoist/package.json b/packages/pieces/community/todoist/package.json
index d3e96128c4..d3dbd499dc 100644
--- a/packages/pieces/community/todoist/package.json
+++ b/packages/pieces/community/todoist/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-todoist",
- "version": "0.3.7"
+ "version": "0.3.8"
}
\ No newline at end of file
diff --git a/packages/pieces/community/todoist/src/index.ts b/packages/pieces/community/todoist/src/index.ts
index 566ee16d21..51d10edd8a 100644
--- a/packages/pieces/community/todoist/src/index.ts
+++ b/packages/pieces/community/todoist/src/index.ts
@@ -3,31 +3,37 @@ import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/piece
import { PieceCategory } from '@activepieces/shared';
import { todoistCreateTaskAction } from './lib/actions/create-task-action';
import { todoistTaskCompletedTrigger } from './lib/triggers/task-completed-trigger';
+import { todoistUpdateTaskAction } from './lib/actions/update-task.action';
+import { todoistFindTaskAction } from './lib/actions/find-task.action';
+import { todoistMarkTaskCompletedAction } from './lib/actions/mark-task-completed.action';
export const todoistAuth = PieceAuth.OAuth2({
- required: true,
- authUrl: 'https://todoist.com/oauth/authorize',
- tokenUrl: 'https://todoist.com/oauth/access_token',
- scope: ['data:read_write'],
+ required: true,
+ authUrl: 'https://todoist.com/oauth/authorize',
+ tokenUrl: 'https://todoist.com/oauth/access_token',
+ scope: ['data:read_write'],
});
export const todoist = createPiece({
- displayName: 'Todoist',
- description: 'To-do list and task manager',
- minimumSupportedRelease: '0.5.0',
- logoUrl: 'https://cdn.activepieces.com/pieces/todoist.png',
- authors: ["MyWay","kishanprmr","MoShizzle","khaledmashaly","abuaboud"],
- categories: [PieceCategory.PRODUCTIVITY],
- auth: todoistAuth,
- actions: [
- todoistCreateTaskAction,
- createCustomApiCallAction({
- baseUrl: () => 'https://api.todoist.com/rest/v2',
- auth: todoistAuth,
- authMapping: (auth) => ({
- Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`,
- }),
- }),
- ],
- triggers: [todoistTaskCompletedTrigger],
+ displayName: 'Todoist',
+ description: 'To-do list and task manager',
+ minimumSupportedRelease: '0.5.0',
+ logoUrl: 'https://cdn.activepieces.com/pieces/todoist.png',
+ authors: ['MyWay', 'kishanprmr', 'MoShizzle', 'khaledmashaly', 'abuaboud'],
+ categories: [PieceCategory.PRODUCTIVITY],
+ auth: todoistAuth,
+ actions: [
+ todoistCreateTaskAction,
+ todoistUpdateTaskAction,
+ todoistFindTaskAction,
+ todoistMarkTaskCompletedAction,
+ createCustomApiCallAction({
+ baseUrl: () => 'https://api.todoist.com/rest/v2',
+ auth: todoistAuth,
+ authMapping: (auth) => ({
+ Authorization: `Bearer ${(auth as OAuth2PropertyValue).access_token}`,
+ }),
+ }),
+ ],
+ triggers: [todoistTaskCompletedTrigger],
});
diff --git a/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts b/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts
index 566d7a63a8..2f0d7411a9 100644
--- a/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts
+++ b/packages/pieces/community/todoist/src/lib/actions/create-task-action.ts
@@ -1,70 +1,65 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { assertNotNullOrUndefined } from '@activepieces/shared';
import { todoistRestClient } from '../common/client/rest-client';
-import { todoistProjectIdDropdown } from '../common/props';
+import { todoistProjectIdDropdown, todoistSectionIdDropdown } from '../common/props';
import { TodoistCreateTaskRequest } from '../common/models';
import { todoistAuth } from '../..';
export const todoistCreateTaskAction = createAction({
- auth: todoistAuth,
- name: 'create_task',
- displayName: 'Create Task',
- description: 'Create task',
- props: {
- project_id: todoistProjectIdDropdown,
- content: Property.LongText({
- displayName: 'content',
- description:
- "The task's content. It may contain some markdown-formatted text and hyperlinks",
- required: true,
- }),
- description: Property.LongText({
- displayName: 'Description',
- description:
- 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.',
- required: false,
- }),
- labels: Property.Array({
- displayName: 'Labels',
- required: false,
- description:
- "The task's labels (a list of names that may represent either personal or shared labels)",
- }),
- priority: Property.Number({
- displayName: 'Priority',
- description: 'Task priority from 1 (normal) to 4 (urgent)',
- required: false,
- }),
- due_date: Property.ShortText({
- displayName: 'Due date',
- description:
- "Specific date in YYYY-MM-DD format relative to user's timezone",
- required: false,
- }),
- section_id: Property.ShortText({
- displayName: 'Section',
- description:
- "A section for the task. It should be a Section ID under the same project",
- required: false,
- }),
- },
+ auth: todoistAuth,
+ name: 'create_task',
+ displayName: 'Create Task',
+ description: 'Create task',
+ props: {
+ project_id: todoistProjectIdDropdown(
+ "Task project ID. If not set, task is put to user's Inbox.",
+ ),
+ content: Property.LongText({
+ displayName: 'content',
+ description: "The task's content. It may contain some markdown-formatted text and hyperlinks",
+ required: true,
+ }),
+ description: Property.LongText({
+ displayName: 'Description',
+ description:
+ 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.',
+ required: false,
+ }),
+ labels: Property.Array({
+ displayName: 'Labels',
+ required: false,
+ description:
+ "The task's labels (a list of names that may represent either personal or shared labels)",
+ }),
+ priority: Property.Number({
+ displayName: 'Priority',
+ description: 'Task priority from 1 (normal) to 4 (urgent)',
+ required: false,
+ }),
+ due_date: Property.ShortText({
+ displayName: 'Due date',
+ description: "Specific date in YYYY-MM-DD format relative to user's timezone",
+ required: false,
+ }),
+ section_id: todoistSectionIdDropdown,
+ },
- async run({ auth, propsValue }) {
- const token = auth.access_token;
- const { project_id, content, description, labels, priority, due_date, section_id} =
- propsValue as TodoistCreateTaskRequest;
+ async run({ auth, propsValue }) {
+ const token = auth.access_token;
+ const { project_id, content, description, labels, priority, due_date, section_id } =
+ propsValue as TodoistCreateTaskRequest;
- assertNotNullOrUndefined(token, 'token');
- assertNotNullOrUndefined(content, 'content');
- return await todoistRestClient.tasks.create({
- token,
- project_id,
- content,
- description,
- labels,
- priority,
- due_date,
- section_id
- });
- },
+ assertNotNullOrUndefined(token, 'token');
+ assertNotNullOrUndefined(content, 'content');
+ return await todoistRestClient.tasks.create({
+ token,
+ project_id,
+ content,
+ description,
+ labels,
+ priority,
+ due_date,
+ section_id,
+ });
+ },
});
diff --git a/packages/pieces/community/todoist/src/lib/actions/find-task.action.ts b/packages/pieces/community/todoist/src/lib/actions/find-task.action.ts
new file mode 100644
index 0000000000..e7cf8a84ba
--- /dev/null
+++ b/packages/pieces/community/todoist/src/lib/actions/find-task.action.ts
@@ -0,0 +1,36 @@
+import { todoistAuth } from '../..';
+import { createAction, Property } from '@activepieces/pieces-framework';
+import { todoistProjectIdDropdown } from '../common/props';
+import { todoistRestClient } from '../common/client/rest-client';
+import { assertNotNullOrUndefined } from '@activepieces/shared';
+
+export const todoistFindTaskAction = createAction({
+ auth: todoistAuth,
+ name: 'find_task',
+ displayName: 'Find Task',
+ description: 'Finds a task by name.',
+ props: {
+ name: Property.ShortText({
+ displayName: 'Name',
+ description: 'The name of the task to search for.',
+ required: true,
+ }),
+ project_id: todoistProjectIdDropdown(
+ 'Search for tasks within the selected project. If left blank, then all projects are searched.',
+ ),
+ },
+ async run(context) {
+ const token = context.auth.access_token;
+ const { name, project_id } = context.propsValue;
+
+ assertNotNullOrUndefined(token, 'token');
+ const tasks = await todoistRestClient.tasks.list({ token, project_id });
+
+ const matchedTask = tasks.find((task) => task.content == name);
+ if (!matchedTask) {
+ throw new Error('Task not found');
+ } else {
+ return matchedTask;
+ }
+ },
+});
diff --git a/packages/pieces/community/todoist/src/lib/actions/mark-task-completed.action.ts b/packages/pieces/community/todoist/src/lib/actions/mark-task-completed.action.ts
new file mode 100644
index 0000000000..307ef05fa8
--- /dev/null
+++ b/packages/pieces/community/todoist/src/lib/actions/mark-task-completed.action.ts
@@ -0,0 +1,25 @@
+import { assertNotNullOrUndefined } from '@activepieces/shared';
+import { todoistAuth } from '../..';
+import { createAction, Property } from '@activepieces/pieces-framework';
+import { todoistRestClient } from '../common/client/rest-client';
+
+export const todoistMarkTaskCompletedAction = createAction({
+ auth: todoistAuth,
+ name: 'mark_task_completed',
+ displayName: 'Mark Task as Completed',
+ description: 'Marks a task as being completed.',
+ props: {
+ task_id: Property.ShortText({
+ displayName: 'Task ID',
+ required: true,
+ }),
+ },
+ async run(context) {
+ const token = context.auth.access_token;
+ const { task_id } = context.propsValue;
+
+ assertNotNullOrUndefined(token, 'token');
+
+ return await todoistRestClient.tasks.close({ token, task_id });
+ },
+});
diff --git a/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts b/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts
new file mode 100644
index 0000000000..5dced83b92
--- /dev/null
+++ b/packages/pieces/community/todoist/src/lib/actions/update-task.action.ts
@@ -0,0 +1,61 @@
+import { createAction, Property } from '@activepieces/pieces-framework';
+import { assertNotNullOrUndefined } from '@activepieces/shared';
+import { todoistRestClient } from '../common/client/rest-client';
+import { todoistAuth } from '../..';
+
+export const todoistUpdateTaskAction = createAction({
+ auth: todoistAuth,
+ name: 'update_task',
+ displayName: 'Update Task',
+ description: 'Updates an existing task.',
+ props: {
+ task_id: Property.ShortText({
+ displayName: 'Task ID',
+ required: true,
+ }),
+ content: Property.LongText({
+ displayName: 'content',
+ description: "The task's content. It may contain some markdown-formatted text and hyperlinks",
+ required: false,
+ }),
+ description: Property.LongText({
+ displayName: 'Description',
+ description:
+ 'A description for the task. This value may contain some markdown-formatted text and hyperlinks.',
+ required: false,
+ }),
+ labels: Property.Array({
+ displayName: 'Labels',
+ required: false,
+ description:
+ "The task's labels (a list of names that may represent either personal or shared labels)",
+ }),
+ priority: Property.Number({
+ displayName: 'Priority',
+ description: 'Task priority from 1 (normal) to 4 (urgent)',
+ required: false,
+ }),
+ due_date: Property.ShortText({
+ displayName: 'Due date',
+ description: "Specific date in YYYY-MM-DD format relative to user's timezone",
+ required: false,
+ }),
+ },
+
+ async run({ auth, propsValue }) {
+ const token = auth.access_token;
+ const { task_id, content, description, priority, due_date } = propsValue;
+ const labels = propsValue.labels as string[];
+
+ assertNotNullOrUndefined(token, 'token');
+ return await todoistRestClient.tasks.update({
+ token,
+ task_id,
+ content,
+ description,
+ labels,
+ priority,
+ due_date,
+ });
+ },
+});
diff --git a/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts b/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts
index 8e54bb1fe0..fdd6d4b411 100644
--- a/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts
+++ b/packages/pieces/community/todoist/src/lib/common/client/rest-client.ts
@@ -1,104 +1,166 @@
import {
- HttpRequest,
- HttpMethod,
- AuthenticationType,
- httpClient,
+ HttpRequest,
+ HttpMethod,
+ AuthenticationType,
+ httpClient,
} from '@activepieces/pieces-common';
import { isNotUndefined, pickBy } from '@activepieces/shared';
import {
- TodoistCreateTaskRequest,
- TodoistProject,
- TodoistTask,
+ TodoistCreateTaskRequest,
+ TodoistProject,
+ TodoistSection,
+ TodoistTask,
+ TodoistUpdateTaskRequest,
} from '../models';
const API = 'https://api.todoist.com/rest/v2';
export const todoistRestClient = {
- projects: {
- async list({ token }: ProjectsListParams): Promise {
- const request: HttpRequest = {
- method: HttpMethod.GET,
- url: `${API}/projects`,
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token,
- },
- };
-
- const response = await httpClient.sendRequest(request);
- return response.body;
- },
- },
-
- tasks: {
- async create({
- token,
- project_id,
- content,
- description,
- labels,
- priority,
- due_date,
- section_id
- }: TasksCreateParams): Promise {
- const request: HttpRequest = {
- method: HttpMethod.POST,
- url: `${API}/tasks`,
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token,
- },
- body: {
- content,
- project_id,
- description,
- labels,
- priority,
- due_date,
- section_id
- },
- };
-
- const response = await httpClient.sendRequest(request);
- return response.body;
- },
-
- async list({
- token,
- project_id,
- filter,
- }: TasksListParams): Promise {
- const queryParams = {
- filter,
- project_id,
- };
-
- const request: HttpRequest = {
- method: HttpMethod.GET,
- url: `${API}/tasks`,
- authentication: {
- type: AuthenticationType.BEARER_TOKEN,
- token,
- },
- queryParams: pickBy(queryParams, isNotUndefined),
- };
-
- const response = await httpClient.sendRequest(request);
- return response.body;
- },
- },
+ projects: {
+ async list({ token }: ProjectsListParams): Promise {
+ const request: HttpRequest = {
+ method: HttpMethod.GET,
+ url: `${API}/projects`,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token,
+ },
+ };
+
+ const response = await httpClient.sendRequest(request);
+ return response.body;
+ },
+ },
+
+ sections: {
+ async list(params: SectionsListPrams): Promise {
+ const qs: Record = {};
+ if (params.project_id) qs['project_id'] = params.project_id;
+
+ const request: HttpRequest = {
+ method: HttpMethod.GET,
+ url: `${API}/sections`,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token: params.token,
+ },
+ queryParams: qs,
+ };
+
+ const response = await httpClient.sendRequest(request);
+ return response.body;
+ },
+ },
+
+ tasks: {
+ async create({
+ token,
+ project_id,
+ content,
+ description,
+ labels,
+ priority,
+ due_date,
+ section_id,
+ }: TasksCreateParams): Promise {
+ const request: HttpRequest = {
+ method: HttpMethod.POST,
+ url: `${API}/tasks`,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token,
+ },
+ body: {
+ content,
+ project_id,
+ description,
+ labels,
+ priority,
+ due_date,
+ section_id,
+ },
+ };
+
+ const response = await httpClient.sendRequest(request);
+ return response.body;
+ },
+
+ async update(params: TasksUpdateParams): Promise {
+ const request: HttpRequest = {
+ method: HttpMethod.POST,
+ url: `${API}/tasks/${params.task_id}`,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token: params.token,
+ },
+ body: {
+ content: params.content,
+ description: params.description,
+ labels: params.labels?.length === 0 ? undefined : params.labels,
+ priority: params.priority,
+ due_date: params.due_date,
+ },
+ };
+
+ const response = await httpClient.sendRequest(request);
+ return response.body;
+ },
+
+ async list({ token, project_id, filter }: TasksListParams): Promise {
+ const queryParams = {
+ filter,
+ project_id,
+ };
+
+ const request: HttpRequest = {
+ method: HttpMethod.GET,
+ url: `${API}/tasks`,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token,
+ },
+ queryParams: pickBy(queryParams, isNotUndefined),
+ };
+
+ const response = await httpClient.sendRequest(request);
+ return response.body;
+ },
+
+ async close({ token, task_id }: { token: string; task_id: string }) {
+ const request: HttpRequest = {
+ method: HttpMethod.POST,
+ url: `${API}/tasks/${task_id}/close`,
+ authentication: {
+ type: AuthenticationType.BEARER_TOKEN,
+ token,
+ },
+ };
+
+ const response = await httpClient.sendRequest(request);
+ return response.body;
+ },
+ },
};
type ProjectsListParams = {
- token: string;
+ token: string;
+};
+
+type SectionsListPrams = {
+ token: string;
+ project_id?: string;
};
type TasksCreateParams = {
- token: string;
+ token: string;
} & TodoistCreateTaskRequest;
+type TasksUpdateParams = {
+ token: string;
+} & TodoistUpdateTaskRequest;
+
type TasksListParams = {
- token: string;
- project_id?: string | undefined;
- filter?: string | undefined;
+ token: string;
+ project_id?: string | undefined;
+ filter?: string | undefined;
};
diff --git a/packages/pieces/community/todoist/src/lib/common/models.ts b/packages/pieces/community/todoist/src/lib/common/models.ts
index 87c5e44cc8..c428ce1773 100644
--- a/packages/pieces/community/todoist/src/lib/common/models.ts
+++ b/packages/pieces/community/todoist/src/lib/common/models.ts
@@ -1,57 +1,73 @@
export type TodoistProject = {
- id: string;
- name: string;
+ id: string;
+ name: string;
+};
+
+export type TodoistSection = {
+ id: string;
+ name: string;
+ project_id: string;
+ order: number;
};
export type TodoistCreateTaskRequest = {
- content: string;
- project_id?: string | undefined;
- description?: string | undefined;
- labels?: Array | undefined;
- priority?: number | undefined;
- due_date?: string | undefined;
- section_id?: string | undefined;
+ content: string;
+ project_id?: string | undefined;
+ description?: string | undefined;
+ labels?: Array | undefined;
+ priority?: number | undefined;
+ due_date?: string | undefined;
+ section_id?: string | undefined;
+};
+
+export type TodoistUpdateTaskRequest = {
+ task_id: string;
+ content?: string;
+ description?: string;
+ labels?: Array;
+ priority?: number;
+ due_date?: string;
};
type TodoistTaskDue = {
- string: string;
- date: string;
- is_recurring: boolean;
- datetime?: string | undefined;
- timezone?: string | undefined;
+ string: string;
+ date: string;
+ is_recurring: boolean;
+ datetime?: string | undefined;
+ timezone?: string | undefined;
};
export type TodoistTask = {
- id: string;
- projectId: string | null;
- sectionId: string | null;
- content: string;
- description?: string | undefined;
- is_completed: boolean;
- labels: string[];
- parent_id: string | null;
- order: number;
- priority: number;
- due: TodoistTaskDue | null;
- url: string;
- comment_count: number;
- created_at: string;
- creator_id: string;
- assignee_id: string | null;
- assigner_id: string | null;
+ id: string;
+ projectId: string | null;
+ sectionId: string | null;
+ content: string;
+ description?: string | undefined;
+ is_completed: boolean;
+ labels: string[];
+ parent_id: string | null;
+ order: number;
+ priority: number;
+ due: TodoistTaskDue | null;
+ url: string;
+ comment_count: number;
+ created_at: string;
+ creator_id: string;
+ assignee_id: string | null;
+ assigner_id: string | null;
};
export type TodoistCompletedTask = {
- id: string;
- task_id: string;
- user_id: string;
- project_id: string;
- section_id: string;
- content: string;
- completed_at: string;
- note_count: number;
+ id: string;
+ task_id: string;
+ user_id: string;
+ project_id: string;
+ section_id: string;
+ content: string;
+ completed_at: string;
+ note_count: number;
};
export type TodoistCompletedListResponse = {
- items: TodoistCompletedTask[];
+ items: TodoistCompletedTask[];
};
diff --git a/packages/pieces/community/todoist/src/lib/common/props.ts b/packages/pieces/community/todoist/src/lib/common/props.ts
index 94973caf6a..0fc04a559f 100644
--- a/packages/pieces/community/todoist/src/lib/common/props.ts
+++ b/packages/pieces/community/todoist/src/lib/common/props.ts
@@ -2,42 +2,76 @@ import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
import { todoistRestClient } from './client/rest-client';
const buildEmptyList = ({ placeholder }: { placeholder: string }) => {
- return {
- disabled: true,
- options: [],
- placeholder,
- };
+ return {
+ disabled: true,
+ options: [],
+ placeholder,
+ };
};
-export const todoistProjectIdDropdown = Property.Dropdown({
- displayName: 'Project',
- refreshers: [],
- description: "Task project ID. If not set, task is put to user's Inbox.",
- required: false,
- options: async ({ auth }) => {
- if (!auth) {
- return buildEmptyList({
- placeholder: 'Please select an authentication',
- });
- }
-
- const token = (auth as OAuth2PropertyValue).access_token;
- const projects = await todoistRestClient.projects.list({ token });
-
- if (projects.length === 0) {
- return buildEmptyList({
- placeholder: 'No projects found! Please create a project.',
- });
- }
-
- const options = projects.map((p) => ({
- label: p.name,
- value: p.id,
- }));
-
- return {
- disabled: false,
- options,
- };
- },
+export const todoistProjectIdDropdown = (description: string) =>
+ Property.Dropdown({
+ displayName: 'Project',
+ refreshers: [],
+ description,
+ required: false,
+ options: async ({ auth }) => {
+ if (!auth) {
+ return buildEmptyList({
+ placeholder: 'Please select an authentication',
+ });
+ }
+
+ const token = (auth as OAuth2PropertyValue).access_token;
+ const projects = await todoistRestClient.projects.list({ token });
+
+ if (projects.length === 0) {
+ return buildEmptyList({
+ placeholder: 'No projects found! Please create a project.',
+ });
+ }
+
+ const options = projects.map((p) => ({
+ label: p.name,
+ value: p.id,
+ }));
+
+ return {
+ disabled: false,
+ options,
+ };
+ },
+ });
+
+export const todoistSectionIdDropdown = Property.Dropdown({
+ displayName: 'Section',
+ refreshers: ['project_id'],
+ required: false,
+ options: async ({ auth, project_id }) => {
+ if (!auth) {
+ return buildEmptyList({
+ placeholder: 'Please select an authentication',
+ });
+ }
+
+ const token = (auth as OAuth2PropertyValue).access_token;
+ const projectId = project_id as string | undefined;
+ const sections = await todoistRestClient.sections.list({ token, project_id: projectId });
+
+ if (sections.length === 0) {
+ return buildEmptyList({
+ placeholder: 'No sections found! Please create a section.',
+ });
+ }
+
+ const options = sections.map((p) => ({
+ label: p.name,
+ value: p.id,
+ }));
+
+ return {
+ disabled: false,
+ options,
+ };
+ },
});
diff --git a/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts b/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts
index fddb9883ec..e6b058e623 100644
--- a/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts
+++ b/packages/pieces/community/todoist/src/lib/triggers/task-completed-trigger.ts
@@ -7,7 +7,7 @@ import { todoistProjectIdDropdown } from '../common/props';
import { todoistAuth } from '../..';
type TriggerData = {
- lastChecked: string;
+ lastChecked: string;
};
const TRIGGER_DATA_STORE_KEY = 'todoist_task_completed_trigger_data';
@@ -17,60 +17,62 @@ const fiveMinutesAgo = () => dayjs().subtract(5, 'minutes').format(ISO_FORMAT);
const now = () => dayjs().format(ISO_FORMAT);
export const todoistTaskCompletedTrigger = createTrigger({
- auth: todoistAuth,
- name: 'task_completed',
- displayName: 'Task Completed',
- description: 'Triggers when a new task is completed',
- type: TriggerStrategy.POLLING,
+ auth: todoistAuth,
+ name: 'task_completed',
+ displayName: 'Task Completed',
+ description: 'Triggers when a new task is completed',
+ type: TriggerStrategy.POLLING,
- sampleData: {
- content: 'Buy Milk',
- meta_data: null,
- user_id: '2671355',
- task_id: '2995104339',
- note_count: 0,
- project_id: '2203306141',
- section_id: '7025',
- completed_at: '2015-02-17T15:40:41.000000Z',
- id: '1899066186',
- },
+ sampleData: {
+ content: 'Buy Milk',
+ meta_data: null,
+ user_id: '2671355',
+ task_id: '2995104339',
+ note_count: 0,
+ project_id: '2203306141',
+ section_id: '7025',
+ completed_at: '2015-02-17T15:40:41.000000Z',
+ id: '1899066186',
+ },
- props: {
- project_id: todoistProjectIdDropdown,
- },
+ props: {
+ project_id: todoistProjectIdDropdown(
+ 'Leave it blank if you want to get completed tasks from all your projects.',
+ ),
+ },
- async onEnable({ store }): Promise {
- await store.put(TRIGGER_DATA_STORE_KEY, {
- lastChecked: now(),
- });
- },
+ async onEnable({ store }): Promise {
+ await store.put(TRIGGER_DATA_STORE_KEY, {
+ lastChecked: now(),
+ });
+ },
- async onDisable({ store }): Promise {
- await store.put(TRIGGER_DATA_STORE_KEY, null);
- },
+ async onDisable({ store }): Promise {
+ await store.put(TRIGGER_DATA_STORE_KEY, null);
+ },
- async run({ auth, propsValue, store }): Promise {
- const token = auth.access_token;
- const { project_id } = propsValue;
+ async run({ auth, propsValue, store }): Promise {
+ const token = auth.access_token;
+ const { project_id } = propsValue;
- assertNotNullOrUndefined(token, 'token');
+ assertNotNullOrUndefined(token, 'token');
- const triggerData = await store.get(TRIGGER_DATA_STORE_KEY);
- const since = triggerData?.lastChecked ?? fiveMinutesAgo();
- const until = now();
+ const triggerData = await store.get(TRIGGER_DATA_STORE_KEY);
+ const since = triggerData?.lastChecked ?? fiveMinutesAgo();
+ const until = now();
- const response = await todoistSyncClient.completed.list({
- token,
- since,
- until,
- project_id,
- });
+ const response = await todoistSyncClient.completed.list({
+ token,
+ since,
+ until,
+ project_id,
+ });
- await store.put(TRIGGER_DATA_STORE_KEY, {
- // It returns data newer than the since parameter, and not equal.
- lastChecked: until,
- });
+ await store.put(TRIGGER_DATA_STORE_KEY, {
+ // It returns data newer than the since parameter, and not equal.
+ lastChecked: until,
+ });
- return response.items;
- },
+ return response.items;
+ },
});
diff --git a/packages/pieces/community/trello/package.json b/packages/pieces/community/trello/package.json
index 3d2166e726..6450e725bd 100644
--- a/packages/pieces/community/trello/package.json
+++ b/packages/pieces/community/trello/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-trello",
- "version": "0.3.5"
+ "version": "0.3.6"
}
\ No newline at end of file
diff --git a/packages/pieces/community/trello/src/lib/actions/get-card.ts b/packages/pieces/community/trello/src/lib/actions/get-card.ts
index 62a173b6d9..10980e9612 100644
--- a/packages/pieces/community/trello/src/lib/actions/get-card.ts
+++ b/packages/pieces/community/trello/src/lib/actions/get-card.ts
@@ -34,8 +34,6 @@ export const getCard = createAction({
headers: {
Accept: 'application/json',
},
- body: {},
- queryParams: {},
};
return (await httpClient.sendRequest(request)).body;
},
diff --git a/packages/pieces/community/trello/src/lib/common/index.ts b/packages/pieces/community/trello/src/lib/common/index.ts
index af1e83b37a..3316d0c7be 100644
--- a/packages/pieces/community/trello/src/lib/common/index.ts
+++ b/packages/pieces/community/trello/src/lib/common/index.ts
@@ -184,8 +184,6 @@ async function getAuthorisedUser(apikey: string, token: string) {
headers: {
Accept: 'application/json',
},
- body: {},
- queryParams: {},
};
const response = await httpClient.sendRequest(request);
@@ -211,8 +209,6 @@ async function listBoards(apikey: string, token: string, user_id: string) {
headers: {
Accept: 'application/json',
},
- body: {},
- queryParams: {},
};
const response = await httpClient.sendRequest<{ id: string; name: string }[]>(
request
@@ -240,8 +236,6 @@ async function listBoardLists(apikey: string, token: string, board_id: string) {
headers: {
Accept: 'application/json',
},
- body: {},
- queryParams: {},
};
const response = await httpClient.sendRequest<{ id: string; name: string }[]>(
request
diff --git a/packages/pieces/community/webhook/package.json b/packages/pieces/community/webhook/package.json
index 2cb8dfae35..63f73a93d1 100644
--- a/packages/pieces/community/webhook/package.json
+++ b/packages/pieces/community/webhook/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-webhook",
- "version": "0.1.2"
-}
\ No newline at end of file
+ "version": "0.1.3"
+}
diff --git a/packages/pieces/community/webhook/src/index.ts b/packages/pieces/community/webhook/src/index.ts
index cfc6e926d7..3a62c1952a 100644
--- a/packages/pieces/community/webhook/src/index.ts
+++ b/packages/pieces/community/webhook/src/index.ts
@@ -7,7 +7,7 @@ export const webhook = createPiece({
description: 'Receive HTTP requests and trigger flows using unique URLs.',
auth: PieceAuth.None(),
categories: [PieceCategory.CORE],
- minimumSupportedRelease: '0.22.0',
+ minimumSupportedRelease: '0.26.0',
logoUrl: 'https://cdn.activepieces.com/pieces/webhook.svg',
authors: ['abuaboud', 'pfernandez98'],
actions: [],
diff --git a/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts b/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts
index 4c413d1a38..a4a4128d0c 100644
--- a/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts
+++ b/packages/pieces/community/webhook/src/lib/triggers/catch-hook.ts
@@ -7,17 +7,21 @@ import {
import { assertNotNullOrUndefined } from '@activepieces/shared';
const message = `
-URL:
+**Production URL:**
\`\`\`text
{{webhookUrl}}
\`\`\`
-If you are expecting a reply from this webhook, append **/sync** to the URL.
-
-In that case, you will also have to add an HTTP step with **return response** at the end of your flow.
+**Testing URL:**
+\`\`\`text
+{{webhookUrl}}/test
+\`\`\`
+***Use this URL for testing the webhook and saving sample data. It won't start the flow***.
-If the flow takes more than **30 seconds**, it will give a **408 Request Timeout** response.
+**Notes:**
+- If you are expecting a reply from this webhook, append **/sync** to the URL in that case, you will also have to add an HTTP step with **return response** at the end of your flow.
+- If the flow takes more than **30 seconds**, it will give a **408 Request Timeout** response.
`;
enum AuthType {
diff --git a/packages/pieces/community/whatsapp/.eslintrc.json b/packages/pieces/community/whatsapp/.eslintrc.json
new file mode 100644
index 0000000000..4a4e695c54
--- /dev/null
+++ b/packages/pieces/community/whatsapp/.eslintrc.json
@@ -0,0 +1,33 @@
+{
+ "extends": [
+ "../../../../.eslintrc.base.json"
+ ],
+ "ignorePatterns": [
+ "!**/*"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "*.ts",
+ "*.tsx",
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.ts",
+ "*.tsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/pieces/community/whatsapp/README.md b/packages/pieces/community/whatsapp/README.md
new file mode 100644
index 0000000000..85a4a45abf
--- /dev/null
+++ b/packages/pieces/community/whatsapp/README.md
@@ -0,0 +1,7 @@
+# pieces-whatsapp
+
+This library was generated with [Nx](https://nx.dev).
+
+## Building
+
+Run `nx build pieces-whatsapp` to build the library.
diff --git a/packages/pieces/community/whatsapp/package.json b/packages/pieces/community/whatsapp/package.json
new file mode 100644
index 0000000000..7ddc589990
--- /dev/null
+++ b/packages/pieces/community/whatsapp/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "@activepieces/piece-whatsapp",
+ "version": "0.0.1"
+}
diff --git a/packages/pieces/community/whatsapp/project.json b/packages/pieces/community/whatsapp/project.json
new file mode 100644
index 0000000000..adad8436e1
--- /dev/null
+++ b/packages/pieces/community/whatsapp/project.json
@@ -0,0 +1,38 @@
+{
+ "name": "pieces-whatsapp",
+ "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "packages/pieces/community/whatsapp/src",
+ "projectType": "library",
+ "targets": {
+ "build": {
+ "executor": "@nx/js:tsc",
+ "outputs": [
+ "{options.outputPath}"
+ ],
+ "options": {
+ "outputPath": "dist/packages/pieces/community/whatsapp",
+ "tsConfig": "packages/pieces/community/whatsapp/tsconfig.lib.json",
+ "packageJson": "packages/pieces/community/whatsapp/package.json",
+ "main": "packages/pieces/community/whatsapp/src/index.ts",
+ "assets": [
+ "packages/pieces/community/whatsapp/*.md"
+ ],
+ "buildableProjectDepsInPackageJsonType": "dependencies",
+ "updateBuildableProjectDepsInPackageJson": true
+ }
+ },
+ "publish": {
+ "command": "node tools/scripts/publish.mjs pieces-whatsapp {args.ver} {args.tag}",
+ "dependsOn": [
+ "build"
+ ]
+ },
+ "lint": {
+ "executor": "@nx/eslint:lint",
+ "outputs": [
+ "{options.outputFile}"
+ ]
+ }
+ },
+ "tags": []
+}
\ No newline at end of file
diff --git a/packages/pieces/community/whatsapp/src/index.ts b/packages/pieces/community/whatsapp/src/index.ts
new file mode 100644
index 0000000000..a856dea7e3
--- /dev/null
+++ b/packages/pieces/community/whatsapp/src/index.ts
@@ -0,0 +1,47 @@
+
+import { createPiece, PieceAuth, Property } from "@activepieces/pieces-framework";
+import { sendMessage } from "./lib/actions/send-message";
+import { sendMedia } from "./lib/actions/send-media";
+
+const markdown = `
+To Obtain a Phone Number ID and a Permanent System User Access Token, follow these steps:
+
+1. Go to https://developers.facebook.com/
+2. Make a new app, Select Other for usecase.
+3. Choose Business as the type of app.
+4. Add new Product -> WhatsApp.
+5. Navigate to WhatsApp Settings > API Setup.
+6. Select a phone number and copy its Phone number ID.
+7. Login to your [Meta Business Manager](https://business.facebook.com/).
+8. Click on Settings.
+9. Create a new System User with access over the app and copy the access token.
+`;
+
+export const whatsappAuth = PieceAuth.CustomAuth({
+ required: true,
+ description: markdown,
+ props: {
+ access_token: PieceAuth.SecretText({
+ displayName: 'System User Access Token',
+ description: 'The system user access token of your WhatsApp business account',
+ required: true,
+ }),
+ phoneNumberId: Property.ShortText({
+ displayName: 'Phone Number ID',
+ description: 'The phone number ID of your WhatsApp business account',
+ required: true,
+ }),
+ },
+});
+
+
+export const whatsapp = createPiece({
+ displayName: "WhatsApp Business",
+ description: 'Manage your WhatsApp business account',
+ auth: whatsappAuth,
+ minimumSupportedRelease: '0.20.0',
+ logoUrl: "https://cdn.activepieces.com/pieces/whatsapp.png",
+ authors: ["LevwTech"],
+ actions: [sendMessage, sendMedia],
+ triggers: [],
+});
diff --git a/packages/pieces/community/whatsapp/src/lib/actions/send-media.ts b/packages/pieces/community/whatsapp/src/lib/actions/send-media.ts
new file mode 100644
index 0000000000..a200ccfacf
--- /dev/null
+++ b/packages/pieces/community/whatsapp/src/lib/actions/send-media.ts
@@ -0,0 +1,70 @@
+import { createAction, Property } from '@activepieces/pieces-framework';
+import { httpClient, HttpMethod } from '@activepieces/pieces-common';
+import { whatsappAuth } from '../..';
+import { supportedMediaTypes, capitalizeFirstLetter, mediaTypeSupportsCaption } from '../common/utils';
+
+export const sendMedia = createAction({
+ auth: whatsappAuth,
+ name: 'sendMedia',
+ displayName: 'Send Media',
+ description: 'Send a media message through WhatsApp',
+ props: {
+ to: Property.ShortText({
+ displayName: 'To',
+ description: 'The recipient of the message',
+ required: true,
+ }),
+ type: Property.Dropdown({
+ displayName: 'Type',
+ description: 'The type of media to send',
+ required: true,
+ options: async () => {
+ return {
+ options: supportedMediaTypes.map((type) => ({
+ label: capitalizeFirstLetter(type),
+ value: type,
+ })),
+ };
+ },
+ refreshers: []
+ }),
+ media: Property.ShortText({
+ displayName: 'Media URL',
+ description: 'The URL of the media to send',
+ required: true,
+ }),
+ caption: Property.LongText({
+ displayName: 'Caption',
+ description: 'A caption for the media',
+ required: false,
+ }),
+ filename: Property.LongText({
+ displayName: 'Filename',
+ description: 'Filename of the document to send',
+ required: false,
+ }),
+ },
+ async run(context) {
+ const { to, caption, media, type, filename } = context.propsValue;
+ const { access_token, phoneNumberId } = context.auth;
+ const body = {
+ messaging_product: "whatsapp",
+ recipient_type: "individual",
+ to,
+ type,
+ [type]: {
+ link: media,
+ }
+ }
+ if (caption && mediaTypeSupportsCaption(type)) (body[type] as any).caption = caption;
+ if (filename && type === 'document') (body[type] as any).filename = filename;
+ return await httpClient.sendRequest({
+ method: HttpMethod.POST,
+ url: `https://graph.facebook.com/v17.0/${phoneNumberId}/messages`,
+ headers: {
+ Authorization: 'Bearer ' + access_token,
+ },
+ body,
+ });
+ },
+});
\ No newline at end of file
diff --git a/packages/pieces/community/whatsapp/src/lib/actions/send-message.ts b/packages/pieces/community/whatsapp/src/lib/actions/send-message.ts
new file mode 100644
index 0000000000..2aba8ac46a
--- /dev/null
+++ b/packages/pieces/community/whatsapp/src/lib/actions/send-message.ts
@@ -0,0 +1,43 @@
+import { createAction, Property } from '@activepieces/pieces-framework';
+import { httpClient, HttpMethod } from '@activepieces/pieces-common';
+import { whatsappAuth } from '../..';
+
+
+export const sendMessage = createAction({
+ auth: whatsappAuth,
+ name: 'sendMessage',
+ displayName: 'Send Message',
+ description: 'Send a text message through WhatsApp',
+ props: {
+ to: Property.ShortText({
+ displayName: 'To',
+ description: 'The recipient of the message',
+ required: true,
+ }),
+ text: Property.LongText({
+ displayName: 'Message',
+ description: 'The message to send',
+ required: true,
+ }),
+ },
+ async run(context) {
+ const { to, text } = context.propsValue;
+ const { access_token, phoneNumberId } = context.auth;
+ return await httpClient.sendRequest({
+ method: HttpMethod.POST,
+ url: `https://graph.facebook.com/v17.0/${phoneNumberId}/messages`,
+ headers: {
+ Authorization: 'Bearer ' + access_token,
+ },
+ body: {
+ messaging_product: "whatsapp",
+ recipient_type: "individual",
+ to,
+ type: "text",
+ text: {
+ body: text
+ }
+ }
+ });
+ },
+});
diff --git a/packages/pieces/community/whatsapp/src/lib/common/utils.ts b/packages/pieces/community/whatsapp/src/lib/common/utils.ts
new file mode 100644
index 0000000000..1e327b8d8a
--- /dev/null
+++ b/packages/pieces/community/whatsapp/src/lib/common/utils.ts
@@ -0,0 +1,3 @@
+export const supportedMediaTypes = ['image', 'audio', 'document', 'sticker', 'video'];
+export const capitalizeFirstLetter = (word: string) => word.charAt(0).toUpperCase() + word.slice(1);
+export const mediaTypeSupportsCaption = (type: string) => ['image', 'video', 'document'].includes(type);
\ No newline at end of file
diff --git a/packages/pieces/community/whatsapp/tsconfig.json b/packages/pieces/community/whatsapp/tsconfig.json
new file mode 100644
index 0000000000..059cd81661
--- /dev/null
+++ b/packages/pieces/community/whatsapp/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/whatsapp/tsconfig.lib.json b/packages/pieces/community/whatsapp/tsconfig.lib.json
new file mode 100644
index 0000000000..0fe2205530
--- /dev/null
+++ b/packages/pieces/community/whatsapp/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", "src/lib/actions/common/utils.ts"]
+}
diff --git a/packages/pieces/community/wordpress/package.json b/packages/pieces/community/wordpress/package.json
index 340d897c2f..d219e9f0e3 100644
--- a/packages/pieces/community/wordpress/package.json
+++ b/packages/pieces/community/wordpress/package.json
@@ -1,4 +1,4 @@
{
"name": "@activepieces/piece-wordpress",
- "version": "0.3.13"
+ "version": "0.3.14"
}
\ No newline at end of file
diff --git a/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts b/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts
index 6fd5849c1f..d14ca4f8c9 100644
--- a/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts
+++ b/packages/pieces/community/wordpress/src/lib/actions/create-post.action.ts
@@ -39,6 +39,12 @@ export const createWordPressPost = createAction({
}),
featured_media_file: wordpressCommon.featured_media_file,
tags: wordpressCommon.tags,
+ acfFields: Property.Object({
+ displayName: 'Custom ACF fields',
+ description:
+ 'Provide field name with value.You can find out field name from ACF plugin menu.',
+ required: false,
+ }),
categories: wordpressCommon.categories,
featured_media: wordpressCommon.featured_media,
status: wordpressCommon.status,
@@ -93,6 +99,13 @@ export const createWordPressPost = createAction({
requestBody['featured_media'] = context.propsValue.featured_media;
}
+ if (
+ context.propsValue.acfFields &&
+ Object.keys(context.propsValue.acfFields).length > 0
+ ) {
+ requestBody['acf'] = context.propsValue.acfFields;
+ }
+
if (context.propsValue.featured_media_file) {
const formData = new FormData();
const { filename, base64 } = context.propsValue.featured_media_file;
diff --git a/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts b/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts
index 2cb42a7832..eeb89a55a0 100644
--- a/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts
+++ b/packages/pieces/community/wordpress/src/lib/actions/update-post.action.ts
@@ -36,6 +36,12 @@ export const updateWordPressPost = createAction({
}),
featured_media_file: wordpressCommon.featured_media_file,
tags: wordpressCommon.tags,
+ acfFields: Property.Object({
+ displayName: 'Custom ACF fields',
+ description:
+ 'Provide field name with value.You can find out field name from ACF plugin menu.',
+ required: false,
+ }),
categories: wordpressCommon.categories,
featured_media: wordpressCommon.featured_media,
status: wordpressCommon.status,
@@ -99,6 +105,13 @@ export const updateWordPressPost = createAction({
requestBody['featured_media'] = context.propsValue.featured_media;
}
+ if (
+ context.propsValue.acfFields &&
+ Object.keys(context.propsValue.acfFields).length > 0
+ ) {
+ requestBody['acf'] = context.propsValue.acfFields;
+ }
+
if (context.propsValue.featured_media_file) {
const formData = new FormData();
const { filename, base64 } = context.propsValue.featured_media_file;
diff --git a/packages/server/api/src/app/app-event-routing/app-event-routing.module.ts b/packages/server/api/src/app/app-event-routing/app-event-routing.module.ts
index fc968cd194..3b50004bac 100644
--- a/packages/server/api/src/app/app-event-routing/app-event-routing.module.ts
+++ b/packages/server/api/src/app/app-event-routing/app-event-routing.module.ts
@@ -9,7 +9,8 @@ import { slack } from '@activepieces/piece-slack'
import { square } from '@activepieces/piece-square'
import { Piece } from '@activepieces/pieces-framework'
import { logger, rejectedPromiseHandler } from '@activepieces/server-shared'
-import { ActivepiecesError, ALL_PRINCIPAL_TYPES,
+import {
+ ActivepiecesError, ALL_PRINCIPAL_TYPES,
ErrorCode,
EventPayload,
isNil,
diff --git a/packages/server/api/src/app/core/security/authz/project-authz-handler.ts b/packages/server/api/src/app/core/security/authz/project-authz-handler.ts
index d4cb817995..a351d13b0f 100644
--- a/packages/server/api/src/app/core/security/authz/project-authz-handler.ts
+++ b/packages/server/api/src/app/core/security/authz/project-authz-handler.ts
@@ -11,7 +11,7 @@ export class ProjectAuthzHandler extends BaseSecurityHandler {
'/v1/users/projects/:projectId/token',
'/v1/webhooks',
'/v1/webhooks/:flowId',
- '/v1/webhooks/:flowId/simulate',
+ '/v1/webhooks/:flowId/test',
'/v1/webhooks/:flowId/sync',
]
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 0282e52894..a60db820c0 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
@@ -78,6 +78,31 @@ async function updateFlowRunToLatestFlowVersionId(
})
}
+function returnHandlerId(pauseMetadata: PauseMetadata | undefined, requestId: string | undefined): string {
+ const handlerId = engineResponseWatcher.getHandlerId()
+ if (isNil(pauseMetadata)) {
+ return handlerId
+ }
+
+ if (pauseMetadata.type === PauseType.WEBHOOK && requestId === pauseMetadata.requestId && pauseMetadata.handlerId) {
+ return pauseMetadata.handlerId
+ }
+ else {
+ return handlerId
+ }
+}
+
+function modifyPauseMetadata(pauseMetadata: PauseMetadata): PauseMetadata {
+ if (pauseMetadata.type === PauseType.WEBHOOK) {
+ return {
+ ...pauseMetadata,
+ handlerId: engineResponseWatcher.getHandlerId(),
+ }
+ }
+
+ return pauseMetadata
+}
+
export const flowRunService = {
async list({
projectId,
@@ -173,6 +198,8 @@ export const flowRunService = {
flowRunId: flowRunToResume.id,
projectId: flowRunToResume.projectId,
flowVersionId: flowRunToResume.flowVersionId,
+ synchronousHandlerId: returnHandlerId(pauseMetadata, requestId),
+ hookType: HookType.AFTER_LOG,
executionType,
environment: RunEnvironment.PRODUCTION,
})
@@ -292,7 +319,7 @@ export const flowRunService = {
status: FlowRunStatus.PAUSED,
logsFileId: logFileId,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- pauseMetadata: pauseMetadata as any,
+ pauseMetadata: modifyPauseMetadata(pauseMetadata) as any,
})
const flowRun = await flowRunRepo.findOneByOrFail({ id: flowRunId })
diff --git a/packages/server/api/src/app/flows/flow.module.ts b/packages/server/api/src/app/flows/flow.module.ts
index d913879d9b..d7bda432ec 100644
--- a/packages/server/api/src/app/flows/flow.module.ts
+++ b/packages/server/api/src/app/flows/flow.module.ts
@@ -9,7 +9,7 @@ import { folderController } from './folder/folder.controller'
import { stepRunService } from './step-run/step-run-service'
import { testTriggerController } from './test-trigger/test-trigger-controller'
import { logger } from '@activepieces/server-shared'
-import { CreateStepRunRequestBody, StepRunResponse, TestFlowRunRequestBody, WebsocketClientEvent, WebsocketServerEvent } from '@activepieces/shared'
+import { CreateStepRunRequestBody, isFlowStateTerminal, StepRunResponse, TestFlowRunRequestBody, WebsocketClientEvent, WebsocketServerEvent } from '@activepieces/shared'
export const flowModule: FastifyPluginAsyncTypebox = async (app) => {
await app.register(flowVersionController, { prefix: '/v1/flows' })
@@ -23,9 +23,21 @@ export const flowModule: FastifyPluginAsyncTypebox = async (app) => {
projectId: principal.projectId,
flowVersionId: data.flowVersionId,
})
+
socket.emit(WebsocketClientEvent.TEST_FLOW_RUN_STARTED, flowRun)
- await engineResponseWatcher.listen(flowRun.id, false)
- socket.emit(WebsocketClientEvent.TEST_FLOW_RUN_FINISHED, flowRun)
+ const eventEmitter = engineResponseWatcher.listen(flowRun.id)
+ eventEmitter.on(async (data) => {
+ const flowRun = await flowRunService.getOneOrThrow({
+ id: data.requestId,
+ projectId: principal.projectId,
+ })
+
+ if (isFlowStateTerminal(flowRun.status)) {
+ engineResponseWatcher.removeListener(flowRun.id)
+ }
+ socket.emit(WebsocketClientEvent.TEST_FLOW_RUN_PROGRESS, flowRun)
+ })
+
}
})
websocketService.addListener(WebsocketServerEvent.TEST_STEP_RUN, (socket) => {
diff --git a/packages/server/api/src/app/webhooks/webhook-controller.ts b/packages/server/api/src/app/webhooks/webhook-controller.ts
index 228d377fa0..7abe7f1041 100644
--- a/packages/server/api/src/app/webhooks/webhook-controller.ts
+++ b/packages/server/api/src/app/webhooks/webhook-controller.ts
@@ -65,7 +65,7 @@ export const webhookController: FastifyPluginAsyncTypebox = async (app) => {
.send(response.body)
})
- app.all('/:flowId/simulate', WEBHOOK_PARAMS, async (request, reply) => {
+ app.all('/:flowId/test', WEBHOOK_PARAMS, async (request, reply) => {
const response = await handleWebhook({
request,
flowId: request.params.flowId,
@@ -104,7 +104,7 @@ async function handleWebhook({ request, flowId, async, simulate }: { request: Fa
headers: {},
}
}
- return engineResponseWatcher.listen(requestId, true)
+ return engineResponseWatcher.oneTimeListener(requestId, true)
}
async function convertRequest(request: FastifyRequest): Promise {
diff --git a/packages/server/api/src/app/webhooks/webhook-service.ts b/packages/server/api/src/app/webhooks/webhook-service.ts
index 8c28f79133..e9e52342c5 100644
--- a/packages/server/api/src/app/webhooks/webhook-service.ts
+++ b/packages/server/api/src/app/webhooks/webhook-service.ts
@@ -1,4 +1,3 @@
-import { flowService } from '../flows/flow/flow.service'
import { flowRunService, HookType } from '../flows/flow-run/flow-run-service'
import { flowVersionService } from '../flows/flow-version/flow-version.service'
import { triggerHooks } from '../flows/trigger'
@@ -66,32 +65,16 @@ export const webhookService = {
const { projectId } = flow
- if (isNil(flow.publishedVersionId)) {
+ if (flow.status !== FlowStatus.ENABLED || isNil(flow.publishedVersionId)) {
logger.info(
- `[WebhookService#callback] flowInstance not found, flowId=${flow.id}`,
+ `[WebhookService#callback] flowInstance not found or not enabled ignoring the webhook, flowId=${flow.id}`,
)
- const flowVersion = (
- await flowService.getOnePopulatedOrThrow({
- projectId,
+ throw new ActivepiecesError({
+ code: ErrorCode.FLOW_NOT_FOUND,
+ params: {
id: flow.id,
- })
- ).version
- const payloads: unknown[] = await triggerHooks.executeTrigger({
- projectId,
- flowVersion,
- payload,
- simulate: false,
+ },
})
- payloads.forEach((resultPayload) => {
- saveSampleDataForWebhookTesting(flow, resultPayload)
- })
- return []
- }
- if (flow.status !== FlowStatus.ENABLED) {
- logger.info(
- `[WebhookService#callback] flowInstance not found or not enabled ignoring the webhook, flowId=${flow.id}`,
- )
- return []
}
const flowVersion = await flowVersionService.getOneOrThrow(
@@ -104,20 +87,20 @@ export const webhookService = {
simulate: false,
})
- payloads.forEach((payload) => {
- triggerEventService
- .saveEvent({
- flowId: flow.id,
- payload,
- projectId,
- })
- .catch((e) =>
- logger.error(
- e,
- '[WebhookService#callback] triggerEventService.saveEvent',
- ),
- )
- })
+ const savePayloads = payloads.map((payload) =>
+ triggerEventService.saveEvent({
+ flowId: flow.id,
+ payload,
+ projectId,
+ }).catch((e) =>
+ logger.error(
+ e,
+ '[WebhookService#callback] triggerEventService.saveEvent',
+ ),
+ ),
+ )
+
+ await Promise.all(savePayloads)
const filterPayloads = await dedupeService.filterUniquePayloads(
flowVersion.id,
@@ -180,7 +163,7 @@ export const webhookService = {
flowId,
simulate,
}: GetWebhookUrlParams): Promise {
- const suffix: WebhookUrlSuffix = simulate ? '/simulate' : ''
+ const suffix: WebhookUrlSuffix = simulate ? '/test' : ''
const webhookPrefix = await this.getWebhookPrefix()
return `${webhookPrefix}/${flowId}${suffix}`
},
@@ -211,22 +194,7 @@ const getLatestFlowVersionOrThrow = async (
return flowVersion
}
-function saveSampleDataForWebhookTesting(flow: Flow, payload: unknown): void {
- triggerEventService
- .saveEvent({
- flowId: flow.id,
- payload,
- projectId: flow.projectId,
- })
- .catch((e) =>
- logger.error(
- e,
- '[WebhookService#saveSampleDataForWebhookTesting] triggerEventService.saveEvent',
- ),
- )
-}
-
-type WebhookUrlSuffix = '' | '/simulate'
+type WebhookUrlSuffix = '' | '/test'
type GetWebhookUrlParams = {
flowId: FlowId
diff --git a/packages/server/api/src/app/webhooks/webhook-simulation/webhook-simulation-service.ts b/packages/server/api/src/app/webhooks/webhook-simulation/webhook-simulation-service.ts
index 4e4dc92e83..61dfd76872 100644
--- a/packages/server/api/src/app/webhooks/webhook-simulation/webhook-simulation-service.ts
+++ b/packages/server/api/src/app/webhooks/webhook-simulation/webhook-simulation-service.ts
@@ -49,7 +49,7 @@ export const webhookSimulationService = {
})
try {
- const webhookSimulationExists = await webhookSimulationRepo.exist({
+ const webhookSimulationExists = await webhookSimulationRepo.exists({
where: { flowId },
})
diff --git a/packages/server/api/src/app/workers/flow-worker/consumer/webook-consumer.ts b/packages/server/api/src/app/workers/flow-worker/consumer/webook-consumer.ts
index 96eddd9757..5852cb8c2b 100644
--- a/packages/server/api/src/app/workers/flow-worker/consumer/webook-consumer.ts
+++ b/packages/server/api/src/app/workers/flow-worker/consumer/webook-consumer.ts
@@ -3,7 +3,7 @@ import { flowService } from '../../../flows/flow/flow.service'
import { webhookService } from '../../../webhooks/webhook-service'
import { EngineHttpResponse, engineResponseWatcher } from '../engine-response-watcher'
import { WebhookJobData } from '../job-data'
-import { isNil } from '@activepieces/shared'
+import { FlowStatus, isNil } from '@activepieces/shared'
export const webhookConsumer = {
async consumeWebhook(data: WebhookJobData): Promise {
@@ -17,6 +17,15 @@ export const webhookConsumer = {
})
return
}
+ const isPublishedAndEnabled = (flow.status !== FlowStatus.ENABLED || isNil(flow.publishedVersionId)) && !simulate
+ if (isPublishedAndEnabled) {
+ await stopAndReply(data, {
+ status: StatusCodes.NOT_FOUND,
+ body: {},
+ headers: {},
+ })
+ return
+ }
const handshakeResponse = await webhookService.handshake({
flow,
payload,
@@ -51,7 +60,7 @@ export const webhookConsumer = {
return
}
const firstRun = runs[0]
- const response = await engineResponseWatcher.listen(firstRun.id, true)
+ const response = await engineResponseWatcher.oneTimeListener(firstRun.id, true)
await stopAndReply(data, response)
},
diff --git a/packages/server/api/src/app/workers/flow-worker/engine-response-watcher.ts b/packages/server/api/src/app/workers/flow-worker/engine-response-watcher.ts
index aae19a1b6e..2975cc1afb 100644
--- a/packages/server/api/src/app/workers/flow-worker/engine-response-watcher.ts
+++ b/packages/server/api/src/app/workers/flow-worker/engine-response-watcher.ts
@@ -1,10 +1,10 @@
import { logger } from '@sentry/utils'
import { StatusCodes } from 'http-status-codes'
import { pubSub } from '../../helper/pubsub'
-import { system, SystemProp } from '@activepieces/server-shared'
+import { system, SystemProp, TypedEventEmitter } from '@activepieces/server-shared'
import { apId } from '@activepieces/shared'
-const listeners = new Map void>()
+const listeners = new Map void>()
export type EngineHttpResponse = {
status: number
@@ -12,7 +12,7 @@ export type EngineHttpResponse = {
headers: Record
}
-type EngineResponseWithId = {
+export type EngineResponseWithId = {
requestId: string
httpResponse: EngineHttpResponse
}
@@ -24,6 +24,9 @@ export const engineResponseWatcher = {
getHandlerId(): string {
return HANDLER_ID
},
+ removeListener(requestId: string): void {
+ listeners.delete(requestId)
+ },
async init(): Promise {
logger.info('[engineWatcher#init] Initializing engine run watcher')
@@ -33,8 +36,7 @@ export const engineResponseWatcher = {
const parsedMessasge: EngineResponseWithId = JSON.parse(message)
const listener = listeners.get(parsedMessasge.requestId)
if (listener) {
- listener(parsedMessasge.httpResponse)
- listeners.delete(parsedMessasge.requestId)
+ listener(parsedMessasge)
}
logger.info(
`[engineWatcher#init] message=${parsedMessasge.requestId}`,
@@ -42,28 +44,37 @@ export const engineResponseWatcher = {
},
)
},
- async listen(requestId: string, timeoutRequest: boolean): Promise {
+ listen(requestId: string): TypedEventEmitter {
+ const eventEmitter = new TypedEventEmitter()
+ listeners.set(requestId, (data) => {
+ eventEmitter.emit(data)
+ })
+ return eventEmitter
+ },
+ async oneTimeListener(requestId: string, timeoutRequest: boolean): Promise {
logger.info(`[engineWatcher#listen] requestId=${requestId}`)
return new Promise((resolve) => {
- const defaultResponse: EngineHttpResponse = {
- status: StatusCodes.NO_CONTENT,
- body: {},
- headers: {},
- }
- const responseHandler = (flowResponse: EngineHttpResponse) => {
- clearTimeout(timeout)
- resolve(flowResponse)
- }
let timeout: NodeJS.Timeout
- if (!timeoutRequest) {
- listeners.set(requestId, resolve)
- }
- else {
+ if (timeoutRequest) {
+ const defaultResponse: EngineHttpResponse = {
+ status: StatusCodes.NO_CONTENT,
+ body: {},
+ headers: {},
+ }
timeout = setTimeout(() => {
+ this.removeListener(requestId)
resolve(defaultResponse)
}, WEBHOOK_TIMEOUT_MS)
- listeners.set(requestId, responseHandler)
+
+ }
+ const responseHandler = (flowResponse: EngineResponseWithId) => {
+ if (timeout) {
+ clearTimeout(timeout)
+ }
+ this.removeListener(requestId)
+ resolve(flowResponse.httpResponse)
}
+ listeners.set(requestId, responseHandler)
})
},
async publish(
diff --git a/packages/server/shared/package.json b/packages/server/shared/package.json
index ef59338eff..8dbc4602ec 100644
--- a/packages/server/shared/package.json
+++ b/packages/server/shared/package.json
@@ -6,7 +6,8 @@
"@sentry/node": "7.64.0",
"pino": "8.18.0",
"pino-loki": "2.1.3",
- "@activepieces/shared": "*"
+ "@activepieces/shared": "*",
+ "events": "3.3.0"
},
"type": "commonjs",
"main": "./src/index.js",
diff --git a/packages/server/shared/src/index.ts b/packages/server/shared/src/index.ts
index 9b0e318118..2821662697 100644
--- a/packages/server/shared/src/index.ts
+++ b/packages/server/shared/src/index.ts
@@ -1,3 +1,5 @@
+export * from './lib/exception-handler'
+export * from './lib/typed-event-emitter'
export * from './lib/semaphore'
export * from './lib/file-compressor'
export * from './lib/file-system'
@@ -5,5 +7,4 @@ export * from './lib/package-manager'
export * from './lib/system/system'
export * from './lib/system/system-prop'
export * from './lib/promise-handler'
-export * from './lib/logger'
-export * from './lib/exception-handler'
\ No newline at end of file
+export * from './lib/logger'
\ No newline at end of file
diff --git a/packages/server/shared/src/lib/typed-event-emitter.ts b/packages/server/shared/src/lib/typed-event-emitter.ts
new file mode 100644
index 0000000000..46b6c74a45
--- /dev/null
+++ b/packages/server/shared/src/lib/typed-event-emitter.ts
@@ -0,0 +1,17 @@
+import EventEmitter from 'events'
+
+export class TypedEventEmitter {
+ private emitter = new EventEmitter()
+
+ emit(eventArg: TEventData) {
+ this.emitter.emit('event', eventArg)
+ }
+
+ on(handler: (eventArg: TEventData) => void) {
+ this.emitter.on('event', handler)
+ }
+
+ off(handler: (eventArg: TEventData) => void) {
+ this.emitter.off('event', handler)
+ }
+}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 93a9663be2..879918c2ba 100755
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -60,6 +60,7 @@ export { DelayPauseMetadata, PauseMetadata, WebhookPauseMetadata } from './lib/f
export * from './lib/federated-authn'
export { STORE_KEY_MAX_LENGTH } from './lib/store-entry/store-entry'
export { RetryFlowRequestBody } from './lib/flow-run/test-flow-run-request'
+export * from './lib/flow-run/flow-status'
export * from './lib/flows/dto/flow-template-request'
// Look at https://github.com/sinclairzx81/typebox/issues/350
TypeSystem.ExactOptionalPropertyTypes = false
diff --git a/packages/shared/src/lib/flow-run/execution/flow-execution.ts b/packages/shared/src/lib/flow-run/execution/flow-execution.ts
index a1adc6e3a5..cbca52894e 100644
--- a/packages/shared/src/lib/flow-run/execution/flow-execution.ts
+++ b/packages/shared/src/lib/flow-run/execution/flow-execution.ts
@@ -29,6 +29,7 @@ export const WebhookPauseMetadata = Type.Object({
type: Type.Literal(PauseType.WEBHOOK),
requestId: Type.String(),
response: Type.Unknown(),
+ handlerId: Type.Optional(Type.String({})),
})
export type WebhookPauseMetadata = Static
diff --git a/packages/shared/src/lib/flow-run/flow-status.ts b/packages/shared/src/lib/flow-run/flow-status.ts
new file mode 100644
index 0000000000..24411d3909
--- /dev/null
+++ b/packages/shared/src/lib/flow-run/flow-status.ts
@@ -0,0 +1,5 @@
+import { FlowRunStatus } from './execution/flow-execution'
+
+export const isFlowStateTerminal = (status: FlowRunStatus): boolean => {
+ return status === FlowRunStatus.SUCCEEDED || status === FlowRunStatus.FAILED || status === FlowRunStatus.INTERNAL_ERROR || status === FlowRunStatus.QUOTA_EXCEEDED
+}
\ No newline at end of file
diff --git a/packages/shared/src/lib/websocket/index.ts b/packages/shared/src/lib/websocket/index.ts
index cc85d14a77..34156832c2 100644
--- a/packages/shared/src/lib/websocket/index.ts
+++ b/packages/shared/src/lib/websocket/index.ts
@@ -2,7 +2,7 @@
export enum WebsocketClientEvent {
TEST_FLOW_RUN_STARTED = 'TEST_FLOW_RUN_STARTED',
- TEST_FLOW_RUN_FINISHED = 'TEST_FLOW_RUN_FINISHED',
+ TEST_FLOW_RUN_PROGRESS = 'TEST_FLOW_RUN_PROGRESS',
GENERATE_CODE_FINISHED = 'GENERATE_CODE_FINIISHED',
TEST_STEP_FINISHED = 'TEST_STEP_FINISHED',
}
diff --git a/packages/ui/common/src/lib/components/upgrade-note/upgrade-note.component.ts b/packages/ui/common/src/lib/components/upgrade-note/upgrade-note.component.ts
index 33e2e50193..9e1b7fa8a4 100644
--- a/packages/ui/common/src/lib/components/upgrade-note/upgrade-note.component.ts
+++ b/packages/ui/common/src/lib/components/upgrade-note/upgrade-note.component.ts
@@ -12,20 +12,15 @@ import { fadeIn400ms } from '../../animation/fade-in.animations';
animations: [fadeIn400ms],
})
export class UpgradeNoteComponent {
- @Input() docsLink = '';
@Input({ required: true }) featureNoteTitle = '';
@Input({ required: true }) featureNote = '';
@Input() videoUrl = '';
- @Input() featureKey: FeatureKey;
+ @Input({ required: true }) featureKey: FeatureKey;
constructor(private contactSalesService: ContactSalesService) {}
@Input() insideTab = false;
- openDocs() {
- window.open(this.docsLink, '_blank', 'noopener noreferrer');
- }
-
openContactSales(): void {
this.contactSalesService.open([this.featureKey]);
}
diff --git a/packages/ui/feature-builder-canvas/src/lib/components/widgets/test-flow-widget/test-flow-widget.component.ts b/packages/ui/feature-builder-canvas/src/lib/components/widgets/test-flow-widget/test-flow-widget.component.ts
index 8d161b2119..56d34955ba 100644
--- a/packages/ui/feature-builder-canvas/src/lib/components/widgets/test-flow-widget/test-flow-widget.component.ts
+++ b/packages/ui/feature-builder-canvas/src/lib/components/widgets/test-flow-widget/test-flow-widget.component.ts
@@ -112,7 +112,7 @@ export class TestFlowWidgetComponent implements OnInit {
);
this.testResult$ = this.websockService.socket
- .fromEvent(WebsocketClientEvent.TEST_FLOW_RUN_FINISHED)
+ .fromEvent(WebsocketClientEvent.TEST_FLOW_RUN_PROGRESS)
.pipe(
switchMap((flowRun) => {
return this.instanceRunService.get(flowRun.id);
diff --git a/packages/ui/feature-builder-form-controls/src/lib/pipes/replace-markdown-consts.pipe.ts b/packages/ui/feature-builder-form-controls/src/lib/pipes/replace-markdown-consts.pipe.ts
index a6d8911d1c..020a8de557 100644
--- a/packages/ui/feature-builder-form-controls/src/lib/pipes/replace-markdown-consts.pipe.ts
+++ b/packages/ui/feature-builder-form-controls/src/lib/pipes/replace-markdown-consts.pipe.ts
@@ -13,7 +13,7 @@ export class ReplaceMarkdownConstsPipe implements PipeTransform {
formPieceTriggerPrefix: string
): string {
return value
- .replace('{{webhookUrl}}', `${webhookPrefix}/${flowId}`)
- .replace('{{formUrl}}', `${formPieceTriggerPrefix}/${flowId}`);
+ .replaceAll('{{webhookUrl}}', `${webhookPrefix}/${flowId}`)
+ .replaceAll('{{formUrl}}', `${formPieceTriggerPrefix}/${flowId}`);
}
}
diff --git a/packages/ui/feature-builder-left-sidebar/src/lib/components/run-details/selected-step-result/selected-step-result.component.html b/packages/ui/feature-builder-left-sidebar/src/lib/components/run-details/selected-step-result/selected-step-result.component.html
index 1b93646346..590b9f013a 100644
--- a/packages/ui/feature-builder-left-sidebar/src/lib/components/run-details/selected-step-result/selected-step-result.component.html
+++ b/packages/ui/feature-builder-left-sidebar/src/lib/components/run-details/selected-step-result/selected-step-result.component.html
@@ -20,7 +20,7 @@
Duration:
- {{ (selectedStepResult.duration || 1) | number: '1.0-1' }} ms
+ {{ ((selectedStepResult.duration || 1) / 1000) | number: '1.1-3' }} s
+ featureKey="GIT_SYNC">
} @else {
[] = steps.map((step) => this.pieceService.getStepDetails(step));
this.urlsToLoad$ = forkJoin(stepMetadata$).pipe(
map((items) => items.map((item) => item.logoUrl!).slice(0, this.maxNumberOfIconsToLoad)));
@@ -46,11 +50,29 @@ export class PiecesIconsFromFlowComponent implements OnInit {
const stepsAppsNames = items.map((item) => item.name);
if (stepsAppsNames.length === 1) {
return stepsAppsNames[0]!;
- } else if (stepsAppsNames.length < 7) {
+ } else if (stepsAppsNames.length <= 7) {
return stepsAppsNames.slice(0, stepsAppsNames.length - 1).join(', ') +
` and ${stepsAppsNames[stepsAppsNames.length - 1]}`;
} else {
return stepsAppsNames.join(', ') + ' and others';
}
}
+
+ private filterOutDuplicates(steps: (Action | Trigger)[])
+ {
+ const seen = new Set();
+ return steps.filter((step) => {
+ let isDuplicate = false;
+ if(step.type === ActionType.PIECE || step.type === TriggerType.PIECE)
+ {
+ isDuplicate = seen.has(step.settings.pieceName);
+ seen.add(step.settings.pieceName);
+ }
+ else{
+ isDuplicate = seen.has(step.type);
+ seen.add(step.type);
+ }
+ return !isDuplicate;
+ });
+ }
}
diff --git a/tsconfig.base.json b/tsconfig.base.json
index acf980862d..6838572878 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -390,6 +390,9 @@
"@activepieces/piece-pipedrive": [
"packages/pieces/community/pipedrive/src/index.ts"
],
+ "@activepieces/piece-poper": [
+ "packages/pieces/community/poper/src/index.ts"
+ ],
"@activepieces/piece-postgres": [
"packages/pieces/community/postgres/src/index.ts"
],
@@ -553,6 +556,9 @@
"@activepieces/piece-whatsable": [
"packages/pieces/community/whatsable/src/index.ts"
],
+ "@activepieces/piece-whatsapp": [
+ "packages/pieces/community/whatsapp/src/index.ts"
+ ],
"@activepieces/piece-woocommerce": [
"packages/pieces/community/woocommerce/src/index.ts"
],