District Core Developer DocsDistrict Core Developer Docs
Developers
Boilerplate
Modules
Bitbucket
Developers
Boilerplate
Modules
Bitbucket
  • Modules

    • ABN
    • ActivityLog
    • AnalyticsApi
    • ApiConnector
    • BlockApi
    • CategoryApi
    • CloneApi
    • CommentApi
    • ContentApi
    • Core
    • Documents
    • EmbedApi
    • Event
    • ExportApi
    • FeatureApi
    • FormApi
    • GTM
    • GalleryApi
    • HelpApi
    • Hotspot
    • IdeaSurvey
    • ImportApi
    • InteractionsApi
    • Intercom
    • MailApi
    • MapApi
    • MapSurvey
    • MediaApi
    • MenuApi
    • MetaTagApi
    • NlpApi
    • NotificationApi
    • Page
    • ParentableContent
    • PaymentApi
    • PermissionsApi
    • Postcode
    • ReCaptcha
    • Redirects
    • Renderer
    • ReportApi
    • RestrictionApi
    • RevisionApi
    • SearchApi
    • Settings
    • ShareableApi
    • Slack
    • SlugApi
    • SubscribableApi
    • Survey
    • Team
    • TenantApi
    • TestApi
    • ThemeApi
    • Timeline
    • TranslationApi
    • Update
    • Users
    • VisualisationApi
    • WorkflowApi
    • Wysiwyg

AnalyticsAPI Module

This module is responsible for talking to Matomo Analytics. It contains both JS tracking code and an abstraction for retrieving analytics via the API.

This module also syncs analytics to the database locally so the data can be used with eloquent queries, adding support for sorting by analytics metrics and reducing load on Matomo.

Adding a new site to matomo

You should add a new site to Matomo for each app/environment combination. Eg Captivate staging and Captivate demo should both have their own site ID.

At time of writing we are using a shared matomo instance https://district.analytics.stack.host.

You can add a new site to this instance with pre-configured defaults via artisan analytics:new-site

You can list all existing sites with artisan analytics:site-list

Config

Ensure the following environment variables are defined

MATOMO_URL=https://url.to.matomo.instance
MATOMO_SITE_ID=site_id_in_matomo
MATOMO_API_TOKEN=matomo_api_key

Tracking

Tracking is done via JS. Firstly Resouces/js/AnalyticsInit.js is called inside createInertiaApp which adds _paq to window along with the correct site id and tracking dependencies. This should generally need to be modified.

Then Resouces/js/AnalyticsHelper.js is responsible for business logic surrounding tracking. Its primary method is pageView() which gets called on Inertia navigate (page load/change). The pageView adds Custom dimensions which can be used for filtering when retrieving results (via segments). It also adds generic tracking data such as current user, page title, etc.

Custom dimensions

Custom dimensions are key to filtering data in Matomo. It is similar to Custom Variables which is now deprecated (and not part of matomo core). This module uses custom dimensions for:

  • Filtering by frontend / dashboard
  • Filtering by a specific model
  • Filtering by a specific model parent
  • Filtering by tenant
  • Filtering by the path

Managing custom dimensions

In Matomo

Custom dimensions will be created with correct ID's if you add a new Matomo site via the analytics:new-site artisan command. If doing it manually, the ID's must align with Config/matomo.php as described below.

In the app

All custom dimension keys should be defined in Enums\CustomDimensionKeys, each dimension should map to an id in Config/matomo.php (matomo.dimension_map). The ID is what is shown in
Matomo > Admin (cog) > Websites > Custom dimensions. It is critical that these all match. You can retrieve an ID via CustomDimensionKeys::getId(). Example:

$id = CustomDimensionKeys::getId(CustomDimensionKeys::CONTENT);
// $id equals config('matomo.dimension_map.content') or 4.

The custom dimension ids should also be replicated in AnalyticsHelper.customDimensionsIdMap. This duplication should be fixed in a future ticket DISTCORE-296.

Custom dimension patterns

Enums\CustomDimensionKeys

  • ROLE expects to be a role id (visit scope)
  • CONTENT expects to be $model->type.':'.$model->id, an attribute is available in HasAnalyticsTrait that exposes this via $model->analytics_key.
  • CONTENT_PARENT same as CUSTOM_DIMENSION_CONTENT but for the parentable content.
  • TENANT expects to be $tenant->id
  • PATH current path (no domain) - calculated automatically by matomo.
  • SECTION either dash or frontend

Custom dimension scopes

We mainly use page scope dimensions which is added to each page change tracking request and great for filtering. The alternative is visit scope which once set, remains in place for the duration of the visit, this is less useful for filtering.

Adding more custom dimensions

Matomo cloud provides 15 custom dimensions per scope, the self hosted version only has 5, but you can add more. Docs.

Self hosted via docker, docker exec bash into the container, then:

cd /opt/bitnami/matomo && php console customdimensions:add-custom-dimension --scope=action --count=5

Filtering data by custom dimensions

This is all nicely abstracted in AnalyticsService and you can just use the method addDimension(). See Enums\AnalyticsSegmentOperators for available operators such as equals, contains, starts with, etc.

Example, get Visits summary, exclude dash, date range 2023-01-01 to today, current tenant for model page and id 3:

    $visits = AnalyticsService::new()
      ->setRange('2023-01-01', 'today')
      ->addDimension(
        CustomDimensionKeys::getId(CustomDimensionKeys::SECTION), 
        AnalyticsService::SECTION_NAME_DASH, 
        'and', 
        AnalyticsSegmentOperators::NOT_EQUALS
      )
      ->addDimension(CustomDimensionKeys::getId(CustomDimensionKeys::TENANT), $currentTenantId)
      ->addDimension(CustomDimensionKeys::getId(CustomDimensionKeys::CONTENT), 'page:3')
      ->getVisitsSummary();

NOTE: Helpers exist in AnalyticsService to abstract dimension adding. For example setCurrentTenant() and setExcludeDash().

Goals

Matomo Goals are used to log conversions. Goals are defined in Matomo > Admin (cog) > Websites > Goals. At time of writing there is only one goal for a survey submission.

In Matomo

Goals will be created with correct ID's if you add a new Matomo site via the analytics:new-site artisan command. If doing it manually, the ID's must align with Config/matomo.php as described below.

In the app

Goal ids are defined in AnalyticsHelper.goals. To trigger a goal, add the code trackGoal(goalName) to the JS callback that indicates a successful goal.

Example in the SurveyForm component:

this.form
    .patch(this.survey.url, {
        onFinish: () => {
            AnalyticsHelper.trackGoal('surveySubmission')
        },
    })

Analytics Service

Services\AnalyticsService

This class is an abstraction on top of Matomo-PHP-API. You should always use this over the Matomo class directly as it deals with loading config, mocking and more.

As per the above example, you can use this to retrieve filtered results from the Matomo API. Filtering is done via Segments. Custom dimensions are just segments with specific keys.

The AnalyticsService class is well documented, read through the methods and doc blocks to see what it can do.

By default, all results are cached, this can be toggled with setCacheEnabled(true/false).

Mocking the API (testing)

To prevent access to the real API or just use dummy data, you can either:

  • Instantiate AnalyticsService with AnalyticsService::fake()
  • Toggle via useMock(true/false)
  • Set via config config(['matomo.fake' => true])
  • Use environment variable MATOMO_API_FAKE=true

This will swap out the Matomo-PHP-API class with MatomoMockResponse which returns the same structure just with fake data.

Syncing/Caching analytics to the database

  • Services\AnalyticsCacheService
  • Models\Analytics
  • Models\Concerns\HasAnalyticsInterface
  • Models\Traits\HasAnalyticsTrait

Any model that implements HasAnalyticsInterface and uses HasAnalyticsTrait will automatically sync getVisitsSummary data. A row is added for each model and the date. This means you can do something like:

    $myModelWithAnalytics = MyModelWithAnalyics::where('id', 1)
      ->with('analytics')
      ->whereHas('analytics', fn ($q) => $q->whereDate('date', '<=', today()->toDateString()))
      ->get();

With this sync/cache it means you can leverage eloquent relationships and easily filter, sort, sum, avg and much more.

Triggering a sync

This is run automatically via cron and the job queue. Run lando artisan analytics:cache-update-all-tenants to queue a refresh of all tenants. Then run lando artisan horizon (if not already running) to process the queue.

Sync queue and jobs

The queue used to sync analytics is analytics. Two jobs are involved, the first looks for all models with HasAnalyticsInterface then for each create a new job that syncs analytics since created_at date. Each DB entry contains a last_update date which ensures that past analytics do not get refreshed unnecessarily. Once a model has had an initial sync, subsequent jobs should only sync the current day.

The AnalyticsCacheService has $minsBetweenRefresh property which is the minimum time between a refresh (regardless of how often cron runs). This is set to 3 hours so a sync should not run more often than that.

Report widgets

A result of all of the above is we have a great collection of reporting widgets that can be used in reporting dashboards. Module specific widgets can also easily pull in analytics metrics to enrich standard data.


Edit this page
Prev
ActivityLog
Next
ApiConnector