mirror of
https://codeberg.org/artfulrobot/contactcats.git
synced 2025-06-25 12:58:05 +02:00
Add first test, and fix the bugs it found!
This commit is contained in:
parent
92db3be27e
commit
78e5b83c1d
3 changed files with 183 additions and 21 deletions
|
@ -19,26 +19,37 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
/**
|
||||
* Start Date
|
||||
*
|
||||
* @required
|
||||
* @var string
|
||||
* @required
|
||||
* @default NULL
|
||||
*/
|
||||
protected string $startDate;
|
||||
protected $startDate = '';
|
||||
|
||||
/**
|
||||
* End Date (optional)
|
||||
*
|
||||
* @var string|NULL
|
||||
* @var string|null
|
||||
* @default NULL
|
||||
*/
|
||||
protected ?string $endDate = NULL;
|
||||
|
||||
private string $catChangesTableName;
|
||||
private int $activityTypeId;
|
||||
|
||||
public function _run(Result $result) {
|
||||
Civi::log()->debug('Begin', ['=start' => 'GetFlows', '=timed' => 1, 'from' => $this->startDate, 'to' => $this->endDate]);
|
||||
|
||||
$this->catChangesTableName = \Civi\Api4\CustomGroup::get(FALSE)
|
||||
->addWhere('name', '=', 'Category_changes')
|
||||
->execute()->single()['table_name'];
|
||||
$this->activityTypeId = (int) \Civi\Api4\OptionValue::get(FALSE)
|
||||
->addWhere('name', '=', 'changed_contact_category')
|
||||
->addWhere('option_group_id:name', '=', 'activity_type')
|
||||
->execute()->single()['value'];
|
||||
// Check dates are valid and get a Ymd format of the day *following* the specified one.
|
||||
// This is so we can find activities *before* this date and thereby capture activities that
|
||||
// occurred *on* the given date.
|
||||
$getValidDate = fn(?string $input) => $input ? (new DateTimeImmutable($input))->modify('+1 day')->format('Ymd'): FALSE;
|
||||
$getValidDate = fn(?string $input) => $input ? (new DateTimeImmutable($input))->modify('+1 day')->format('Ymd') : FALSE;
|
||||
|
||||
$endDate = NULL;
|
||||
$startDate = $getValidDate($this->startDate);
|
||||
|
@ -54,7 +65,7 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
throw new CRM_Core_Exception(E::ts("This version of CiviCRM does not support twisting the space time continuum. End date cannot be before start date."));
|
||||
}
|
||||
}
|
||||
else if ($startDate > date('Ymd', strtotime('tomorrow'))) {
|
||||
elseif ($startDate > date('Ymd', strtotime('tomorrow'))) {
|
||||
throw new CRM_Core_Exception(E::ts("This version of CiviCRM does not support predicting the future. Start time cannot be in the future."));
|
||||
}
|
||||
|
||||
|
@ -79,18 +90,19 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
*/
|
||||
protected function windowFunctionsSupported() {
|
||||
$cache = \CRM_Utils_Cache::create(['type' => ['SqlGroup'], 'name' => 'contactcats']);
|
||||
$supported = $cache->get('windowFunctionsSupported'); // Will return default if cached value expired.
|
||||
$supported = null;
|
||||
// Will return default if cached value expired.
|
||||
$supported = $cache->get('windowFunctionsSupported');
|
||||
$supported = NULL;
|
||||
if ($supported === NULL) {
|
||||
// Need to test for window functions.
|
||||
$supported = FALSE;
|
||||
// Prevent Civi handling possible execption
|
||||
$handler = set_exception_handler(fn() => false);
|
||||
$handler = set_exception_handler(fn() => FALSE);
|
||||
try {
|
||||
\CRM_Core_DAO::executeQuery("CREATE TEMPORARY TABLE contactcats_window_check (i int unsigned not null, x int unsigned not null)");
|
||||
\CRM_Core_DAO::executeQuery("INSERT INTO contactcats_window_check VALUES (1, 1), (2, 1), (3, 2);");
|
||||
$rows = \CRM_Core_DAO::executeQuery("SELECT x, ROW_NUMBER() OVER ( PARTITION BY (x) ORDER BY i DESC) rn FROM contactcats_window_check ORDER BY x, rn")->fetchAll();
|
||||
$supported = ($rows === [ [ 'x' => "1", 'rn' => "1"], [ 'x' => "1", 'rn' => "2"], [ 'x' => "2", 'rn' => "1"] ]) ;
|
||||
$supported = ($rows === [['x' => "1", 'rn' => "1"], ['x' => "1", 'rn' => "2"], ['x' => "2", 'rn' => "1"]]);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Silent fail.
|
||||
|
@ -114,9 +126,8 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
FROM civicrm_activity a
|
||||
INNER JOIN civicrm_activity_contact ac
|
||||
ON a.id = ac.activity_id AND ac.record_type_id = 3
|
||||
INNER JOIN civicrm_contact_category cc
|
||||
ON cc.id = ac.contact_id
|
||||
WHERE a.activity_type_id = 89
|
||||
/*INNER JOIN civicrm_contact_category cc ON cc.id = ac.contact_id */
|
||||
WHERE a.activity_type_id = $this->activityTypeId
|
||||
),
|
||||
|
||||
/* startActivity is the latest activity before the window's start date */
|
||||
|
@ -141,15 +152,15 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
WHERE a2.activity_date_time BETWEEN %1 AND %2
|
||||
)
|
||||
|
||||
/* join startActivity and 2 to count changes between each shift */
|
||||
SELECT cat1.new_category_id from_category_id, cat2.new_category_id to_category_id, count(*) contact_count
|
||||
/* join startActivity and endActivity to count changes between each shift */
|
||||
SELECT startCat.new_category_id from_category_id, endCat.new_category_id to_category_id, count(*) contact_count
|
||||
FROM endActivity
|
||||
INNER JOIN civicrm_value_category_chan_41 cat1
|
||||
ON endActivity.activity_id = cat1.entity_id
|
||||
INNER JOIN $this->catChangesTableName endCat
|
||||
ON endActivity.activity_id = endCat.entity_id
|
||||
LEFT JOIN (
|
||||
startActivity
|
||||
INNER JOIN civicrm_value_category_chan_41 cat2
|
||||
ON startActivity.activity_id = cat2.entity_id
|
||||
INNER JOIN $this->catChangesTableName startCat
|
||||
ON startActivity.activity_id = startCat.entity_id
|
||||
)
|
||||
ON startActivity.contact_id = endActivity.contact_id AND startActivity.rn = 1
|
||||
WHERE endActivity.rn = 1
|
||||
|
@ -157,6 +168,8 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
;
|
||||
SQL;
|
||||
|
||||
// print "\n$sql\n %1 $startDateYmd, %2 $endDateYmd\n";
|
||||
|
||||
if (!$endDateYmd) {
|
||||
$endDateYmd = date('Ymd', strtotime('tomorrow'));
|
||||
}
|
||||
|
@ -168,14 +181,18 @@ class GetFlows extends \Civi\Api4\Generic\AbstractAction {
|
|||
// Don't use exchange array so as not to gazump non-array data(?)
|
||||
foreach ($data as $row) {
|
||||
$result[] = [
|
||||
'from_category_id' => (int) $row['new_category_id'],
|
||||
'from_category_id' => (int) $row['from_category_id'],
|
||||
'to_category_id' => (int) $row['to_category_id'],
|
||||
'contact_count' => (int) $row['contact_count'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function dump(string $msg, string $sql) {
|
||||
print "\n$msg ===============================\n$sql\n";
|
||||
$data = CRM_Core_DAO::executeQuery($sql)->fetchAll();
|
||||
print_r($data);
|
||||
print "\n ===============================\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Civi\Api4;
|
||||
|
||||
use Civi\Api4\Action\ContactCategory\GetFlows;
|
||||
use Civi\Api4\Action\ContactCategory\Sync;
|
||||
|
||||
/**
|
||||
|
@ -19,4 +20,11 @@ class ContactCategory extends Generic\DAOEntity {
|
|||
return new Sync('ContactCategory', 'sync');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function getFlows(): GetFlows {
|
||||
return new GetFlows('ContactCategory', 'getFlows');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
137
tests/phpunit/Civi/Api4/Action/ContactCategory/GetFlowsTest.php
Normal file
137
tests/phpunit/Civi/Api4/Action/ContactCategory/GetFlowsTest.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
namespace Civi\Api4\Action\ContactCategory;
|
||||
|
||||
use Civi\Api4\Contact;
|
||||
use Civi\Api4\Activity;
|
||||
use Civi\Api4\ContactCategory;
|
||||
use Civi\Api4\ContactCategoryDefinition;
|
||||
use Civi\Api4\Group;
|
||||
use CRM_Contactcats_ExtensionUtil as E;
|
||||
use Civi\Test\CiviEnvBuilder;
|
||||
use Civi\Test\HeadlessInterface;
|
||||
use Civi\Test\HookInterface;
|
||||
use Civi\Test\TransactionalInterface;
|
||||
|
||||
/**
|
||||
* Check our stats are predicatable.
|
||||
*
|
||||
* Tips:
|
||||
* - With HookInterface, you may implement CiviCRM hooks directly in the test class.
|
||||
* Simply create corresponding functions (e.g. "hook_civicrm_post(...)" or similar).
|
||||
* - With TransactionalInterface, any data changes made by setUp() or test****() functions will
|
||||
* rollback automatically -- as long as you don't manipulate schema or truncate tables.
|
||||
* If this test needs to manipulate schema or truncate tables, then either:
|
||||
* a. Do all that using setupHeadless() and Civi\Test.
|
||||
* b. Disable TransactionalInterface, and handle all setup/teardown yourself.
|
||||
*
|
||||
* @method void assertNotEmpty()
|
||||
* @method void assertEquals()
|
||||
* @method void assertMatchesRegularExpression()
|
||||
* @group headless
|
||||
*/
|
||||
class GetFlowsTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
|
||||
|
||||
/**
|
||||
* Setup used when HeadlessInterface is implemented.
|
||||
*
|
||||
* Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
|
||||
*
|
||||
* @link https://github.com/civicrm/org.civicrm.testapalooza/blob/master/civi-test.md
|
||||
*
|
||||
* @return \Civi\Test\CiviEnvBuilder
|
||||
*
|
||||
* @throws \CRM_Extension_Exception_ParseException
|
||||
*/
|
||||
public function setUpHeadless(): CiviEnvBuilder {
|
||||
return \Civi\Test::headless()
|
||||
->installMe(__DIR__)
|
||||
->apply();
|
||||
}
|
||||
|
||||
public function setUp():void {
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function tearDown():void {
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: Test that a version is returned.
|
||||
*/
|
||||
// public function testWellFormedVersion():void {
|
||||
// $this->assertNotEmpty(E::SHORT_NAME);
|
||||
// $this->assertMatchesRegularExpression('/^([0-9\.]|alpha|beta)*$/', \CRM_Utils_System::version());
|
||||
|
||||
/**
|
||||
* }
|
||||
*/
|
||||
public function testGetFlows():void {
|
||||
// Create groups for our cats.
|
||||
[$amazingGroupID, $mehGroupID] = Group::save(FALSE)
|
||||
->setRecords([
|
||||
['name' => 'amazing', 'frontend_title' => 'amazing'],
|
||||
['name' => 'meh', 'frontend_title' => 'meh'],
|
||||
])->execute()->column('id');
|
||||
|
||||
// Create cats.
|
||||
[$amazingCatID, $mehCatID] = ContactCategoryDefinition::save(FALSE)
|
||||
->setRecords([
|
||||
[
|
||||
'label' => 'Amazing',
|
||||
'search_type:name' => 'group',
|
||||
'search_data' => ['group_id' => $amazingGroupID],
|
||||
'presentation_order' => 1,
|
||||
'execution_order' => 1,
|
||||
'color' => '#ff0000',
|
||||
],
|
||||
[
|
||||
'label' => 'Meh',
|
||||
'search_type:name' => 'group',
|
||||
'search_data' => ['group_id' => $mehGroupID],
|
||||
'presentation_order' => 2,
|
||||
'execution_order' => 2,
|
||||
'color' => '#fff000',
|
||||
],
|
||||
])
|
||||
->execute()->column('id');
|
||||
|
||||
// Create a contact
|
||||
$ctID = Contact::create(FALSE)
|
||||
->setValues([
|
||||
'contact_type' => 'Individual',
|
||||
'display_name' => 'Wilma',
|
||||
])->execute()->first()['id'];
|
||||
// print "xxxxx created ct $ctID\n";
|
||||
|
||||
// Create two activities.
|
||||
$activityIDs = Activity::save(FALSE)
|
||||
->setDefaults([
|
||||
'activity_type_id:name' => 'changed_contact_category',
|
||||
'target_contact_id' => $ctID,
|
||||
'source_contact_id' => $ctID,
|
||||
])
|
||||
->setRecords([
|
||||
[
|
||||
'activity_date_time' => '2025-01-01',
|
||||
'Category_changes.new_category_id' => $amazingCatID,
|
||||
],
|
||||
[
|
||||
'activity_date_time' => '2025-02-01',
|
||||
'Category_changes.new_category_id' => $mehCatID,
|
||||
],
|
||||
])->execute()->column('id');
|
||||
|
||||
// $acts = Activity::get(FALSE)->addWhere('id', 'IN', $activityIDs)->execute()->getArrayCopy(); print_r($acts);
|
||||
|
||||
// FIXME: this is returning backwards.
|
||||
$flows = ContactCategory::getFlows()
|
||||
->setStartDate('2025-01-03')
|
||||
->execute()->getArrayCopy();
|
||||
// print_r($flows);
|
||||
$this->assertEquals([
|
||||
['from_category_id' => $amazingCatID, 'to_category_id' => $mehCatID, 'contact_count' => 1],
|
||||
], $flows);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue