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

ReportAPI module

The purpose of this module is to provide admin reporting dashboards. A reporting dashboard consists of a bunch of widgets which show data filtered by date or a context.

Defining default report dashboards

See Config/reportapi_dashboards.php for default dashboards for different areas of the admin area. A dashboard must define the following

return [
    'my_dash' => [
        'enabled' => true, // If dash will show.
        'layout' => ReportDashboardLayoutType::TWO_COLUMN, // See ReportDashboardLayoutType Enum for options.
        'parent' => null, // The parentableContent or owner of the dash
        'include_provides' => [ // What widgets are available to the dash. A widget defines what it provides.
            ReportDashboardWidgetProvides::GLOBAL, // Global eg Html, group
            ReportDashboardWidgetProvides::ANALYTICS, // Analytics widgets. Eg unique visits
            ReportDashboardWidgetProvides::PARENTABLE, // Widgets that assume a parentable (or site wide). Eg event attendees
            // ReportDashboardWidgetProvides::SUBMISSIONS, // Widgets that provide data about submissions. Eg input aggregate
        ],
        'widgets' => [
            // Widget array
        ]
]

Widgets

Widgets are auto discovered by being placed in Plugins/Widgets/Plugins and implementing WidgetInterface. Any new widget should really extend any of the base widgets:

  • BaseWidget - The most generic of widgets
  • BaseCountWidget - A count widget, displaying a number and optionally a trend chart
  • BaseDynamicTableWiodget - A table of records
  • BaseTimeSeriesWidget - A time series chart

By extending these widgets you should be able to add new widgets will all the structure handled and consistent. You should only need to define how the data is retrieved and additional settings. Helpers are provided for column selection and anything else that is reused more than once.

Widget default settings

When defining a widget class, you set the default settings. If not overridden these settings will be used with default dashboards. Example below will use defaults for missing settings such as size, variant and subject.

    [
        'id' => 100,
        'type' => AnalyticsCount::MACHINE_NAME,
        'settings' => [
            'title' => 'Unique visitors',
            'region' => ReportDashboardLayoutRegions::MAIN,
            'metric' => AnalyticsRemoteFields::UNIQ_VISITORS,
        ],
    ],

Examples

At time of writing there is at least 10 different widget plugins across various modules. Look for a widget that is closest to what you want to achieve then follow the same pattern.

Modules with widget plugins

  • ContentApi
  • AnalyticsApi
  • Documents
  • Events
  • Surveys

For each module, checkout the Plugins/Widgets/Plugins dirs.

Testing widgets

A number of helpers are available via Tests/Traits/WidgetPluginTestTrait.

  • executeWidgetGetData() will return the response from a widget getData
  • assertTableWidgetResponseCorrect() will check common props on a table widget
  • assertCountWidgetResponseCorrect() will check common props on a count widget

A Good example of testing a few widget types is WidgetPluginEventsTest

Dynamic table example

$settings = ['columns' => ['first_row_colum_name']];

$this->executeWidgetGetData(
    TableWidget::class,
    $settings,
    fn ($response) => $this->assertTableWidgetResponseCorrect($response, $settings, $expectedCount, ['first_row_colum_name' => $first_row_colum_value])
);

Count example

$settings = ['show_chart' => true];

$this->executeWidgetGetData(
    CountWidget::class,
    $settings,
    fn ($response) => $this->assertCountWidgetResponseCorrect($response, $settings, $expectedCount)
);

Saving dashboards

Saved dashboards use the Report model. These have practically the same structure as a report config. If a report dashboard has a matching report model then the dashboard will use that for its config, otherwise it will fall back to reportapi_dashboards config.

Getting the appropriate dashboard is done via static helper methods on the Report model

// Get a report without parent context (eg homepage) for the current user.
$report = Report::getCurrentUserReport('report_group_name'); 

// Get a report with a parent context (eg project/team report) for all users.
$contextModel = ModelThatImplementsHasReportDashboardInterface::find(1);
$report = Report::getCurrentParentReport($contextModel);
$groupName = Report::getParentGroup($contextModel);

If $report is null, fall back to config('reportapi_dashboards.GROUP_NAME')

Dashboards with parent context

Dashboards may have a parent context, this enables the parent to be passed down to widgets as a context filter. Widgets should implement the logic to filter by the context.

For a parent to have a report dashboard, the following is required

  • Parent context model implements HasReportDashboardInterface
  • A reportapi_dashboards.GROUP_NAME config with GROUP_NAME being a singular version of table name.
  • Parent dashboard controller has the following methods reportIndex, reportStore & reportDestroy. It should also implement ReportDashboardControllerInterface and use ReportDashboardControllerTrait.
  • Routes are defined, this can be done by nesting ReportDashboardRouteService::registerDashboardRoutes($parentController)
  • Feature tests implement all assertions in ReportDashboardFeatureTestTrait

Examples

Model

Models/ParentModel.php

class ParentModel extends BaseModel implements HasReportDashboardInterface
{
  public function getActionUrlsAttribute(): array
  {
    // Add report route.
  }
}

Config

Config/reportapi_dashboards.php

return [
  'parent_model' => [
    // fallback config/default dash.
  ]
]

Controller

Http/Controllers/DashboardParentModelController.php

class DashboardParentModelController extends BaseDashboardController implements ReportDashboardControllerInterface
{
    use ReportDashboardControllerTrait;

    public function reportIndex(ParentModel $parentModel): Response
    {
        return $this->renderDashboard(
            $this->getParentReportConfiguration($parentModel),
            $this->parentRoute,
            $parentModel->title,
            $parentModel->append(self::$globalAppends)
        );
    }

    public function reportStore(ReportSaveRequest $request, ParentModel $parentModel): JsonResponse
    {
        return $this->saveDashboard($request, Report::getParentGroup($parentModel), Report::getCurrentParentReport($parentModel), $parentModel);
    }

    public function reportDestroy(ParentModel $parentModel): JsonResponse
    {
        return $this->deleteDashboard(Report::getCurrentParentReport($parentModel));
    }
}

Routes

Routes/dash.php

Route::prefix('parent_model/{parent_model}/report')
  ->group(fn () => ReportDashboardRouteService::registerDashboardRoutes(DashboardParentModelController::class))

Tests

Tests/Feature/ReportDashboardTest.php

class ReportDashboardTest extends TestCase
{
    use ReportDashboardFeatureTestTrait;
    
    ...
    
    public function test_can_get_index_with_no_saved_report()
    {
      $this->assertViewNonSavedReportHasNoErrors();
    }
    
    ...
}

See Comments in ReportDashboardFeatureTestTrait and HomepageReportDashboardTest for something you can duplicate.

Setting a parent context

This is hidden away in reportIndex via getParentReportConfiguration, the context is set on the DTO via setContext(). This context is passed all the way through to a widget.

Widgets need to implement context logic

Widgets have access to:

  • hasContext() if any context is set
  • getContext() get array of current context. This is lightweight, does no queries and returns the model class name (type) and model id.
  • getContextLoaded() get array of fully loaded context models with original relationships loaded this is always cached as every widget in a report may request this.

Edit this page
Prev
Renderer
Next
RestrictionApi