Add "by stream" stats.
This commit is contained in:
parent
910b8161a2
commit
945c9dc2a5
|
@ -517,6 +517,11 @@ return static function (RouteCollectorProxy $group) {
|
||||||
Controller\Api\Stations\Reports\Overview\ByCountry::class
|
Controller\Api\Stations\Reports\Overview\ByCountry::class
|
||||||
)->setName('api:stations:reports:by-country');
|
)->setName('api:stations:reports:by-country');
|
||||||
|
|
||||||
|
$group->get(
|
||||||
|
'/overview/by-stream',
|
||||||
|
Controller\Api\Stations\Reports\Overview\ByStream::class
|
||||||
|
)->setName('api:stations:reports:by-stream');
|
||||||
|
|
||||||
$group->get(
|
$group->get(
|
||||||
'/soundexchange',
|
'/soundexchange',
|
||||||
Controller\Api\Stations\Reports\SoundExchangeAction::class
|
Controller\Api\Stations\Reports\SoundExchangeAction::class
|
||||||
|
|
|
@ -30,6 +30,15 @@
|
||||||
</listeners-by-time-period-tab>
|
</listeners-by-time-period-tab>
|
||||||
</b-tab>
|
</b-tab>
|
||||||
|
|
||||||
|
<b-tab>
|
||||||
|
<template #title>
|
||||||
|
<translate key="tab_streams">Streams</translate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<streams-tab :api-url="byStreamUrl" :date-range="dateRange">
|
||||||
|
</streams-tab>
|
||||||
|
</b-tab>
|
||||||
|
|
||||||
<b-tab v-if="showFullAnalytics">
|
<b-tab v-if="showFullAnalytics">
|
||||||
<template #title>
|
<template #title>
|
||||||
<translate key="tab_browsers">Browsers</translate>
|
<translate key="tab_browsers">Browsers</translate>
|
||||||
|
@ -58,9 +67,11 @@ import ListenersByTimePeriodTab from "./Overview/ListenersByTimePeriodTab";
|
||||||
import BestAndWorstTab from "./Overview/BestAndWorstTab";
|
import BestAndWorstTab from "./Overview/BestAndWorstTab";
|
||||||
import BrowsersTab from "./Overview/BrowsersTab";
|
import BrowsersTab from "./Overview/BrowsersTab";
|
||||||
import CountriesTab from "~/components/Stations/Reports/Overview/CountriesTab";
|
import CountriesTab from "~/components/Stations/Reports/Overview/CountriesTab";
|
||||||
|
import StreamsTab from "~/components/Stations/Reports/Overview/StreamsTab";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
StreamsTab,
|
||||||
CountriesTab,
|
CountriesTab,
|
||||||
BrowsersTab,
|
BrowsersTab,
|
||||||
BestAndWorstTab,
|
BestAndWorstTab,
|
||||||
|
@ -72,6 +83,7 @@ export default {
|
||||||
showFullAnalytics: Boolean,
|
showFullAnalytics: Boolean,
|
||||||
listenersByTimePeriodUrl: String,
|
listenersByTimePeriodUrl: String,
|
||||||
bestAndWorstUrl: String,
|
bestAndWorstUrl: String,
|
||||||
|
byStreamUrl: String,
|
||||||
byBrowserUrl: String,
|
byBrowserUrl: String,
|
||||||
byCountryUrl: String,
|
byCountryUrl: String,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<b-overlay variant="card" :show="loading">
|
||||||
|
<div class="card-body py-5" v-if="loading">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="card-body">
|
||||||
|
<b-row>
|
||||||
|
<b-col md="6" class="mb-4">
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
<translate key="hdr_top_by_listeners">Top Streams by Listeners</translate>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<pie-chart style="width: 100%;" :data="top_listeners.datasets"
|
||||||
|
:labels="top_listeners.labels">
|
||||||
|
<span v-html="top_listeners.alt"></span>
|
||||||
|
</pie-chart>
|
||||||
|
</fieldset>
|
||||||
|
</b-col>
|
||||||
|
<b-col md="6" class="mb-4">
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
<translate
|
||||||
|
key="hdr_top_by_connected_seconds">Top Streams by Connected Time</translate>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<pie-chart style="width: 100%;" :data="top_connected_time.datasets"
|
||||||
|
:labels="top_connected_time.labels">
|
||||||
|
<span v-html="top_connected_time.alt"></span>
|
||||||
|
</pie-chart>
|
||||||
|
</fieldset>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<data-table ref="datatable" id="streams_table" paginated handle-client-side
|
||||||
|
:fields="fields" :responsive="false" :items="all">
|
||||||
|
<template #cell(connected_seconds_calc)="row">
|
||||||
|
{{ formatTime(row.item.connected_seconds) }}
|
||||||
|
</template>
|
||||||
|
</data-table>
|
||||||
|
</div>
|
||||||
|
</b-overlay>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {DateTime} from "luxon";
|
||||||
|
import PieChart from "~/components/Common/PieChart";
|
||||||
|
import formatTime from "~/functions/formatTime";
|
||||||
|
import DataTable from "~/components/Common/DataTable";
|
||||||
|
import IsMounted from "~/components/Common/IsMounted";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'StreamsTab',
|
||||||
|
components: {DataTable, PieChart},
|
||||||
|
mixins: [IsMounted],
|
||||||
|
props: {
|
||||||
|
dateRange: Object,
|
||||||
|
apiUrl: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
all: [],
|
||||||
|
top_listeners: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [],
|
||||||
|
alt: ''
|
||||||
|
},
|
||||||
|
top_connected_time: {
|
||||||
|
labels: [],
|
||||||
|
datasets: [],
|
||||||
|
alt: ''
|
||||||
|
},
|
||||||
|
fields: [
|
||||||
|
{key: 'stream', label: this.$gettext('Stream'), sortable: true},
|
||||||
|
{key: 'listeners', label: this.$gettext('Listeners'), sortable: true},
|
||||||
|
{key: 'connected_seconds_calc', label: this.$gettext('Time'), sortable: false},
|
||||||
|
{key: 'connected_seconds', label: this.$gettext('Time (sec)'), sortable: true}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
dateRange() {
|
||||||
|
if (this.isMounted) {
|
||||||
|
this.relist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.relist();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
relist() {
|
||||||
|
this.loading = true;
|
||||||
|
this.axios.get(this.apiUrl, {
|
||||||
|
params: {
|
||||||
|
start: DateTime.fromJSDate(this.dateRange.startDate).toISO(),
|
||||||
|
end: DateTime.fromJSDate(this.dateRange.endDate).toISO()
|
||||||
|
}
|
||||||
|
}).then((response) => {
|
||||||
|
this.all = response.data.all;
|
||||||
|
this.top_listeners = response.data.top_listeners;
|
||||||
|
this.top_connected_time = response.data.top_connected_time;
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
formatTime(time) {
|
||||||
|
return formatTime(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller\Api\Stations\Reports\Overview;
|
||||||
|
|
||||||
|
use App\Entity;
|
||||||
|
use App\Entity\Repository\SettingsRepository;
|
||||||
|
use App\Http\Response;
|
||||||
|
use App\Http\ServerRequest;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
final class ByStream extends AbstractReportAction
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
SettingsRepository $settingsRepo,
|
||||||
|
EntityManagerInterface $em,
|
||||||
|
private readonly Entity\Repository\StationMountRepository $mountRepo,
|
||||||
|
private readonly Entity\Repository\StationRemoteRepository $remoteRepo
|
||||||
|
) {
|
||||||
|
parent::__construct($settingsRepo, $em);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(
|
||||||
|
ServerRequest $request,
|
||||||
|
Response $response,
|
||||||
|
string $station_id
|
||||||
|
): ResponseInterface {
|
||||||
|
// Get current analytics level.
|
||||||
|
if (!$this->isAnalyticsEnabled()) {
|
||||||
|
return $response->withStatus(400)
|
||||||
|
->withJson(new Entity\Api\Status(false, 'Reporting is restricted due to system analytics level.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$station = $request->getStation();
|
||||||
|
$stationTz = $station->getTimezoneObject();
|
||||||
|
|
||||||
|
$dateRange = $this->getDateRange($request, $stationTz);
|
||||||
|
|
||||||
|
$statsRaw = $this->em->getConnection()->fetchAllAssociative(
|
||||||
|
<<<'SQL'
|
||||||
|
SELECT l.stream_id,
|
||||||
|
COUNT(l.listener_hash) AS listeners,
|
||||||
|
SUM(l.connected_seconds) AS connected_seconds
|
||||||
|
FROM (
|
||||||
|
SELECT IF (
|
||||||
|
mount_id IS NOT NULL,
|
||||||
|
CONCAT('local_', mount_id),
|
||||||
|
CONCAT('remote_', remote_id)
|
||||||
|
) AS stream_id,
|
||||||
|
SUM(timestamp_end - timestamp_start) AS connected_seconds,
|
||||||
|
listener_hash
|
||||||
|
FROM listener
|
||||||
|
WHERE station_id = :station_id
|
||||||
|
AND timestamp_end >= :start
|
||||||
|
AND timestamp_start <= :end
|
||||||
|
GROUP BY listener_hash
|
||||||
|
) AS l
|
||||||
|
GROUP BY l.stream_id
|
||||||
|
SQL,
|
||||||
|
[
|
||||||
|
'station_id' => $station->getIdRequired(),
|
||||||
|
'start' => $dateRange->getStartTimestamp(),
|
||||||
|
'end' => $dateRange->getEndTimestamp(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$streamLookup = [];
|
||||||
|
foreach ($this->mountRepo->getDisplayNames($station) as $id => $displayName) {
|
||||||
|
$streamLookup['local_' . $id] = $displayName;
|
||||||
|
}
|
||||||
|
foreach ($this->remoteRepo->getDisplayNames($station) as $id => $displayName) {
|
||||||
|
$streamLookup['remote_' . $id] = $displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$listenersByStream = [];
|
||||||
|
$connectedTimeByStream = [];
|
||||||
|
$stats = [];
|
||||||
|
|
||||||
|
foreach ($statsRaw as $row) {
|
||||||
|
if (!isset($streamLookup[$row['stream_id']])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row['stream'] = $streamLookup[$row['stream_id']];
|
||||||
|
$stats[] = $row;
|
||||||
|
|
||||||
|
$listenersByStream[$row['stream']] = $row['listeners'];
|
||||||
|
$connectedTimeByStream[$row['stream']] = $row['connected_seconds'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->withJson([
|
||||||
|
'all' => $stats,
|
||||||
|
'top_listeners' => $this->buildChart($listenersByStream, __('Listeners')),
|
||||||
|
'top_connected_time' => $this->buildChart($connectedTimeByStream, __('Connected Seconds')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ final class OverviewAction
|
||||||
'showFullAnalytics' => Entity\Enums\AnalyticsLevel::All === $analyticsLevel,
|
'showFullAnalytics' => Entity\Enums\AnalyticsLevel::All === $analyticsLevel,
|
||||||
'listenersByTimePeriodUrl' => (string)$router->fromHere('api:stations:reports:overview-charts'),
|
'listenersByTimePeriodUrl' => (string)$router->fromHere('api:stations:reports:overview-charts'),
|
||||||
'bestAndWorstUrl' => (string)$router->fromHere('api:stations:reports:best-and-worst'),
|
'bestAndWorstUrl' => (string)$router->fromHere('api:stations:reports:best-and-worst'),
|
||||||
|
'byStreamUrl' => (string)$router->fromHere('api:stations:reports:by-stream'),
|
||||||
'byBrowserUrl' => (string)$router->fromHere('api:stations:reports:by-browser'),
|
'byBrowserUrl' => (string)$router->fromHere('api:stations:reports:by-browser'),
|
||||||
'byCountryUrl' => (string)$router->fromHere('api:stations:reports:by-country'),
|
'byCountryUrl' => (string)$router->fromHere('api:stations:reports:by-country'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -26,6 +26,9 @@ class Api_Stations_ReportsCest extends CestAbstract
|
||||||
$I->sendGet($uriBase . '/reports/overview/best-and-worst');
|
$I->sendGet($uriBase . '/reports/overview/best-and-worst');
|
||||||
$I->seeResponseCodeIs(200);
|
$I->seeResponseCodeIs(200);
|
||||||
|
|
||||||
|
$I->sendGet($uriBase . '/reports/overview/by-stream');
|
||||||
|
$I->seeResponseCodeIs(200);
|
||||||
|
|
||||||
$I->sendGet($uriBase . '/reports/overview/by-browser');
|
$I->sendGet($uriBase . '/reports/overview/by-browser');
|
||||||
$I->seeResponseCodeIs(200);
|
$I->seeResponseCodeIs(200);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue