Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.70% covered (success)
98.70%
380 / 385
71.43% covered (warning)
71.43%
5 / 7
CRAP
n/a
0 / 0
Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_layout_for
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_layout_response
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
Automattic\Jetpack\PremiumAnalytics\register_dashboard_default_layout_route
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
1.00
Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_widget_instance
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_section_layouts
100.00% covered (success)
100.00%
328 / 328
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\get_dashboard_default_section_id_for
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
Automattic\Jetpack\PremiumAnalytics\seed_default_dashboard_layout
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * Dashboard Layout: Premium Analytics server-side defaults.
4 *
5 * Premium Analytics owns its dashboard, so it ships its own default layout
6 * rather than relying on the core dashboard endpoint (which is Gutenberg-only
7 * and returns the core dashboard's widgets). The default is served on the
8 * `dashboardSection` REST shape and a per-dashboard `default-layout` route; the
9 * frontend reads it from the section entity, so there is no server-seeded
10 * preference.
11 *
12 * @package automattic/jetpack-premium-analytics
13 */
14
15namespace Automattic\Jetpack\PremiumAnalytics;
16
17require_once __DIR__ . '/dashboard-grammar.php';
18require_once __DIR__ . '/rest-namespace.php';
19
20/**
21 * Identifier of the Premium Analytics dashboard, formatted as `<plugin>_<page>`
22 * to match the underscore form produced by the wp-build pipeline. Used as the
23 * `{name}` segment of the REST route.
24 */
25const DASHBOARD_NAME = 'jetpack-premium-analytics_dashboard';
26
27/**
28 * Filter through which the default layout for a dashboard is resolved.
29 */
30const DASHBOARD_DEFAULT_LAYOUT_FILTER = 'jetpack_premium_analytics_dashboard_default_layout';
31
32/**
33 * Preference keys used by the dashboard route's tabbed sections.
34 */
35const DASHBOARD_TRAFFIC_SECTION_ID     = 'traffic';
36const DASHBOARD_INSIGHTS_SECTION_ID    = 'insights';
37const DASHBOARD_SUBSCRIBERS_SECTION_ID = 'subscribers';
38const DASHBOARD_STORE_SECTION_ID       = 'store';
39
40/**
41 * Resolves the default layout registered for a dashboard.
42 *
43 * Returns a fresh evaluation of the filter chain each call, so callers always
44 * see the current code default rather than a hydrated copy.
45 *
46 * @param string $dashboard_name Identifier of the dashboard or dashboard section.
47 * @return array Array of widget instances (possibly empty).
48 */
49function get_dashboard_default_layout_for( $dashboard_name ) {
50    /**
51     * Filters the default dashboard layout served to users who have not
52     * customized theirs.
53     *
54     * Each entry should match the dashboard's widget instance shape: `uuid`,
55     * `type`, optional `attributes`, optional `placement`.
56     *
57     * @param array  $default_layout Default array of widget instances.
58     * @param string $dashboard_name Identifier of the dashboard or dashboard
59     *                               section receiving the default. Callbacks
60     *                               targeting a specific default should switch
61     *                               on this value.
62     */
63    $default = apply_filters( DASHBOARD_DEFAULT_LAYOUT_FILTER, array(), $dashboard_name );
64
65    return is_array( $default ) ? array_values( $default ) : array();
66}
67
68/**
69 * REST callback returning the default layout for the requested dashboard.
70 *
71 * @param \WP_REST_Request $request REST request carrying the dashboard name.
72 * @return \WP_REST_Response Response wrapping the default layout array.
73 */
74function get_dashboard_default_layout_response( $request ) {
75    return rest_ensure_response( get_dashboard_default_layout_for( $request['name'] ) );
76}
77
78/**
79 * Registers the REST route that exposes per-dashboard default layouts.
80 *
81 * @return void
82 */
83function register_dashboard_default_layout_route() {
84    register_rest_route(
85        DASHBOARD_REST_NAMESPACE,
86        '/dashboards/(?P<name>' . get_dashboard_name_pattern() . ')/default-layout',
87        array(
88            'methods'             => \WP_REST_Server::READABLE,
89            'callback'            => __NAMESPACE__ . '\\get_dashboard_default_layout_response',
90            'permission_callback' => static function () {
91                return current_user_can( 'manage_options' );
92            },
93            'args'                => array(
94                'name' => array(
95                    'description' => __( 'Dashboard identifier as produced by the build pipeline.', 'jetpack-premium-analytics' ),
96                    'type'        => 'string',
97                ),
98            ),
99        )
100    );
101}
102
103/**
104 * Builds a widget instance for bundled dashboard defaults.
105 *
106 * @param string $uuid       Widget instance UUID.
107 * @param string $type       Widget type.
108 * @param int    $order      Widget placement order.
109 * @param int    $width      Widget placement width.
110 * @param int    $height     Widget placement height.
111 * @param array  $attributes Optional widget attributes.
112 * @return array Widget instance.
113 */
114function get_dashboard_default_widget_instance(
115    $uuid,
116    $type,
117    $order,
118    $width = 1,
119    $height = 1,
120    $attributes = array()
121) {
122    $widget = array(
123        'uuid' => $uuid,
124        'type' => $type,
125    );
126
127    if ( ! empty( $attributes ) ) {
128        $widget['attributes'] = $attributes;
129    }
130
131    $widget['placement'] = array(
132        'width'  => $width,
133        'height' => $height,
134        'order'  => $order,
135    );
136
137    return $widget;
138}
139
140/**
141 * Returns the bundled default widget layouts keyed by dashboard tab.
142 *
143 * @return array Map of tab IDs to widget layout arrays.
144 */
145function get_dashboard_default_section_layouts() {
146    return array(
147        DASHBOARD_TRAFFIC_SECTION_ID     => array(
148            get_dashboard_default_widget_instance(
149                'default-traffic-chart-widget-instance',
150                'jpa/traffic-chart',
151                0,
152                2,
153                2,
154                array(
155                    'granularity' => 'auto',
156                )
157            ),
158            get_dashboard_default_widget_instance(
159                'default-stats-top-posts-widget-instance',
160                'jpa/stats-top-posts',
161                1,
162                1,
163                2,
164                array(
165                    'max' => 10,
166                )
167            ),
168            get_dashboard_default_widget_instance(
169                'default-referrers-widget-instance',
170                'jpa/referrers',
171                2,
172                1,
173                2,
174                array(
175                    'max' => 10,
176                )
177            ),
178            get_dashboard_default_widget_instance(
179                'default-locations-widget-instance',
180                'jpa/locations',
181                3,
182                2,
183                2,
184                array(
185                    'max' => 10,
186                )
187            ),
188            get_dashboard_default_widget_instance(
189                'default-devices-widget-instance',
190                'jpa/devices',
191                4,
192                1,
193                2,
194                array(
195                    'max' => 5,
196                )
197            ),
198            get_dashboard_default_widget_instance(
199                'default-top-platforms-widget-instance',
200                'jpa/top-platforms',
201                5,
202                1,
203                2,
204                array(
205                    'max' => 10,
206                )
207            ),
208            get_dashboard_default_widget_instance(
209                'default-search-terms-widget-instance',
210                'jpa/search-terms',
211                6,
212                1,
213                2,
214                array(
215                    'max' => 10,
216                )
217            ),
218            get_dashboard_default_widget_instance(
219                'default-utm-insights-widget-instance',
220                'jpa/utm-insights',
221                7,
222                1,
223                2,
224                array(
225                    'utmDimension' => 'utm_source,utm_medium',
226                    'max'          => 10,
227                )
228            ),
229            get_dashboard_default_widget_instance(
230                'default-file-downloads-widget-instance',
231                'jpa/file-downloads',
232                8,
233                1,
234                2,
235                array(
236                    'max' => 10,
237                )
238            ),
239            get_dashboard_default_widget_instance(
240                'default-clicks-widget-instance',
241                'jpa/clicks',
242                9,
243                1,
244                2,
245                array(
246                    'max' => 10,
247                )
248            ),
249            get_dashboard_default_widget_instance(
250                'default-authors-widget-instance',
251                'jpa/authors',
252                10,
253                1,
254                2,
255                array(
256                    'max' => 7,
257                )
258            ),
259            get_dashboard_default_widget_instance(
260                'default-videopress-widget-instance',
261                'jpa/videopress',
262                11,
263                1,
264                2,
265                array(
266                    'max' => 7,
267                )
268            ),
269            get_dashboard_default_widget_instance(
270                'default-plan-usage-widget-instance',
271                'jpa/plan-usage',
272                12,
273                2,
274                1
275            ),
276        ),
277        DASHBOARD_INSIGHTS_SECTION_ID    => array(
278            get_dashboard_default_widget_instance(
279                'default-annual-highlights-widget-instance',
280                'jpa/annual-highlights',
281                0,
282                2,
283                1
284            ),
285            get_dashboard_default_widget_instance(
286                'default-all-time-stats-widget-instance',
287                'jpa/all-time-stats',
288                1,
289                2,
290                1
291            ),
292            get_dashboard_default_widget_instance(
293                'default-latest-post-widget-instance',
294                'jpa/latest-post',
295                2,
296                1,
297                1
298            ),
299            get_dashboard_default_widget_instance(
300                'default-posting-activity-widget-instance',
301                'jpa/posting-activity',
302                3,
303                2,
304                2
305            ),
306            get_dashboard_default_widget_instance(
307                'default-emails-widget-instance',
308                'jpa/stats-emails',
309                4,
310                1,
311                2,
312                array(
313                    'max'    => 10,
314                    'metric' => 'opens',
315                )
316            ),
317            get_dashboard_default_widget_instance(
318                'default-shares-widget-instance',
319                'jpa/shares',
320                5,
321                1,
322                2,
323                array(
324                    'max' => 10,
325                )
326            ),
327            get_dashboard_default_widget_instance(
328                'default-tags-widget-instance',
329                'jpa/tags',
330                6,
331                2,
332                2,
333                array(
334                    'max' => 10,
335                )
336            ),
337            get_dashboard_default_widget_instance(
338                'default-comments-widget-instance',
339                'jpa/comments',
340                7,
341                2,
342                2,
343                array(
344                    'max' => 10,
345                )
346            ),
347            get_dashboard_default_widget_instance(
348                'default-most-popular-day-widget-instance',
349                'jpa/most-popular-day',
350                8,
351                1,
352                1
353            ),
354            get_dashboard_default_widget_instance(
355                'default-most-popular-time-widget-instance',
356                'jpa/most-popular-time',
357                9,
358                1,
359                1
360            ),
361        ),
362        DASHBOARD_SUBSCRIBERS_SECTION_ID => array(
363            get_dashboard_default_widget_instance(
364                'default-subscriber-highlights-widget-instance',
365                'jpa/subscriber-highlights',
366                0,
367                4,
368                1,
369                array(
370                    'showTotal'  => true,
371                    'showPaid'   => true,
372                    'showFree'   => true,
373                    'showSocial' => true,
374                )
375            ),
376            get_dashboard_default_widget_instance(
377                'default-subscribers-chart-widget-instance',
378                'jpa/subscribers-chart',
379                1,
380                4,
381                1,
382                array(
383                    'granularity' => 'auto',
384                )
385            ),
386            get_dashboard_default_widget_instance(
387                'default-subscribers-list-widget-instance',
388                'jpa/subscribers-list',
389                2,
390                2,
391                2,
392                array(
393                    'max' => 6,
394                )
395            ),
396            get_dashboard_default_widget_instance(
397                'default-subscribers-emails-widget-instance',
398                'jpa/stats-emails',
399                3,
400                2,
401                2,
402                array(
403                    'max'    => 10,
404                    'metric' => 'opens',
405                )
406            ),
407        ),
408        DASHBOARD_STORE_SECTION_ID       => array(
409            get_dashboard_default_widget_instance(
410                'default-store-performance-widget-instance',
411                'jpa/store-performance',
412                0,
413                2,
414                1
415            ),
416            get_dashboard_default_widget_instance(
417                'default-total-sales-over-time-widget-instance',
418                'jpa/total-sales-over-time',
419                1,
420                1,
421                1
422            ),
423            get_dashboard_default_widget_instance(
424                'default-conversion-rate-widget-instance',
425                'jpa/conversion-rate',
426                2,
427                1,
428                1
429            ),
430            get_dashboard_default_widget_instance(
431                'default-orders-over-time-widget-instance',
432                'jpa/orders-over-time',
433                3,
434                1,
435                1
436            ),
437            get_dashboard_default_widget_instance(
438                'default-average-order-value-widget-instance',
439                'jpa/average-order-value',
440                4,
441                1,
442                1
443            ),
444            get_dashboard_default_widget_instance(
445                'default-top-performing-products-widget-instance',
446                'jpa/top-performing-products',
447                5,
448                1,
449                1
450            ),
451            get_dashboard_default_widget_instance(
452                'default-new-vs-returning-customer-widget-instance',
453                'jpa/new-vs-returning-customer',
454                6,
455                1,
456                1
457            ),
458            get_dashboard_default_widget_instance(
459                'default-payment-status-widget-instance',
460                'jpa/payment-status',
461                7,
462                1,
463                1
464            ),
465            get_dashboard_default_widget_instance(
466                'default-orders-fulfillment-widget-instance',
467                'jpa/orders-fulfillment',
468                8,
469                1,
470                1
471            ),
472        ),
473    );
474}
475
476/**
477 * Resolves a dashboard or section identifier to one of the bundled tab IDs.
478 *
479 * @param string $dashboard_name Dashboard or section identifier.
480 * @return string|null Bundled tab ID, or null when the identifier is unsupported.
481 */
482function get_dashboard_default_section_id_for( $dashboard_name ) {
483    $aliases = array(
484        DASHBOARD_NAME                   => DASHBOARD_TRAFFIC_SECTION_ID,
485        DASHBOARD_TRAFFIC_SECTION_ID     => DASHBOARD_TRAFFIC_SECTION_ID,
486        'analytics/traffic'              => DASHBOARD_TRAFFIC_SECTION_ID,
487        DASHBOARD_INSIGHTS_SECTION_ID    => DASHBOARD_INSIGHTS_SECTION_ID,
488        'analytics/insights'             => DASHBOARD_INSIGHTS_SECTION_ID,
489        DASHBOARD_SUBSCRIBERS_SECTION_ID => DASHBOARD_SUBSCRIBERS_SECTION_ID,
490        'analytics/subscribers'          => DASHBOARD_SUBSCRIBERS_SECTION_ID,
491        DASHBOARD_STORE_SECTION_ID       => DASHBOARD_STORE_SECTION_ID,
492        'woocommerce/store'              => DASHBOARD_STORE_SECTION_ID,
493    );
494
495    return $aliases[ $dashboard_name ] ?? null;
496}
497
498/**
499 * Seeds the bundled default layouts for the Premium Analytics dashboard tabs.
500 *
501 * Only contributes to the known Premium Analytics dashboard and tab aliases;
502 * other dashboards are left untouched so the filter can be reused if more
503 * dashboards are added later.
504 *
505 * @param array  $dashboard_layout Default layout from earlier callbacks.
506 * @param string $dashboard_name   Identifier of the dashboard receiving the
507 *                                 default.
508 * @return array The layout extended with the bundled widget instances.
509 */
510function seed_default_dashboard_layout( $dashboard_layout, $dashboard_name = '' ) {
511    $section_id = get_dashboard_default_section_id_for( $dashboard_name );
512
513    if ( null === $section_id ) {
514        return $dashboard_layout;
515    }
516
517    $uuids   = array_column( $dashboard_layout, 'uuid' );
518    $layouts = get_dashboard_default_section_layouts();
519
520    foreach ( $layouts[ $section_id ] as $widget ) {
521        if ( ! in_array( $widget['uuid'], $uuids, true ) ) {
522            $dashboard_layout[] = $widget;
523            $uuids[]            = $widget['uuid'];
524        }
525    }
526
527    return $dashboard_layout;
528}
529add_filter( DASHBOARD_DEFAULT_LAYOUT_FILTER, __NAMESPACE__ . '\\seed_default_dashboard_layout', 10, 2 );