Skip to content

Commit e736d33

Browse files
authored
Add support for client-rendered widgets (#24353)
* Add support for client-rendered widgets * fix initial dashboard widget loading * update expected test files * apply further fixes * add changelog entry
1 parent 3eb640f commit e736d33

19 files changed

Lines changed: 679 additions & 87 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The Product Changelog at **[matomo.org/changelog](https://matomo.org/changelog)*
66

77
## Matomo 5.10.0
88

9+
### New APIs
10+
* Widgets can now be declared as client-rendered through `WidgetConfig::setClientSideComponent()` and `WidgetConfig::setClientSideProps()`. `API.getWidgetMetadata` and `API.getReportPagesMetadata` now expose a `clientComponent` field for these widgets, and Widgetize/dashboard rendering supports bootstrapping them without an extra widget controller request.
11+
912
### Deprecations
1013
* The methods `ArchiveTableCreator::getNumericTable()` and `ArchiveTableCreator::getBlobTable()` now support a `$createIfMissing` parameter. Omitting this parameter is deprecated; pass `true` to create missing archive tables or `false` to return only existing tables. In Matomo 6 the default behavior for omitted calls will change to lookup-only.
1114

core/Widget/WidgetConfig.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class WidgetConfig
2727
protected $action = '';
2828
protected $parameters = array();
2929
protected $middlewareParameters = array();
30+
protected $clientSideComponent = array();
31+
protected $clientSideProps = array();
3032
protected $name = '';
3133
protected $order = 99;
3234
protected $isEnabled = true;
@@ -124,6 +126,10 @@ public function getAction()
124126
* Sets (overwrites) the parameters of the widget. These parameters will be added to the URL when rendering the
125127
* widget. You can access these parameters via `Piwik\Common::getRequestVar(...)`.
126128
*
129+
* This applies to widgets rendered through their controller/action request. Client-rendered widgets do not receive
130+
* these parameters automatically and should instead derive request state from the browser context or load data via
131+
* API requests.
132+
*
127133
* @param array $parameters eg. ('urlparam' => 'urlvalue')
128134
* @return static
129135
*/
@@ -137,6 +143,9 @@ public function setParameters($parameters)
137143
/**
138144
* Add new parameters and only overwrite parameters that have the same name. See {@link setParameters()}
139145
*
146+
* Like {@link setParameters()}, these parameters are only used for widgets rendered through their
147+
* controller/action request and are not forwarded automatically to client-rendered widgets.
148+
*
140149
* @param array $parameters eg. ('urlparam' => 'urlvalue')
141150
* @return static
142151
*/
@@ -352,6 +361,64 @@ public function getMiddlewareParameters()
352361
return $this->middlewareParameters;
353362
}
354363

364+
/**
365+
* Marks this widget as client-rendered by a Vue component exported by the given plugin bundle.
366+
*
367+
* Client-rendered widgets do not execute the widget controller/action in a separate request before rendering.
368+
* They should derive dynamic state from the current browser request or load data through API requests instead.
369+
*
370+
* @param string $plugin eg 'Transitions'
371+
* @param string $component eg 'TransitionsPage'
372+
* @return static
373+
* @since 5.10.0
374+
*/
375+
public function setClientSideComponent(string $plugin, string $component)
376+
{
377+
$this->clientSideComponent = array(
378+
'plugin' => $plugin,
379+
'name' => $component,
380+
);
381+
382+
return $this;
383+
}
384+
385+
/**
386+
* Returns the configured client-rendered component definition.
387+
* @return array{}|array{plugin: string, name: string}
388+
* @since 5.10.0
389+
*/
390+
public function getClientSideComponent(): array
391+
{
392+
return $this->clientSideComponent;
393+
}
394+
395+
/**
396+
* Sets static props that should be passed to the client-rendered Vue widget.
397+
*
398+
* Use this for configuration known when the widget is registered. Request-specific widget parameters are not
399+
* forwarded to client-rendered widgets through this mechanism.
400+
*
401+
* @param array $props
402+
* @return static
403+
* @since 5.10.0
404+
*/
405+
public function setClientSideProps(array $props)
406+
{
407+
$this->clientSideProps = $props;
408+
409+
return $this;
410+
}
411+
412+
/**
413+
* Returns props configured for the client-rendered Vue widget.
414+
* @return array
415+
* @since 5.10.0
416+
*/
417+
public function getClientSideProps(): array
418+
{
419+
return $this->clientSideProps;
420+
}
421+
355422
/**
356423
* Marks this widget as a "wide" widget that requires the full width.
357424
*

plugins/API/WidgetMetadata.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ public function buildWidgetMetadata(WidgetConfig $widget, $categoryList = null,
116116
$item['middlewareParameters'] = $middleware;
117117
}
118118

119+
$clientComponent = $widget->getClientSideComponent();
120+
121+
if (!empty($clientComponent)) {
122+
$item['clientComponent'] = $clientComponent;
123+
124+
$clientProps = $widget->getClientSideProps();
125+
if (!empty($clientProps)) {
126+
$item['clientComponent']['props'] = $clientProps;
127+
}
128+
}
129+
119130
if ($widget instanceof ReportWidgetConfig) {
120131
$item['viewDataTable'] = $widget->getViewDataTable();
121132
$item['isReport'] = true;

plugins/API/tests/Unit/WidgetMetadataTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ public function testBuildWidgetMetadataShouldAddOptionalMiddlewareParameters()
114114
$this->assertSame(array('module' => 'Goals', 'action' => 'hasAnyConversions'), $metadata['middlewareParameters']);
115115
}
116116

117+
public function testBuildWidgetMetadataShouldAddOptionalClientComponent()
118+
{
119+
$config = $this->createWidgetConfig('Test', 'CategoryId', 'SubcategoryId');
120+
$config->setClientSideComponent('Transitions', 'TransitionsPage');
121+
$config->setClientSideProps(array('foo' => 'bar'));
122+
$metadata = $this->metadata->buildWidgetMetadata($config);
123+
124+
$this->assertSame(array(
125+
'plugin' => 'Transitions',
126+
'name' => 'TransitionsPage',
127+
'props' => array('foo' => 'bar'),
128+
), $metadata['clientComponent']);
129+
}
130+
117131
public function testBuildWidgetMetadataShouldAddReportInformtionIfReportWidgetConfigGiven()
118132
{
119133
$config = new ReportWidgetConfig();

0 commit comments

Comments
 (0)