public function getCostCenterPeriodData($ccId, $mode, $startDate, $endDate)
{
$analytics = $this->getContainer()->analytics;
$utcTz = new DateTimeZone('UTC');
$iterator = ChartPeriodIterator::create($mode, new DateTime($startDate, $utcTz), new DateTime($endDate, $utcTz), 'UTC');
$timelineEvents = $analytics->events->count($iterator->getInterval(), $iterator->getStart(), $iterator->getEnd(), ['ccId' => $ccId]);
//Interval which is used in the database query for grouping
$queryInterval = preg_replace('/^1 /', '', $iterator->getInterval());
//Current period data
$collectionSet = (new AggregationCollectionSet(['byPlatformDetailed' => new AggregationCollection(['period', 'platform', 'projectId'], ['cost' => 'sum']), 'byProjectDetailed' => new AggregationCollection(['period', 'projectId', 'platform'], ['cost' => 'sum']), 'byPlatform' => new AggregationCollection(['platform', 'projectId'], ['cost' => 'sum']), 'byProject' => new AggregationCollection(['projectId', 'platform'], ['cost' => 'sum'])]))->load($this->get(['ccId' => $ccId], $iterator->getStart(), $iterator->getEnd(), [$queryInterval, TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true))->calculatePercentage();
//Previous period data
$collectionSetPrev = (new AggregationCollectionSet(['byPlatformDetailed' => new AggregationCollection(['period', 'platform', 'projectId'], ['cost' => 'sum']), 'byProjectDetailed' => new AggregationCollection(['period', 'projectId', 'platform'], ['cost' => 'sum']), 'byPlatform' => new AggregationCollection(['platform', 'projectId'], ['cost' => 'sum']), 'byProject' => new AggregationCollection(['projectId', 'platform'], ['cost' => 'sum'])]))->load($this->get(['ccId' => $ccId], $iterator->getPreviousStart(), $iterator->getPreviousEnd(), [$queryInterval, TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true))->calculatePercentage();
$quarterIterator = $this->getCurrentQuarterIterator();
$queryQuarterInterval = preg_replace('/^1 /', '', $quarterIterator->getInterval());
$rawQuarterUsage = $this->get(['ccId' => $ccId], $quarterIterator->getStart(), $quarterIterator->getEnd(), [TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true);
$itemsRollingAvg = $this->getRollingAvg(['ccId' => $ccId], $queryQuarterInterval, $quarterIterator->getEnd(), null, $rawQuarterUsage, ['projectId' => 'projects', 'platform' => 'clouds']);
//Gets the list of the projects which is assigned to cost center at current moment to optimize
//retrieving its names with one query
$projects = new ArrayCollection();
foreach (ProjectEntity::findByCcId($ccId) as $i) {
$projects[$i->projectId] = $i;
}
//Function retrieves the name of the project by specified identifier
$fnGetProjectName = function ($projectId) use($projects) {
if (empty($projectId)) {
$projectName = 'Unassigned resources';
} else {
if (!isset($projects[$projectId])) {
//Trying to find the name of the project in the tag values history
if (null === ($pe = AccountTagEntity::findOne([['tagId' => TagEntity::TAG_ID_PROJECT], ['valueId' => $projectId]]))) {
$projectName = $projectId;
} else {
$projectName = $pe->valueName;
unset($pe);
}
} else {
$projectName = $projects[$projectId]->name;
}
}
return $projectName;
};
$cloudsData = [];
$projectsData = [];
$timeline = [];
$prevPointKey = null;
foreach ($iterator as $chartPoint) {
/* @var $chartPoint \Scalr\Stats\CostAnalytics\ChartPointInfo */
$i = $chartPoint->i;
$timeline[] = array('datetime' => $chartPoint->dt->format('Y-m-d H:00'), 'label' => $chartPoint->label, 'onchart' => $chartPoint->show, 'cost' => round(isset($collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['cost']) ? $collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['cost'] : 0, 2), 'events' => isset($timelineEvents[$chartPoint->key]) ? $timelineEvents[$chartPoint->key] : null);
//Period - Platform - Projects subtotals
if (!isset($collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'])) {
foreach ($cloudsData as $platform => $v) {
if (!$iterator->isFuture()) {
//Previous period details
if (isset($collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform])) {
$pp = $collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform];
} else {
$pp = null;
}
//Previous point details
if (isset($collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform])) {
$ppt = $collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform];
} else {
$ppt = null;
}
$r = $this->getPointDataArray(null, $pp, $ppt);
//Projects data is empty
$r['projects'] = [];
$cloudsData[$platform]['data'][] = $r;
} else {
$cloudsData[$platform]['data'][] = null;
}
}
} else {
//Initializes with empty values to prevent data shifts on charts.
if (!isset($collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'])) {
$collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'] = [];
}
$combined =& $collectionSet['byPlatformDetailed']['data'][$chartPoint->key]['data'];
if (!empty($cloudsData)) {
foreach ($cloudsData as $platform => $t) {
if (!array_key_exists($platform, $combined)) {
$combined[$platform] = [];
}
}
}
foreach ($combined as $platform => $v) {
//Previous period details
if (isset($collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform])) {
$pp = $collectionSetPrev['byPlatformDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$platform];
} else {
$pp = null;
}
//Previous point details
if (isset($collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform])) {
$ppt = $collectionSet['byPlatformDetailed']['data'][$prevPointKey]['data'][$platform];
} else {
$ppt = null;
}
if (!isset($cloudsData[$platform]) && $i > 0) {
$cloudsData[$platform]['name'] = $platform;
//initializes platfrorm legend for the not filled period
$cloudsData[$platform]['data'] = array_fill(0, $i, null);
}
if (!$iterator->isFuture()) {
$r = $this->getPointDataArray($v, $pp, $ppt);
// projects data
$cloudProjectData = [];
if (!empty($v['data'])) {
foreach ($v['data'] as $projectId => $pv) {
if (isset($pp['data'][$projectId])) {
$ppp = $pp['data'][$projectId];
} else {
$ppp = null;
}
if (isset($ppt['data'][$projectId])) {
$pppt = $ppt['data'][$projectId];
} else {
$pppt = null;
}
$cloudProjectData[] = $this->getDetailedPointDataArray($projectId, $fnGetProjectName($projectId), $pv, $ppp, $pppt);
}
}
$r['projects'] = $cloudProjectData;
$cloudsData[$platform]['name'] = $platform;
$cloudsData[$platform]['data'][] = $r;
} else {
$cloudsData[$platform]['data'][] = null;
}
}
}
//Period - Project - Platform subtotal
if (!isset($collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'])) {
foreach ($projectsData as $projectId => $v) {
if (!$iterator->isFuture()) {
//Previous period details
if (isset($collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId])) {
$pp = $collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId];
} else {
$pp = null;
}
//Previous point details
if (isset($collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId])) {
$ppt = $collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId];
} else {
$ppt = null;
}
$r = $this->getPointDataArray(null, $pp, $ppt);
//Projects data is empty
$r['clouds'] = [];
$projectsData[$projectId]['data'][] = $r;
} else {
$projectsData[$projectId]['data'][] = null;
}
}
} else {
//Initializes with empty values to prevent data shifts on charts.
if (!isset($collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'])) {
$collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'] = [];
}
$combined =& $collectionSet['byProjectDetailed']['data'][$chartPoint->key]['data'];
if (!empty($projectsData)) {
foreach ($projectsData as $projectId => $t) {
if (!array_key_exists($projectId, $combined)) {
$combined[$projectId] = [];
}
}
}
foreach ($combined as $projectId => $v) {
//Previous period details
if (isset($collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId])) {
$pp = $collectionSetPrev['byProjectDetailed']['data'][$chartPoint->previousPeriodKey]['data'][$projectId];
} else {
$pp = null;
}
//Previous point details
if (isset($collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId])) {
$ppt = $collectionSet['byProjectDetailed']['data'][$prevPointKey]['data'][$projectId];
} else {
$ppt = null;
}
if (!isset($projectsData[$projectId]) && $i > 0) {
$projectsData[$projectId]['name'] = $fnGetProjectName($projectId);
//initializes project legend for the not filled period
$projectsData[$projectId]['data'] = array_fill(0, $i, null);
}
if (!$iterator->isFuture()) {
$r = $this->getPointDataArray($v, $pp, $ppt);
// platform data
$cloudPlatformData = [];
if (!empty($v['data'])) {
foreach ($v['data'] as $platform => $pv) {
if (isset($pp['data'][$platform])) {
$ppp = $pp['data'][$platform];
} else {
$ppp = null;
}
if (isset($ppt['data'][$platform])) {
$pppt = $ppt['data'][$platform];
} else {
$pppt = null;
}
$cloudPlatformData[] = $this->getDetailedPointDataArray($platform, $platform, $pv, $ppp, $pppt);
}
}
$r['clouds'] = $cloudPlatformData;
$projectsData[$projectId]['name'] = $fnGetProjectName($projectId);
$projectsData[$projectId]['data'][] = $r;
} else {
$projectsData[$projectId]['data'][] = null;
}
}
}
$prevPointKey = $chartPoint->key;
}
//complete arrays for cloud data and project data
$cntpoints = count($timeline);
foreach ($cloudsData as $platform => $v) {
if (($j = count($v['data'])) < $cntpoints) {
while ($j < $cntpoints) {
$cloudsData[$platform]['data'][] = null;
$j++;
}
}
}
foreach ($projectsData as $projectId => $v) {
if (($j = count($v['data'])) < $cntpoints) {
while ($j < $cntpoints) {
$projectsData[$projectId]['data'][] = null;
$j++;
}
}
}
//Spending trends uses daily usage precalculated data
$trends = $this->calculateSpendingTrends(['ccId' => $ccId], $timeline, $queryInterval, $iterator->getEnd());
if ($iterator->getWholePreviousPeriodEnd() != $iterator->getPreviousEnd()) {
$rawPrevUsageWhole = $this->get(['ccId' => $ccId], $iterator->getPreviousStart(), $iterator->getWholePreviousPeriodEnd(), [TagEntity::TAG_ID_PLATFORM, TagEntity::TAG_ID_PROJECT], true);
//Previous whole period usage subtotals by platform
$prevUsageWhole = (new AggregationCollection(['platform'], ['cost' => 'sum']))->load($rawPrevUsageWhole);
//Previous whole period usage subtotals by project
$prevUsageWhole2 = (new AggregationCollection(['projectId'], ['cost' => 'sum']))->load($rawPrevUsageWhole);
} else {
$prevUsageWhole = $collectionSetPrev['byPlatform'];
$prevUsageWhole2 = $collectionSetPrev['byProject'];
}
//Build cloud platforms total
$cloudsTotal = [];
$it = $collectionSet['byPlatform']->getIterator();
foreach ($it as $platform => $p) {
$pp = isset($collectionSetPrev['byPlatform']['data'][$platform]) ? $collectionSetPrev['byPlatform']['data'][$platform] : null;
$pw = isset($prevUsageWhole['data'][$platform]) ? $prevUsageWhole['data'][$platform] : null;
$cl = $this->getTotalDataArray($platform, $platform, $p, $pp, $pw, $cloudsData, $iterator);
if ($it->hasChildren()) {
$clProjects = [];
foreach ($it->getChildren() as $projectId => $c) {
$cp = isset($collectionSetPrev['byPlatform']['data'][$platform]['data'][$projectId]) ? $collectionSetPrev['byPlatform']['data'][$platform]['data'][$projectId] : null;
$clProjects[] = $this->getTotalDataArray($projectId, $fnGetProjectName($projectId), $c, $cp, null, $projectsData, $iterator, true);
}
$cl['projects'] = $clProjects;
} else {
$cl['projects'] = [];
}
$cloudsTotal[] = $cl;
}
//Build projects total
$projectsTotal = [];
$it = $collectionSet['byProject']->getIterator();
//For each assigned project wich is not archived we should display
//zero dollar spend even if there are not any spend for
//the selected period.
$projectsWithoutSpend = [];
foreach ($projects as $projectEntity) {
/* @var $projectEntity \Scalr\Stats\CostAnalytics\Entity\ProjectEntity */
if ($projectEntity->archived) {
continue;
}
if (!isset($collectionSet['byProject']['data'][$projectEntity->projectId])) {
$projectsWithoutSpend[$projectEntity->projectId] = ['cost' => 0, 'cost_percentage' => 0, 'id' => $projectEntity->projectId];
}
}
//Passing projects with spend and then assigned projects without spend for the selected period
foreach ([$it, $projectsWithoutSpend] as $internalIterator) {
foreach ($internalIterator as $projectId => $p) {
$pp = isset($collectionSetPrev['byProject']['data'][$projectId]) ? $collectionSetPrev['byProject']['data'][$projectId] : null;
$pw = isset($prevUsageWhole2['data'][$projectId]) ? $prevUsageWhole2['data'][$projectId] : null;
$cl = $this->getTotalDataArray($projectId, $fnGetProjectName($projectId), $p, $pp, $pw, $projectsData, $iterator);
if ($internalIterator instanceof ArrayIterator && $internalIterator->hasChildren()) {
$clPlatforms = [];
foreach ($internalIterator->getChildren() as $platform => $c) {
$cp = isset($collectionSetPrev['byProject']['data'][$projectId]['data'][$platform]) ? $collectionSetPrev['byProject']['data'][$projectId]['data'][$platform] : null;
$clPlatforms[] = $this->getTotalDataArray($platform, $platform, $c, $cp, null, $cloudsData, $iterator, true);
}
$cl['clouds'] = $clPlatforms;
} else {
$cl['clouds'] = [];
}
$projectsTotal[] = $cl;
}
}
$data = ['reportVersion' => '0.1.0', 'totals' => ['cost' => round($collectionSet['byPlatform']['cost'], 2), 'prevCost' => round($collectionSetPrev['byPlatform']['cost'], 2), 'growth' => round($collectionSet['byPlatform']['cost'] - $collectionSetPrev['byPlatform']['cost'], 2), 'growthPct' => $collectionSetPrev['byPlatform']['cost'] == 0 ? null : round(abs(($collectionSet['byPlatform']['cost'] - $collectionSetPrev['byPlatform']['cost']) / $collectionSetPrev['byPlatform']['cost'] * 100), 0), 'clouds' => $cloudsTotal, 'projects' => $projectsTotal, 'trends' => $trends, 'forecastCost' => null], 'timeline' => $timeline, 'clouds' => $cloudsData, 'projects' => $projectsData, 'interval' => $queryInterval, 'startDate' => $iterator->getStart()->format('Y-m-d'), 'endDate' => $iterator->getEnd()->format('Y-m-d'), 'previousStartDate' => $iterator->getPreviousStart()->format('Y-m-d'), 'previousEndDate' => $iterator->getPreviousEnd()->format('Y-m-d')];
if ($iterator->getTodayDate() < $iterator->getEnd()) {
$data['totals']['forecastCost'] = self::calculateForecast($data['totals']['cost'], $iterator->getStart(), $iterator->getEnd(), $prevUsageWhole['cost'], ($data['totals']['growth'] >= 0 ? 1 : -1) * $data['totals']['growthPct'], isset($itemsRollingAvg['rollingAverageDaily']) ? $itemsRollingAvg['rollingAverageDaily'] : null);
}
$budgetRequest = ['ccId' => $ccId, 'usage' => $data['totals']['cost']];
if ($mode != 'custom') {
//We need to get budget for the appropriate quarter
$budgetRequest['period'] = $iterator->getQuarterPeriod();
}
$budget = $this->getBudgetUsedPercentage($budgetRequest);
$this->calculateBudgetEstimateOverspend($budget);
$data['totals']['budget'] = $budget;
return $data;
}