Skip to content

Commit e9ec523

Browse files
committed
introduce view tabs per year with daily/monthly data
1 parent 1d33bc3 commit e9ec523

File tree

6 files changed

+373
-37
lines changed

6 files changed

+373
-37
lines changed

css/dashboard.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ body.rtl .statify-chart * {
6161

6262

6363
.statify-table th,
64-
.statify-table td:last-child {
64+
.statify-table tr.statify-table-sum td,
65+
.statify-table td.statify-table-sum {
6566
font-weight: 700;
6667
}
6768

inc/class-statify-api.php

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,25 @@ public static function get_extended( WP_REST_Request $request ): WP_REST_Respons
183183
);
184184
}
185185

186+
// Parse year, if provided.
187+
$yr = $request->get_param( 'year' );
188+
if ( ! empty( $yr ) ) {
189+
$yr = intval( $yr );
190+
if ( $yr <= 0 ) {
191+
return new WP_REST_Response(
192+
array( 'error' => 'invalid year' ),
193+
400
194+
);
195+
}
196+
} else {
197+
$yr = 0;
198+
}
199+
186200
// Retrieve from cache, if data is not post-specific.
187201
$post = $request->get_param( 'post' );
188202
$stats = false;
189203
if ( ! $post ) {
190-
$stats = self::from_cache( $scope );
204+
$stats = self::from_cache( $scope, $yr );
191205
}
192206

193207
if ( ! $stats ) {
@@ -217,12 +231,12 @@ public static function get_extended( WP_REST_Request $request ): WP_REST_Respons
217231
$last_ym = $year_month;
218232
}
219233
} elseif ( 'day' === $scope ) {
220-
$stats = Statify_Evaluation::get_views_for_all_days( $post );
234+
$stats = Statify_Evaluation::get_views_for_all_days( $yr, $post );
221235
}
222236

223237
// Update cache, if data is not post-specific.
224238
if ( ! $post ) {
225-
self::update_cache( $scope, $stats );
239+
self::update_cache( $scope, $yr, $stats );
226240
}
227241
}
228242

@@ -233,22 +247,24 @@ public static function get_extended( WP_REST_Request $request ): WP_REST_Respons
233247
* Retrieve data from cache.
234248
*
235249
* @param string $scope Scope (year, month, day).
250+
* @param int $index Optional index (e.g. year).
236251
*
237252
* @return array|false Transient data or FALSE.
238253
*/
239-
private static function from_cache( string $scope ) {
240-
return get_transient( 'statify_data_' . $scope );
254+
private static function from_cache( string $scope, int $index = 0 ) {
255+
return get_transient( 'statify_data_' . $scope . ( $index > 0 ? '_' . $index : '' ) );
241256
}
242257

243258
/**
244259
* Update data cache.
245260
*
246261
* @param string $scope Scope (year, month, day).
262+
* @param int $index Optional index (e.g. year).
247263
* @param array $data Data.
248264
*/
249-
private static function update_cache( string $scope, array $data ): void {
265+
private static function update_cache( string $scope, int $index, array $data ): void {
250266
set_transient(
251-
'statify_data_' . $scope,
267+
'statify_data_' . $scope . ( $index > 0 ? '_' . $index : '' ),
252268
$data,
253269
30 * MINUTE_IN_SECONDS
254270
);

inc/class-statify-evaluation.php

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -126,36 +126,37 @@ public static function get_years(): array {
126126
* Returns the views for all days.
127127
* If the given URL is not the empty string, the result is restricted to the given post.
128128
*
129-
* @param string $post_url the URL of the post to select for (or the empty string for all posts).
129+
* @param int $single_year single year.
130+
* @param string $post_url the URL of the post to select for (or the empty string for all posts).
130131
*
131132
* @return array an array with date as key and views as value
132133
*/
133-
public static function get_views_for_all_days( string $post_url = '' ): array {
134+
public static function get_views_for_all_days( int $single_year = 0, string $post_url = '' ): array {
134135
global $wpdb;
135136

136-
if ( empty( $post_url ) ) {
137-
// For all posts.
138-
$results = $wpdb->get_results(
139-
'SELECT `created` as `date`, COUNT(`created`) as `count`' .
140-
" FROM `$wpdb->statify`" .
141-
' GROUP BY `created`' .
142-
' ORDER BY `created`',
143-
ARRAY_A
144-
);
145-
} else {
146-
// Only for selected posts.
147-
$results = $wpdb->get_results(
148-
$wpdb->prepare(
149-
'SELECT `created` as `date`, COUNT(`created`) as `count`' .
150-
" FROM `$wpdb->statify`" .
151-
' WHERE `target` = %s' .
152-
' GROUP BY `created`' .
153-
' ORDER BY `created`',
154-
$post_url
155-
),
156-
ARRAY_A
157-
);
137+
$query = 'SELECT `created` as `date`, COUNT(`created`) as `count`' .
138+
" FROM `$wpdb->statify`";
139+
$args = array();
140+
141+
if ( $single_year > 0 ) {
142+
$query .= ' WHERE YEAR(`created`) = %d';
143+
$args[] = $single_year;
144+
}
145+
146+
if ( ! empty( $post_url ) ) {
147+
$query .= ( $single_year > 0 ? ' AND' : ' WHERE' ) . ' `target` = %s';
148+
$args[] = $post_url;
149+
}
150+
151+
$query .= ' GROUP BY `created`' .
152+
' ORDER BY `created`';
153+
154+
if ( ! empty( $args ) ) {
155+
$query = $wpdb->prepare( $query, $args );
158156
}
157+
158+
$results = $wpdb->get_results( $query, ARRAY_A );
159+
159160
$views_for_all_days = array();
160161
foreach ( $results as $result ) {
161162
$views_for_all_days[ $result['date'] ] = intval( $result['count'] );

js/dashboard.js

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
);
1313
const refreshBtn = document.getElementById('statify_refresh');
1414

15+
const chartElemDaily = document.getElementById('statify_chart_daily');
1516
const chartElemMonthly = document.getElementById('statify_chart_monthly');
1617
const chartElemYearly = document.getElementById('statify_chart_yearly');
1718
const yearlyTable = document.getElementById('statify-table-yearly');
19+
const dailyTable = document.getElementById('statify-table-daily');
1820

1921
/**
2022
* Update the dashboard widget
@@ -61,6 +63,22 @@
6163
});
6264
}
6365

66+
/**
67+
* Render monthly statistics.
68+
*
69+
* @param {number} year Year to load data for.
70+
*
71+
* @return {Promise<{[key: string]: number}>} Data promise from API.
72+
*/
73+
function loadDaily(year) {
74+
year = encodeURIComponent(year);
75+
76+
// Load data from API.
77+
return wp.apiFetch({
78+
path: `/statify/v1/stats/extended?scope=day&year=${year}`,
79+
});
80+
}
81+
6482
/**
6583
* Render monthly statistics.
6684
*
@@ -71,6 +89,19 @@
7189
return wp.apiFetch({ path: '/statify/v1/stats/extended?scope=month' });
7290
}
7391

92+
/**
93+
* Render daily statistics.
94+
*
95+
* @param {HTMLElement} root Root element.
96+
* @param {{[key: string]: number}} data Data from API.
97+
*/
98+
function renderDaily(root, data) {
99+
const labels = Object.keys(data);
100+
const values = Object.values(data);
101+
102+
render(root, labels, values);
103+
}
104+
74105
/**
75106
* Render monthly statistics.
76107
*
@@ -308,13 +339,109 @@
308339
}
309340

310341
col = document.createElement('TD');
342+
col.classList.add('statify-table-sum');
311343
col.innerText = sum;
312344
row.appendChild(col);
313345

314346
tbody.insertBefore(row, tbody.firstChild);
315347
}
316348
}
317349

350+
/**
351+
* Render yearly table.
352+
*
353+
* @param {HTMLElement} table Root element.
354+
* @param {any} data Data from API.
355+
*/
356+
function renderDailyTable(table, data) {
357+
const rows = Array.from(table.querySelectorAll('tbody > tr'));
358+
const cols = rows.map((row) => Array.from(row.querySelectorAll('td')));
359+
let out = cols.slice(0, 31);
360+
361+
const sum = Array(12).fill(0);
362+
const vls = Array(12).fill(0);
363+
const min = Array(12).fill(Number.MAX_SAFE_INTEGER);
364+
const max = Array(12).fill(0);
365+
366+
for (const [day, count] of Object.entries(data)) {
367+
const d = new Date(day);
368+
const m = d.getMonth();
369+
sum[m] += count;
370+
++vls[m];
371+
min[m] = Math.min(min[m], count);
372+
max[m] = Math.max(max[m], count);
373+
out[d.getDate() - 1][m].innerText = count;
374+
}
375+
376+
out =
377+
cols[
378+
rows.findIndex((row) =>
379+
row.classList.contains('statify-table-sum')
380+
)
381+
];
382+
const avg =
383+
cols[
384+
rows.findIndex((row) =>
385+
row.classList.contains('statify-table-avg')
386+
)
387+
];
388+
for (const [m, s] of sum.entries()) {
389+
if (vls[m] > 0) {
390+
out[m].innerText = s;
391+
avg[m].innerText = Math.round(s / vls[m]);
392+
} else {
393+
out[m].innerText = '-';
394+
avg[m].innerText = '-';
395+
}
396+
}
397+
398+
out =
399+
cols[
400+
rows.findIndex((row) =>
401+
row.classList.contains('statify-table-min')
402+
)
403+
];
404+
for (const [m, s] of min.entries()) {
405+
out[m].innerText = vls[m] > 0 ? s : '-';
406+
}
407+
408+
out =
409+
cols[
410+
rows.findIndex((row) =>
411+
row.classList.contains('statify-table-max')
412+
)
413+
];
414+
for (const [m, s] of max.entries()) {
415+
out[m].innerText = vls[m] > 0 ? s : '-';
416+
}
417+
418+
for (const row of rows) {
419+
row.classList.remove('placeholder');
420+
}
421+
}
422+
423+
/**
424+
* Convert daily to monthly data.
425+
*
426+
* @param {{[key: string]: number}} data Daily data.
427+
* @return {{visits: {[key: string]: {[key: string]: number}}}} Monthly data.
428+
*/
429+
function dailyToMonthly(data) {
430+
const monthly = { visits: {} };
431+
for (const [day, count] of Object.entries(data)) {
432+
const date = new Date(day);
433+
const year = date.getFullYear();
434+
const month = date.getMonth();
435+
436+
if (!(year in monthly.visits)) {
437+
monthly.visits[year] = {};
438+
}
439+
monthly.visits[year][month] =
440+
count + (monthly.visits[year][month] || 0);
441+
}
442+
return monthly;
443+
}
444+
318445
// Abort if config or target element is not present.
319446
if (typeof statifyDashboard !== 'undefined') {
320447
if (chartElem) {
@@ -332,7 +459,19 @@
332459
updateDashboard(false);
333460
}
334461

335-
if (chartElemMonthly) {
462+
if (chartElemDaily) {
463+
loadDaily(chartElemDaily.dataset.year).then((data) => {
464+
renderDaily(chartElemDaily, data);
465+
466+
if (chartElemMonthly) {
467+
renderMonthly(chartElemMonthly, dailyToMonthly(data));
468+
}
469+
470+
if (dailyTable) {
471+
renderDailyTable(dailyTable, data);
472+
}
473+
});
474+
} else if (chartElemMonthly) {
336475
loadMonthly()
337476
.then((data) => {
338477
renderMonthly(chartElemMonthly, data);

tests/test-evaluation.php

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,45 @@ public function test_get_years() {
7373
* Test views for all days.
7474
*/
7575
public function test_get_views_for_all_days() {
76+
$this->insert_test_data( '2022-10-20', '', '/test/' );
7677
$this->insert_test_data( '2023-03-23', '', '/', 3 );
7778
$this->insert_test_data( '2023-03-23', '', '/test/' );
7879
$this->insert_test_data( '2023-03-25', '', '/' );
7980
$this->insert_test_data( '2023-03-25', '', '/test/', 2 );
8081

8182
self::assertSame(
8283
array(
84+
'2022-10-20' => 1,
8385
'2023-03-23' => 4,
8486
'2023-03-25' => 3,
8587
),
86-
Statify_Evaluation::get_views_for_all_days()
88+
Statify_Evaluation::get_views_for_all_days(),
89+
'unexpected results without any filter'
8790
);
8891
self::assertSame(
8992
array(
93+
'2022-10-20' => 1,
9094
'2023-03-23' => 1,
9195
'2023-03-25' => 2,
9296
),
93-
Statify_Evaluation::get_views_for_all_days( '/test/' )
97+
Statify_Evaluation::get_views_for_all_days( 0, '/test/' ),
98+
'unexpected results with post filter'
99+
);
100+
self::assertSame(
101+
array(
102+
'2023-03-23' => 4,
103+
'2023-03-25' => 3,
104+
),
105+
Statify_Evaluation::get_views_for_all_days( 2023 ),
106+
'unexpected results with year filter'
107+
);
108+
self::assertSame(
109+
array(
110+
'2023-03-23' => 1,
111+
'2023-03-25' => 2,
112+
),
113+
Statify_Evaluation::get_views_for_all_days( 2023, '/test/' ),
114+
'unexpected results with year and post filter'
94115
);
95116
}
96117

0 commit comments

Comments
 (0)