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:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "Du kannst deinen API-Schlüssel erhalten, indem du auf **Einstellungen->Erweitert** klickst.",
"Create Task": "Aufgabe erstellen",
"Create Time Entry": "Erstelle Zeiteintrag",
"Start Timer": "Timer starten",
"Stop Timer": "Timer stoppen",
"Find Task": "Aufgabe finden",
"Find Time Entry": "Zeiteintrag finden",
"Find Running Timer": "Suche Laufzeit",
"Custom API Call": "Eigener API-Aufruf",
"Creates a new in a specific project.": "Erstellt ein neues in einem bestimmten Projekt.",
"Creates a new time entry.": "Erstellt einen neuen Zeiteintrag.",
"Starts a new time entry.": "Startet einen neuen Zeiteintrag.",
"Stops currently running timer on specified workspace.": "Stoppt derzeit den Timer auf dem angegebenen Arbeitsbereich.",
"Finds an existing task in a specific project.": "Findet eine bestehende Aufgabe in einem bestimmten Projekt.",
"Finds a time entry by description, start datetime or end datetime.": "Findet einen Zeiteintrag nach Beschreibung, Startdatum oder Enddatum.",
"Finds currently running timer on specified workspace.": "Findet derzeit einen Timer auf dem angegebenen Arbeitsbereich.",
"Make a custom API call to a specific endpoint": "Einen benutzerdefinierten API-Aufruf an einen bestimmten Endpunkt machen",
"Workspace": "Arbeitsbereich",
"Project": "Projekt",
"Task Name": "Aufgabenname",
"Status": "Status",
"Assignee": "Assignee",
"Start Datetime": "Beginndatum",
"End Datetime": "Enddatum",
"Entry Description": "Eintragsbeschreibung",
"Task": "Aufgabe",
"Billable": "Abrechenbar",
"Tags": "Tags",
"Exact Match ?": "Genaues Match ?",
"Method": "Methode",
"Headers": "Kopfzeilen",
"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)",
"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..",
"Active": "Aktiv",
"Done": "Done",
"All": "Alle",
"GET": "ERHALTEN",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "LÖSCHEN",
"HEAD": "HEAD",
"New Task": "Neue Aufgabe",
"New Time Entry": "Neuer Zeiteintrag",
"New Timer Started": "Neuer Timer gestartet",
"Triggers when a new task is created in specified project.": "Wird ausgelöst, wenn eine neue Aufgabe in einem bestimmten Projekt erstellt wird.",
"Triggers when a new time entry is created.": "Wird ausgelöst, wenn ein neuer Zeiteintrag erstellt wird.",
"Triggers when a new entry is started and running.": "Wird ausgelöst, wenn ein neuer Eintrag gestartet wird und läuft."
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "Puedes obtener tu clave de API navegando a **Preferencias->Avanzadas**.",
"Create Task": "Crear tarea",
"Create Time Entry": "Crear entrada de tiempo",
"Start Timer": "Iniciar Temporizador",
"Stop Timer": "Detener temporizador",
"Find Task": "Buscar tarea",
"Find Time Entry": "Encontrar entrada de hora",
"Find Running Timer": "Encontrar temporizador en ejecución",
"Custom API Call": "Llamada API personalizada",
"Creates a new in a specific project.": "Crea un nuevo en un proyecto específico.",
"Creates a new time entry.": "Crea una nueva entrada de hora.",
"Starts a new time entry.": "Inicia una nueva entrada de hora.",
"Stops currently running timer on specified workspace.": "Detiene el temporizador actual en un área de trabajo especificada.",
"Finds an existing task in a specific project.": "Encuentra una tarea existente en un proyecto específico.",
"Finds a time entry by description, start datetime or end datetime.": "Encuentra una entrada de hora por descripción, fecha de inicio o fecha de fin.",
"Finds currently running timer on specified workspace.": "Encuentra el temporizador en ejecución en el área de trabajo especificada.",
"Make a custom API call to a specific endpoint": "Hacer una llamada API personalizada a un extremo específico",
"Workspace": "Espacio de trabajo",
"Project": "Projekt",
"Task Name": "Nombre de tarea",
"Status": "Estado",
"Assignee": "Assignee",
"Start Datetime": "Fecha de inicio",
"End Datetime": "Fecha de fin",
"Entry Description": "Descripción de la entrada",
"Task": "Tarea",
"Billable": "Facturable",
"Tags": "Etiquetas",
"Exact Match ?": "Partida exacta ?",
"Method": "Método",
"Headers": "Encabezados",
"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)",
"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.",
"Active": "Activo",
"Done": "Hecho",
"All": "Todos",
"GET": "RECOGER",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "BORRAR",
"HEAD": "LIMPIO",
"New Task": "Nueva tarea",
"New Time Entry": "Nueva entrada de hora",
"New Timer Started": "Nuevo temporizador iniciado",
"Triggers when a new task is created in specified project.": "Se activa cuando se crea una nueva tarea en un proyecto especificado.",
"Triggers when a new time entry is created.": "Dispara cuando se crea una nueva entrada de tiempo.",
"Triggers when a new entry is started and running.": "Se activa cuando se inicia y se ejecuta una nueva entrada."
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "Vous pouvez obtenir votre clé API en accédant à **Preferences->Advanced**.",
"Create Task": "Créer une tâche",
"Create Time Entry": "Créer une entrée de temps",
"Start Timer": "Démarrer le minuteur",
"Stop Timer": "Arrêter minuteur",
"Find Task": "Trouver une tâche",
"Find Time Entry": "Rechercher la saisie de l'heure",
"Find Running Timer": "Trouver le minuteur en cours",
"Custom API Call": "Appel API personnalisé",
"Creates a new in a specific project.": "Crée un nouveau dans un projet spécifique.",
"Creates a new time entry.": "Crée une nouvelle entrée horaire.",
"Starts a new time entry.": "Démarre une nouvelle entrée de temps.",
"Stops currently running timer on specified workspace.": "Arrête le minuteur en cours d'exécution sur l'espace de travail spécifié.",
"Finds an existing task in a specific project.": "Trouve une tâche existante dans un projet spécifique.",
"Finds a time entry by description, start datetime or end datetime.": "Trouve une entrée heure par description, date de début ou date de fin.",
"Finds currently running timer on specified workspace.": "Trouve le minuteur en cours d'exécution sur l'espace de travail spécifié.",
"Make a custom API call to a specific endpoint": "Passez un appel API personnalisé à un point de terminaison spécifique",
"Workspace": "Espace de travail",
"Project": "Projet",
"Task Name": "Nom de la tâche",
"Status": "Statut",
"Assignee": "Assignee",
"Start Datetime": "Date de début",
"End Datetime": "Date de fin",
"Entry Description": "Description de l'entrée",
"Task": "Tâche",
"Billable": "Facturable",
"Tags": "Tags",
"Exact Match ?": "Correspondance exacte ?",
"Method": "Méthode",
"Headers": "En-têtes",
"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'attente (en secondes)",
"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.",
"Active": "Actif",
"Done": "Terminé",
"All": "Tous",
"GET": "OBTENIR",
"POST": "POSTER",
"PATCH": "PATCH",
"PUT": "EFFACER",
"DELETE": "SUPPRIMER",
"HEAD": "TÊTE",
"New Task": "Nouvelle tâche",
"New Time Entry": "Nouvelle entrée de temps",
"New Timer Started": "Nouveau minuteur démarré",
"Triggers when a new task is created in specified project.": "Déclenche lorsqu'une nouvelle tâche est créée dans le projet spécifié.",
"Triggers when a new time entry is created.": "Déclenche quand une nouvelle entrée de temps est créée.",
"Triggers when a new entry is started and running.": "Déclenche quand une nouvelle entrée est démarrée et en cours d'exécution."
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "API キーは **Preferences->Advanced** に移動して取得できます。",
"Create Task": "タスクを作成",
"Create Time Entry": "タイムエントリを作成",
"Start Timer": "開始タイマー",
"Stop Timer": "停止タイマー",
"Find Task": "タスクを検索",
"Find Time Entry": "タイムエントリを検索",
"Find Running Timer": "実行中のタイマーを検索",
"Custom API Call": "カスタムAPI通話",
"Creates a new in a specific project.": "特定のプロジェクトに新規作成します。",
"Creates a new time entry.": "新しいタイムエントリを作成します。",
"Starts a new time entry.": "新しい時刻の入力を開始します。",
"Stops currently running timer on specified workspace.": "指定されたワークスペースで現在実行中のタイマーを停止します。",
"Finds an existing task in a specific project.": "特定のプロジェクトの既存のタスクを検索します。",
"Finds a time entry by description, start datetime or end datetime.": "時刻の項目を説明、開始日時、終了日時で検索します。",
"Finds currently running timer on specified workspace.": "指定されたワークスペースで現在実行中のタイマーを検索します。",
"Make a custom API call to a specific endpoint": "特定のエンドポイントへのカスタム API コールを実行します。",
"Workspace": "ワークスペース",
"Project": "プロジェクト",
"Task Name": "タスク名",
"Status": "ステータス",
"Assignee": "Assignee",
"Start Datetime": "開始日時",
"End Datetime": "終了日時",
"Entry Description": "エントリの説明",
"Task": "タスク",
"Billable": "請求可能",
"Tags": "タグ",
"Exact Match ?": "完全一致 ?",
"Method": "方法",
"Headers": "ヘッダー",
"Query Parameters": "クエリパラメータ",
"Body": "本文",
"Response is Binary ?": "応答はバイナリですか?",
"No Error on Failure": "失敗時にエラーはありません",
"Timeout (in seconds)": "タイムアウト(秒)",
"Authorization headers are injected automatically from your connection.": "認証ヘッダは接続から自動的に注入されます。",
"Enable for files like PDFs, images, etc..": "PDF、画像などのファイルを有効にします。",
"Active": "有効",
"Done": "完了",
"All": "すべて",
"GET": "取得",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "削除",
"HEAD": "頭",
"New Task": "新しいタスク",
"New Time Entry": "新しい時間入力",
"New Timer Started": "新しいタイマーが開始されました",
"Triggers when a new task is created in specified project.": "指定したプロジェクトで新しいタスクが作成されたときにトリガーされます。",
"Triggers when a new time entry is created.": "新しい時間エントリが作成されたときにトリガーします。",
"Triggers when a new entry is started and running.": "新しいエントリが起動して実行されたときにトリガーします。"
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "U kunt uw API-sleutel verkrijgen door naar **Voorkeuren>Geavanceerd**.",
"Create Task": "Taak maken",
"Create Time Entry": "Tijdsinvoer aanmaken",
"Start Timer": "Start tijdmeter",
"Stop Timer": "Stop timer",
"Find Task": "Zoek Taak",
"Find Time Entry": "Vind tijdsinvoer",
"Find Running Timer": "Zoek Running Timer",
"Custom API Call": "Custom API Call",
"Creates a new in a specific project.": "Maakt een nieuw in een specifiek project.",
"Creates a new time entry.": "Maakt een nieuwe tijdsinvoer aan.",
"Starts a new time entry.": "Start een nieuwe tijd invoer.",
"Stops currently running timer on specified workspace.": "Stopt momenteel timer op opgegeven werkruimte.",
"Finds an existing task in a specific project.": "Vindt een bestaande taak in een specifiek project.",
"Finds a time entry by description, start datetime or end datetime.": "Vindt een tijdsvermelding op beschrijving, startdatum of einddatetime.",
"Finds currently running timer on specified workspace.": "Gevonden caches met momenteel lopende timer op opgegeven werkruimte.",
"Make a custom API call to a specific endpoint": "Maak een aangepaste API call naar een specifiek eindpunt",
"Workspace": "werkruimte",
"Project": "Project",
"Task Name": "Taak naam",
"Status": "status",
"Assignee": "Assignee",
"Start Datetime": "Start datum",
"End Datetime": "Eind datum",
"Entry Description": "Beschrijving item",
"Task": "Opdracht",
"Billable": "Factureerbaar",
"Tags": "Labels",
"Exact Match ?": "Exacte overeenkomst ?",
"Method": "Methode",
"Headers": "Kopteksten",
"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)",
"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..",
"Active": "Actief",
"Done": "Voltooid",
"All": "Allemaal",
"GET": "KRIJG",
"POST": "POSTE",
"PATCH": "BEKIJK",
"PUT": "PUT",
"DELETE": "VERWIJDEREN",
"HEAD": "HOOFD",
"New Task": "Nieuwe taak",
"New Time Entry": "Nieuwe tijd invoer",
"New Timer Started": "Nieuwe Timer gestart",
"Triggers when a new task is created in specified project.": "Triggert wanneer een nieuwe taak wordt aangemaakt in het opgegeven project.",
"Triggers when a new time entry is created.": "Triggert wanneer een nieuwe tijdsvermelding wordt aangemaakt.",
"Triggers when a new entry is started and running.": "Triggert wanneer een nieuwe invoer wordt gestart en uitgevoerd."
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "Você pode obter sua chave de API navegando até **Preferências->Avançado**.",
"Create Task": "Criar tarefa",
"Create Time Entry": "Criar Registro de Tempo",
"Start Timer": "Iniciar Temporizador",
"Stop Timer": "Parar Temporizador",
"Find Task": "Procurar Tarefa",
"Find Time Entry": "Encontrar Registro de Tempo",
"Find Running Timer": "Encontrar Temporizador em Execução",
"Custom API Call": "Chamada de API personalizada",
"Creates a new in a specific project.": "Cria um novo projeto específico.",
"Creates a new time entry.": "Cria uma nova entrada de tempo.",
"Starts a new time entry.": "Inicia uma nova entrada de tempo.",
"Stops currently running timer on specified workspace.": "Para o temporizador atual no espaço de trabalho especificado.",
"Finds an existing task in a specific project.": "Encontra uma tarefa existente em um projeto específico.",
"Finds a time entry by description, start datetime or end datetime.": "Localiza uma entrada de hora por descrição, inicia data ou hora de término.",
"Finds currently running timer on specified workspace.": "Localiza o temporizador atualmente em execução no espaço de trabalho especificado.",
"Make a custom API call to a specific endpoint": "Faça uma chamada de API personalizada para um ponto de extremidade específico",
"Workspace": "Workspace",
"Project": "Projecto",
"Task Name": "Nome da tarefa",
"Status": "Estado",
"Assignee": "Assignee",
"Start Datetime": "Data de início",
"End Datetime": "Data de término",
"Entry Description": "Descrição da postagem",
"Task": "Tarefas",
"Billable": "Faturável",
"Tags": "Etiquetas",
"Exact Match ?": "Partida exata ?",
"Method": "Método",
"Headers": "Cabeçalhos",
"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)",
"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..",
"Active": "Ativo",
"Done": "Concluído",
"All": "TODOS",
"GET": "OBTER",
"POST": "POSTAR",
"PATCH": "COMPRAR",
"PUT": "COLOCAR",
"DELETE": "EXCLUIR",
"HEAD": "CABEÇA",
"New Task": "Nova tarefa",
"New Time Entry": "Novo Registro de Tempo",
"New Timer Started": "Novo Temporizador Iniciado",
"Triggers when a new task is created in specified project.": "Aciona quando uma nova tarefa é criada no projeto especificado.",
"Triggers when a new time entry is created.": "Aciona quando uma nova entrada é criada.",
"Triggers when a new entry is started and running.": "Dispara quando uma nova entrada é iniciada e executada."
}

View File

@@ -0,0 +1,54 @@
{
"Clockify": "Клокафировать",
"You can obtain your API key by navigating to **Preferences->Advanced**.": "Ключ API можно получить, перейдя по ссылке **Preferences->Advanced**.",
"Create Task": "Создать задачу",
"Create Time Entry": "Создать запись",
"Start Timer": "Запустить таймер",
"Stop Timer": "Остановить таймер",
"Find Task": "Найти задачу",
"Find Time Entry": "Поиск времени входа",
"Find Running Timer": "Найти таймер",
"Custom API Call": "Пользовательский вызов API",
"Creates a new in a specific project.": "Создает новый в конкретном проекте.",
"Creates a new time entry.": "Создает новую запись времени.",
"Starts a new time entry.": "Начинает новую запись.",
"Stops currently running timer on specified workspace.": "Остановка таймера на указанном рабочем пространстве.",
"Finds an existing task in a specific project.": "Находит существующее задание в конкретном проекте.",
"Finds a time entry by description, start datetime or end datetime.": "Находит запись времени по описанию, дате начала или дате окончания.",
"Finds currently running timer on specified workspace.": "Идет текущий таймер на указанном рабочем пространстве.",
"Make a custom API call to a specific endpoint": "Сделать пользовательский API вызов к определенной конечной точке",
"Workspace": "Рабочая область",
"Project": "Проект",
"Task Name": "Название задачи",
"Status": "Status",
"Assignee": "Assignee",
"Start Datetime": "Дата начала",
"End Datetime": "Дата окончания",
"Entry Description": "Описание записи",
"Task": "Задача",
"Billable": "Оплачиваемый",
"Tags": "Теги",
"Exact Match ?": "Точное совпадение ?",
"Method": "Метод",
"Headers": "Заголовки",
"Query Parameters": "Параметры запроса",
"Body": "Тело",
"No Error on Failure": "Нет ошибок при ошибке",
"Timeout (in seconds)": "Таймаут (в секундах)",
"Authorization headers are injected automatically from your connection.": "Заголовки авторизации включаются автоматически из вашего соединения.",
"Active": "Активен",
"Done": "Done",
"All": "Все",
"GET": "ПОЛУЧИТЬ",
"POST": "ПОСТ",
"PATCH": "ПАТЧ",
"PUT": "ПОКУПИТЬ",
"DELETE": "УДАЛИТЬ",
"HEAD": "HEAD",
"New Task": "Новая задача",
"New Time Entry": "Новое время",
"New Timer Started": "Новый таймер начался",
"Triggers when a new task is created in specified project.": "Запускает при создании новой задачи в указанном проекте.",
"Triggers when a new time entry is created.": "Триггеры при создании новой записи времени.",
"Triggers when a new entry is started and running.": "Триггеры при запуске и запуске новой записи."
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "You can obtain your API key by navigating to **Preferences->Advanced**.",
"Create Task": "Create Task",
"Create Time Entry": "Create Time Entry",
"Start Timer": "Start Timer",
"Stop Timer": "Stop Timer",
"Find Task": "Find Task",
"Find Time Entry": "Find Time Entry",
"Find Running Timer": "Find Running Timer",
"Custom API Call": "Custom API Call",
"Creates a new in a specific project.": "Creates a new in a specific project.",
"Creates a new time entry.": "Creates a new time entry.",
"Starts a new time entry.": "Starts a new time entry.",
"Stops currently running timer on specified workspace.": "Stops currently running timer on specified workspace.",
"Finds an existing task in a specific project.": "Finds an existing task in a specific project.",
"Finds a time entry by description, start datetime or end datetime.": "Finds a time entry by description, start datetime or end datetime.",
"Finds currently running timer on specified workspace.": "Finds currently running timer on specified workspace.",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"Workspace": "Workspace",
"Project": "Project",
"Task Name": "Task Name",
"Status": "Status",
"Assignee": "Assignee",
"Start Datetime": "Start Datetime",
"End Datetime": "End Datetime",
"Entry Description": "Entry Description",
"Task": "Task",
"Billable": "Billable",
"Tags": "Tags",
"Exact Match ?": "Exact Match ?",
"Method": "Method",
"Headers": "Headers",
"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)",
"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..",
"Active": "Active",
"Done": "Done",
"All": "All",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD",
"New Task": "New Task",
"New Time Entry": "New Time Entry",
"New Timer Started": "New Timer Started",
"Triggers when a new task is created in specified project.": "Triggers when a new task is created in specified project.",
"Triggers when a new time entry is created.": "Triggers when a new time entry is created.",
"Triggers when a new entry is started and running.": "Triggers when a new entry is started and running."
}

View File

@@ -0,0 +1,54 @@
{
"Clockify": "Clockify",
"You can obtain your API key by navigating to **Preferences->Advanced**.": "You can obtain your API key by navigating to **Preferences->Advanced**.",
"Create Task": "Create Task",
"Create Time Entry": "Create Time Entry",
"Start Timer": "Start Timer",
"Stop Timer": "Stop Timer",
"Find Task": "Find Task",
"Find Time Entry": "Find Time Entry",
"Find Running Timer": "Find Running Timer",
"Custom API Call": "Custom API Call",
"Creates a new in a specific project.": "Creates a new in a specific project.",
"Creates a new time entry.": "Creates a new time entry.",
"Starts a new time entry.": "Starts a new time entry.",
"Stops currently running timer on specified workspace.": "Stops currently running timer on specified workspace.",
"Finds an existing task in a specific project.": "Finds an existing task in a specific project.",
"Finds a time entry by description, start datetime or end datetime.": "Finds a time entry by description, start datetime or end datetime.",
"Finds currently running timer on specified workspace.": "Finds currently running timer on specified workspace.",
"Make a custom API call to a specific endpoint": "Make a custom API call to a specific endpoint",
"Workspace": "Workspace",
"Project": "Project",
"Task Name": "Task Name",
"Status": "Status",
"Assignee": "Assignee",
"Start Datetime": "Start Datetime",
"End Datetime": "End Datetime",
"Entry Description": "Entry Description",
"Task": "Task",
"Billable": "Billable",
"Tags": "Tags",
"Exact Match ?": "Exact Match ?",
"Method": "Method",
"Headers": "Headers",
"Query Parameters": "Query Parameters",
"Body": "Body",
"No Error on Failure": "No Error on Failure",
"Timeout (in seconds)": "Timeout (in seconds)",
"Authorization headers are injected automatically from your connection.": "Authorization headers are injected automatically from your connection.",
"Active": "Tích cực",
"Done": "Done",
"All": "Tất cả",
"GET": "GET",
"POST": "POST",
"PATCH": "PATCH",
"PUT": "PUT",
"DELETE": "DELETE",
"HEAD": "HEAD",
"New Task": "New Task",
"New Time Entry": "New Time Entry",
"New Timer Started": "New Timer Started",
"Triggers when a new task is created in specified project.": "Triggers when a new task is created in specified project.",
"Triggers when a new time entry is created.": "Triggers when a new time entry is created.",
"Triggers when a new entry is started and running.": "Triggers when a new entry is started and running."
}

View File

@@ -0,0 +1,55 @@
{
"You can obtain your API key by navigating to **Preferences->Advanced**.": "You can obtain your API key by navigating to **Preferences->Advanced**.",
"Create Task": "Create Task",
"Create Time Entry": "Create Time Entry",
"Start Timer": "Start Timer",
"Stop Timer": "Stop Timer",
"Find Task": "Find Task",
"Find Time Entry": "Find Time Entry",
"Find Running Timer": "Find Running Timer",
"Custom API Call": "自定义 API 呼叫",
"Creates a new in a specific project.": "Creates a new in a specific project.",
"Creates a new time entry.": "Creates a new time entry.",
"Starts a new time entry.": "Starts a new time entry.",
"Stops currently running timer on specified workspace.": "Stops currently running timer on specified workspace.",
"Finds an existing task in a specific project.": "Finds an existing task in a specific project.",
"Finds a time entry by description, start datetime or end datetime.": "Finds a time entry by description, start datetime or end datetime.",
"Finds currently running timer on specified workspace.": "Finds currently running timer on specified workspace.",
"Make a custom API call to a specific endpoint": "将一个自定义 API 调用到一个特定的终点",
"Workspace": "Workspace",
"Project": "项目",
"Task Name": "Task Name",
"Status": "状态",
"Assignee": "Assignee",
"Start Datetime": "Start Datetime",
"End Datetime": "End Datetime",
"Entry Description": "Entry Description",
"Task": "Task",
"Billable": "Billable",
"Tags": "标签",
"Exact Match ?": "精确匹配 ?",
"Method": "方法",
"Headers": "信头",
"Query Parameters": "查询参数",
"Body": "正文内容",
"Response is Binary ?": "Response is Binary ?",
"No Error on Failure": "失败时没有错误",
"Timeout (in seconds)": "超时(秒)",
"Authorization headers are injected automatically from your connection.": "授权头自动从您的连接中注入。",
"Enable for files like PDFs, images, etc..": "Enable for files like PDFs, images, etc..",
"Active": "使用中",
"Done": "完成",
"All": "所有的",
"GET": "获取",
"POST": "帖子",
"PATCH": "PATCH",
"PUT": "弹出",
"DELETE": "删除",
"HEAD": "黑色",
"New Task": "New Task",
"New Time Entry": "New Time Entry",
"New Timer Started": "New Timer Started",
"Triggers when a new task is created in specified project.": "Triggers when a new task is created in specified project.",
"Triggers when a new time entry is created.": "Triggers when a new time entry is created.",
"Triggers when a new entry is started and running.": "Triggers when a new entry is started and running."
}

View File

@@ -0,0 +1,64 @@
import { createCustomApiCallAction, HttpMethod } from '@activepieces/pieces-common';
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { createTaskAction } from './lib/actions/create-task';
import { createTimeEntryAction } from './lib/actions/create-time-entry';
import { findRunningTimerAction } from './lib/actions/find-running-timer';
import { findTaskAction } from './lib/actions/find-task';
import { findTimeEntryAction } from './lib/actions/find-time-entry';
import { startTimerAction } from './lib/actions/start-timer';
import { stopTimerAction } from './lib/actions/stop-timer';
import { BASE_URL, clockifyApiCall } from './lib/common/client';
import { newTaskTrigger } from './lib/triggers/new-task';
import { newTimeEntryTrigger } from './lib/triggers/new-time-entry';
import { newTimerStartedTrigger } from './lib/triggers/new-timer-started';
export const clockifyAuth = PieceAuth.SecretText({
displayName:'API Key',
description: `You can obtain your API key by navigating to **Preferences->Advanced**.`,
required: true,
validate: async ({ auth }) => {
try {
await clockifyApiCall({
apiKey: auth,
method: HttpMethod.GET,
resourceUri: '/user',
});
return {
valid: true,
};
} catch {
return {
valid: false,
error: 'Invalid API Key.',
};
}
},
});
export const clockify = createPiece({
displayName: 'Clockify',
auth: clockifyAuth,
minimumSupportedRelease: '0.36.1',
logoUrl: 'https://cdn.activepieces.com/pieces/clockify.png',
authors: ['rimjhimyadav', 'kishanprmr'],
actions: [
createTaskAction,
createTimeEntryAction,
startTimerAction,
stopTimerAction,
findTaskAction,
findTimeEntryAction,
findRunningTimerAction,
createCustomApiCallAction({
auth: clockifyAuth,
baseUrl: () => BASE_URL,
authMapping: async (auth) => {
return {
'X-Api-Key': auth.secret_text,
};
},
}),
],
triggers: [newTaskTrigger, newTimeEntryTrigger, newTimerStartedTrigger],
});

View File

@@ -0,0 +1,68 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { assigneeIds, projectId, workspaceId } from '../common/props';
export const createTaskAction = createAction({
auth: clockifyAuth,
name: 'create-task',
displayName: 'Create Task',
description: 'Creates a new in a specific project.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
projectId: projectId({
displayName: 'Project',
required: true,
}),
name: Property.ShortText({
displayName: 'Task Name',
required: true,
}),
status: Property.StaticDropdown({
displayName: 'Status',
required: false,
options: {
disabled: false,
options: [
{
label: 'Active',
value: 'ACTIVE',
},
{
label: 'Done',
value: 'DONE',
},
{
label: 'All',
value: 'ALL',
},
],
},
}),
assigneeIds: assigneeIds({
displayName: 'Assignee',
required: false,
}),
},
async run(context) {
const { workspaceId, projectId, name, status } = context.propsValue;
const assigneeIds = context.propsValue.assigneeIds ?? [];
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`,
body: {
name,
status,
assigneeIds,
},
});
return response;
},
});

View File

@@ -0,0 +1,69 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { projectId, tagIds, taskId, workspaceId } from '../common/props';
export const createTimeEntryAction = createAction({
auth: clockifyAuth,
name: 'create-time-entry',
displayName: 'Create Time Entry',
description: 'Creates a new time entry.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
start: Property.DateTime({
displayName: 'Start Datetime',
required: true,
}),
end: Property.DateTime({
displayName: 'End Datetime',
required: true,
}),
description: Property.LongText({
displayName: 'Entry Description',
required: false,
}),
projectId: projectId({
displayName: 'Project',
required: false,
}),
taskId: taskId({
displayName: 'Task',
required: false,
}),
billable: Property.Checkbox({
displayName: 'Billable',
required: false,
}),
tagIds: tagIds({
displayName: 'Tags',
required: false,
}),
},
async run(context) {
const { workspaceId, projectId, start, end, description, billable, taskId } =
context.propsValue;
const tagIds = context.propsValue.tagIds ?? [];
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/workspaces/${workspaceId}/time-entries`,
body: {
billable,
description,
start,
end,
projectId,
taskId,
tagIds,
type: 'REGULAR',
},
});
return response;
},
});

View File

@@ -0,0 +1,29 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { workspaceId } from '../common/props';
export const findRunningTimerAction = createAction({
auth: clockifyAuth,
name: 'find-running-timer',
displayName: 'Find Running Timer',
description: 'Finds currently running timer on specified workspace.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
},
async run(context) {
const { workspaceId } = context.propsValue;
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/time-entries/status/in-progress`,
});
return response;
},
});

View File

@@ -0,0 +1,45 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { projectId, workspaceId } from '../common/props';
export const findTaskAction = createAction({
auth: clockifyAuth,
name: 'find-task',
displayName: 'Find Task',
description: 'Finds an existing task in a specific project.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
projectId: projectId({
displayName: 'Project',
required: true,
}),
name: Property.ShortText({
displayName: 'Task Name',
required: true,
}),
exactMatch: Property.Checkbox({
displayName: 'Exact Match ?',
required: false,
}),
},
async run(context) {
const { workspaceId, projectId, name, exactMatch } = context.propsValue;
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`,
query: {
name,
'strict-name-search': exactMatch ? 'true' : 'false',
},
});
return response;
},
});

View File

@@ -0,0 +1,66 @@
import { HttpMethod, QueryParams } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { projectId, taskId, workspaceId } from '../common/props';
export const findTimeEntryAction = createAction({
auth: clockifyAuth,
name: 'find-time-entry',
displayName: 'Find Time Entry',
description: 'Finds a time entry by description, start datetime or end datetime.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
start: Property.DateTime({
displayName: 'Start Datetime',
required: false,
}),
end: Property.DateTime({
displayName: 'End Datetime',
required: false,
}),
description: Property.LongText({
displayName: 'Entry Description',
required: false,
}),
projectId: projectId({
displayName: 'Project',
required: false,
}),
taskId: taskId({
displayName: 'Task',
required: false,
}),
},
async run(context) {
const { workspaceId, projectId, start, end, description, taskId } = context.propsValue;
const currentUserResponse = await clockifyApiCall<{ id: string; email: string }>({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/user`,
});
const userId = currentUserResponse.id;
const qs: QueryParams = { hydrated: 'true' };
if (description) qs['description'] = description;
if (start) qs['start'] = start;
if (end) qs['end'] = end;
if (projectId) qs['project'] = projectId as string;
if (taskId) qs['task'] = taskId as string;
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/user/${userId}/time-entries`,
query: qs,
});
return response;
},
});

View File

@@ -0,0 +1,59 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction, Property } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { projectId, tagIds, taskId, workspaceId } from '../common/props';
export const startTimerAction = createAction({
auth: clockifyAuth,
name: 'start-timer',
displayName: 'Start Timer',
description: 'Starts a new time entry.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
description: Property.LongText({
displayName: 'Entry Description',
required: false,
}),
projectId: projectId({
displayName: 'Project',
required: false,
}),
taskId: taskId({
displayName: 'Task',
required: false,
}),
billable: Property.Checkbox({
displayName: 'Billable',
required: false,
}),
tagIds: tagIds({
displayName: 'Tags',
required: false,
}),
},
async run(context) {
const { workspaceId, projectId, description, billable, taskId } = context.propsValue;
const tagIds = context.propsValue.tagIds ?? [];
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/workspaces/${workspaceId}/time-entries`,
body: {
billable,
description,
start: new Date().toISOString(),
projectId,
taskId,
tagIds,
type: 'REGULAR',
},
});
return response;
},
});

View File

@@ -0,0 +1,40 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createAction } from '@activepieces/pieces-framework';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { workspaceId } from '../common/props';
export const stopTimerAction = createAction({
auth: clockifyAuth,
name: 'stop-timer',
displayName: 'Stop Timer',
description: 'Stops currently running timer on specified workspace.',
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
},
async run(context) {
const { workspaceId } = context.propsValue;
const currentUserResponse = await clockifyApiCall<{ id: string; email: string }>({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/user`,
});
const userId = currentUserResponse.id;
const response = await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.PATCH,
resourceUri: `/workspaces/${workspaceId}/user/${userId}/time-entries`,
body: {
end: new Date().toISOString(),
},
});
return response;
},
});

View File

@@ -0,0 +1,48 @@
import {
httpClient,
HttpMessageBody,
HttpMethod,
HttpRequest,
QueryParams,
} from '@activepieces/pieces-common';
export type ClockifyApiCallParams = {
apiKey: string;
method: HttpMethod;
resourceUri: string;
query?: Record<string, string | number | string[] | undefined>;
body?: any;
};
export const BASE_URL = 'https://api.clockify.me/api/v1';
export async function clockifyApiCall<T extends HttpMessageBody>({
apiKey,
method,
resourceUri,
query,
body,
}: ClockifyApiCallParams): Promise<T> {
const qs: QueryParams = {};
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value !== null && value !== undefined) {
qs[key] = String(value);
}
}
}
const request: HttpRequest = {
method,
url: BASE_URL + resourceUri,
headers: {
'X-Api-Key': apiKey,
},
queryParams: qs,
body,
};
const response = await httpClient.sendRequest<T>(request);
return response.body;
}

View File

@@ -0,0 +1,209 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { Property } from '@activepieces/pieces-framework';
import { clockifyApiCall } from './client';
import { clockifyAuth } from '../..';
interface DropdownParams {
displayName: string;
description?: string;
required: boolean;
}
export const workspaceId = (params: DropdownParams) =>
Property.Dropdown({
auth: clockifyAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
const response = await clockifyApiCall<{ id: string; name: string }[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: '/workspaces',
});
return {
disabled: false,
options: response.map((workspace) => ({
label: workspace.name,
value: workspace.id,
})),
};
},
});
export const projectId = (params: DropdownParams) =>
Property.Dropdown({
auth: clockifyAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: ['workspaceId'],
options: async ({ auth, workspaceId }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
if (!workspaceId) {
return {
disabled: true,
options: [],
placeholder: 'Please select workspace first.',
};
}
const response = await clockifyApiCall<{ id: string; name: string }[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/projects`,
});
return {
disabled: false,
options: response.map((project) => ({
label: project.name,
value: project.id,
})),
};
},
});
export const assigneeIds = (params: DropdownParams) =>
Property.MultiSelectDropdown({
auth: clockifyAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: ['workspaceId'],
options: async ({ auth, workspaceId }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
if (!workspaceId) {
return {
disabled: true,
options: [],
placeholder: 'Please select workspace first.',
};
}
const response = await clockifyApiCall<{ id: string; email: string }[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/users`,
});
return {
disabled: false,
options: response.map((user) => ({
label: user.email,
value: user.id,
})),
};
},
});
export const taskId = (params: DropdownParams) =>
Property.Dropdown({
auth: clockifyAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: ['workspaceId', 'projectId'],
options: async ({ auth, workspaceId, projectId }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
if (!workspaceId) {
return {
disabled: true,
options: [],
placeholder: 'Please select workspace first.',
};
}
if (!projectId) {
return {
disabled: true,
options: [],
placeholder: 'Please select project first.',
};
}
const response = await clockifyApiCall<{ id: string; name: string }[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`,
});
return {
disabled: false,
options: response.map((task) => ({
label: task.name,
value: task.id,
})),
};
},
});
export const tagIds = (params: DropdownParams) =>
Property.MultiSelectDropdown({
auth: clockifyAuth,
displayName: params.displayName,
description: params.description,
required: params.required,
refreshers: ['workspaceId'],
options: async ({ auth, workspaceId }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your account first.',
};
}
if (!workspaceId) {
return {
disabled: true,
options: [],
placeholder: 'Please select workspace first.',
};
}
const response = await clockifyApiCall<{ id: string; name: string }[]>({
apiKey: auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/tags`,
});
return {
disabled: false,
options: response.map((tag) => ({
label: tag.name,
value: tag.id,
})),
};
},
});

View File

@@ -0,0 +1,91 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { projectId, workspaceId } from '../common/props';
const TRIGGER_KEY = 'new-task-trigger';
export const newTaskTrigger = createTrigger({
auth: clockifyAuth,
name: 'new-task',
displayName: 'New Task',
description: 'Triggers when a new task is created in specified project.',
type: TriggerStrategy.WEBHOOK,
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
projectId: projectId({
displayName: 'Project',
required: true,
}),
},
async onEnable(context) {
const { workspaceId, projectId } = context.propsValue;
const response = await clockifyApiCall<{ id: string }>({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/workspaces/${workspaceId}/webhooks`,
body: {
url: context.webhookUrl,
webhookEvent: 'NEW_TASK',
triggerSourceType: 'PROJECT_ID',
triggerSource: [projectId],
},
});
await context.store.put<string>(TRIGGER_KEY, response.id);
},
async onDisable(context) {
const { workspaceId } = context.propsValue;
const webhookId = await context.store.get<string>(TRIGGER_KEY);
if (!isNil(webhookId)) {
await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.DELETE,
resourceUri: `/workspaces/${workspaceId}/webhooks/${webhookId}`,
});
}
},
async test(context) {
const { workspaceId, projectId } = context.propsValue;
const response = await clockifyApiCall<{ id: string }[]>({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/projects/${projectId}/tasks`,
query: {
page: '1',
'page-size': 5,
},
});
return response;
},
async run(context) {
return [context.payload.body];
},
sampleData: {
id: '684538940300f917a02f642f',
name: 'Test',
projectId: '68444b15551a9934b5034263',
workspaceId: '684446430300f917a02c198b',
assigneeIds: [],
assigneeId: '',
userGroupIds: [],
estimate: 'PT0S',
status: 'ACTIVE',
budgetEstimate: 0,
billable: true,
hourlyRate: null,
costRate: null,
progress: null,
duration: 'PT0S',
},
});

View File

@@ -0,0 +1,187 @@
import { HttpMethod, QueryParams } from '@activepieces/pieces-common';
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { projectId, taskId, workspaceId } from '../common/props';
const TRIGGER_KEY = 'new-time-entry-trigger';
export const newTimeEntryTrigger = createTrigger({
auth: clockifyAuth,
name: 'new-time-entry',
displayName: 'New Time Entry',
description: 'Triggers when a new time entry is created.',
type: TriggerStrategy.WEBHOOK,
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
projectId: projectId({
displayName: 'Project',
required: false,
}),
taskId: taskId({
displayName: 'Task',
required: false,
}),
},
async onEnable(context) {
const { workspaceId, projectId, taskId } = context.propsValue;
const payload: Record<string, any> = {
url: context.webhookUrl,
webhookEvent: 'NEW_TIME_ENTRY',
};
if (workspaceId) {
payload['triggerSourceType'] = 'WORKSPACE_ID';
payload['triggerSource'] = [workspaceId];
}
if (projectId) {
payload['triggerSourceType'] = 'PROJECT_ID';
payload['triggerSource'] = [projectId];
}
if (taskId) {
payload['triggerSourceType'] = 'TASK_ID';
payload['triggerSource'] = [taskId];
}
const response = await clockifyApiCall<{ id: string }>({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/workspaces/${workspaceId}/webhooks`,
body: payload,
});
await context.store.put<string>(TRIGGER_KEY, response.id);
},
async onDisable(context) {
const { workspaceId } = context.propsValue;
const webhookId = await context.store.get<string>(TRIGGER_KEY);
if (!isNil(webhookId)) {
await clockifyApiCall({
apiKey: context.auth.secret_text,
method: HttpMethod.DELETE,
resourceUri: `/workspaces/${workspaceId}/webhooks/${webhookId}`,
});
}
},
async test(context) {
const { workspaceId, projectId, taskId } = context.propsValue;
const currentUserResponse = await clockifyApiCall<{ id: string; email: string }>({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/user`,
});
const userId = currentUserResponse.id;
const qs: QueryParams = { hydrated: 'true', page: '1', 'page-size': '5' };
if (projectId) qs['project'] = projectId as string;
if (taskId) qs['task'] = taskId as string;
const response = await clockifyApiCall<{id:string}[]>({
apiKey: context.auth.secret_text,
method: HttpMethod.GET,
resourceUri: `/workspaces/${workspaceId}/user/${userId}/time-entries`,
query: qs,
});
return response;
},
async run(context) {
return [context.payload.body];
},
sampleData: {
id: '68453d0cf0c88d522704ab76',
description: 'Test',
userId: '684446430300f917a02c198a',
billable: true,
projectId: '68444b15551a9934b5034263',
timeInterval: {
start: '2025-06-16T07:05:00Z',
end: '2025-06-16T18:53:00Z',
duration: 'PT11H48M',
timeZone: 'Asia/Calcutta',
offsetStart: 19800,
offsetEnd: 19800,
zonedStart: '2025-06-16T12:35:00',
zonedEnd: '2025-06-17T00:23:00',
},
workspaceId: '684446430300f917a02c198b',
isLocked: false,
hourlyRate: null,
costRate: null,
customFieldValues: [],
type: 'REGULAR',
kioskId: null,
approvalStatus: null,
projectCurrency: null,
currentlyRunning: false,
project: {
name: 'Test',
clientId: '',
workspaceId: '684446430300f917a02c198b',
billable: true,
estimate: {
estimate: 'PT0S',
type: 'AUTO',
},
color: '#039BE5',
archived: false,
clientName: '',
duration: 'PT47H12M45S',
note: '',
activeEstimate: 'NONE',
timeEstimate: {
includeNonBillable: true,
estimate: 0,
type: 'AUTO',
resetOption: null,
},
budgetEstimate: null,
id: '68444b15551a9934b5034263',
public: true,
template: false,
},
task: {
name: 'test',
projectId: '68444b15551a9934b5034263',
assigneeId: '684446430300f917a02c198a',
assigneeIds: ['684446430300f917a02c198a'],
userGroupIds: [],
estimate: 'PT0S',
status: 'ACTIVE',
workspaceId: '684446430300f917a02c198b',
budgetEstimate: 0,
billable: true,
hourlyRate: null,
costRate: null,
auditMetadata: {
updatedAt: 1749306307000,
},
id: '68444badb7688b61f151284d',
duration: 'PT35H24M41S',
},
user: {
id: '684446430300f917a02c198a',
name: 'johndoe',
status: 'ACTIVE',
},
tags: [
{
name: 'new',
workspaceId: '684446430300f917a02c198b',
archived: false,
id: '68452a1b0300f917a02f30bc',
},
],
},
});

View File

@@ -0,0 +1,72 @@
import { HttpMethod } from '@activepieces/pieces-common';
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
import { isNil } from '@activepieces/shared';
import { clockifyAuth } from '../../index';
import { clockifyApiCall } from '../common/client';
import { workspaceId } from '../common/props';
const TRIGGER_KEY = 'new-timer-started-trigger';
export const newTimerStartedTrigger = createTrigger({
auth: clockifyAuth,
name: 'new-timer-started',
displayName: 'New Timer Started',
description: 'Triggers when a new entry is started and running.',
type: TriggerStrategy.WEBHOOK,
props: {
workspaceId: workspaceId({
displayName: 'Workspace',
required: true,
}),
},
async onEnable(context) {
const { workspaceId } = context.propsValue;
const response = await clockifyApiCall<{ id: string }>({
apiKey: context.auth.secret_text,
method: HttpMethod.POST,
resourceUri: `/workspaces/${workspaceId}/webhooks`,
body: {
url: context.webhookUrl,
webhookEvent: 'NEW_TIMER_STARTED',
triggerSourceType: 'WORKSPACE_ID',
triggerSource: [workspaceId],
},
});
await context.store.put<string>(TRIGGER_KEY, response.id);
},
async onDisable(context) {
const { workspaceId } = context.propsValue;
const webhookId = await context.store.get<string>(TRIGGER_KEY);
if (!isNil(webhookId)) {
await clockifyApiCall<{ id: string }>({
apiKey: context.auth.secret_text ,
method: HttpMethod.DELETE,
resourceUri: `/workspaces/${workspaceId}/webhooks/${webhookId}`,
});
}
},
async run(context) {
return [context.payload.body];
},
sampleData: {
id: '684538940300f917a02f642f',
name: 'Test',
projectId: '68444b15551a9934b5034263',
workspaceId: '684446430300f917a02c198b',
assigneeIds: [],
assigneeId: '',
userGroupIds: [],
estimate: 'PT0S',
status: 'ACTIVE',
budgetEstimate: 0,
billable: true,
hourlyRate: null,
costRate: null,
progress: null,
duration: 'PT0S',
},
});