Skip to content
14 changes: 14 additions & 0 deletions .example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
GA_TID=G-XXXXXXXXXX
GA_CID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

PA_DOMAIN=example.com
PA_APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

OR_WORKSPACEID=orbxxxxxxxxxx
OR_APIKEY=orbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

MP_PROJECT_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

HS_APIKEY=pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

REO_APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
services:
[
Plausible,
HubSpot
ReoDev
]

steps:
Expand All @@ -29,6 +29,7 @@ jobs:
echo "HS_APIKEY=${{ secrets.HS_APIKEY }}" >> .env
echo "OR_APIKEY=${{ secrets.OR_APIKEY }}" >> .env
echo "OR_WORKSPACEID=${{ secrets.OR_WORKSPACEID }}" >> .env
echo "REO_APIKEY=${{ secrets.REO_APIKEY }}" >> .env

- name: Run ${{matrix.services}} Tests
run: |
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
# Utopia Analytics

[![Build Status](https://travis-ci.com/utopia-php/system.svg?branch=main)](https://travis-ci.com/utopia-php/analytics)
![Total Downloads](https://img.shields.io/packagist/dt/utopia-php/analytics.svg)
[![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://appwrite.io/discord)

Utopia Analytics is a simple and lite library to send information about events or pageviews to Google Analytics. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io).
Utopia Analytics is a simple and lite library to send information about events or pageviews to various analytics platforms. This library is aiming to be as simple and easy to learn and use. This library is maintained by the [Appwrite team](https://appwrite.io).

Although this library is part of the [Utopia Framework](https://github.com/utopia-php/framework) project it is dependency free and can be used as standalone with any other PHP project or framework.

## Getting Started

Install using composer:

```bash
composer require utopia-php/analytics
```

Init in your application:

```php
<?php

Expand All @@ -30,11 +31,21 @@ $ga->createPageView("appwrite.io", "/docs/installation");

// Sends an event indicating an installation.
$ga->createEvent("Installation", "setup.cli");

// Sends an event indicating that the fall campaign promotional video was played.
$ga->createEvent("Videos", "play", "Fall Campaign");

```

## Supported Platforms

- [Google Analytics](https://analytics.google.com)
- [Plausible](https://plausible.io)
- [Mixpanel](https://mixpanel.com)
- [HubSpot](https://hubspot.com)
- [Orbit](https://orbit.love)
- [Reo.dev](https://reo.dev)

## System Requirements

Utopia Framework requires PHP 7.4 or later. We recommend using the latest PHP version whenever possible.
Expand Down
117 changes: 117 additions & 0 deletions src/Analytics/Adapter/ReoDev.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace Utopia\Analytics\Adapter;

use Utopia\Analytics\Adapter;
use Utopia\Analytics\Event;

class ReoDev extends Adapter
{
/**
* Endpoint for ReoDev Product API
*/
protected string $endpoint = 'https://ingest.reo.dev/api/product/usage';

/**
* API Key
*/
protected string $apiKey;

/**
* Allowed event types to send. If empty, all types are allowed.
*/
private array $allowedEventTypes = [];

/**
* Gets the name of the adapter.
*/
public function getName(): string
{
return 'ReoDev';
}

/**
* @return ReoDev
*/
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
}

/**
* Set the event types that should be sent.
*
* @param array $types An array of event type strings.
*/
public function setAllowedEventTypes(array $types): self
{
$this->allowedEventTypes = $types;

return $this;
}

/**
* Sends an event to the ReoDev Product API. Requires 'email' prop and checks against allowed types.
*/
public function send(Event $event): bool
{
if (! $this->validate($event)) {
return false;
}

$meta = $event->getProps();
unset($meta['email']);
unset($meta['account']);
$payload = [
'activity_type' => $event->getType(),
'source' => 'PRODUCT_CLOUD',
'environment' => $event->getProp('environment'),
'user_id' => $event->getProp('email'),
'user_id_type' => 'EMAIL',
'ip_addr' => $this->clientIP,
'event_at' => time(),
'product_id' => $event->getProp('account'),
'user_agent' => $this->userAgent,
'meta' => $meta,
];

$payload = array_filter($payload, fn ($value) => ! is_null($value));

$body = ['payload' => $payload];

$this->call('POST', $this->endpoint, [
'Content-Type' => 'application/json',
'X-API-KEY' => $this->apiKey,
], $body);

return true;
}

/**
* Validates the event.
*
* Checks if:
* - the event has an 'email' property
* - the event type is allowed (if a filter is set)
* - the environment is either PRODUCTION or DEVELOPMENT
*
* @param Event $event The event to validate.
*/
public function validate(Event $event): bool
{
if (empty($event->getProp('email'))) {
return false;
}

if (! empty($this->allowedEventTypes) && ! in_array($event->getType(), $this->allowedEventTypes)) {
return false;
}

$environment = $event->getProp('environment');
if (! in_array($environment, ['PRODUCTION', 'DEVELOPMENT'])) {
return false;
}

return true;
}
}
69 changes: 68 additions & 1 deletion tests/Analytics/AnalyticsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Utopia\Analytics\Adapter\Mixpanel;
use Utopia\Analytics\Adapter\Orbit;
use Utopia\Analytics\Adapter\Plausible;
use Utopia\Analytics\Adapter\ReoDev;
use Utopia\Analytics\Event;
use Utopia\System\System;

Expand All @@ -28,13 +29,17 @@ class AnalyticsTest extends TestCase
/** @var \Utopia\Analytics\Adapter\HubSpot */
public $hs;

/** @var \Utopia\Analytics\Adapter\ReoDev */
public $reodev;

public function setUp(): void
{
$this->ga = new GoogleAnalytics(System::getEnv('GA_TID') ?? '', System::getEnv('GA_CID') ?? '');
$this->pa = new Plausible(System::getEnv('PA_DOMAIN') ?? '', System::getEnv('PA_APIKEY') ?? '', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', '192.168.0.1');
$this->orbit = new Orbit(System::getEnv('OR_WORKSPACEID') ?? '', System::getEnv('OR_APIKEY') ?? '', 'Utopia Testing Suite');
$this->mp = new Mixpanel(System::getEnv('MP_PROJECT_TOKEN') ?? '');
$this->hs = new HubSpot(System::getEnv('HS_APIKEY') ?? '');
$this->reodev = new ReoDev(System::getEnv('REO_APIKEY') ?? '');
}

/**
Expand Down Expand Up @@ -246,7 +251,7 @@ public function testCleanup(): void
}

/**
* @group mixpanel
* @group Mixpanel
*/
public function testMixpanel()
{
Expand Down Expand Up @@ -282,4 +287,66 @@ public function testMixpanel()
$res = $this->mp->appendProperties('[email protected]', ['union_field' => ['value2', 'value3']]);
$this->assertTrue($res);
}

/**
* @group ReoDev
*/
public function testReoDev()
{
$this->reodev
->setClientIP('127.0.0.1')
->setUserAgent('Utopia Test Suite');

// Test successful event with all required fields and no filter
$event = new Event;
$event
->setName('appwrite_docs')
->setType('button_click')
->setUrl('appwrite.io/docs')
->setProps([
'email' => '[email protected]',
'name' => 'Test Developer',
'account' => 'cloud',
'environment' => 'DEVELOPMENT',
'custom_prop1' => 'value1',
'custom_prop2' => 'value2',
]);

$this->assertTrue($this->reodev->validate($event));
$this->assertTrue($this->reodev->send($event));

// Test event without email (should fail validation and send)
$invalidEvent = new Event;
$invalidEvent
->setName('appwrite_docs')
->setType('page_view') // Use a different type for clarity
->setUrl('appwrite.io/docs')
->setProps([
'name' => 'Test Developer',
'account' => 'cloud',
'environment' => 'DEVELOPMENT',
]);

$this->assertFalse($this->reodev->validate($invalidEvent));
$this->assertFalse($this->reodev->send($invalidEvent));

// Test event type filtering
$allowedTypes = ['submit_account_login'];
$this->reodev->setAllowedEventTypes($allowedTypes);

// Disallowed event
$disallowedEvent = new Event;
$disallowedEvent
->setName('signup')
->setType('submit_signup')
->setUrl('appwrite.io/signup')
->setProps([
'email' => '[email protected]',
'account' => 'cloud',
'environment' => 'DEVELOPMENT',
]);

$this->assertFalse($this->reodev->validate($disallowedEvent));
$this->assertFalse($this->reodev->send($disallowedEvent));
}
}