Add more stats; abstract common metrics view.
This commit is contained in:
parent
945c9dc2a5
commit
c840bb084e
|
@ -522,6 +522,16 @@ return static function (RouteCollectorProxy $group) {
|
|||
Controller\Api\Stations\Reports\Overview\ByStream::class
|
||||
)->setName('api:stations:reports:by-stream');
|
||||
|
||||
$group->get(
|
||||
'/overview/by-client',
|
||||
Controller\Api\Stations\Reports\Overview\ByClient::class
|
||||
)->setName('api:stations:reports:by-client');
|
||||
|
||||
$group->get(
|
||||
'/overview/by-listening-time',
|
||||
Controller\Api\Stations\Reports\Overview\ByListeningTime::class
|
||||
)->setName('api:stations:reports:by-listening-time');
|
||||
|
||||
$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_listening_time">Listening Time</translate>
|
||||
</template>
|
||||
|
||||
<listening-time-tab :api-url="listeningTimeUrl" :date-range="dateRange">
|
||||
</listening-time-tab>
|
||||
</b-tab>
|
||||
|
||||
<b-tab>
|
||||
<template #title>
|
||||
<translate key="tab_streams">Streams</translate>
|
||||
|
@ -39,6 +48,15 @@
|
|||
</streams-tab>
|
||||
</b-tab>
|
||||
|
||||
<b-tab v-if="showFullAnalytics">
|
||||
<template #title>
|
||||
<translate key="tab_clients">Clients</translate>
|
||||
</template>
|
||||
|
||||
<clients-tab :api-url="byClientUrl" :date-range="dateRange">
|
||||
</clients-tab>
|
||||
</b-tab>
|
||||
|
||||
<b-tab v-if="showFullAnalytics">
|
||||
<template #title>
|
||||
<translate key="tab_browsers">Browsers</translate>
|
||||
|
@ -66,11 +84,15 @@ import DateRangeDropdown from "~/components/Common/DateRangeDropdown";
|
|||
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";
|
||||
import CountriesTab from "./Overview/CountriesTab";
|
||||
import StreamsTab from "./Overview/StreamsTab";
|
||||
import ClientsTab from "./Overview/ClientsTab";
|
||||
import ListeningTimeTab from "~/components/Stations/Reports/Overview/ListeningTimeTab";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ListeningTimeTab,
|
||||
ClientsTab,
|
||||
StreamsTab,
|
||||
CountriesTab,
|
||||
BrowsersTab,
|
||||
|
@ -84,8 +106,10 @@ export default {
|
|||
listenersByTimePeriodUrl: String,
|
||||
bestAndWorstUrl: String,
|
||||
byStreamUrl: String,
|
||||
byClientUrl: String,
|
||||
byBrowserUrl: String,
|
||||
byCountryUrl: String,
|
||||
listeningTimeUrl: String
|
||||
},
|
||||
data() {
|
||||
let nowTz = DateTime.now().setZone(this.stationTimeZone);
|
||||
|
|
|
@ -1,114 +1,28 @@
|
|||
<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 Browsers 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 Browsers 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="browsers_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>
|
||||
<common-metrics-view :date-range="dateRange" :api-url="apiUrl"
|
||||
field-key="browser" :field-label="langFieldLabel">
|
||||
<template #by_listeners_legend>
|
||||
<translate key="hdr_top_by_listeners">Top Browsers by Listeners</translate>
|
||||
</template>
|
||||
<template #by_connected_time_legend>
|
||||
<translate key="hdr_top_by_connected_seconds">Top Browsers by Connected Time</translate>
|
||||
</template>
|
||||
</common-metrics-view>
|
||||
</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";
|
||||
import CommonMetricsView from "./CommonMetricsView";
|
||||
|
||||
export default {
|
||||
name: 'BrowsersTab',
|
||||
components: {DataTable, PieChart},
|
||||
mixins: [IsMounted],
|
||||
components: {CommonMetricsView},
|
||||
props: {
|
||||
dateRange: Object,
|
||||
apiUrl: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
all: [],
|
||||
top_listeners: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
top_connected_time: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
fields: [
|
||||
{key: 'browser', label: this.$gettext('Browser'), 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);
|
||||
computed: {
|
||||
langFieldLabel() {
|
||||
return this.$gettext('Browser');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<common-metrics-view :date-range="dateRange" :api-url="apiUrl"
|
||||
field-key="client" :field-label="langFieldLabel">
|
||||
<template #by_listeners_legend>
|
||||
<translate key="hdr_top_by_listeners">Clients by Listeners</translate>
|
||||
</template>
|
||||
<template #by_connected_time_legend>
|
||||
<translate key="hdr_top_by_connected_seconds">Clients by Connected Time</translate>
|
||||
</template>
|
||||
</common-metrics-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonMetricsView from "./CommonMetricsView";
|
||||
|
||||
export default {
|
||||
name: 'ClientsTab',
|
||||
components: {CommonMetricsView},
|
||||
props: {
|
||||
dateRange: Object,
|
||||
apiUrl: String,
|
||||
},
|
||||
computed: {
|
||||
langFieldLabel() {
|
||||
return this.$gettext('Client');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,116 @@
|
|||
<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>
|
||||
<slot name="by_listeners_legend"></slot>
|
||||
</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>
|
||||
<slot name="by_connected_time_legend"></slot>
|
||||
</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="fieldKey+'_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: 'CommonMetricsView',
|
||||
components: {DataTable, PieChart},
|
||||
mixins: [IsMounted],
|
||||
props: {
|
||||
dateRange: Object,
|
||||
apiUrl: String,
|
||||
fieldKey: String,
|
||||
fieldLabel: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
all: [],
|
||||
top_listeners: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
top_connected_time: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
fields: [
|
||||
{key: this.fieldKey, label: this.fieldLabel, 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>
|
|
@ -1,114 +1,28 @@
|
|||
<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 Countries 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 Countries 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="browsers_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>
|
||||
<common-metrics-view :date-range="dateRange" :api-url="apiUrl"
|
||||
field-key="country" :field-label="langFieldLabel">
|
||||
<template #by_listeners_legend>
|
||||
<translate key="hdr_top_by_listeners">Top Countries by Listeners</translate>
|
||||
</template>
|
||||
<template #by_connected_time_legend>
|
||||
<translate key="hdr_top_by_connected_seconds">Top Countries by Connected Time</translate>
|
||||
</template>
|
||||
</common-metrics-view>
|
||||
</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";
|
||||
import CommonMetricsView from "./CommonMetricsView";
|
||||
|
||||
export default {
|
||||
name: 'CountriesTab',
|
||||
components: {DataTable, PieChart},
|
||||
mixins: [IsMounted],
|
||||
components: {CommonMetricsView},
|
||||
props: {
|
||||
dateRange: Object,
|
||||
apiUrl: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
all: [],
|
||||
top_listeners: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
top_connected_time: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
fields: [
|
||||
{key: 'country', label: this.$gettext('Country'), 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);
|
||||
computed: {
|
||||
langFieldLabel() {
|
||||
return this.$gettext('Country');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<b-overlay variant="card" :show="loading">
|
||||
<div class="card-body py-5" v-if="loading">
|
||||
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="card-body">
|
||||
<fieldset>
|
||||
<legend>
|
||||
<translate key="chart_listening_time">Listeners by Listening Time</translate>
|
||||
</legend>
|
||||
|
||||
<pie-chart style="width: 100%;" :data="chart.datasets"
|
||||
:labels="chart.labels" :aspect-ratio="4">
|
||||
<span v-html="chart.alt"></span>
|
||||
</pie-chart>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<data-table ref="datatable" id="listening_time_table" paginated handle-client-side
|
||||
:fields="fields" :responsive="false" :items="all">
|
||||
</data-table>
|
||||
</div>
|
||||
</b-overlay>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {DateTime} from "luxon";
|
||||
import PieChart from "~/components/Common/PieChart";
|
||||
import DataTable from "~/components/Common/DataTable";
|
||||
import IsMounted from "~/components/Common/IsMounted";
|
||||
|
||||
export default {
|
||||
name: 'ListeningTimeTab',
|
||||
components: {DataTable, PieChart},
|
||||
mixins: [IsMounted],
|
||||
props: {
|
||||
dateRange: Object,
|
||||
apiUrl: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
all: [],
|
||||
chart: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
alt: ''
|
||||
},
|
||||
fields: [
|
||||
{key: 'label', label: this.$gettext('Listening Time'), sortable: false},
|
||||
{key: 'value', label: this.$gettext('Listeners'), sortable: false}
|
||||
]
|
||||
};
|
||||
},
|
||||
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.chart = response.data.chart;
|
||||
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,114 +1,28 @@
|
|||
<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>
|
||||
<common-metrics-view :date-range="dateRange" :api-url="apiUrl"
|
||||
field-key="stream" :field-label="langFieldLabel">
|
||||
<template #by_listeners_legend>
|
||||
<translate key="hdr_top_by_listeners">Top Streams by Listeners</translate>
|
||||
</template>
|
||||
<template #by_connected_time_legend>
|
||||
<translate key="hdr_top_by_connected_seconds">Top Streams by Connected Time</translate>
|
||||
</template>
|
||||
</common-metrics-view>
|
||||
</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";
|
||||
import CommonMetricsView from "./CommonMetricsView";
|
||||
|
||||
export default {
|
||||
name: 'StreamsTab',
|
||||
components: {DataTable, PieChart},
|
||||
mixins: [IsMounted],
|
||||
components: {CommonMetricsView},
|
||||
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);
|
||||
computed: {
|
||||
langFieldLabel() {
|
||||
return this.$gettext('Stream');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ abstract class AbstractReportAction
|
|||
|
||||
protected function isAllAnalyticsEnabled(): bool
|
||||
{
|
||||
return AnalyticsLevel::All !== $this->settingsRepo->readSettings()->getAnalyticsEnum();
|
||||
return AnalyticsLevel::All === $this->settingsRepo->readSettings()->getAnalyticsEnum();
|
||||
}
|
||||
|
||||
protected function isAnalyticsEnabled(): bool
|
||||
|
@ -31,10 +31,14 @@ abstract class AbstractReportAction
|
|||
|
||||
protected function buildChart(
|
||||
array $rows,
|
||||
string $valueLabel
|
||||
string $valueLabel,
|
||||
?int $limitResults = 10
|
||||
): array {
|
||||
arsort($rows);
|
||||
$topRows = array_slice($rows, 0, 10);
|
||||
|
||||
$topRows = (null !== $limitResults)
|
||||
? array_slice($rows, 0, $limitResults)
|
||||
: $rows;
|
||||
|
||||
$alt = ['<dl>'];
|
||||
$labels = [];
|
||||
|
|
|
@ -17,7 +17,7 @@ final class ByBrowser extends AbstractReportAction
|
|||
string $station_id
|
||||
): ResponseInterface {
|
||||
// Get current analytics level.
|
||||
if ($this->isAllAnalyticsEnabled()) {
|
||||
if (!$this->isAllAnalyticsEnabled()) {
|
||||
return $response->withStatus(400)
|
||||
->withJson(new Entity\Api\Status(false, 'Reporting is restricted due to system analytics level.'));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Stations\Reports\Overview;
|
||||
|
||||
use App\Entity;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class ByClient extends AbstractReportAction
|
||||
{
|
||||
public function __invoke(
|
||||
ServerRequest $request,
|
||||
Response $response,
|
||||
string $station_id
|
||||
): ResponseInterface {
|
||||
// Get current analytics level.
|
||||
if (!$this->isAllAnalyticsEnabled()) {
|
||||
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.client_raw,
|
||||
COUNT(l.listener_hash) AS listeners,
|
||||
SUM(l.connected_seconds) AS connected_seconds
|
||||
FROM (
|
||||
SELECT
|
||||
CASE
|
||||
WHEN device_is_bot = 1 THEN 'bot'
|
||||
WHEN device_is_mobile = 1 THEN 'mobile'
|
||||
WHEN device_is_browser = 1 THEN 'desktop'
|
||||
ELSE 'non_browser'
|
||||
END AS client_raw,
|
||||
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.client_raw
|
||||
SQL,
|
||||
[
|
||||
'station_id' => $station->getIdRequired(),
|
||||
'start' => $dateRange->getStartTimestamp(),
|
||||
'end' => $dateRange->getEndTimestamp(),
|
||||
]
|
||||
);
|
||||
|
||||
$clientTypes = [
|
||||
'bot' => __('Bot/Crawler'),
|
||||
'mobile' => __('Mobile Device'),
|
||||
'desktop' => __('Desktop Browser'),
|
||||
'non_browser' => __('Non-Browser'),
|
||||
];
|
||||
|
||||
$listenersByClient = [];
|
||||
$connectedTimeByClient = [];
|
||||
$stats = [];
|
||||
|
||||
foreach ($statsRaw as $row) {
|
||||
$row['client'] = $clientTypes[$row['client_raw']];
|
||||
$stats[] = $row;
|
||||
|
||||
$listenersByClient[$row['client']] = $row['listeners'];
|
||||
$connectedTimeByClient[$row['client']] = $row['connected_seconds'];
|
||||
}
|
||||
|
||||
return $response->withJson([
|
||||
'all' => $stats,
|
||||
'top_listeners' => $this->buildChart($listenersByClient, __('Listeners'), null),
|
||||
'top_connected_time' => $this->buildChart($connectedTimeByClient, __('Connected Seconds'), null),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ final class ByCountry extends AbstractReportAction
|
|||
string $station_id
|
||||
): ResponseInterface {
|
||||
// Get current analytics level.
|
||||
if ($this->isAllAnalyticsEnabled()) {
|
||||
if (!$this->isAllAnalyticsEnabled()) {
|
||||
return $response->withStatus(400)
|
||||
->withJson(new Entity\Api\Status(false, 'Reporting is restricted due to system analytics level.'));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\Stations\Reports\Overview;
|
||||
|
||||
use App\Entity;
|
||||
use App\Http\Response;
|
||||
use App\Http\ServerRequest;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
final class ByListeningTime extends AbstractReportAction
|
||||
{
|
||||
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 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
|
||||
SQL,
|
||||
[
|
||||
'station_id' => $station->getIdRequired(),
|
||||
'start' => $dateRange->getStartTimestamp(),
|
||||
'end' => $dateRange->getEndTimestamp(),
|
||||
]
|
||||
);
|
||||
|
||||
$ranges = [
|
||||
[30, __('Less than Thirty Seconds')],
|
||||
[60, __('Thirty Seconds to One Minute')],
|
||||
[300, __('One Minute to Five Minutes')],
|
||||
[600, __('Five Minutes to Ten Minutes')],
|
||||
[1800, __('Ten Minutes to Thirty Minutes')],
|
||||
[3600, __('Thirty Minutes to One Hour')],
|
||||
[7200, __('One Hour to Two Hours')],
|
||||
[PHP_INT_MAX, __('More than Two Hours')],
|
||||
];
|
||||
|
||||
$statsByRange = [];
|
||||
foreach ($ranges as [$max, $label]) {
|
||||
$statsByRange[$label] = 0;
|
||||
}
|
||||
|
||||
foreach ($statsRaw as $row) {
|
||||
$listenerTime = (int)$row['connected_seconds'];
|
||||
|
||||
foreach ($ranges as [$max, $label]) {
|
||||
if ($listenerTime <= $max) {
|
||||
$statsByRange[$label]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stats = [];
|
||||
foreach ($statsByRange as $key => $row) {
|
||||
$stats[] = [
|
||||
'label' => $key,
|
||||
'value' => $row,
|
||||
];
|
||||
}
|
||||
|
||||
return $response->withJson([
|
||||
'all' => $stats,
|
||||
'chart' => $this->buildChart(
|
||||
array_filter($statsByRange),
|
||||
__('Listeners'),
|
||||
null
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -45,6 +45,8 @@ final class OverviewAction
|
|||
'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'),
|
||||
'byClientUrl' => (string)$router->fromHere('api:stations:reports:by-client'),
|
||||
'listeningTimeUrl' => (string)$router->fromHere('api:stations:reports:by-listening-time'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue