Add "by stream" stats.

This commit is contained in:
Buster "Silver Eagle" Neece 2022-06-14 18:15:46 -05:00
parent 910b8161a2
commit 945c9dc2a5
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
6 changed files with 235 additions and 0 deletions

View File

@ -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

View File

@ -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,
},

View File

@ -0,0 +1,115 @@
<template>
<b-overlay variant="card" :show="loading">
<div class="card-body py-5" v-if="loading">
&nbsp;
</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>

View File

@ -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')),
]);
}
}

View File

@ -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'),
]

View File

@ -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);