Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["../../../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# pieces-vtiger
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build pieces-vtiger` to build the library.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@activepieces/piece-vtiger",
|
||||
"version": "1.2.8",
|
||||
"dependencies": {
|
||||
"crypto-js": "4.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "pieces-vtiger",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/vtiger/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/pieces/community/vtiger",
|
||||
"tsConfig": "packages/pieces/community/vtiger/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/vtiger/package.json",
|
||||
"main": "packages/pieces/community/vtiger/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/vtiger/*.md",
|
||||
{
|
||||
"input": "packages/pieces/community/vtiger/src/i18n",
|
||||
"output": "./src/i18n",
|
||||
"glob": "**/!(i18n.json)"
|
||||
}
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"updateBuildableProjectDepsInPackageJson": true
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"prebuild"
|
||||
]
|
||||
},
|
||||
"publish": {
|
||||
"command": "node tools/scripts/publish.mjs pieces-vtiger {args.ver} {args.tag}",
|
||||
"dependsOn": [
|
||||
"build"
|
||||
]
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
},
|
||||
"prebuild": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/pieces/community/vtiger",
|
||||
"command": "bun install --no-save --silent"
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "CRM-Software für Vertrieb, Marketing und Support-Teams",
|
||||
"VTiger Instance URL": "URL der VTiger-Instanz",
|
||||
"Username": "Benutzername",
|
||||
"Access Key": "Zugangsschlüssel",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "Für die Instanz URL die URL ohne Endpunkt hinzufügen. Geben Sie zum Beispiel https://<instance>.od2.vtiger.com anstelle von https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default ein",
|
||||
"Enter your username/email": "Geben Sie Ihren Benutzername/E-Mail ein",
|
||||
"Enter your access Key": "Zugangsschlüssel eingeben",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nUm Ihren Access Key zu erhalten, folgen Sie diesen Schritten:\n. Melden Sie sich bei Vtiger CRM:\nÖffnen Sie einen Webbrowser und melden Sie sich bei Ihrer Vtiger CRM-Instanz an.\n\n2. Navigieren Sie zu Benutzerprofil:\nIn der oberen rechten Ecke klicken Sie auf Ihren Profilnamen.\nWählen Sie \"Meine Einstellungen\"\n\n3. Das System generiert einen Zugangsschlüssel für Sie.\nDen Access-Key kopieren und sicher speichern. Dieser Schlüssel wird zur Authentifizierung verwendet, wenn API-Anfragen gestellt werden.\nHinweis:\n\nZugriffsschlüssel sind sensible Informationen und sollten sicher gehalten werden.\nDen Zugangsschlüssel wie ein Passwort behandeln. Teilen Sie ihn nicht öffentlich oder stellen Sie ihn unsicher aus. \n",
|
||||
"Create Record": "Datensatz erstellen",
|
||||
"Get Record": "Datensatz abrufen",
|
||||
"Update Record": "Datensatz aktualisieren",
|
||||
"Delete Record": "Datensatz löschen",
|
||||
"Query Records": "Abfrageeinträge",
|
||||
"Search Records": "Datensätze suchen",
|
||||
"Custom API Call (Deprecated)": "Eigener API-Aufruf (veraltet)",
|
||||
"Custom API Call": "Eigener API-Aufruf",
|
||||
"Create a Record": "Datensatz erstellen",
|
||||
"Get a Record by value": "Datensatz nach Wert holen",
|
||||
"Update a Record": "Datensatz aktualisieren",
|
||||
"Delete a Record": "Datensatz löschen",
|
||||
"Query records by SQL statement.": "Datensätze nach SQL-Anweisung abfragen.",
|
||||
"Search for a record.": "Suche nach einem Datensatz.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Führt einen beliebigen autorisierten API-Aufruf aus. (Veraltet)",
|
||||
"Make a custom API call to a specific endpoint": "Einen benutzerdefinierten API-Aufruf an einen bestimmten Endpunkt machen",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Datensatzfelder",
|
||||
"Id": "Id",
|
||||
"Query": "Abfrage",
|
||||
"Search Fields": "Suchfelder",
|
||||
"Limit": "Limit",
|
||||
"Http Method": "Http Methode",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "URL-Pfad (veraltet)",
|
||||
"Headers": "Kopfzeilen",
|
||||
"Data": "Daten",
|
||||
"Method": "Methode",
|
||||
"Query Parameters": "Abfrageparameter",
|
||||
"Body": "Körper",
|
||||
"Response is Binary ?": "Antwort ist binär?",
|
||||
"No Error on Failure": "Kein Fehler bei Fehler",
|
||||
"Timeout (in seconds)": "Timeout (in Sekunden)",
|
||||
"The module / element type": "Modul/Elementtyp",
|
||||
"Add new fields to be created in the new record": "Fügen Sie neue Felder hinzu, die im neuen Datensatz erstellt werden sollen",
|
||||
"The record's id": "Die Datensatz Id",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Geben Sie die Abfrageanweisung ein, z.B. SELECT-Zähler (*) VON Kontakte;",
|
||||
"Enter your filter criteria": "Geben Sie Ihre Filterkriterien ein",
|
||||
"Enter the maximum number of records to return.": "Geben Sie die maximale Anzahl an Datensätzen ein, die zurückgegeben werden sollen.",
|
||||
"Select the HTTP method you want to use": "Wählen Sie die HTTP-Methode aus, die Sie verwenden möchten",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "Absolute URL oder Pfad. Wenn ein relativer Pfad angegeben wird (z.B. /me, /listtypes, /describe), wird er gegen die REST-Basis aufgerufen.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Veraltet. Verwenden Sie stattdessen 'URL'. API-Endpunkt-URL-Pfad (Beispiel: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Geben Sie die gewünschten Request-Header ein. Überspringen Sie die Autorisierungs-Header",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Geben Sie die zu übertragenden Daten ein. Wenn die POST als Body-Daten gesendet wird und wenn GET, als Query-String",
|
||||
"Authorization headers are injected automatically from your connection.": "Autorisierungs-Header werden automatisch von Ihrer Verbindung injiziert.",
|
||||
"Enable for files like PDFs, images, etc..": "Aktivieren für Dateien wie PDFs, Bilder, etc..",
|
||||
"GET": "ERHALTEN",
|
||||
"POST": "POST",
|
||||
"PUT": "PUT",
|
||||
"PATCH": "PATCH",
|
||||
"DELETE": "LÖSCHEN",
|
||||
"HEAD": "HEAD",
|
||||
"New or Updated Record": "Neuer oder aktualisierter Datensatz",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Wird ausgelöst, wenn ein neuer Datensatz eingeführt oder ein Datensatz aktualisiert wird.",
|
||||
"Watch By": "Beobachten von",
|
||||
"Sync Scope": "Sync-Bereich",
|
||||
"Column to watch for trigger": "Zu beobachtende Spalte",
|
||||
"Records visibility scope for sync": "Sichtbarkeitsbereich für die Synchronisierung",
|
||||
"Created Time": "Erstellte Zeit",
|
||||
"Modified Time": "Geänderte Zeit",
|
||||
"Application (all records)": "Anwendung (alle Datensätze)",
|
||||
"User's groups": "Benutzergruppen",
|
||||
"User only": "Nur Benutzer"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "Software CRM para equipos de ventas, mercadeo y soporte",
|
||||
"VTiger Instance URL": "URL de instancia de VTiger",
|
||||
"Username": "Usuario",
|
||||
"Access Key": "Clave de acceso",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "Para la URL de la instancia, agregue la url sin el punto final. Por ejemplo, introduzca https://<instance>.od2.vtiger.com en lugar de https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Introduce tu nombre de usuario/email",
|
||||
"Enter your access Key": "Introduzca su clave de acceso",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Crear registro",
|
||||
"Get Record": "Obtener registro",
|
||||
"Update Record": "Actualizar registro",
|
||||
"Delete Record": "Eliminar registro",
|
||||
"Query Records": "Consultar registros",
|
||||
"Search Records": "Buscar registros",
|
||||
"Custom API Call (Deprecated)": "Llamada API personalizada (obsoleta)",
|
||||
"Custom API Call": "Llamada API personalizada",
|
||||
"Create a Record": "Crear un registro",
|
||||
"Get a Record by value": "Obtener un registro por valor",
|
||||
"Update a Record": "Actualizar un registro",
|
||||
"Delete a Record": "Eliminar un registro",
|
||||
"Query records by SQL statement.": "Consultar registros por sentencia SQL.",
|
||||
"Search for a record.": "Buscar un registro.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Realiza una llamada de API arbitraria autorizada. (Obsoleto)",
|
||||
"Make a custom API call to a specific endpoint": "Hacer una llamada API personalizada a un extremo específico",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Campos de registro",
|
||||
"Id": "Id",
|
||||
"Query": "Consulta",
|
||||
"Search Fields": "Campos de búsqueda",
|
||||
"Limit": "Límite",
|
||||
"Http Method": "Método Http",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "Ruta URL (obsoleta)",
|
||||
"Headers": "Encabezados",
|
||||
"Data": "Datos",
|
||||
"Method": "Método",
|
||||
"Query Parameters": "Parámetros de consulta",
|
||||
"Body": "Cuerpo",
|
||||
"Response is Binary ?": "¿Respuesta es binaria?",
|
||||
"No Error on Failure": "No hay ningún error en fallo",
|
||||
"Timeout (in seconds)": "Tiempo de espera (en segundos)",
|
||||
"The module / element type": "El tipo de elemento / módulo",
|
||||
"Add new fields to be created in the new record": "Añadir nuevos campos para ser creados en el nuevo registro",
|
||||
"The record's id": "El id del registro",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Introduzca la instrucción de consulta, por ejemplo, contador SELECT (*) FROM Contacts;",
|
||||
"Enter your filter criteria": "Introduzca sus criterios de filtro",
|
||||
"Enter the maximum number of records to return.": "Introduzca el número máximo de registros a regresar.",
|
||||
"Select the HTTP method you want to use": "Seleccione el método HTTP que desea utilizar",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "URL o ruta absoluta. Si se proporciona una ruta relativa (por ejemplo, /me, /listtypes, /describe), se llamará contra la base REST.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Obsoleto. Utilice 'URL' en su lugar. Ruta URL del endpoint de la API (ejemplo: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Introduzca los encabezados de solicitud deseados. Omita los encabezados de autorización",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Introduzca los datos a pasar. Si su POST, se enviará como datos corporales, y si GET, como cadena de consulta",
|
||||
"Authorization headers are injected automatically from your connection.": "Las cabeceras de autorización se inyectan automáticamente desde tu conexión.",
|
||||
"Enable for files like PDFs, images, etc..": "Activar para archivos como PDFs, imágenes, etc.",
|
||||
"GET": "RECOGER",
|
||||
"POST": "POST",
|
||||
"PUT": "PUT",
|
||||
"PATCH": "PATCH",
|
||||
"DELETE": "BORRAR",
|
||||
"HEAD": "LIMPIO",
|
||||
"New or Updated Record": "Registro nuevo o actualizado",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Se activa cuando se introduce un nuevo registro o se actualiza un registro.",
|
||||
"Watch By": "Ver por",
|
||||
"Sync Scope": "Sincronizar ámbito",
|
||||
"Column to watch for trigger": "Columna para ver si se activa",
|
||||
"Records visibility scope for sync": "Ámbito de visibilidad de registros para sincronizar",
|
||||
"Created Time": "Hora de creación",
|
||||
"Modified Time": "Tiempo modificado",
|
||||
"Application (all records)": "Aplicación (todos los registros)",
|
||||
"User's groups": "Grupos del usuario",
|
||||
"User only": "Sólo usuario"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "Logiciel CRM pour les équipes de vente, de marketing et de support",
|
||||
"VTiger Instance URL": "URL d'instance VTiger",
|
||||
"Username": "Nom d'utilisateur",
|
||||
"Access Key": "Clé d'accès",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "Pour l'URL de l'instance, ajouter l'url sans le terminal. Par exemple entrez https://<instance>.od2.vtiger.com au lieu de https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Entrez votre nom d'utilisateur/email",
|
||||
"Enter your access Key": "Entrez votre clé d'accès",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Créer un enregistrement",
|
||||
"Get Record": "Obtenir un enregistrement",
|
||||
"Update Record": "Mettre à jour l'enregistrement",
|
||||
"Delete Record": "Supprimer l'enregistrement",
|
||||
"Query Records": "Enregistrements de requête",
|
||||
"Search Records": "Rechercher des enregistrements",
|
||||
"Custom API Call (Deprecated)": "Appel API personnalisé (obsolète)",
|
||||
"Custom API Call": "Appel d'API personnalisé",
|
||||
"Create a Record": "Créer un enregistrement",
|
||||
"Get a Record by value": "Obtenir un enregistrement par valeur",
|
||||
"Update a Record": "Mettre à jour un enregistrement",
|
||||
"Delete a Record": "Supprimer un enregistrement",
|
||||
"Query records by SQL statement.": "Enregistrements de requête par requête SQL.",
|
||||
"Search for a record.": "Rechercher un enregistrement.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Effectue un appel d'API arbitraire autorisé. (Obsolète)",
|
||||
"Make a custom API call to a specific endpoint": "Passer un appel API personnalisé à un endpoint spécifique",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Champs d'enregistrement",
|
||||
"Id": "Id",
|
||||
"Query": "Requête",
|
||||
"Search Fields": "Champs de recherche",
|
||||
"Limit": "Limite",
|
||||
"Http Method": "Méthode Http",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "Chemin URL (obsolète)",
|
||||
"Headers": "En-têtes",
|
||||
"Data": "Donnée",
|
||||
"Method": "Méthode",
|
||||
"Query Parameters": "Paramètres de requête",
|
||||
"Body": "Corps",
|
||||
"Response is Binary ?": "La réponse est Binaire ?",
|
||||
"No Error on Failure": "Aucune erreur en cas d'échec",
|
||||
"Timeout (in seconds)": "Délai d'expiration (en secondes)",
|
||||
"The module / element type": "Le module / type d'élément",
|
||||
"Add new fields to be created in the new record": "Ajouter de nouveaux champs à créer dans le nouvel enregistrement",
|
||||
"The record's id": "L'identifiant de l'enregistrement",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Entrez l'instruction de requête, par exemple SELECT count(*) FROM Contacts;",
|
||||
"Enter your filter criteria": "Entrez vos critères de filtre",
|
||||
"Enter the maximum number of records to return.": "Entrez le nombre maximum d'enregistrements à retourner.",
|
||||
"Select the HTTP method you want to use": "Sélectionnez la méthode HTTP que vous souhaitez utiliser",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "URL ou chemin absolu. Si un chemin relatif est fourni (par exemple, /me, /listtypes, /describe), il sera appelé sur la base REST.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Obsolète. Utilisez 'URL' à la place. Chemin de l'URL du point de terminaison de l'API (exemple: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Entrez les en-têtes de requête souhaités. Ignorer les en-têtes d'autorisation",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Entrez les données à passer. Si son POST, il sera envoyé en tant que données de corps, et si GET, en tant que chaîne de requête",
|
||||
"Authorization headers are injected automatically from your connection.": "Les en-têtes d'autorisation sont injectés automatiquement à partir de votre connexion.",
|
||||
"Enable for files like PDFs, images, etc..": "Activer pour les fichiers comme les PDFs, les images, etc.",
|
||||
"GET": "GET",
|
||||
"POST": "POST",
|
||||
"PUT": "PUT",
|
||||
"PATCH": "PATCH",
|
||||
"DELETE": "DELETE",
|
||||
"HEAD": "HEAD",
|
||||
"New or Updated Record": "Nouvel enregistrement ou mis à jour",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Déclenche lorsqu'un nouvel enregistrement est introduit ou qu'un enregistrement est mis à jour.",
|
||||
"Watch By": "Regarder par",
|
||||
"Sync Scope": "Périmètre de synchronisation",
|
||||
"Column to watch for trigger": "Colonne à surveiller pour le déclenchement",
|
||||
"Records visibility scope for sync": "Portée de visibilité des enregistrements pour la synchronisation",
|
||||
"Created Time": "Date de création",
|
||||
"Modified Time": "Date de modification",
|
||||
"Application (all records)": "Application (tous les enregistrements)",
|
||||
"User's groups": "Groupes d'utilisateurs",
|
||||
"User only": "Utilisateur seulement"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "営業、マーケティング、サポートチームのためのCRMソフトウェア",
|
||||
"VTiger Instance URL": "VTigerインスタンスURL",
|
||||
"Username": "ユーザー名",
|
||||
"Access Key": "アクセスキー",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "例えば、https://<instance>.od2.vtiger.com の代わりに https://<instance>.od2.vtiger.comと入力してください。.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "ユーザー名/メールアドレスを入力してください",
|
||||
"Enter your access Key": "アクセスキーを入力",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "レコードを作成",
|
||||
"Get Record": "レコードを取得",
|
||||
"Update Record": "更新記録",
|
||||
"Delete Record": "レコードを削除",
|
||||
"Query Records": "クエリレコード",
|
||||
"Search Records": "レコードを検索",
|
||||
"Custom API Call (Deprecated)": "カスタムAPI通話(非推奨)",
|
||||
"Custom API Call": "カスタムAPI通話",
|
||||
"Create a Record": "レコードを作成",
|
||||
"Get a Record by value": "値でレコードを取得",
|
||||
"Update a Record": "レコードを更新",
|
||||
"Delete a Record": "レコードを削除",
|
||||
"Query records by SQL statement.": "SQL文によるクエリのレコード。",
|
||||
"Search for a record.": "レコードを検索",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "任意の認証済みの API 呼び出しを実行します。(非推奨)",
|
||||
"Make a custom API call to a specific endpoint": "特定のエンドポイントへのカスタム API コールを実行します。",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "レコードフィールド",
|
||||
"Id": "Id",
|
||||
"Query": "クエリ",
|
||||
"Search Fields": "検索フィールド",
|
||||
"Limit": "制限",
|
||||
"Http Method": "Http メソッド",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "URL パス (非推奨)",
|
||||
"Headers": "ヘッダー",
|
||||
"Data": "データ",
|
||||
"Method": "方法",
|
||||
"Query Parameters": "クエリパラメータ",
|
||||
"Body": "本文",
|
||||
"Response is Binary ?": "応答はバイナリですか?",
|
||||
"No Error on Failure": "失敗時にエラーはありません",
|
||||
"Timeout (in seconds)": "タイムアウト(秒)",
|
||||
"The module / element type": "モジュール / 要素タイプ",
|
||||
"Add new fields to be created in the new record": "新しいレコードに作成する新規項目を追加",
|
||||
"The record's id": "レコードの id",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "クエリ文を入力します。例えば、SELECT count(*) FROM Contacts;",
|
||||
"Enter your filter criteria": "フィルター条件を入力してください",
|
||||
"Enter the maximum number of records to return.": "返すレコードの最大数を入力します。",
|
||||
"Select the HTTP method you want to use": "使用する HTTP メソッドを選択してください",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "絶対URLまたはパス。相対パスが指定されている場合(例:/me、/listtypes、/describe)、RESTベースに対して呼び出されます。",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "非推奨です。代わりに 'URL' を使用してください。API エンドポイントの URL パス (例: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "必要な要求ヘッダーを入力します。認証ヘッダーをスキップします。",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "パスするデータを入力します。POST の場合は、本体データとして、GETの場合はクエリ文字列として送信されます。",
|
||||
"Authorization headers are injected automatically from your connection.": "認証ヘッダは接続から自動的に注入されます。",
|
||||
"Enable for files like PDFs, images, etc..": "PDF、画像などのファイルを有効にします。",
|
||||
"GET": "取得",
|
||||
"POST": "POST",
|
||||
"PUT": "PUT",
|
||||
"PATCH": "PATCH",
|
||||
"DELETE": "削除",
|
||||
"HEAD": "頭",
|
||||
"New or Updated Record": "新規または更新されたレコード",
|
||||
"Triggers when a new record is introduced or a record is updated.": "新しいレコードが導入されたとき、またはレコードが更新されたときにトリガーします。",
|
||||
"Watch By": "監視者",
|
||||
"Sync Scope": "同期範囲",
|
||||
"Column to watch for trigger": "トリガーを見る列",
|
||||
"Records visibility scope for sync": "同期のためのレコードの可視範囲を記録",
|
||||
"Created Time": "作成日時",
|
||||
"Modified Time": "変更日時",
|
||||
"Application (all records)": "アプリケーション (すべてのレコード)",
|
||||
"User's groups": "ユーザーのグループ",
|
||||
"User only": "ユーザーのみ"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "CRM software voor verkoop, marketing en ondersteunende teams",
|
||||
"VTiger Instance URL": "VTiger instantie URL",
|
||||
"Username": "Gebruikersnaam",
|
||||
"Access Key": "Toegang Sleutel",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "Voor de instance URL, voeg de url toe zonder het eindpunt. Bijvoorbeeld: vul https://<instance>.od2.vtiger.com in plaats van https://<instance>.od2.vtiger.com/restapi/v1/vtiger/standaard in",
|
||||
"Enter your username/email": "Voer uw gebruikersnaam/e-mailadres in",
|
||||
"Enter your access Key": "Voer uw toegangssleutel in",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nVolg deze stappen om uw toegangssleutel te krijgen:\n. Log in om Vtiger CRM:\nOpen een webbrowser en log in op uw Vtiger CRM-instantie.\n\n2. Navigeer naar gebruikersprofiel:\nIn de rechterbovenhoek klikt u op uw profielnaam.\nSelecteer \"Mijn Voorkeuren\"\n\n3. Het systeem zal een toegangssleutel voor u genereren.\nKopieer en sla de toegangssleutel veilig op. Deze sleutel wordt gebruikt voor authenticatie bij het maken van API-aanvragen.\nOpmerking:\n\ntoegangssleutels zijn gevoelige informatie en ze moeten beveiligd blijven.\nBehandel de toegangssleutel als een wachtwoord. Deel deze niet publiekelijk of laat hem bloot op een onveilige manier. \n",
|
||||
"Create Record": "Record Maken",
|
||||
"Get Record": "Krijg Record",
|
||||
"Update Record": "Update Record",
|
||||
"Delete Record": "Record verwijderen",
|
||||
"Query Records": "Records opzoeken",
|
||||
"Search Records": "Records zoeken",
|
||||
"Custom API Call (Deprecated)": "Aangepaste API Call (Afgekeurd)",
|
||||
"Custom API Call": "Custom API Call",
|
||||
"Create a Record": "Een record maken",
|
||||
"Get a Record by value": "Krijg een record per waarde",
|
||||
"Update a Record": "Een record bijwerken",
|
||||
"Delete a Record": "Een record verwijderen",
|
||||
"Query records by SQL statement.": "Query records door SQL statement.",
|
||||
"Search for a record.": "Zoek een record.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Voert een willekeurige geautoriseerde API-oproep uit. (Afgekeurd)",
|
||||
"Make a custom API call to a specific endpoint": "Maak een aangepaste API call naar een specifiek eindpunt",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Record velden",
|
||||
"Id": "Id",
|
||||
"Query": "Zoekopdracht",
|
||||
"Search Fields": "Zoek velden",
|
||||
"Limit": "Limiet",
|
||||
"Http Method": "Http Methode",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "URL-pad (verouderd)",
|
||||
"Headers": "Kopteksten",
|
||||
"Data": "Gegevens",
|
||||
"Method": "Methode",
|
||||
"Query Parameters": "Query parameters",
|
||||
"Body": "Lichaam",
|
||||
"Response is Binary ?": "Antwoord is binair?",
|
||||
"No Error on Failure": "Geen fout bij fout",
|
||||
"Timeout (in seconds)": "Time-out (in seconden)",
|
||||
"The module / element type": "De module / element type",
|
||||
"Add new fields to be created in the new record": "Nieuwe velden toevoegen die worden aangemaakt in het nieuwe record",
|
||||
"The record's id": "ID van het record",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Voer de query verklaring in, bijv. SELECT teller (*) FROM contacten;",
|
||||
"Enter your filter criteria": "Voer uw filtercriteria in",
|
||||
"Enter the maximum number of records to return.": "Voer het maximum aantal te retourneren records in.",
|
||||
"Select the HTTP method you want to use": "Selecteer de HTTP-methode die u wilt gebruiken",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "Absolute URL of pad. Als er een relatief pad wordt opgegeven (bijv. /me, /listtypes, /describe), dan wordt het aangeroepen tegen de REST basis.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Gebruik 'URL' als alternatief. API endpoint's URL pad (voorbeeld: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Voer de gewenste aanvraagkopteksten in. Sla de autorisatieheaders over",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Voer de gegevens in om door te gaan. Als de POST, zal het worden verzonden als body data en als GET, als query string",
|
||||
"Authorization headers are injected automatically from your connection.": "Autorisatie headers worden automatisch geïnjecteerd vanuit uw verbinding.",
|
||||
"Enable for files like PDFs, images, etc..": "Inschakelen voor bestanden zoals PDF's, afbeeldingen etc..",
|
||||
"GET": "KRIJG",
|
||||
"POST": "POSTE",
|
||||
"PUT": "PUT",
|
||||
"PATCH": "BEKIJK",
|
||||
"DELETE": "VERWIJDEREN",
|
||||
"HEAD": "HOOFD",
|
||||
"New or Updated Record": "Nieuwe of bijgewerkte record",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Triggert wanneer een nieuw record wordt ingevoerd of een record wordt bijgewerkt.",
|
||||
"Watch By": "Bekijk door",
|
||||
"Sync Scope": "Synchroniseer draagwijdte",
|
||||
"Column to watch for trigger": "Te bekijken kolom voor trigger",
|
||||
"Records visibility scope for sync": "Records zichtbaarheid scope voor synchronisatie",
|
||||
"Created Time": "Aangemaakt op",
|
||||
"Modified Time": "Gewijzigde tijd",
|
||||
"Application (all records)": "Toepassing (alle records)",
|
||||
"User's groups": "Gebruikersgroepen",
|
||||
"User only": "Alleen gebruiker"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "Software CRM para vendas, marketing e equipes de suporte",
|
||||
"VTiger Instance URL": "Instância URL do VTiger",
|
||||
"Username": "Usuário:",
|
||||
"Access Key": "Chave Acesso",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "Para a instância do URL, adicione a url sem o endpoint. Por exemplo, digite https://<instance>.od2.vtiger.com em vez de https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Digite seu usuário/e-mail",
|
||||
"Enter your access Key": "Digite sua chave de acesso",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Criar Registro",
|
||||
"Get Record": "Obter Registro",
|
||||
"Update Record": "Atualizar Registro",
|
||||
"Delete Record": "Excluir registro",
|
||||
"Query Records": "Registros de Consulta",
|
||||
"Search Records": "Buscar Registros",
|
||||
"Custom API Call (Deprecated)": "Chamada de API personalizada (obsoleta)",
|
||||
"Custom API Call": "Chamada de API personalizada",
|
||||
"Create a Record": "Criar um Registro",
|
||||
"Get a Record by value": "Obter um Registro por valor",
|
||||
"Update a Record": "Atualizar um Registro",
|
||||
"Delete a Record": "Apagar um registro",
|
||||
"Query records by SQL statement.": "Consultar registros por instrução SQL.",
|
||||
"Search for a record.": "Procurar por um registro.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Executa uma chamada de API autorizada arbitrária. (Descontinuado)",
|
||||
"Make a custom API call to a specific endpoint": "Faça uma chamada de API personalizada para um ponto de extremidade específico",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Recorde Campos",
|
||||
"Id": "Id",
|
||||
"Query": "Requisição",
|
||||
"Search Fields": "Campos De Busca",
|
||||
"Limit": "Limitar",
|
||||
"Http Method": "Método http",
|
||||
"URL": "URL:",
|
||||
"URL Path (deprecated)": "Caminho da URL (obsoleto)",
|
||||
"Headers": "Cabeçalhos",
|
||||
"Data": "Dado",
|
||||
"Method": "Método",
|
||||
"Query Parameters": "Parâmetros da consulta",
|
||||
"Body": "Conteúdo",
|
||||
"Response is Binary ?": "A resposta é binária ?",
|
||||
"No Error on Failure": "Nenhum erro no Failure",
|
||||
"Timeout (in seconds)": "Tempo limite (em segundos)",
|
||||
"The module / element type": "O módulo / tipo de elemento",
|
||||
"Add new fields to be created in the new record": "Adicionar novos campos para serem criados no novo registro",
|
||||
"The record's id": "ID do registro",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Digite a declaração de consulta, por exemplo, contagem SELECT (*) DE Contatos;",
|
||||
"Enter your filter criteria": "Digite seu critério de filtro",
|
||||
"Enter the maximum number of records to return.": "Insira o número máximo de registros para retornar.",
|
||||
"Select the HTTP method you want to use": "Selecione o método HTTP que você deseja usar",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "URL ou caminho absoluto. Se um caminho relativo for fornecido (por exemplo, /me, /listtypes, /describe), ele será chamado contra a base REST.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Obsoleto. Use 'URL' em vez disso. O caminho de URL do ponto de extremidade da API (exemplo: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Digite os cabeçalhos de solicitação desejados. Pule os cabeçalhos de autorização",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Insira os dados a serem aprovados. Se for POST, serão enviados como dados corporais e se GET, como string de consulta",
|
||||
"Authorization headers are injected automatically from your connection.": "Os cabeçalhos de autorização são inseridos automaticamente a partir da sua conexão.",
|
||||
"Enable for files like PDFs, images, etc..": "Habilitar para arquivos como PDFs, imagens, etc..",
|
||||
"GET": "OBTER",
|
||||
"POST": "POSTAR",
|
||||
"PUT": "COLOCAR",
|
||||
"PATCH": "COMPRAR",
|
||||
"DELETE": "EXCLUIR",
|
||||
"HEAD": "CABEÇA",
|
||||
"New or Updated Record": "Registro novo ou atualizado",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Dispara quando um novo registro é introduzido ou um registro é atualizado.",
|
||||
"Watch By": "Assistir por",
|
||||
"Sync Scope": "Âmbito de Sincronização",
|
||||
"Column to watch for trigger": "Coluna para assistir ao gatilho",
|
||||
"Records visibility scope for sync": "Escopo de visibilidade de registros para sincronização",
|
||||
"Created Time": "Horário de Criação",
|
||||
"Modified Time": "Hora Modificação",
|
||||
"Application (all records)": "Aplicativo (todos os registros)",
|
||||
"User's groups": "Grupos de usuários",
|
||||
"User only": "Somente usuário"
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"Vtiger": "Вольная русская",
|
||||
"CRM software for sales, marketing, and support teams": "CRM программное обеспечение для продаж, маркетинга и команд поддержки",
|
||||
"VTiger Instance URL": "URL-адрес экземпляра VTiger",
|
||||
"Username": "Имя пользователя",
|
||||
"Access Key": "Ключ доступа",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "Для URL экземпляра, добавьте url без конечной точки. Например, введите https://<instance>.od2.vtiger.com вместо https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Введите имя пользователя/адрес электронной почты",
|
||||
"Enter your access Key": "Введите ваш ключ доступа",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Создать запись",
|
||||
"Get Record": "Получить запись",
|
||||
"Update Record": "Обновить запись",
|
||||
"Delete Record": "Удалить запись",
|
||||
"Query Records": "Записей",
|
||||
"Search Records": "Поиск записей",
|
||||
"Custom API Call": "Пользовательский вызов API",
|
||||
"Create a Record": "Создать запись",
|
||||
"Get a Record by value": "Получить запись по значению",
|
||||
"Update a Record": "Обновить запись",
|
||||
"Delete a Record": "Удалить запись",
|
||||
"Query records by SQL statement.": "Запрашивать записи по выражению SQL.",
|
||||
"Search for a record.": "Поиск записи.",
|
||||
"Performs an arbitrary authorized API call. ": "Выполняет произвольный авторизованный вызов API. ",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Поля записи",
|
||||
"Id": "Id",
|
||||
"Query": "Запрос",
|
||||
"Search Fields": "Поля поиска",
|
||||
"Limit": "Лимит",
|
||||
"Http Method": "Метод Http",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "URL путь (устаревший)",
|
||||
"Headers": "Заголовки",
|
||||
"Data": "Данные",
|
||||
"The module / element type": "Модуль / тип элемента",
|
||||
"Add new fields to be created in the new record": "Добавить новые поля для создания в новой записи",
|
||||
"The record's id": "Идентификатор записи",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Введите заявление запроса, например SELECT count(*) FROM Contacts;",
|
||||
"Enter your filter criteria": "Введите критерии фильтра",
|
||||
"Enter the maximum number of records to return.": "Введите максимальное количество записей для возврата.",
|
||||
"Select the HTTP method you want to use": "Выберите HTTP метод, который вы хотите использовать",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "Абсолютный URL или путь. Если указан относительный путь (например, /me, /listtypes, /describe), он будет вызван против REST-базы.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Устарел. Используйте 'URL'. URL конечной точки API (пример: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Введите желаемые заголовки запроса. Пропустите заголовки авторизации",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Введите данные для передачи. Если их POST, они будут отправлены как данные тела, и если GET, как строка запроса",
|
||||
"GET": "ПОЛУЧИТЬ",
|
||||
"POST": "ПОСТ",
|
||||
"PUT": "ПОКУПИТЬ",
|
||||
"PATCH": "ПАТЧ",
|
||||
"DELETE": "УДАЛИТЬ",
|
||||
"New or Updated Record": "Новая или Обновленная запись",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Триггеры при вводе новой записи или обновлении записи.",
|
||||
"Watch By": "Смотреть от",
|
||||
"Sync Scope": "Область синхронизации",
|
||||
"Column to watch for trigger": "Столбец для просмотра триггера",
|
||||
"Records visibility scope for sync": "Область видимости записей для синхронизации",
|
||||
"Created Time": "Создано",
|
||||
"Modified Time": "Изменено время",
|
||||
"Application (all records)": "Приложение (все записи)",
|
||||
"User's groups": "Группы пользователей",
|
||||
"User only": "Только для пользователей"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "CRM software for sales, marketing, and support teams",
|
||||
"VTiger Instance URL": "VTiger Instance URL",
|
||||
"Username": "Username",
|
||||
"Access Key": "Access Key",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Enter your username/email",
|
||||
"Enter your access Key": "Enter your access Key",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Create Record",
|
||||
"Get Record": "Get Record",
|
||||
"Update Record": "Update Record",
|
||||
"Delete Record": "Delete Record",
|
||||
"Query Records": "Query Records",
|
||||
"Search Records": "Search Records",
|
||||
"Custom API Call (Deprecated)": "Custom API Call (Deprecated)",
|
||||
"Custom API Call": "Custom API Call",
|
||||
"Create a Record": "Create a Record",
|
||||
"Get a Record by value": "Get a Record by value",
|
||||
"Update a Record": "Update a Record",
|
||||
"Delete a Record": "Delete a Record",
|
||||
"Query records by SQL statement.": "Query records by SQL statement.",
|
||||
"Search for a record.": "Search for a record.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Performs an arbitrary authorized API call. (Deprecated)",
|
||||
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Record Fields",
|
||||
"Id": "Id",
|
||||
"Query": "Query",
|
||||
"Search Fields": "Search Fields",
|
||||
"Limit": "Limit",
|
||||
"Http Method": "Http Method",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "URL Path (deprecated)",
|
||||
"Headers": "Headers",
|
||||
"Data": "Data",
|
||||
"Method": "Method",
|
||||
"Query Parameters": "Query Parameters",
|
||||
"Body": "Body",
|
||||
"Response is Binary ?": "Response is Binary ?",
|
||||
"No Error on Failure": "No Error on Failure",
|
||||
"Timeout (in seconds)": "Timeout (in seconds)",
|
||||
"The module / element type": "The module / element type",
|
||||
"Add new fields to be created in the new record": "Add new fields to be created in the new record",
|
||||
"The record's id": "The record's id",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Enter the query statement, e.g. SELECT count(*) FROM Contacts;",
|
||||
"Enter your filter criteria": "Enter your filter criteria",
|
||||
"Enter the maximum number of records to return.": "Enter the maximum number of records to return.",
|
||||
"Select the HTTP method you want to use": "Select the HTTP method you want to use",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Enter the desired request headers. Skip the authorization headers",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string",
|
||||
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
|
||||
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
|
||||
"GET": "GET",
|
||||
"POST": "POST",
|
||||
"PUT": "PUT",
|
||||
"PATCH": "PATCH",
|
||||
"DELETE": "DELETE",
|
||||
"HEAD": "HEAD",
|
||||
"New or Updated Record": "New or Updated Record",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Triggers when a new record is introduced or a record is updated.",
|
||||
"Watch By": "Watch By",
|
||||
"Sync Scope": "Sync Scope",
|
||||
"Column to watch for trigger": "Column to watch for trigger",
|
||||
"Records visibility scope for sync": "Records visibility scope for sync",
|
||||
"Created Time": "Created Time",
|
||||
"Modified Time": "Modified Time",
|
||||
"Application (all records)": "Application (all records)",
|
||||
"User's groups": "User's groups",
|
||||
"User only": "User only"
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"Vtiger": "Vtiger",
|
||||
"CRM software for sales, marketing, and support teams": "CRM software for sales, marketing, and support teams",
|
||||
"VTiger Instance URL": "VTiger Instance URL",
|
||||
"Username": "Username",
|
||||
"Access Key": "Access Key",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Enter your username/email",
|
||||
"Enter your access Key": "Enter your access Key",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Create Record",
|
||||
"Get Record": "Get Record",
|
||||
"Update Record": "Update Record",
|
||||
"Delete Record": "Delete Record",
|
||||
"Query Records": "Query Records",
|
||||
"Search Records": "Search Records",
|
||||
"Custom API Call": "Custom API Call",
|
||||
"Create a Record": "Create a Record",
|
||||
"Get a Record by value": "Get a Record by value",
|
||||
"Update a Record": "Update a Record",
|
||||
"Delete a Record": "Delete a Record",
|
||||
"Query records by SQL statement.": "Query records by SQL statement.",
|
||||
"Search for a record.": "Search for a record.",
|
||||
"Performs an arbitrary authorized API call. ": "Performs an arbitrary authorized API call. ",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Record Fields",
|
||||
"Id": "Id",
|
||||
"Query": "Query",
|
||||
"Search Fields": "Search Fields",
|
||||
"Limit": "Limit",
|
||||
"Http Method": "Http Method",
|
||||
"Headers": "Headers",
|
||||
"Data": "Data",
|
||||
"The module / element type": "The module / element type",
|
||||
"Add new fields to be created in the new record": "Add new fields to be created in the new record",
|
||||
"The record's id": "The record's id",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Enter the query statement, e.g. SELECT count(*) FROM Contacts;",
|
||||
"Enter your filter criteria": "Enter your filter criteria",
|
||||
"Enter the maximum number of records to return.": "Enter the maximum number of records to return.",
|
||||
"Select the HTTP method you want to use": "Select the HTTP method you want to use",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Enter the desired request headers. Skip the authorization headers",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string",
|
||||
"Accounts": "Accounts",
|
||||
"Assets": "Assets",
|
||||
"CompanyDetails": "CompanyDetails",
|
||||
"Contacts": "Contacts",
|
||||
"Currency": "Currency",
|
||||
"DocumentFolders": "DocumentFolders",
|
||||
"Documents": "Documents",
|
||||
"Emails": "Emails",
|
||||
"Events": "Events",
|
||||
"Faq": "Faq",
|
||||
"Groups": "Groups",
|
||||
"HelpDesk": "HelpDesk",
|
||||
"Invoice": "Invoice",
|
||||
"Leads": "Leads",
|
||||
"LineItem": "LineItem",
|
||||
"ModComments": "ModComments",
|
||||
"Potentials": "Potentials",
|
||||
"PriceBooks": "PriceBooks",
|
||||
"Products": "Products",
|
||||
"ProductTaxes": "ProductTaxes",
|
||||
"Project": "Project",
|
||||
"ProjectMilestone": "ProjectMilestone",
|
||||
"ProjectTask": "ProjectTask",
|
||||
"PurchaseOrder": "PurchaseOrder",
|
||||
"Quotes": "Quotes",
|
||||
"SalesOrder": "SalesOrder",
|
||||
"ServiceContracts": "ServiceContracts",
|
||||
"Services": "Services",
|
||||
"SLA": "SLA",
|
||||
"Tax": "Tax",
|
||||
"Users": "Users",
|
||||
"Vendors": "Vendors",
|
||||
"GET": "GET",
|
||||
"POST": "POST",
|
||||
"New or Updated Record": "New or Updated Record",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Triggers when a new record is introduced or a record is updated.",
|
||||
"Watch By": "Watch By",
|
||||
"Column to watch for trigger": "Column to watch for trigger",
|
||||
"Created Time": "Created Time",
|
||||
"Modified Time": "Modified Time"
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"CRM software for sales, marketing, and support teams": "CRM software for sales, marketing, and support teams",
|
||||
"VTiger Instance URL": "VTiger Instance URL",
|
||||
"Username": "用户名",
|
||||
"Access Key": "Access Key",
|
||||
"For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default": "For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default",
|
||||
"Enter your username/email": "Enter your username/email",
|
||||
"Enter your access Key": "Enter your access Key",
|
||||
"\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a passwor": "\nTo obtain your Access Key, follow these steps:\n\n1. Login to Vtiger CRM:\nOpen a web browser and log in to your Vtiger CRM instance.\n\n2. Navigate to User Profile:\nIn the top right corner, click on your profile name.\nSelect \"My Preferences.\"\n\n3. The system will generate an access key for you.\nCopy and securely store the access key. This key will be used for authentication when making API requests.\nNote:\n\nAccess keys are sensitive information, and they should be kept secure.\nTreat the access key like a password. Do not share it publicly or expose it in an insecure manner.\n",
|
||||
"Create Record": "Create Record",
|
||||
"Get Record": "Get Record",
|
||||
"Update Record": "Update Record",
|
||||
"Delete Record": "Delete Record",
|
||||
"Query Records": "Query Records",
|
||||
"Search Records": "Search Records",
|
||||
"Custom API Call (Deprecated)": "Custom API Call (Deprecated)",
|
||||
"Custom API Call": "自定义 API 呼叫",
|
||||
"Create a Record": "Create a Record",
|
||||
"Get a Record by value": "Get a Record by value",
|
||||
"Update a Record": "Update a Record",
|
||||
"Delete a Record": "Delete a Record",
|
||||
"Query records by SQL statement.": "Query records by SQL statement.",
|
||||
"Search for a record.": "Search for a record.",
|
||||
"Performs an arbitrary authorized API call. (Deprecated)": "Performs an arbitrary authorized API call. (Deprecated)",
|
||||
"Make a custom API call to a specific endpoint": "将一个自定义 API 调用到一个特定的终点",
|
||||
"Module Type": "Module Type",
|
||||
"Record Fields": "Record Fields",
|
||||
"Id": "Id",
|
||||
"Query": "Query",
|
||||
"Search Fields": "Search Fields",
|
||||
"Limit": "Limit",
|
||||
"Http Method": "Http Method",
|
||||
"URL": "URL",
|
||||
"URL Path (deprecated)": "URL Path (deprecated)",
|
||||
"Headers": "信头",
|
||||
"Data": "Data",
|
||||
"Method": "方法",
|
||||
"Query Parameters": "查询参数",
|
||||
"Body": "正文内容",
|
||||
"Response is Binary ?": "Response is Binary ?",
|
||||
"No Error on Failure": "失败时没有错误",
|
||||
"Timeout (in seconds)": "超时(秒)",
|
||||
"The module / element type": "The module / element type",
|
||||
"Add new fields to be created in the new record": "Add new fields to be created in the new record",
|
||||
"The record's id": "The record's id",
|
||||
"Enter the query statement, e.g. SELECT count(*) FROM Contacts;": "Enter the query statement, e.g. SELECT count(*) FROM Contacts;",
|
||||
"Enter your filter criteria": "Enter your filter criteria",
|
||||
"Enter the maximum number of records to return.": "Enter the maximum number of records to return.",
|
||||
"Select the HTTP method you want to use": "Select the HTTP method you want to use",
|
||||
"Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.": "Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.",
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)": "Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)",
|
||||
"Enter the desired request headers. Skip the authorization headers": "Enter the desired request headers. Skip the authorization headers",
|
||||
"Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string": "Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string",
|
||||
"Authorization headers are injected automatically from your connection.": "授权头自动从您的连接中注入。",
|
||||
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
|
||||
"GET": "获取",
|
||||
"POST": "帖子",
|
||||
"PUT": "弹出",
|
||||
"PATCH": "PATCH",
|
||||
"DELETE": "删除",
|
||||
"HEAD": "黑色",
|
||||
"New or Updated Record": "New or Updated Record",
|
||||
"Triggers when a new record is introduced or a record is updated.": "Triggers when a new record is introduced or a record is updated.",
|
||||
"Watch By": "Watch By",
|
||||
"Sync Scope": "Sync Scope",
|
||||
"Column to watch for trigger": "Column to watch for trigger",
|
||||
"Records visibility scope for sync": "Records visibility scope for sync",
|
||||
"Created Time": "Created Time",
|
||||
"Modified Time": "Modified Time",
|
||||
"Application (all records)": "Application (all records)",
|
||||
"User's groups": "User's groups",
|
||||
"User only": "User only"
|
||||
}
|
||||
133
activepieces-fork/packages/pieces/community/vtiger/src/index.ts
Normal file
133
activepieces-fork/packages/pieces/community/vtiger/src/index.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import {
|
||||
createPiece,
|
||||
PieceAuth,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { PieceCategory } from '@activepieces/shared';
|
||||
import { createRecord } from './lib/actions/create-record';
|
||||
import { deleteRecord } from './lib/actions/delete-record';
|
||||
import { getRecord } from './lib/actions/get-record';
|
||||
import { makeAPICall } from './lib/actions/make-api-call';
|
||||
import { searchRecords } from './lib/actions/search-record';
|
||||
import { updateRecord } from './lib/actions/update-record';
|
||||
import { instanceLogin, isBaseUrl } from './lib/common';
|
||||
import { newOrUpdatedRecord } from './lib/triggers/new-or-updated-record';
|
||||
import { queryRecords } from './lib/actions/query-records';
|
||||
import { createCustomApiCallAction } from '@activepieces/pieces-common';
|
||||
|
||||
const markdownProperty = `
|
||||
To obtain your Access Key, follow these steps:
|
||||
|
||||
1. Login to Vtiger CRM:
|
||||
Open a web browser and log in to your Vtiger CRM instance.
|
||||
|
||||
2. Navigate to User Profile:
|
||||
In the top right corner, click on your profile name.
|
||||
Select "My Preferences."
|
||||
|
||||
3. The system will generate an access key for you.
|
||||
Copy and securely store the access key. This key will be used for authentication when making API requests.
|
||||
Note:
|
||||
|
||||
Access keys are sensitive information, and they should be kept secure.
|
||||
Treat the access key like a password. Do not share it publicly or expose it in an insecure manner.
|
||||
`;
|
||||
|
||||
export const vtigerAuth = PieceAuth.CustomAuth({
|
||||
description: markdownProperty,
|
||||
props: {
|
||||
instance_url: Property.ShortText({
|
||||
displayName: 'VTiger Instance URL',
|
||||
description:
|
||||
'For the instance URL, add the url without the endpoint. For example enter https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default',
|
||||
required: true,
|
||||
}),
|
||||
username: Property.ShortText({
|
||||
displayName: 'Username',
|
||||
description: 'Enter your username/email',
|
||||
required: true,
|
||||
}),
|
||||
password: PieceAuth.SecretText({
|
||||
displayName: 'Access Key',
|
||||
description: 'Enter your access Key',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
validate: async ({ auth }) => {
|
||||
const { instance_url, username, password } = auth;
|
||||
|
||||
try {
|
||||
if (!isBaseUrl(instance_url)) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
'Please ensure that the website is valid and does not contain any paths, for example, https://<instance>.od2.vtiger.com ',
|
||||
};
|
||||
}
|
||||
if (instance_url.endsWith('/')) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
'Please enter the URL without a trailing slash. E.g. https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/',
|
||||
};
|
||||
}
|
||||
if (instance_url.includes('restapi/')) {
|
||||
return {
|
||||
valid: false,
|
||||
error:
|
||||
'Add the url without the endpoint. For example add https://<instance>.od2.vtiger.com instead of https://<instance>.od2.vtiger.com/restapi/v1/vtiger/default',
|
||||
};
|
||||
}
|
||||
|
||||
const instance = await instanceLogin(instance_url, username, password);
|
||||
if (!instance) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Invalid credentials, check and try again.',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Unexpected error. Please check your credentials and try again.',
|
||||
};
|
||||
}
|
||||
},
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const vtiger = createPiece({
|
||||
displayName: 'Vtiger',
|
||||
description: 'CRM software for sales, marketing, and support teams',
|
||||
auth: vtigerAuth,
|
||||
minimumSupportedRelease: '0.30.0',
|
||||
logoUrl: 'https://cdn.activepieces.com/pieces/vtiger.png',
|
||||
categories: [PieceCategory.SALES_AND_CRM],
|
||||
authors: ["kanarelo","kishanprmr","abuaboud", "privatestefans"],
|
||||
actions: [
|
||||
createRecord,
|
||||
getRecord,
|
||||
updateRecord,
|
||||
deleteRecord,
|
||||
queryRecords,
|
||||
searchRecords,
|
||||
makeAPICall, // deprecated
|
||||
createCustomApiCallAction({
|
||||
auth: vtigerAuth,
|
||||
baseUrl: (auth: any) => `${auth.instance_url}/restapi/v1/vtiger/default`,
|
||||
authMapping: async (auth: any) => {
|
||||
const { username, password } = auth;
|
||||
return {
|
||||
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString(
|
||||
'base64'
|
||||
)}`,
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
triggers: [newOrUpdatedRecord],
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import { instanceLogin, recordProperty } from '../common';
|
||||
import { elementTypeProperty } from '../common';
|
||||
|
||||
//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs
|
||||
//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual
|
||||
|
||||
export const createRecord = createAction({
|
||||
name: 'create_record',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Create Record',
|
||||
description: 'Create a Record',
|
||||
props: {
|
||||
elementType: elementTypeProperty,
|
||||
record: recordProperty(),
|
||||
},
|
||||
async run({ propsValue: { elementType, record }, auth }) {
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
|
||||
if (instance !== null) {
|
||||
const response = await httpClient.sendRequest<Record<string, unknown>[]>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
operation: 'create',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType: elementType,
|
||||
element: JSON.stringify(record),
|
||||
},
|
||||
});
|
||||
|
||||
console.debug({
|
||||
operation: 'create',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType: elementType,
|
||||
element: JSON.stringify(record),
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import { instanceLogin, recordIdProperty } from '../common';
|
||||
import { elementTypeProperty } from '../common';
|
||||
|
||||
//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs
|
||||
//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual
|
||||
|
||||
export const deleteRecord = createAction({
|
||||
name: 'delete_record',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Delete Record',
|
||||
description: 'Delete a Record',
|
||||
props: {
|
||||
elementType: elementTypeProperty,
|
||||
record: recordIdProperty(),
|
||||
},
|
||||
async run({
|
||||
propsValue: { elementType, record },
|
||||
auth
|
||||
}) {
|
||||
const instance = await instanceLogin(auth.props.instance_url, auth.props.username, auth.props.password);
|
||||
|
||||
if (instance !== null) {
|
||||
const response = await httpClient.sendRequest<Record<string, unknown>[]>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
operation: 'delete',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType,
|
||||
id: record['id'],
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import { createAction } from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import { instanceLogin, recordIdProperty } from '../common';
|
||||
import { elementTypeProperty } from '../common';
|
||||
|
||||
//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs
|
||||
//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual
|
||||
|
||||
export const getRecord = createAction({
|
||||
name: 'get_record',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Get Record',
|
||||
description: 'Get a Record by value',
|
||||
props: {
|
||||
elementType: elementTypeProperty,
|
||||
record: recordIdProperty(),
|
||||
},
|
||||
async run({
|
||||
propsValue: { elementType, record },
|
||||
auth
|
||||
}) {
|
||||
const instance = await instanceLogin(auth.props.instance_url, auth.props.username, auth.props.password);
|
||||
|
||||
if (instance !== null) {
|
||||
const response = await httpClient.sendRequest<Record<string, unknown>[]>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
operation: 'retrieve',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType: elementType as unknown as string,
|
||||
...record,
|
||||
},
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,195 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import { instanceLogin } from '../common';
|
||||
import {
|
||||
AuthenticationType,
|
||||
HttpHeaders,
|
||||
HttpMessageBody,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs
|
||||
//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual
|
||||
|
||||
export const makeAPICall = createAction({
|
||||
name: 'make_api_call',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Custom API Call (Deprecated)',
|
||||
description: 'Performs an arbitrary authorized API call. (Deprecated)',
|
||||
props: {
|
||||
method: Property.StaticDropdown<HttpMethod>({
|
||||
displayName: 'Http Method',
|
||||
description: 'Select the HTTP method you want to use',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'GET', value: HttpMethod.GET },
|
||||
{ label: 'POST', value: HttpMethod.POST },
|
||||
{ label: 'PUT', value: HttpMethod.PUT },
|
||||
{ label: 'PATCH', value: HttpMethod.PATCH },
|
||||
{ label: 'DELETE', value: HttpMethod.DELETE },
|
||||
],
|
||||
},
|
||||
}),
|
||||
url: Property.ShortText({
|
||||
displayName: 'URL',
|
||||
description:
|
||||
'Absolute URL or path. If a relative path is provided (e.g., /me, /listtypes, /describe), it will be called against the REST base.',
|
||||
required: false,
|
||||
}),
|
||||
urlPath: Property.ShortText({
|
||||
displayName: 'URL Path (deprecated)',
|
||||
description:
|
||||
"Deprecated. Use 'URL' instead. API endpoint's URL path (example: /me, /listtypes, /describe)",
|
||||
required: false,
|
||||
}),
|
||||
headers: Property.Json({
|
||||
displayName: 'Headers',
|
||||
description: `Enter the desired request headers. Skip the authorization headers`,
|
||||
required: false,
|
||||
defaultValue: {},
|
||||
}),
|
||||
data: Property.Json({
|
||||
displayName: 'Data',
|
||||
description: `Enter the data to pass. if its POST, it will be sent as body data, and if GET, as query string`,
|
||||
required: false,
|
||||
defaultValue: {},
|
||||
}),
|
||||
},
|
||||
async run({ propsValue, auth }) {
|
||||
const method = propsValue.method ?? HttpMethod.GET;
|
||||
const urlPath = propsValue.urlPath;
|
||||
const url = propsValue.url;
|
||||
|
||||
if (urlPath && !urlPath.startsWith('/')) {
|
||||
return {
|
||||
error:
|
||||
'URL path must start with a slash, example: /me, /listtypes, /describe',
|
||||
};
|
||||
}
|
||||
|
||||
let finalUrl = `${auth.props.instance_url}/webservice.php`;
|
||||
let useRestAuth = false;
|
||||
|
||||
if (url) {
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
finalUrl = url;
|
||||
} else if (url.startsWith('/')) {
|
||||
finalUrl = `${auth.props.instance_url}/restapi/v1/vtiger/default${url}`;
|
||||
useRestAuth = true;
|
||||
} else {
|
||||
finalUrl = `${auth.props.instance_url}/restapi/v1/vtiger/default/${url}`;
|
||||
useRestAuth = true;
|
||||
}
|
||||
} else if (urlPath) {
|
||||
finalUrl = `${auth.props.instance_url}/restapi/v1/vtiger/default${urlPath}`;
|
||||
useRestAuth = true;
|
||||
}
|
||||
|
||||
const normalizeHeaders = (h: unknown): HttpHeaders => {
|
||||
const out: HttpHeaders = {};
|
||||
if (h && typeof h === 'object' && !Array.isArray(h)) {
|
||||
for (const [k, v] of Object.entries(h as Record<string, unknown>)) {
|
||||
if (v === undefined || v === null) {
|
||||
out[k] = undefined;
|
||||
} else if (Array.isArray(v)) {
|
||||
out[k] = (v as unknown[]).map((x) => String(x));
|
||||
} else if (typeof v === 'string') {
|
||||
out[k] = v;
|
||||
} else {
|
||||
out[k] = String(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const headers: HttpHeaders = normalizeHeaders(propsValue.headers);
|
||||
|
||||
if (useRestAuth) {
|
||||
// Default JSON for REST when not GET and no explicit content-type provided
|
||||
if (
|
||||
method !== HttpMethod.GET &&
|
||||
!Object.keys(headers).some(
|
||||
(k) => k.toLowerCase() === 'content-type'
|
||||
)
|
||||
) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
} else {
|
||||
// webservice.php defaults to urlencoded for POST operations
|
||||
if (
|
||||
method !== HttpMethod.GET &&
|
||||
!Object.keys(headers).some(
|
||||
(k) => k.toLowerCase() === 'content-type'
|
||||
)
|
||||
) {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
}
|
||||
|
||||
const httpRequest: HttpRequest<HttpMessageBody> = {
|
||||
url: finalUrl,
|
||||
method,
|
||||
headers,
|
||||
};
|
||||
|
||||
let data: Record<string, unknown> = propsValue.data ?? {};
|
||||
|
||||
const toQueryParams = (obj: Record<string, unknown>): Record<string, string> => {
|
||||
const qp: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(obj ?? {})) {
|
||||
if (v === undefined || v === null) continue;
|
||||
qp[k] = typeof v === 'string' ? v : JSON.stringify(v);
|
||||
}
|
||||
return qp;
|
||||
};
|
||||
|
||||
if (useRestAuth) {
|
||||
httpRequest.authentication = {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.username,
|
||||
password: auth.props.password,
|
||||
};
|
||||
} else {
|
||||
const vtigerInstance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (vtigerInstance === null) return;
|
||||
|
||||
data = {
|
||||
sessionName: vtigerInstance.sessionId ?? vtigerInstance.sessionName,
|
||||
...(propsValue.data ?? {}),
|
||||
};
|
||||
}
|
||||
|
||||
if (method === HttpMethod.GET) {
|
||||
httpRequest['queryParams'] = toQueryParams(data);
|
||||
} else {
|
||||
// For REST with JSON default, send raw object; else url-encode
|
||||
const contentType = Object.entries(headers).find(([k]) => k.toLowerCase() === 'content-type')?.[1];
|
||||
const ct = Array.isArray(contentType) ? contentType[0] : contentType;
|
||||
if (useRestAuth && ct === 'application/json') {
|
||||
httpRequest['body'] = data;
|
||||
} else {
|
||||
httpRequest['body'] = toQueryParams(data);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest<Record<string, unknown>[]>(
|
||||
httpRequest
|
||||
);
|
||||
|
||||
if ([200, 201].includes(response.status)) {
|
||||
return response.body;
|
||||
}
|
||||
|
||||
return {
|
||||
error: 'Unexpected outcome!',
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import {
|
||||
Operation,
|
||||
elementTypeProperty,
|
||||
instanceLogin,
|
||||
prepareHttpRequest,
|
||||
} from '../common';
|
||||
import { httpClient } from '@activepieces/pieces-common';
|
||||
|
||||
//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs
|
||||
//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual
|
||||
|
||||
export const queryRecords = createAction({
|
||||
name: 'query_records',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Query Records',
|
||||
description: 'Query records by SQL statement.',
|
||||
props: {
|
||||
query: Property.LongText({
|
||||
displayName: 'Query',
|
||||
description:
|
||||
'Enter the query statement, e.g. SELECT count(*) FROM Contacts;',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run({ propsValue, auth }) {
|
||||
const vtigerInstance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (vtigerInstance === null) return;
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, unknown>[];
|
||||
}>(
|
||||
prepareHttpRequest(
|
||||
auth.props.instance_url,
|
||||
vtigerInstance.sessionId ?? vtigerInstance.sessionName,
|
||||
'query' as Operation,
|
||||
{ query: propsValue.query }
|
||||
)
|
||||
);
|
||||
|
||||
if (response.body.success) {
|
||||
return response.body.result;
|
||||
} else {
|
||||
console.debug(response);
|
||||
|
||||
return {
|
||||
error: 'Unexpected outcome!',
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
createAction,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import {
|
||||
VTigerAuthValue,
|
||||
queryRecords,
|
||||
countRecords,
|
||||
elementTypeProperty,
|
||||
generateElementFields,
|
||||
instanceLogin,
|
||||
} from '../common';
|
||||
|
||||
//Docs: https://code.vtiger.com/vtiger/vtigercrm-manual/-/wikis/Webservice-Docs
|
||||
//Extra: https://help.vtiger.com/article/147111249-Rest-API-Manual
|
||||
|
||||
export const searchRecords = createAction({
|
||||
name: 'search_records',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Search Records',
|
||||
description: 'Search for a record.',
|
||||
props: {
|
||||
elementType: elementTypeProperty,
|
||||
fields: Property.DynamicProperties({
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Search Fields',
|
||||
description: 'Enter your filter criteria',
|
||||
required: true,
|
||||
refreshers: ['elementType'],
|
||||
props: async ({ auth, elementType }) => {
|
||||
if (!auth || !elementType) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
|
||||
if (instance === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return generateElementFields(
|
||||
auth,
|
||||
elementType as unknown as string,
|
||||
{},
|
||||
true
|
||||
);
|
||||
},
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description: 'Enter the maximum number of records to return.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run({ propsValue, auth }) {
|
||||
const vtigerInstance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (vtigerInstance === null) return;
|
||||
|
||||
const count = await countRecords(auth, propsValue.elementType as string);
|
||||
if (count > 0) {
|
||||
const records: Record<string, unknown>[] = await queryRecords(
|
||||
auth,
|
||||
propsValue.elementType as string,
|
||||
0,
|
||||
count
|
||||
);
|
||||
|
||||
const filtered = records.filter((record) => {
|
||||
return Object.entries(propsValue['fields']).every(([key, value]) => {
|
||||
const recordValue = record[key];
|
||||
if (typeof value === 'string') {
|
||||
const rv = typeof recordValue === 'string' ? recordValue : String(recordValue ?? '');
|
||||
return rv.toLowerCase().includes(value.toLowerCase());
|
||||
}
|
||||
return recordValue === value;
|
||||
});
|
||||
});
|
||||
|
||||
return propsValue.limit ? filtered.slice(0, propsValue.limit) : filtered;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
|
||||
import {
|
||||
DropdownState,
|
||||
DynamicPropsValue,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
createAction,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import {
|
||||
instanceLogin,
|
||||
VTigerAuthValue,
|
||||
Modules,
|
||||
Field,
|
||||
getRecordReference,
|
||||
} from '../common';
|
||||
import { elementTypeProperty } from '../common';
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const updateRecord = createAction({
|
||||
name: 'update_record',
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Update Record',
|
||||
description: 'Update a Record',
|
||||
props: {
|
||||
elementType: elementTypeProperty,
|
||||
id: Property.Dropdown({
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Id',
|
||||
description: "The record's id",
|
||||
required: true,
|
||||
refreshers: ['elementType'],
|
||||
options: async ({ auth, elementType }) => {
|
||||
if (!auth || !elementType) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder:
|
||||
'Please select the element type and setup authentication to continue.',
|
||||
};
|
||||
}
|
||||
|
||||
let c = 0;
|
||||
let instance = null;
|
||||
while (!instance && c < 3) {
|
||||
instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
await sleep(1500);
|
||||
c++;
|
||||
}
|
||||
|
||||
if (!instance) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Authentication failed.',
|
||||
};
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, string>[];
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
operation: 'query',
|
||||
elementType: elementType as unknown as string,
|
||||
query: `SELECT * FROM ${elementType} LIMIT 100;`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.body.success)
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Request unsuccessful.',
|
||||
};
|
||||
|
||||
const element: string = elementType as unknown as string;
|
||||
|
||||
return {
|
||||
options: await Promise.all(response.body.result.map(async (record) => {
|
||||
return {
|
||||
label: await Modules[element]?.(record) || record['id'],
|
||||
value: record['id'] as string,
|
||||
};
|
||||
})),
|
||||
disabled: false,
|
||||
};
|
||||
},
|
||||
}),
|
||||
record: Property.DynamicProperties({
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Record Fields',
|
||||
description: 'Add new fields to be created in the new record',
|
||||
required: true,
|
||||
refreshers: ['id', 'elementType'],
|
||||
props: async ({ auth, id, elementType }) => {
|
||||
if (!auth || !elementType) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (!instance) return {};
|
||||
|
||||
let defaultValue: Record<string, unknown>;
|
||||
if (id && 'id') {
|
||||
const retrieve_response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, unknown>;
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
operation: 'retrieve',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType: elementType as unknown as string,
|
||||
id: id as unknown as string,
|
||||
},
|
||||
});
|
||||
if (retrieve_response.body.result) {
|
||||
defaultValue = retrieve_response.body.result;
|
||||
} else {
|
||||
defaultValue = {};
|
||||
}
|
||||
} else {
|
||||
defaultValue = {};
|
||||
}
|
||||
|
||||
const describe_response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: { fields: Field[] };
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
operation: 'describe',
|
||||
elementType: elementType as unknown as string,
|
||||
},
|
||||
});
|
||||
|
||||
const fields: DynamicPropsValue = {};
|
||||
|
||||
if (describe_response.body.success) {
|
||||
let limit = 30; // Limit to show 30 input property, more than this will cause frontend unresponsive
|
||||
|
||||
const generateField = async (field: Field) => {
|
||||
const params = {
|
||||
displayName: field.label,
|
||||
description: `Field ${field.name} of object type ${elementType}`,
|
||||
required: field.mandatory,
|
||||
};
|
||||
|
||||
if (
|
||||
[
|
||||
'string',
|
||||
'text',
|
||||
'mediumtext',
|
||||
'phone',
|
||||
'url',
|
||||
'email',
|
||||
].includes(field.type.name)
|
||||
) {
|
||||
if (['mediumtext', 'url'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.LongText({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
});
|
||||
} else {
|
||||
fields[field.name] = Property.ShortText({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
['picklist', 'reference', 'owner'].includes(field.type.name)
|
||||
) {
|
||||
let options: DropdownState<string>;
|
||||
if (field.type.name === 'picklist') {
|
||||
options = {
|
||||
disabled: false,
|
||||
options: field.type.picklistValues ?? [],
|
||||
};
|
||||
} else if (field.type.name === 'owner') {
|
||||
options = await getRecordReference(
|
||||
auth,
|
||||
['Users']
|
||||
);
|
||||
} else if (field.type.refersTo) {
|
||||
options = await getRecordReference(
|
||||
auth,
|
||||
field.type.refersTo ?? []
|
||||
);
|
||||
} else {
|
||||
options = { disabled: false, options: [] };
|
||||
}
|
||||
|
||||
fields[field.name] = Property.StaticDropdown({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
options,
|
||||
});
|
||||
} else if (
|
||||
['double', 'integer', 'currency'].includes(field.type.name)
|
||||
) {
|
||||
fields[field.name] = Property.Number({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as number,
|
||||
});
|
||||
} else if (['boolean'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.Checkbox({
|
||||
displayName: field.label,
|
||||
description: `The fields to fill in the object type ${elementType}`,
|
||||
required: field.mandatory,
|
||||
defaultValue: defaultValue?.[field.name] ? true : false,
|
||||
});
|
||||
} else if (['date', 'datetime', 'time'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.DateTime({
|
||||
displayName: field.label,
|
||||
description: `The fields to fill in the object type ${elementType}`,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
required: field.mandatory,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const skipFields = [
|
||||
'id',
|
||||
];
|
||||
|
||||
// Prioritize mandatory fields
|
||||
for (const field of describe_response.body.result.fields) {
|
||||
if (skipFields.includes(field.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.mandatory) {
|
||||
await generateField(field);
|
||||
limit--;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's add the rest...
|
||||
for (const field of describe_response.body.result.fields) {
|
||||
if (skipFields.includes(field.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the rest of field to avoid unresponsive frontend
|
||||
if (limit < 0) break;
|
||||
|
||||
if (!field.mandatory) {
|
||||
await generateField(field);
|
||||
limit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run({ propsValue: { elementType, id, record }, auth }) {
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
|
||||
if (instance !== null) {
|
||||
const response = await httpClient.sendRequest<Record<string, unknown>[]>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
operation: 'update',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType: elementType,
|
||||
element: JSON.stringify({
|
||||
id: id,
|
||||
...record,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
console.debug({
|
||||
operation: 'update',
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
elementType: elementType,
|
||||
element: JSON.stringify({
|
||||
id: id,
|
||||
...record,
|
||||
}),
|
||||
});
|
||||
|
||||
return response.body;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,635 @@
|
||||
import {
|
||||
AuthenticationType,
|
||||
HttpHeaders,
|
||||
HttpMessageBody,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
DropdownState,
|
||||
DynamicPropsValue,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import * as crypto from 'crypto-js';
|
||||
import { Challenge, Instance } from './models';
|
||||
import { vtigerAuth } from '..';
|
||||
|
||||
export const isBaseUrl = (urlString: string): boolean => {
|
||||
try {
|
||||
const url = new URL(urlString);
|
||||
return !url.pathname || url.pathname === '/';
|
||||
} catch (error) {
|
||||
// Handle invalid URLs here, e.g., return false or throw an error
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const md5 = (contents: string) => crypto.MD5(contents).toString();
|
||||
export const calculateAuthKey = (
|
||||
challengeToken: string,
|
||||
accessKey: string
|
||||
): string => crypto.MD5(challengeToken + accessKey).toString(crypto.enc.Hex);
|
||||
|
||||
export const instanceLogin = async (
|
||||
instance_url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
debug = false
|
||||
) => {
|
||||
const endpoint = `${instance_url}/webservice.php`;
|
||||
const challenge = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Challenge;
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${endpoint}?operation=getchallenge&username=${username}`,
|
||||
});
|
||||
|
||||
const accessKey = calculateAuthKey(challenge.body.result.token, password);
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Instance;
|
||||
}>({
|
||||
method: HttpMethod.POST,
|
||||
url: `${endpoint}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: {
|
||||
operation: 'login',
|
||||
username,
|
||||
accessKey,
|
||||
},
|
||||
});
|
||||
|
||||
if (debug) {
|
||||
console.debug('>>>>>>>>>>>> LOGIN', response.body, {
|
||||
method: HttpMethod.POST,
|
||||
url: `${endpoint}`,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
body: {
|
||||
operation: 'login',
|
||||
username,
|
||||
accessKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (response.body.success) {
|
||||
return response.body.result;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export type Operation =
|
||||
| 'create'
|
||||
| 'retrieve'
|
||||
| 'delete'
|
||||
| 'update'
|
||||
| 'query'
|
||||
| 'listtypes';
|
||||
|
||||
export const Operations: Record<Operation, BodyParams> = {
|
||||
listtypes: {
|
||||
method: HttpMethod.GET,
|
||||
},
|
||||
create: {
|
||||
method: HttpMethod.POST,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
retrieve: {
|
||||
method: HttpMethod.GET,
|
||||
},
|
||||
delete: {
|
||||
method: HttpMethod.POST,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
update: {
|
||||
method: HttpMethod.POST,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
query: {
|
||||
method: HttpMethod.GET,
|
||||
},
|
||||
};
|
||||
|
||||
export const prepareHttpRequest = (
|
||||
instanceUrl: string,
|
||||
sessionName: string,
|
||||
operation: Operation,
|
||||
record: Record<string, string>
|
||||
) => {
|
||||
const data: Record<string, string> = {
|
||||
operation,
|
||||
sessionName,
|
||||
...record,
|
||||
};
|
||||
if ('element' in record) data['element'] = JSON.stringify(record['element']);
|
||||
|
||||
const httpRequest: HttpRequest<HttpMessageBody> = {
|
||||
url: `${instanceUrl}/webservice.php`,
|
||||
method: Operations[operation].method,
|
||||
headers: Operations[operation].headers,
|
||||
};
|
||||
|
||||
if (Operations[operation].method === HttpMethod.GET) {
|
||||
httpRequest['queryParams'] = data;
|
||||
} else if (Operations[operation].method === HttpMethod.POST) {
|
||||
httpRequest['body'] = data;
|
||||
}
|
||||
|
||||
return httpRequest;
|
||||
};
|
||||
|
||||
interface BodyParams {
|
||||
method: HttpMethod;
|
||||
headers?: HttpHeaders;
|
||||
}
|
||||
|
||||
export const Modules: Record<string, (record: Record<string, string>) => Promise<string>> = {
|
||||
Contacts: async (record) => `${record['email']}`, // firstname,lastname
|
||||
Documents: async (record) => `${record['notes_title']}`, // title
|
||||
Faq: async (record) => `${record['faq_no']}`, // question
|
||||
// HelpDesk: async (record) => `${record['ticket_no']}`, // this module not exist
|
||||
Invoice: async (record) => `${record['invoice_no']}`, // subject
|
||||
Leads: async (record) => `${record['lead_no']}: ${record['firstname']} ${record['lastname']}`, // firstname,lastname
|
||||
LineItem: async (record) => `${record['productid']}`, // no label field
|
||||
ProductTaxes: async (record) => `#${record['taxid']} pid: ${record['productid']}`, // no label field
|
||||
// ProjectTask: async (record) => `${record['projecttaskname']}`, // this module not exist
|
||||
SalesOrder: async (record) => `${record['salesorder_no']}`, // subject
|
||||
Tax: async (record) => `${record['taxname']}`, // taxlabel
|
||||
Users: async (record) => `${record['user_name']}`, // first_name,last_name
|
||||
};
|
||||
|
||||
export async function refreshModules(auth: VTigerAuthValue){
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/restapi/v1/vtiger/default/listtypes?fieldTypeList=null`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.username,
|
||||
password: auth.props.password,
|
||||
},
|
||||
});
|
||||
|
||||
if(response.body.success !== true){
|
||||
throw new Error('Failed to retrieve module types');
|
||||
}
|
||||
|
||||
const types = response.body.result.types;
|
||||
for (let i = 0; i < types.length; i++) {
|
||||
const element = types[i];
|
||||
|
||||
let labelFields = '';
|
||||
let isModuleLabelUnknown = false;
|
||||
Modules[element] ??= async (record) => {
|
||||
if(labelFields !== '') return labelFields;
|
||||
if(isModuleLabelUnknown) return '';
|
||||
|
||||
const response = await httpClient.sendRequest({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/restapi/v1/vtiger/default/describe?elementType=${element}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.username,
|
||||
password: auth.props.password,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.body.success) return '';
|
||||
|
||||
const result = response.body.result;
|
||||
const lf = result.labelFields;
|
||||
if (Array.isArray(lf)) {
|
||||
labelFields = (lf[0] ?? '') as string;
|
||||
} else if (typeof lf === 'string') {
|
||||
labelFields = lf.includes(',') ? lf.split(',')[0] : lf;
|
||||
} else {
|
||||
labelFields = '';
|
||||
}
|
||||
|
||||
if(labelFields === '') {
|
||||
if(!result.fields?.length){
|
||||
isModuleLabelUnknown = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
labelFields = result.fields[0].name;
|
||||
}
|
||||
|
||||
return record[labelFields];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const elementTypeProperty = Property.Dropdown({
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Module Type',
|
||||
description: 'The module / element type',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async (props) => {
|
||||
const { auth } = props;
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
placeholder: 'Please setup authentication to continue',
|
||||
};
|
||||
}
|
||||
|
||||
await refreshModules(auth);
|
||||
|
||||
const modules = Object.keys(Modules).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
return {
|
||||
disabled: false,
|
||||
options: modules.map((module) => ({
|
||||
label: module,
|
||||
value: module,
|
||||
}))
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export interface Field {
|
||||
name: string;
|
||||
dblabel: string;
|
||||
label: string;
|
||||
default: string;
|
||||
mandatory: boolean;
|
||||
type: {
|
||||
name: string;
|
||||
length?: string;
|
||||
refersTo?: string[];
|
||||
picklistValues?: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export type VTigerAuthValue = AppConnectionValueForAuthProperty<typeof vtigerAuth>;
|
||||
|
||||
export const recordIdProperty = () =>
|
||||
Property.DynamicProperties({
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Record Fields',
|
||||
description: 'Add new fields to be created in the new record',
|
||||
required: true,
|
||||
refreshers: ['elementType'],
|
||||
props: async ({ auth, elementType }) => {
|
||||
if (!auth || !elementType) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (!instance) return {};
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, string>[];
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
operation: 'query',
|
||||
elementType: elementType as unknown as string,
|
||||
query: `SELECT * FROM ${elementType} LIMIT 100;`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.body.success) return {};
|
||||
|
||||
const fields: DynamicPropsValue = {};
|
||||
const _type: string = elementType as unknown as string;
|
||||
const _module: CallableFunction = Modules[_type];
|
||||
|
||||
fields['id'] = Property.StaticDropdown<string>({
|
||||
displayName: 'Id',
|
||||
description: "The record's id",
|
||||
required: true,
|
||||
options: {
|
||||
options: response.body.result.map((r) => ({
|
||||
label: _module?.(r) || r['id'],
|
||||
value: r['id'],
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
return fields;
|
||||
},
|
||||
});
|
||||
|
||||
export const FieldMapping = {
|
||||
autogenerated: Property.ShortText,
|
||||
string: Property.ShortText,
|
||||
text: Property.ShortText,
|
||||
double: Property.Number,
|
||||
integer: Property.Number,
|
||||
mediumtext: Property.LongText,
|
||||
phone: Property.LongText,
|
||||
url: Property.LongText,
|
||||
email: Property.LongText,
|
||||
picklist: Property.StaticDropdown,
|
||||
reference: Property.StaticDropdown,
|
||||
currency: Property.Number,
|
||||
boolean: Property.Checkbox,
|
||||
owner: Property.StaticDropdown,
|
||||
date: Property.DateTime,
|
||||
datetime: Property.DateTime,
|
||||
file: Property.File,
|
||||
time: Property.DateTime,
|
||||
};
|
||||
|
||||
export async function getRecordReference(
|
||||
auth: AppConnectionValueForAuthProperty<typeof vtigerAuth>,
|
||||
modules: string[]
|
||||
): Promise<DropdownState<string>> {
|
||||
const module = modules[0]; //Limit to the first reference for now
|
||||
const vtigerInstance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (vtigerInstance === null)
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
};
|
||||
|
||||
const httpRequest = prepareHttpRequest(
|
||||
auth.props.instance_url,
|
||||
vtigerInstance.sessionId ?? vtigerInstance.sessionName,
|
||||
'query' as Operation,
|
||||
{ query: `SELECT * FROM ${module};` }
|
||||
);
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, any>[];
|
||||
}>(httpRequest);
|
||||
|
||||
if (response.body.success) {
|
||||
return {
|
||||
disabled: false,
|
||||
options: await Promise.all(response.body.result.map(async (record) => {
|
||||
return {
|
||||
label: await Modules[module]?.(record) || record['id'],
|
||||
value: record['id'] as string,
|
||||
};
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disabled: true,
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
export const recordProperty = () =>
|
||||
Property.DynamicProperties({
|
||||
auth: vtigerAuth,
|
||||
displayName: 'Record Fields',
|
||||
description: 'Add new fields to be created in the new record',
|
||||
required: true,
|
||||
refreshers: ['elementType'],
|
||||
props: async ({ auth, id, elementType }) => {
|
||||
if (!auth || !elementType) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
return generateElementFields(
|
||||
auth,
|
||||
elementType as unknown as string,
|
||||
{}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const queryRecords = async (
|
||||
auth: AppConnectionValueForAuthProperty<typeof vtigerAuth>,
|
||||
elementType: string,
|
||||
page = 0,
|
||||
limit = 100
|
||||
) => {
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (!instance) return [];
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, unknown>[];
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
operation: 'query',
|
||||
elementType: elementType as unknown as string,
|
||||
query: `SELECT * FROM ${elementType} LIMIT ${page}, ${limit};`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.body.success) {
|
||||
return response.body.result;
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const countRecords = async (
|
||||
auth: VTigerAuthValue,
|
||||
elementType: string
|
||||
) => {
|
||||
const instance = await instanceLogin(
|
||||
auth.props.instance_url,
|
||||
auth.props.username,
|
||||
auth.props.password
|
||||
);
|
||||
if (!instance) return 0;
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: { count: string }[];
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/webservice.php`,
|
||||
queryParams: {
|
||||
sessionName: instance.sessionId ?? instance.sessionName,
|
||||
operation: 'query',
|
||||
elementType: elementType as unknown as string,
|
||||
query: `SELECT count(*) FROM ${elementType};`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.body.success) {
|
||||
return Number.parseInt(response.body.result[0].count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const generateElementFields = async (
|
||||
auth: VTigerAuthValue,
|
||||
elementType: string,
|
||||
defaultValue: Record<string, unknown>,
|
||||
skipMandatory = false
|
||||
): Promise<DynamicPropsValue> => {
|
||||
const describe_response = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: { fields: Field[] };
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${auth.props.instance_url}/restapi/v1/vtiger/default/describe`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.username,
|
||||
password: auth.props.password,
|
||||
},
|
||||
queryParams: {
|
||||
elementType: elementType,
|
||||
},
|
||||
});
|
||||
|
||||
const fields: DynamicPropsValue = {};
|
||||
|
||||
if (describe_response.body.success) {
|
||||
let limit = 30; // Limit to show 30 input property, more than this will cause frontend unresponsive
|
||||
|
||||
const generateField = async (field: Field) => {
|
||||
const params = {
|
||||
displayName: field.label,
|
||||
description: `Field ${field.name} of object type ${elementType}`,
|
||||
required: !skipMandatory ? field.mandatory : false,
|
||||
};
|
||||
|
||||
if (
|
||||
['string', 'text', 'mediumtext', 'phone', 'url', 'email'].includes(
|
||||
field.type.name
|
||||
)
|
||||
) {
|
||||
if (['mediumtext', 'url'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.LongText({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
});
|
||||
} else {
|
||||
fields[field.name] = Property.ShortText({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
});
|
||||
}
|
||||
} else if (['picklist', 'reference', 'owner'].includes(field.type.name)) {
|
||||
let options: DropdownState<string>;
|
||||
if (field.type.name === 'picklist') {
|
||||
options = {
|
||||
disabled: false,
|
||||
options: field.type.picklistValues ?? [],
|
||||
};
|
||||
} else if (field.type.name === 'owner') {
|
||||
options = await getRecordReference(
|
||||
auth,
|
||||
['Users']
|
||||
);
|
||||
} else if (field.type.refersTo) {
|
||||
options = await getRecordReference(
|
||||
auth,
|
||||
field.type.refersTo ?? []
|
||||
);
|
||||
} else {
|
||||
options = { disabled: false, options: [] };
|
||||
}
|
||||
|
||||
fields[field.name] = Property.StaticDropdown({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
options,
|
||||
});
|
||||
} else if (['double', 'integer', 'currency'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.Number({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as number,
|
||||
});
|
||||
} else if (['boolean'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.Checkbox({
|
||||
displayName: field.label,
|
||||
description: `The fields to fill in the object type ${elementType}`,
|
||||
required: !skipMandatory ? field.mandatory : false,
|
||||
defaultValue: defaultValue?.[field.name] ? true : false,
|
||||
});
|
||||
} else if (['date', 'datetime', 'time'].includes(field.type.name)) {
|
||||
fields[field.name] = Property.DateTime({
|
||||
displayName: field.label,
|
||||
description: `The fields to fill in the object type ${elementType}`,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
required: !skipMandatory ? field.mandatory : false,
|
||||
});
|
||||
} else if(params.required) {
|
||||
// Add the mandatory field for unknown input type, but with text input
|
||||
fields[field.name] = Property.ShortText({
|
||||
...params,
|
||||
defaultValue: defaultValue?.[field.name] as string,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const skipFields = [
|
||||
'id',
|
||||
'modifiedtime',
|
||||
'createdtime',
|
||||
'modifiedby',
|
||||
'created_user_id',
|
||||
];
|
||||
|
||||
// Prioritize mandatory fields
|
||||
for (const field of describe_response.body.result.fields) {
|
||||
if (skipFields.includes(field.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.mandatory) {
|
||||
await generateField(field);
|
||||
limit--;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's add the rest...
|
||||
for (const field of describe_response.body.result.fields) {
|
||||
if (skipFields.includes(field.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip the rest of field to avoid unresponsive frontend
|
||||
if (limit < 0) break;
|
||||
|
||||
if (!field.mandatory) {
|
||||
await generateField(field);
|
||||
limit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
else throw new Error("Failed to get module description");
|
||||
|
||||
return fields;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface Challenge {
|
||||
token: string; // Challenge token to be used for login.
|
||||
serverTime: string; // Current Server time
|
||||
expireTime: string;
|
||||
}
|
||||
|
||||
export interface Instance {
|
||||
sessionId: string;
|
||||
sessionName: string;
|
||||
userId: string;
|
||||
version: string;
|
||||
vtigerVersion: string;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
time_zone: string;
|
||||
hour_format: string;
|
||||
date_format: string;
|
||||
is_admin: string;
|
||||
call_duration: string;
|
||||
other_event_duration: string;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import {
|
||||
AuthenticationType,
|
||||
DedupeStrategy,
|
||||
HttpMethod,
|
||||
Polling,
|
||||
httpClient,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
createTrigger,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
TriggerStrategy,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { vtigerAuth } from '../..';
|
||||
import {
|
||||
elementTypeProperty,
|
||||
instanceLogin,
|
||||
prepareHttpRequest,
|
||||
} from '../common';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const newOrUpdatedRecord = createTrigger({
|
||||
auth: vtigerAuth,
|
||||
name: 'new_or_updated_record',
|
||||
displayName: 'New or Updated Record',
|
||||
description:
|
||||
'Triggers when a new record is introduced or a record is updated.',
|
||||
props: {
|
||||
elementType: elementTypeProperty,
|
||||
watchBy: Property.StaticDropdown({
|
||||
displayName: 'Watch By',
|
||||
description: 'Column to watch for trigger',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ value: 'createdtime', label: 'Created Time' },
|
||||
{ value: 'modifiedtime', label: 'Modified Time' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
syncType: Property.StaticDropdown({
|
||||
displayName: 'Sync Scope',
|
||||
description: 'Records visibility scope for sync',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ value: 'application', label: 'Application (all records)' },
|
||||
{ value: 'userandgroup', label: "User's groups" },
|
||||
{ value: 'user', label: 'User only' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'application',
|
||||
}),
|
||||
limit: Property.Number({
|
||||
displayName: 'Limit',
|
||||
description: 'Enter the maximum number of records to return.',
|
||||
defaultValue: 100,
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
sampleData: {
|
||||
success: true,
|
||||
result: [
|
||||
{
|
||||
id: '3x291',
|
||||
createdtime: '2020-07-22 12:46:55',
|
||||
modifiedtime: '2020-07-22 12:46:55',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test(ctx) {
|
||||
return await pollingHelper.test(polling, { ...ctx });
|
||||
},
|
||||
async onEnable(ctx) {
|
||||
await pollingHelper.onEnable(polling, { ...ctx });
|
||||
},
|
||||
async onDisable(ctx) {
|
||||
await pollingHelper.onDisable(polling, { ...ctx });
|
||||
},
|
||||
async run(ctx) {
|
||||
return await pollingHelper.poll(polling, { ...ctx });
|
||||
},
|
||||
});
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof vtigerAuth>,
|
||||
{ elementType?: string; watchBy?: string; limit?: number; syncType?: string }
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const items = await fetchRecords({ auth, propsValue, lastFetchEpochMS });
|
||||
|
||||
return (items ?? []).map((item) => {
|
||||
return {
|
||||
epochMilliSeconds: dayjs(
|
||||
propsValue.watchBy === 'createdtime'
|
||||
? item['createdtime']
|
||||
: item['modifiedtime']
|
||||
).valueOf(),
|
||||
data: item,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const fetchRecords = async ({
|
||||
auth,
|
||||
propsValue,
|
||||
lastFetchEpochMS,
|
||||
}: {
|
||||
auth: AppConnectionValueForAuthProperty<typeof vtigerAuth>;
|
||||
propsValue: Record<string, unknown>;
|
||||
lastFetchEpochMS: number;
|
||||
}) => {
|
||||
const elementType = propsValue['elementType'] as string;
|
||||
const limit = (propsValue['limit'] as number) ?? 100;
|
||||
const syncType = (propsValue['syncType'] as string) ?? 'application';
|
||||
|
||||
const baseUrl = `${auth.props.instance_url}/restapi/v1/vtiger/default`;
|
||||
|
||||
// Vtiger expects UNIX timestamp (seconds)
|
||||
let modifiedTimeSec = Math.floor((lastFetchEpochMS || 0) / 1000);
|
||||
|
||||
const updatedIds: string[] = [];
|
||||
let more = true;
|
||||
let safety = 0;
|
||||
|
||||
while (more && updatedIds.length < limit && safety < 10) {
|
||||
const syncResp = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: { updated: string[]; deleted: string[]; more: boolean; lastModifiedTime: number };
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${baseUrl}/sync`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.username,
|
||||
password: auth.props.password,
|
||||
},
|
||||
queryParams: {
|
||||
modifiedTime: String(modifiedTimeSec),
|
||||
elementType,
|
||||
syncType,
|
||||
},
|
||||
});
|
||||
|
||||
if (!syncResp.body?.success) break;
|
||||
|
||||
const res = syncResp.body.result;
|
||||
more = res.more === true;
|
||||
modifiedTimeSec = res.lastModifiedTime || modifiedTimeSec;
|
||||
|
||||
for (const id of res.updated ?? []) {
|
||||
if (updatedIds.length < limit) updatedIds.push(id);
|
||||
}
|
||||
|
||||
safety++;
|
||||
}
|
||||
|
||||
if (updatedIds.length === 0) return [];
|
||||
|
||||
const idsToFetch = updatedIds.slice(0, limit);
|
||||
|
||||
const results: Record<string, any>[] = [];
|
||||
for (const id of idsToFetch) {
|
||||
const retrieveResp = await httpClient.sendRequest<{
|
||||
success: boolean;
|
||||
result: Record<string, any>;
|
||||
}>({
|
||||
method: HttpMethod.GET,
|
||||
url: `${baseUrl}/retrieve`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BASIC,
|
||||
username: auth.props.username,
|
||||
password: auth.props.password,
|
||||
},
|
||||
queryParams: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
if (retrieveResp.body?.success && retrieveResp.body.result) {
|
||||
results.push(retrieveResp.body.result);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user