Skip to content

Commit

Permalink
Reports: Add Proof of Play MySQL grouping (#2737)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgbaybay authored Sep 20, 2024
1 parent 3b09d00 commit 2cb3e69
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 24 deletions.
99 changes: 85 additions & 14 deletions lib/Report/ProofOfPlay.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use Xibo\Factory\LayoutFactory;
use Xibo\Factory\MediaFactory;
use Xibo\Factory\ReportScheduleFactory;
use Xibo\Factory\DisplayGroupFactory;
use Xibo\Helper\ApplicationState;
use Xibo\Helper\DateFormatHelper;
use Xibo\Helper\SanitizerService;
Expand Down Expand Up @@ -69,6 +70,11 @@ class ProofOfPlay implements ReportInterface
*/
private $reportScheduleFactory;

/**
* @var DisplayGroupFactory
*/
private $displayGroupFactory;

/**
* @var SanitizerService
*/
Expand All @@ -94,6 +100,7 @@ public function setFactories(ContainerInterface $container)
$this->mediaFactory = $container->get('mediaFactory');
$this->layoutFactory = $container->get('layoutFactory');
$this->reportScheduleFactory = $container->get('reportScheduleFactory');
$this->displayGroupFactory = $container->get('displayGroupFactory');
$this->sanitizer = $container->get('sanitizerService');

return $this;
Expand Down Expand Up @@ -325,6 +332,12 @@ public function getResults(SanitizerInterface $sanitizedParams)
$operator = $sanitizedParams->getString('logicalOperator', ['default' => 'OR']);
$parentCampaignId = $sanitizedParams->getInt('parentCampaignId');

// Group the data by display, display group, or by tag
$groupBy = $sanitizedParams->getString('groupBy');

// Used with groupBy in case we want to filter by specific display groups only
$displayGroupIds = $sanitizedParams->getIntArray('displayGroupId', ['default' => []]);

// Display filter.
try {
// Get an array of display id this user has access to.
Expand Down Expand Up @@ -472,7 +485,8 @@ public function getResults(SanitizerInterface $sanitizedParams)
$tags,
$tagsType,
$exactTags,
$operator
$operator,
$groupBy
);
}

Expand Down Expand Up @@ -505,7 +519,10 @@ public function getResults(SanitizerInterface $sanitizedParams)
$entry['minStart'] = Carbon::createFromTimestamp($row['minStart'])->format(DateFormatHelper::getSystemFormat());
$entry['maxEnd'] = Carbon::createFromTimestamp($row['maxEnd'])->format(DateFormatHelper::getSystemFormat());
$entry['mediaId'] = $sanitizedRow->getInt('mediaId');

$entry['displayGroup'] = $sanitizedRow->getString('displayGroup');
$entry['displayGroupId'] = $sanitizedRow->getInt('displayGroupId');
$entry['tagName'] = $sanitizedRow->getString('tagName');
$entry['tagId'] = $sanitizedRow->getInt('tagId');
$rows[] = $entry;
}

Expand Down Expand Up @@ -541,6 +558,7 @@ public function getResults(SanitizerInterface $sanitizedParams)
* @param $tags string
* @param $tagsType string
* @param $exactTags mixed
* @param $groupBy string
* @return array[array result, date periodStart, date periodEnd, int count, int totalStats]
*/
private function getProofOfPlayReportMySql(
Expand All @@ -555,15 +573,15 @@ private function getProofOfPlayReportMySql(
$tags,
$tagsType,
$exactTags,
$logicalOperator
$logicalOperator,
$groupBy
) {
$fromDt = $fromDt->format('U');
$toDt = $toDt->format('U');

// Media on Layouts Ran
$select = '
SELECT stat.type,
display.Display,
stat.parentCampaignId,
campaign.campaign as parentCampaign,
IFNULL(layout.Layout,
Expand All @@ -581,10 +599,23 @@ private function getProofOfPlayReportMySql(
stat.tag,
stat.layoutId,
stat.mediaId,
stat.widgetId,
stat.displayId
stat.widgetId
';

// We get the ID and name - either by display, display group or tag
if ($groupBy === 'display') {
$select .= ', display.Display, stat.displayId ';
} else if ($groupBy === 'displayGroup') {
$select .= ', displaydg.displayGroup, displaydg.displayGroupId ';
} else if ($groupBy === 'tag') {
if ($tagsType === 'dg' || $tagsType === 'media') {
$select .= ', taglink.value, taglink.tagId ';
} else {
// For layouts, we need to manually select taglink.tag
$select .= ', taglink.tag AS value, taglink.tagId ';
}
}

$body = '
FROM stat
LEFT OUTER JOIN display
Expand Down Expand Up @@ -615,6 +646,17 @@ private function getProofOfPlayReportMySql(
}
}

if ($groupBy === 'displayGroup') {
// Group the data by display group
$body .= 'INNER JOIN `lkdisplaydg` AS linkdg
ON linkdg.DisplayID = display.displayid
INNER JOIN `displaygroup` AS displaydg
ON displaydg.displaygroupId = linkdg.displaygroupId
AND `displaydg`.isDisplaySpecific = 0 ';
} else if ($groupBy === 'tag') {
$body .= $this->groupByTagType($tagsType);
}

$body .= ' WHERE stat.type <> \'displaydown\'
AND stat.end > :fromDt
AND stat.start < :toDt
Expand Down Expand Up @@ -799,23 +841,29 @@ private function getProofOfPlayReportMySql(
$body .= ' AND `media`.mediaId IN (' . trim($mediaSql, ',') . ')';
}

// We first implement default groupings
$body .= '
GROUP BY stat.type,
stat.tag,
display.Display,
stat.parentCampaignId,
stat.displayId,
stat.campaignId,
layout.layout,
IFNULL(stat.mediaId, stat.widgetId),
IFNULL(`media`.name, IFNULL(`widgetoption`.value, `widget`.type)),
stat.tag,
stat.layoutId,
stat.mediaId,
stat.widgetId,
stat.displayId
stat.widgetId
';

// Then add the optional groupings
if ($groupBy === 'display') {
$body .= ', display.Display, stat.displayId';
} else if ($groupBy === 'displayGroup') {
$body .= ', displaydg.displayGroupId, displaydg.displayGroup';
} else if ($groupBy === 'tag') {
$body .= ', value, taglink.tagId';
}

$order = '';
if ($columns != null) {
$order = 'ORDER BY ' . implode(',', $columns);
Expand All @@ -829,8 +877,8 @@ private function getProofOfPlayReportMySql(
$entry = [];

$entry['type'] = $row['type'];
$entry['displayId'] = $row['displayId'];
$entry['display'] = $row['Display'];
$entry['displayId'] = $row['displayId'] ?? '';
$entry['display'] = $row['Display'] ?? '';
$entry['layout'] = $row['Layout'];
$entry['parentCampaignId'] = $row['parentCampaignId'];
$entry['parentCampaign'] = $row['parentCampaign'];
Expand All @@ -843,7 +891,10 @@ private function getProofOfPlayReportMySql(
$entry['widgetId'] = $row['widgetId'];
$entry['mediaId'] = $row['mediaId'];
$entry['tag'] = $row['tag'];

$entry['displayGroupId'] = $row['displayGroupId'] ?? '';
$entry['displayGroup'] = $row['displayGroup'] ?? '';
$entry['tagId'] = $row['tagId'] ?? '';
$entry['tagName'] = $row['value'] ?? '';
$rows[] = $entry;
}

Expand Down Expand Up @@ -1157,4 +1208,24 @@ private function getProofOfPlayReportMongoDb(
'count' => count($rows)
];
}

/**
* Add grouping by tag type
* @param string $tagType
* @return string
*/
private function groupByTagType(string $tagType) : string
{
return match ($tagType) {
'media' => 'INNER JOIN `lktagmedia` AS taglink ON taglink.mediaId = stat.mediaId',
'layout' => 'INNER JOIN `lktaglayout` ON `lktaglayout`.layoutId = stat.layoutId
INNER JOIN `tag` AS taglink ON taglink.tagId = `lktaglayout`.tagId',
'dg' => 'INNER JOIN `lkdisplaydg` AS linkdg
ON linkdg.DisplayID = display.displayid
INNER JOIN `displaygroup` AS displaydg
ON displaydg.displaygroupId = linkdg.displaygroupId
AND `displaydg`.isDisplaySpecific = 1 INNER JOIN
`lktagdisplaygroup` AS taglink ON taglink.displaygroupId = displaydg.displaygroupId',
};
}
}
76 changes: 66 additions & 10 deletions reports/proofofplay-report-form.twig
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
{% set title %}{% trans "Time" %}{% endset %}
{{ inline.time("statsToDtTime", title, "00:00", "", "stats-to-dt-time") }}

{% set title %}{% trans "Group By" %}{% endset %}
{% set options = [
{ id: "display", name: "Display" },
{ id: "displayGroup", name: "Display Group"|trans },
{ id: "tag", name: "Tag"|trans }
] %}
{{ inline.dropdown("groupBy", "single", title, "", options, "id", "name", "") }}

{% set title %}{% trans "Display" %}{% endset %}
{% set attributes = [
{ name: "data-width", value: "200px" },
Expand Down Expand Up @@ -164,7 +172,7 @@
{{ inline.dropdown("type", "single", title, "", options, "typeid", "type") }}

{% set title %}{% trans "Tags from" %}{% endset %}
{% set dg %}{% trans "Display Group" %}{% endset %}
{% set dg %}{% trans "Display" %}{% endset %}
{% set layout %}{% trans "Layout" %}{% endset %}
{% set media %}{% trans "Media" %}{% endset %}
{% set options = [
Expand Down Expand Up @@ -219,6 +227,10 @@
<th>{% trans "Type" %}</th>
<th>{% trans "Display ID" %}</th>
<th>{% trans "Display" %}</th>
<th>{% trans "Display Group ID" %}</th>
<th>{% trans "Display Group" %}</th>
<th>{% trans "Tag ID" %}</th>
<th>{% trans "Tag Name" %}</th>
<th>{% trans "Campaign" %}</th>
<th>{% trans "Layout ID" %}</th>
<th>{% trans "Layout" %}</th>
Expand Down Expand Up @@ -251,6 +263,10 @@
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</tfoot>
</table>
Expand Down Expand Up @@ -290,6 +306,21 @@
setTimeout(function() {
$("#applyBtn").removeClass('disabled');
}, 300);
// We hide empty columns and display appropriate columns (ie ID and name)
switch ($('select[name="groupBy"]').val()) {
case 'displayGroup':
$(this.api().columns([1, 2, 5, 6]).visible(false));
$(this.api().columns([3, 4]).visible(true));
break;
case 'tag':
$(this.api().columns([1,2, 3, 4]).visible(false));
$(this.api().columns([5, 6]).visible(true));
break;
default:
$(this.api().columns([3, 4, 5, 6]).visible(false));
$(this.api().columns([1, 2]).visible(true));
}
},
filter: false,
"order": [[1, "asc"]],
Expand All @@ -298,6 +329,10 @@
{"data": "type"},
{"data": "displayId"},
{"data": "display"},
{"data": "displayGroupId"},
{"data": "displayGroup"},
{"data": "tagId"},
{"data": "tagName"},
{"data": "parentCampaign"},
{"data": "layoutId"},
{"data": "layout"},
Expand Down Expand Up @@ -341,28 +376,27 @@
{"data": "duration"},
{"data": "minStart"},
{"data": "maxEnd"}
],
footerCallback: function (row, data, start, end, display) {
let api = this.api();
// Total over all pages
let totalNumberPlays = api.column(9).data().reduce(function (a, b) {
let totalNumberPlays = api.column(13).data().reduce(function (a, b) {
return a + b;
}, 0);
let totalDuration = api.column(11).data().reduce(function (a, b) {
let totalDuration = api.column(15).data().reduce(function (a, b) {
return a + b;
}, 0);
let totalNumberPlaysPage = api.column(9, { page: 'current'}).data().reduce(function (a, b) {
let totalNumberPlaysPage = api.column(13, { page: 'current'}).data().reduce(function (a, b) {
return a + b;
}, 0);
let totalDurationPage = api.column(11, { page: 'current'}).data().reduce(function (a, b) {
let totalDurationPage = api.column(13, { page: 'current'}).data().reduce(function (a, b) {
return a + b;
}, 0);
// Update footer
$(api.column(9).footer()).html(totalNumberPlaysPage + ' (' + totalNumberPlays + ' total)');
$(api.column(11).footer()).html(Math.floor(totalDurationPage) + ' (' + Math.floor(totalDuration) + ' total)');
$(api.column(13).footer()).html(totalNumberPlaysPage + ' (' + totalNumberPlays + ' total)');
$(api.column(15).footer()).html(Math.floor(totalDurationPage) + ' (' + Math.floor(totalDuration) + ' total)');
},
});
Expand Down Expand Up @@ -442,16 +476,38 @@
getData($dataTable.data().url);
});
// If we select a displayId we hide the display group filter
// If we select a displayId, we hide the display group filter
$('#displayId').off('change').change( function() {
let displayId = $('#displayId').val();
if (displayId) {
$('select[name="displayGroupId[]"] option').remove();
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
$('select[name="groupBy[]"] option').remove();
$('select[name="groupBy"]').parent().hide();
} else {
$('#displayId option').remove();
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
$('select[name="groupBy"]').parent().show();
}
});
// If we select a groupBy data, we hide the display filter
$("select[name='groupBy']").on('change', function() {
let optionSelected = $(this).find("option:selected").val();
if (optionSelected === 'displayGroup') {
$('select[name="groupBy"]').parent().show();
$('select[name="displayGroupId[]"]').next(".select2-container").parent().show();
} else {
$('select[name="displayGroupId[]"] option').remove();
$('select[name="displayGroupId[]"]').next(".select2-container").parent().hide();
}
if (optionSelected === 'display') {
$("select[name='displayId']").parent().show();
} else {
$("select[name='displayId']").parent().hide();
}
});
Expand Down
19 changes: 19 additions & 0 deletions tests/XMDS.http
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,25 @@ Content-Type: application/xml

###

POST {{url}}/xmds.php?v=7
Content-Type: application/xml

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="urn:xmds" xmlns:types="urn:xmds/encodedTypes"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:SubmitStats>
<serverKey xsi:type="xsd:string">{{serverKey}}</serverKey>
<hardwareKey xsi:type="xsd:string">{{hardwareKey}}</hardwareKey>
<statXml xsi:type-="xsd:string">&lt;records&gt;&lt;stat fromdt=&quot;2024-08-01 00:00:00&quot; todt=&quot;2024-08-01 00:05:00&quot; type=&quot;layout&quot; scheduleid=&quot;48&quot; layoutid=&quot;133&quot; mediaid=&quot;null&quot; tag=&quot;&quot; count=&quot;250&quot; /&gt;&lt;/records&gt;</statXml>
</tns:SubmitStats>
</soap:Body>
</soap:Envelope>

###

# Get the fileID from the Required Files response.
GET {{url}}/xmds.php?file=12.xlf&displayId=1&type=L&itemId=12&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230502T000000Z&X-Amz-Expires=1683048895&X-Amz-SignedHeaders=host&X-Amz-Signature=7c876be170afb29d194e7b035be6969198c22a32c22e163e2696754bb1163f5d

Expand Down

0 comments on commit 2cb3e69

Please sign in to comment.