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
|
||||
)->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(
|
||||
'/soundexchange',
|
||||
Controller\Api\Stations\Reports\SoundExchangeAction::class
|
||||
|
|
|
@ -30,6 +30,15 @@
|
|||
</listeners-by-time-period-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">
|
||||
<template #title>
|
||||
<translate key="tab_browsers">Browsers</translate>
|
||||
|
@ -58,9 +67,11 @@ import ListenersByTimePeriodTab from "./Overview/ListenersByTimePeriodTab";
|
|||
import BestAndWorstTab from "./Overview/BestAndWorstTab";
|
||||
import BrowsersTab from "./Overview/BrowsersTab";
|
||||
import CountriesTab from "~/components/Stations/Reports/Overview/CountriesTab";
|
||||
import StreamsTab from "~/components/Stations/Reports/Overview/StreamsTab";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StreamsTab,
|
||||
CountriesTab,
|
||||
BrowsersTab,
|
||||
BestAndWorstTab,
|
||||
|
@ -72,6 +83,7 @@ export default {
|
|||
showFullAnalytics: Boolean,
|
||||
listenersByTimePeriodUrl: String,
|
||||
bestAndWorstUrl: String,
|
||||
byStreamUrl: String,
|
||||
byBrowserUrl: 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,
|
||||
'listenersByTimePeriodUrl' => (string)$router->fromHere('api:stations:reports:overview-charts'),
|
||||
'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'),
|
||||
'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->seeResponseCodeIs(200);
|
||||
|
||||
$I->sendGet($uriBase . '/reports/overview/by-stream');
|
||||
$I->seeResponseCodeIs(200);
|
||||
|
||||
$I->sendGet($uriBase . '/reports/overview/by-browser');
|
||||
$I->seeResponseCodeIs(200);
|
||||
|
||||
|
|
Loading…
Reference in New Issue