diff --git a/README.md b/README.md index daf7f04..44e5822 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,62 @@ # ClickUp <-> Phabricator / Phorge bot Bot to help migration from ClickUp to Phabricator / Phorge (and vice-versa honestly). ## Available features * automatically import new ClickUp Tasks in Phabricator (with title, description) * automatically update the ClickUp Task to mention the Phabricator Task URL * automatically update the Phabricator Task to mention the ClickUp Task URL * automatically assign the right Phabricator Tag (associated to the corresponding ClickUp folder) * automatically import new Phabricator Tasks in ClickUp (and keeping its ClickUp parent) * automatically import new ClickUp Task comments into their related Phabricator Task (and vice-versa) +* automatically update the ClickUp Task when a commit closes a Task from Phabricator * automatically import new Phabricator Task owner when set from ClickUp * automatically update the Phabricator Task status when changed from ClickUp (and vice-versa) * automatically update the Phabricator Task title when changed from ClickUp * automatically create (and attach) a Phabricator parent Task if a ClickUp task has a parent task ## Warnings * ClickUp has a limited sub-task concept, only one level is allowed. The bot tries to workaround this limitation. ## Dev preparation ``` sudo apt install php-cli composer ``` ## Dev installation ``` composer install ``` ## Configuration Copy the file `config-example.php` to `config.php`. Read carefully the example configuration file. ## License Copyright (C) 2023 Valerio Bozzolan, contributors This software was developed and tested and released in non-working hours so the copyright does NOT belong to the company. Any company can use, study, modify, share this software (as long as the terms are respected). Any company can do this, included ER Informatica, without Valerio's written consent. You're welcome. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/public/webhook-phabricator/index.php b/public/webhook-phabricator/index.php index 5d862c4..f20d821 100644 --- a/public/webhook-phabricator/index.php +++ b/public/webhook-phabricator/index.php @@ -1,136 +1,141 @@ ClickUp bot # Copyright (C) 2023 Valerio Bozzolan, contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . /** * Receive Phabricator webhooks * * Documentation: * https://we.phorge.it/conduit/ * * See also Herald rule. */ // default response code http_response_code( 500 ); // load needed libraries require realpath( __DIR__ ) . '/../autoload.php'; // parse HTTP payload $response = file_get_contents('php://input'); // parse as an array since native Conduit also handles everything as an array $response_data = json_decode( $response, JSON_OBJECT_AS_ARRAY ); // no signature no party $signature_header = $_SERVER['HTTP_X_PHABRICATOR_WEBHOOK_SIGNATURE'] ?? null; if( !$signature_header ) { http_response_code( 400 ); echo "This is not a request from an official Phabricator webhook.\n"; exit; } // do the math $signature_calculated = hash_hmac( 'sha256', $response, PHABRICATOR_WEBHOOK_HMAC_KEY ); if( $signature_calculated !== $signature_header ) { http_response_code( 400 ); throw new Exception( "invalid signature, calculated $signature_calculated expected $signature_header" ); exit; } // no object no party $object = $response_data['object'] ?? null; if( !$object ) { http_response_code( 400 ); throw new Exception( "received empty Phabricator object" ); } $transactions = $response_data['transactions'] ?? []; $object_type = $object['type']; $object_phid = $object['phid']; -$cache = ClickUpPhabricatorCache::instance(); - -switch( $object_type ) { - - case 'TASK': - - // query fresh Task data - $phab_task = PhabricatorAPI::getTaskByPHID( $object_phid ); - if( !$phab_task ) { - throw new Exception( "missing Task: {$object_phid}" ); - } - - $phab_task_id = $phab_task['id']; - $phab_task_phid = $phab_task['phid']; - $phab_task_status = $phab_task['fields']['status']['value']; - $phab_owner_phid = $phab_task['fields']['ownerPHID'] ?? null; - - // create or update the ClickUp task from Phabricator Task data - $clickup_task = ClickUpAPI::createOrUpdateFromPhabricatorTaskData( $phab_task ); - - if( $transactions ) { - $transaction_results = PhabricatorAPI::searchObjectTransactionsFromTransactions( $phab_task_phid, $transactions ); - foreach( $transaction_results as $transaction_result ) { - - // found some new Phabricator comments - $phab_comments = $transaction_result['comments'] ?? []; - foreach( $phab_comments as $phab_comment ) { - - // Phabricator comment content - $comment_content_raw = $phab_comment['content']['raw']; - - // Phabricator author (NOTE: it MUST exists) - $comment_author = PhabricatorAPI::searchSingleUserByPHID( $phab_comment['authorPHID'] ); - $comment_author_name = $comment_author['fields']['realName']; - - // post the comment on ClickUp - if( $clickup_task ) { - // "From Phabricator: Mario Rossi:\n\nHello world!" - // IMPORTANT: keep the COMMENT_PREFIX_FROM_PHABRICATOR at startup since now - // the ClickUp webhook controls this token at startup. - $clickup_comment = sprintf( - "%s %s:\n\n%s", - COMMENT_PREFIX_FROM_PHABRICATOR, - $comment_author_name, - $comment_content_raw - ); - ClickupAPI::addTaskComment( $clickup_task->id, $clickup_comment ); - } +// check whenever the PHID is actually a Task +$PHID_TASK_PREFIX = 'PHID-TASK-'; +$object_phid_is_task = substr( $object_phid, 0, strlen( $PHID_TASK_PREFIX ) ) === $PHID_TASK_PREFIX; + +if( $object_phid_is_task ) { + + $cache = ClickUpPhabricatorCache::instance(); + + // query fresh Task data + $phab_task = PhabricatorAPI::getTaskByPHID( $object_phid ); + if( !$phab_task ) { + throw new Exception( "missing Task: {$object_phid}" ); + } + + $phab_task_id = $phab_task['id']; + $phab_task_phid = $phab_task['phid']; + $phab_task_status = $phab_task['fields']['status']['value']; + $phab_owner_phid = $phab_task['fields']['ownerPHID'] ?? null; + + // create or update the ClickUp task from Phabricator Task data + $clickup_task = ClickUpAPI::createOrUpdateFromPhabricatorTaskData( $phab_task ); + + if( $transactions ) { + $transaction_results = PhabricatorAPI::searchObjectTransactionsFromTransactions( $phab_task_phid, $transactions ); + foreach( $transaction_results as $transaction_result ) { + + // found some new Phabricator comments + $phab_comments = $transaction_result['comments'] ?? []; + foreach( $phab_comments as $phab_comment ) { + + // Phabricator comment content + $comment_content_raw = $phab_comment['content']['raw']; + + // Phabricator author (NOTE: it MUST exists) + $comment_author = PhabricatorAPI::searchSingleUserByPHID( $phab_comment['authorPHID'] ); + $comment_author_name = $comment_author['fields']['realName']; + + // post the comment on ClickUp + if( $clickup_task ) { + // "From Phabricator: Mario Rossi:\n\nHello world!" + // IMPORTANT: keep the COMMENT_PREFIX_FROM_PHABRICATOR at startup since now + // the ClickUp webhook controls this token at startup. + $clickup_comment = sprintf( + "%s %s:\n\n%s", + COMMENT_PREFIX_FROM_PHABRICATOR, + $comment_author_name, + $comment_content_raw + ); + ClickupAPI::addTaskComment( $clickup_task->id, $clickup_comment ); } + } - // check if we have missed something to be integrated - unset( $transaction_result['comments'] ); - if( $transaction_result ) { - error_log( "received unsupported Phabricator transaction: " . json_encode( $transaction_result ) ); - } + // check if we have missed something to be integrated + unset( $transaction_result['comments'] ); + if( $transaction_result ) { + error_log( "received unsupported Phabricator transaction: " . json_encode( $transaction_result ) ); } } + } +} +// debug incoming weird stuff +switch( $object_type ) { + case 'TASK': + // nothing interesting break; - case 'HWBH': error_log( "received Phabricator Webhook (test?)" ); break; - default: - error_log( "unknown Phabricator webhook: {$response}" ); + error_log( "received unknown Phabricator webhook: {$response}" ); } // success http_response_code( 200 );