Skip to content
Projects
Groups
Snippets
Help
Sign in / Register
Toggle navigation
Minds Backend - Engine
Project overview
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
286
Merge Requests
38
CI / CD
Security & Compliance
Packages
Wiki
Snippets
Members
Collapse sidebar
Close sidebar
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Minds
Minds Backend - Engine
Commits
c966ecce
Commit
c966ecce
authored
1 hour ago
by
Guy Thouret
Browse files
Options
Download
Boost Analytics and API endpoint -
#1201
parent
2a794180
epic/boost-campaign
1 merge request
!417
WIP: Boost Campaigns
Pipeline
#101951875
passed with stages
in 7 minutes and 11 seconds
Changes
4
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
466 additions
and
0 deletions
+466
-0
Api/AbstractApi.php
0 → 100644
View file @
c966ecce
<?php
namespace
Minds\Api
;
use
Minds\Interfaces
;
abstract
class
AbstractApi
implements
Interfaces\Api
{
protected
$accessControlAllowOrigin
=
[
'*'
];
protected
$accessControlAllowHeaders
=
[];
protected
$accessControlAllowMethods
=
[];
protected
$defaultResponse
=
[
'status'
=>
'success'
];
const
HTTP_CODES
=
[
100
=>
'Continue'
,
101
=>
'Switching Protocols'
,
200
=>
'OK'
,
201
=>
'Created'
,
202
=>
'Accepted'
,
203
=>
'Non-Authoritative Information'
,
204
=>
'No Content'
,
205
=>
'Reset Content'
,
206
=>
'Partial Content'
,
300
=>
'Multiple Choices'
,
301
=>
'Moved Permanently'
,
302
=>
'Moved Temporarily'
,
303
=>
'See Other'
,
304
=>
'Not Modified'
,
305
=>
'Use Proxy'
,
400
=>
'Bad Request'
,
401
=>
'Unauthorized'
,
402
=>
'Payment Required'
,
403
=>
'Forbidden'
,
404
=>
'Not Found'
,
405
=>
'Method Not Allowed'
,
406
=>
'Not Acceptable'
,
407
=>
'Proxy Authentication Required'
,
408
=>
'Request Time-out'
,
409
=>
'Conflict'
,
410
=>
'Gone'
,
411
=>
'Length Required'
,
412
=>
'Precondition Failed'
,
413
=>
'Request Entity Too Large'
,
414
=>
'Request-URI Too Large'
,
415
=>
'Unsupported Media Type'
,
500
=>
'Internal Server Error'
,
501
=>
'Not Implemented'
,
502
=>
'Bad Gateway'
,
503
=>
'Service Unavailable'
,
504
=>
'Gateway Time-out'
,
505
=>
'HTTP Version not supported'
,
];
public
function
__construct
()
{
$this
->
sendAccessControlHeaders
();
}
protected
function
sendAccessControlHeaders
()
:
void
{
$this
->
sendAccessControlAllowOrigin
();
$this
->
sendAccessControlAllowHeaders
();
$this
->
sendAccessControlAllowMethods
();
}
protected
function
sendAccessControlAllowOrigin
()
:
void
{
if
(
!
empty
(
$this
->
accessControlAllowOrigin
))
{
header
(
"Access-Control-Allow-Origin: "
.
$this
->
parseAccessControlArray
(
$this
->
accessControlAllowOrigin
),
false
);
}
}
protected
function
sendAccessControlAllowHeaders
()
:
void
{
if
(
!
empty
(
$this
->
accessControlAllowHeaders
))
{
header
(
"Access-Control-Allow-Headers: "
.
$this
->
parseAccessControlArray
(
$this
->
accessControlAllowHeaders
),
false
);
}
}
protected
function
sendAccessControlAllowMethods
()
:
void
{
if
(
!
empty
(
$this
->
accessControlAllowMethods
))
{
header
(
"Access-Control-Allow-Methods: "
.
$this
->
parseAccessControlArray
(
$this
->
accessControlAllowMethods
),
false
);
}
}
protected
function
parseAccessControlArray
(
array
$accessControlArray
)
:
string
{
$output
=
""
;
$lastHeader
=
end
(
$accessControlArray
);
foreach
(
$accessControlArray
as
$header
)
{
$output
.=
$header
;
if
(
$header
!==
$lastHeader
)
{
$output
.=
","
;
}
}
return
$output
;
}
protected
function
setResponseCode
(
int
$code
=
200
)
:
int
{
if
(
!
isset
(
self
::
HTTP_CODES
[
$code
]))
{
exit
(
'Unknown http status code "'
.
htmlentities
(
$code
)
.
'"'
);
}
$text
=
self
::
HTTP_CODES
[
$code
];
$protocol
=
(
isset
(
$_SERVER
[
'SERVER_PROTOCOL'
])
?
$_SERVER
[
'SERVER_PROTOCOL'
]
:
'HTTP/1.0'
);
header
(
"${protocol} ${code} ${text}"
);
return
$code
;
}
protected
function
sendArrayOfObjects
(
$array
,
int
$code
=
200
)
:
void
{
$this
->
send
(
array_values
(
$array
),
$code
);
}
protected
function
send
(
$responseArray
,
int
$code
=
200
,
$jsonOptions
=
0
)
:
void
{
$responseArray
=
array_merge
(
$this
->
defaultResponse
,
$responseArray
);
$returnString
=
json_encode
(
$responseArray
,
$jsonOptions
);
$this
->
sendJsonString
(
$returnString
,
$code
);
}
protected
function
sendJsonString
(
string
$jsonString
,
int
$code
=
200
)
:
void
{
header
(
'Content-Type: application/json'
);
header
(
'Content-Length:'
.
strlen
(
$jsonString
));
$this
->
setResponseCode
(
$code
);
echo
$jsonString
;
}
protected
function
sendInternalServerError
()
:
void
{
$this
->
sendError
(
500
);
}
protected
function
sendBadRequest
()
:
void
{
$this
->
sendError
(
400
);
}
protected
function
sendNotImplemented
()
:
void
{
$this
->
sendError
(
501
);
}
protected
function
sendNotModified
()
:
void
{
$this
->
sendError
(
304
);
}
protected
function
sendNotAcceptable
()
:
void
{
$this
->
sendError
(
406
);
}
protected
function
sendUnauthorised
()
:
void
{
$this
->
sendError
(
401
);
}
protected
function
sendSuccess
()
:
void
{
$this
->
send
([]);
}
protected
function
sendError
(
int
$code
=
406
,
string
$message
=
null
)
:
void
{
if
(
is_null
(
$message
))
{
$message
=
self
::
HTTP_CODES
[
$code
];
}
$this
->
send
(
$this
->
buildError
(
$message
),
$code
);
}
protected
function
buildError
(
string
$message
)
:
array
{
return
[
'status'
=>
'error'
,
'message'
=>
$message
];
}
public
function
get
(
$pages
)
:
void
{
$this
->
sendNotImplemented
();
}
public
function
post
(
$pages
)
:
void
{
$this
->
sendNotImplemented
();
}
public
function
put
(
$pages
)
:
void
{
$this
->
sendNotImplemented
();
}
public
function
delete
(
$pages
)
:
void
{
$this
->
sendNotImplemented
();
}
}
This diff is collapsed.
Controllers/api/v2/boost/campaigns/analytics.php
0 → 100644
View file @
c966ecce
<?php
namespace
Minds\Controllers\api\v2\boost\campaigns
;
use
Minds\Api\AbstractApi
;
use
Minds\Core\Analytics\EntityCentric\BoostViewsDaily
;
class
analytics
extends
AbstractApi
{
public
function
get
(
$pages
)
:
void
{
switch
(
$pages
[
0
])
{
case
'rate'
:
// Get current boost rate
$avgRate
=
(
new
BoostViewsDaily
())
->
lastSevenDays
()
->
getAvg
();
$this
->
send
([
'rate'
=>
$avgRate
]);
break
;
case
'days'
:
$days
=
(
new
BoostViewsDaily
())
->
lastSevenDays
()
->
getAll
();
$this
->
send
([
'days'
=>
$days
]);
break
;
default
:
$this
->
sendBadRequest
();
}
}
}
This diff is collapsed.
Core/Analytics/EntityCentric/BoostViewsDaily.php
0 → 100644
View file @
c966ecce
<?php
namespace
Minds\Core\Analytics\EntityCentric
;
use
Minds\Core\Data\ElasticSearch\Client
;
use
Minds\Core\Data\ElasticSearch\Prepared\Search
;
use
Minds\Core\Di\Di
;
use
Minds\Helpers\Time
;
class
BoostViewsDaily
{
/** @var Client */
protected
$es
;
/** @var array */
protected
$dailyViews
=
[];
/** @var int */
protected
$totalViews
=
0
;
/** @var int */
protected
$startDayMs
;
/** @var int */
protected
$endDayMs
;
public
function
__construct
(
Client
$esClient
=
null
)
{
$this
->
es
=
$esClient
?:
Di
::
_
()
->
get
(
'Database\ElasticSearch'
);
$this
->
lastSevenDays
();
}
protected
function
clearData
()
:
void
{
$this
->
dailyViews
=
[];
$this
->
totalViews
=
0
;
}
public
function
lastSevenDays
()
:
self
{
return
$this
->
dateRange
(
strtotime
(
'yesterday -1 week'
),
strtotime
(
'yesterday'
));
}
public
function
dateRange
(
int
$start
,
int
$end
)
:
self
{
$this
->
clearData
();
$this
->
startDayMs
=
Time
::
toInterval
(
$start
,
Time
::
ONE_DAY
)
*
1000
;
$this
->
endDayMs
=
Time
::
toInterval
(
$end
,
Time
::
ONE_DAY
)
*
1000
;
return
$this
;
}
protected
function
query
()
:
void
{
if
(
!
empty
(
$this
->
dailyViews
))
{
return
;
}
$prepared
=
new
Search
();
$prepared
->
query
(
$this
->
buildQuery
());
$response
=
$this
->
es
->
request
(
$prepared
);
if
(
isset
(
$response
[
'aggregations'
][
'boost_views_total'
]))
{
$this
->
totalViews
=
$response
[
'aggregations'
][
'boost_views_total'
][
'value'
];
}
if
(
isset
(
$response
[
'aggregations'
][
'boost_views_daily'
][
'buckets'
]))
{
foreach
(
$response
[
'aggregations'
][
'boost_views_daily'
][
'buckets'
]
as
$bucket
)
{
$this
->
dailyViews
[
$bucket
[
'key'
]]
=
$bucket
[
'boost_views'
][
'value'
];
}
}
}
protected
function
buildQuery
()
:
array
{
$must
=
[
'range'
=>
[
'@timestamp'
=>
[
'gte'
=>
$this
->
startDayMs
,
'lte'
=>
$this
->
endDayMs
,
]
]
];
$query
=
[
'index'
=>
'minds-entitycentric-*'
,
'size'
=>
0
,
'body'
=>
[
'query'
=>
[
'bool'
=>
[
'must'
=>
$must
,
],
],
'aggs'
=>
[
'boost_views_total'
=>
[
'sum'
=>
[
'field'
=>
'views::boosted'
,
],
],
'boost_views_daily'
=>
[
'date_histogram'
=>
[
'field'
=>
'@timestamp'
,
'interval'
=>
'1d'
],
'aggs'
=>
[
'boost_views'
=>
[
'sum'
=>
[
'field'
=>
'views::boosted'
]
]
]
]
]
]
];
return
$query
;
}
public
function
getAll
()
:
array
{
$this
->
query
();
return
$this
->
dailyViews
;
}
public
function
getTotal
()
:
int
{
$this
->
query
();
return
$this
->
totalViews
;
}
public
function
getMax
()
:
int
{
$this
->
query
();
return
max
(
$this
->
dailyViews
);
}
public
function
getAvg
()
:
float
{
$this
->
query
();
return
!
empty
(
$this
->
dailyViews
)
?
array_sum
(
$this
->
dailyViews
)
/
count
(
$this
->
dailyViews
)
:
0
;
}
}
This diff is collapsed.
Spec/Core/Analytics/EntityCentric/BoostViewsDailySpec.php
0 → 100644
View file @
c966ecce
<?php
namespace
Spec\Minds\Core\Analytics\EntityCentric
;
use
Minds\Core\Analytics\EntityCentric\BoostViewsDaily
;
use
Minds\Core\Data\ElasticSearch\Client
;
use
Minds\Core\Data\ElasticSearch\Prepared\Search
;
use
PhpSpec\ObjectBehavior
;
use
Prophecy\Argument
;
class
BoostViewsDailySpec
extends
ObjectBehavior
{
/** @var Client */
protected
$esClient
;
/** @var array */
protected
$response
;
public
function
let
(
Client
$esClient
)
{
$this
->
beConstructedWith
(
$esClient
);
$this
->
esClient
=
$esClient
;
$this
->
response
=
[
'aggregations'
=>
[
'boost_views_total'
=>
[
'value'
=>
1887
],
'boost_views_daily'
=>
[
'buckets'
=>
[
[
'key'
=>
'1570060800'
,
'boost_views'
=>
[
'value'
=>
242
]],
[
'key'
=>
'1570147200'
,
'boost_views'
=>
[
'value'
=>
256
]],
[
'key'
=>
'1570233600'
,
'boost_views'
=>
[
'value'
=>
287
]],
[
'key'
=>
'1570320000'
,
'boost_views'
=>
[
'value'
=>
267
]],
[
'key'
=>
'1570406400'
,
'boost_views'
=>
[
'value'
=>
249
]],
[
'key'
=>
'1570492800'
,
'boost_views'
=>
[
'value'
=>
290
]],
[
'key'
=>
'1570579200'
,
'boost_views'
=>
[
'value'
=>
296
]]
]
]
]
];
}
public
function
it_is_initializable
()
{
$this
->
shouldHaveType
(
BoostViewsDaily
::
class
);
}
public
function
it_should_set_last_seven_days_range
()
{
$this
->
lastSevenDays
()
->
shouldReturn
(
$this
);
}
public
function
it_should_set_date_range
()
{
$start
=
strtotime
(
'yesterday -1 day'
);
$end
=
strtotime
(
'yesterday'
);
$this
->
dateRange
(
$start
,
$end
)
->
shouldReturn
(
$this
);
}
public
function
it_should_return_array_of_daily_views
()
{
$this
->
esClient
->
request
(
Argument
::
type
(
Search
::
class
))
->
shouldBeCalled
()
->
willReturn
(
$this
->
response
);
$this
->
getAll
()
->
shouldReturn
([
'1570060800'
=>
242
,
'1570147200'
=>
256
,
'1570233600'
=>
287
,
'1570320000'
=>
267
,
'1570406400'
=>
249
,
'1570492800'
=>
290
,
'1570579200'
=>
296
]);
}
public
function
it_should_return_total_views
()
{
$this
->
esClient
->
request
(
Argument
::
type
(
Search
::
class
))
->
shouldBeCalled
()
->
willReturn
(
$this
->
response
);
$this
->
getTotal
()
->
shouldReturn
(
1887
);
}
public
function
it_should_return_max_views
()
{
$this
->
esClient
->
request
(
Argument
::
type
(
Search
::
class
))
->
shouldBeCalled
()
->
willReturn
(
$this
->
response
);
$this
->
getMax
()
->
shouldReturn
(
296
);
}
public
function
it_should_return_avg_views
()
{
$this
->
esClient
->
request
(
Argument
::
type
(
Search
::
class
))
->
shouldBeCalled
()
->
willReturn
(
$this
->
response
);
$this
->
getAvg
()
->
shouldBeApproximately
(
269.57
,
0.01
);
}
}
This diff is collapsed.
Please
register
or
sign in
to comment