458 lines
26 KiB
JavaScript
458 lines
26 KiB
JavaScript
"use strict";
|
|
(function () {
|
|
'use strict';
|
|
angular.module('chartModule')
|
|
.controller('ChartController', ['$scope', 'chartModel', 'userModel', '$q', 'i18nService', '$timeout', 'configurationModel', '$filter', 'metadataModel', 'authService', 'roles', '$window', 'searchModel',
|
|
function ($scope, chartModel, userModel, $q, i18nService, $timeout, configurationModel, $filter, metadataModel, authService, roles, $window, searchModel) {
|
|
var userPreferenceGroup = 'Dashboard', windowResizeHandler;
|
|
function init() {
|
|
$scope.state = {
|
|
incidentDataIsLoading: true,
|
|
workorderDataIsLoading: true,
|
|
requestDataIsLoading: true,
|
|
changeDataIsLoading: true,
|
|
tooManyCompanies: false
|
|
};
|
|
$scope.selections = {
|
|
companies: []
|
|
};
|
|
$scope.incidentMultiBarChartData = [];
|
|
$scope.workorderMultiBarChartData = [];
|
|
$scope.servicerequestMultiBarChartData = [];
|
|
$scope.incidentAreaLineChartData = [];
|
|
$scope.workorderAreaLineChartData = [];
|
|
$scope.requestAreaLineChartData = [];
|
|
$scope.changePieChartData = [];
|
|
$scope.changeBarChartData = [];
|
|
$scope.supportGroups = [];
|
|
$scope.myCompanySelected = true;
|
|
$scope.selectedCompanyItem = {};
|
|
$scope.myitsmLocale = window.myitsmLocale;
|
|
$scope.perspectiveDropdown = {
|
|
data: [],
|
|
selectedItem: {}
|
|
};
|
|
var userFullDataPromise = userModel.getFullCurrentUserData().then(function () {
|
|
$scope.userData = userModel.userFullData;
|
|
});
|
|
$scope.isITSMAgent = authService.isAuthorized(roles.ITSM_AGENT_ROLE);
|
|
$scope.isSRMInstalled = configurationModel.isServerApplicationEnabled(EntityVO.TYPE_SERVICEREQUEST)
|
|
&& authService.isAuthorized(roles.ITSM_AGENT_ROLE);
|
|
$scope.isWOInstalled = configurationModel.isServerApplicationEnabled(EntityVO.TYPE_WORKORDER)
|
|
&& authService.isAuthorized(roles.ITSM_AGENT_ROLE);
|
|
$scope.isChangeInstalled = configurationModel.isServerApplicationEnabled(EntityVO.TYPE_CHANGE)
|
|
&& authService.isAuthorized(roles.ITSM_CHANGE_USER_ROLE);
|
|
var userCompaniesPromise = searchModel.getOperatingCompanies(null, -1).then(function (response) {
|
|
$scope.selections.companies = _.cloneDeep(response.companies);
|
|
$scope.state.tooManyCompanies = response.exceedsChunkSize;
|
|
});
|
|
$scope.getCompaniesByName = function (name) {
|
|
return searchModel.getCompaniesByText(name).then(function (response) {
|
|
return { list: response.companies, exceedsChunkSize: response.exceedsChunkSize };
|
|
});
|
|
};
|
|
$scope.setCompany = function (company) {
|
|
$scope.selectedCompany = { name: company.name };
|
|
$scope.selectedCompanyItem = company;
|
|
$scope.fetchDataByCompany($scope.selectedCompany);
|
|
};
|
|
//todo: Viktor: in my opinion local storage is more proper place to save this small data. + It is not critical to have this data over several machines.
|
|
var userPreferencesPromise = userModel.getUserPreferences(userPreferenceGroup).then(function (userConfig) {
|
|
if (userConfig.length) {
|
|
_.forEach(userConfig, function (property) {
|
|
chartModel[property.name] = property.value;
|
|
});
|
|
}
|
|
});
|
|
var metadataPromise = metadataModel.getMetadataByTypes([EntityVO.TYPE_INCIDENT, EntityVO.TYPE_WORKORDER, EntityVO.TYPE_SERVICEREQUEST]);
|
|
$q.all([userFullDataPromise, userCompaniesPromise, userPreferencesPromise, metadataPromise]).then(function () {
|
|
$scope.incidentRadioModel = chartModel.incidentBacklogDateRange;
|
|
$scope.workorderRadioModel = chartModel.workorderBacklogDateRange;
|
|
$scope.requestRadioModel = chartModel.requestBacklogDateRange;
|
|
$scope.selectedCompany = chartModel.statsCompany ? chartModel.statsCompany : { name: $scope.userData.company.name };
|
|
$scope.selectedCompany = { name: $scope.selectedCompany.name };
|
|
$scope.selectedCompanyItem = _.find($scope.selections.companies, $scope.selectedCompany) || $scope.selectedCompany;
|
|
populatePerpectiveList($scope.selectedCompany);
|
|
if (chartModel.statsPerspective && chartModel.statsPerspective !== 'myGroups' && chartModel.statsPerspective !== 'allGroups'
|
|
&& !_.filter($scope.supportGroups, function (item) {
|
|
return item.name === chartModel.statsPerspective;
|
|
}).length) {
|
|
$scope.clearCache();
|
|
}
|
|
if (chartModel.statsPerspective === 'myGroups' || chartModel.statsPerspective === 'allGroups') {
|
|
$timeout(function () {
|
|
$scope.perspectiveDropdown.selectedItem = _.find($scope.perspectiveDropdown.data, { name: chartModel.statsPerspective });
|
|
}, 500);
|
|
if (chartModel.statsPerspective === 'myGroups') {
|
|
$scope.filterGroups = $scope.supportGroups.map(function (group) {
|
|
return { name: group.name };
|
|
});
|
|
}
|
|
else {
|
|
$scope.filterGroups = [];
|
|
}
|
|
}
|
|
else {
|
|
$scope.perspectiveDropdown.selectedItem = _.find($scope.perspectiveDropdown.data, { name: chartModel.statsPerspective });
|
|
$scope.filterGroups = [{ name: chartModel.statsPerspective }];
|
|
}
|
|
$scope.fetchData();
|
|
});
|
|
}
|
|
$scope.refreshBacklogData = function (ticketType, days) {
|
|
$scope[ticketType + 'RadioModel'] = days;
|
|
chartModel[ticketType + 'BacklogDateRange'] = days;
|
|
var userPreference = _.find(userModel.userConfig[userPreferenceGroup], { name: ticketType + 'BacklogDateRange' }), userPrefId = userPreference ? userPreference.id : null;
|
|
userModel.updateUserPreferences(userPreferenceGroup, {
|
|
id: userPrefId,
|
|
name: ticketType + 'BacklogDateRange',
|
|
value: days
|
|
});
|
|
$scope.state[ticketType + 'BacklogIsLoading'] = true;
|
|
chartModel.fetchData(ticketType, ['area'], [$scope.selectedCompany], $scope.filterGroups, $scope[ticketType + 'RadioModel']).then(function () {
|
|
$scope[ticketType + 'AreaLineChartData'] = chartModel[ticketType + 'StatsDataCache'][ticketType + 'AreaLineChartData'];
|
|
}).finally(function () {
|
|
$scope.state[ticketType + 'BacklogIsLoading'] = false;
|
|
});
|
|
};
|
|
$scope.colorFunction = function () {
|
|
return function (d, i) {
|
|
var colorArray = ['#f83200', '#EE8F43', '#ECC35B', '#89C341'];
|
|
return colorArray[i];
|
|
};
|
|
};
|
|
$scope.xAreaLineChartFunction = function () {
|
|
return function (d) {
|
|
if (window.isRtl) {
|
|
return $filter('date')(d, '\u200F d \u05d1MMM yyyy');
|
|
}
|
|
return $filter('date')(d);
|
|
};
|
|
};
|
|
$scope.yMultiBarChartFunction = function () {
|
|
return function (d) {
|
|
return d3.round(d);
|
|
};
|
|
};
|
|
$scope.xchangePieChartFunction = function () {
|
|
return function (d) {
|
|
return d.label;
|
|
};
|
|
};
|
|
$scope.ychangePieChartFunction = function () {
|
|
return function (d) {
|
|
return d.value;
|
|
};
|
|
};
|
|
var barColorArray = ['#95DAED', '#f83200', '#95DAED', '#95DAED', '#95DAED', '#95DAED'];
|
|
$scope.changeBarColorFunction = function () {
|
|
return function (d, i) {
|
|
return barColorArray[i];
|
|
};
|
|
};
|
|
var pieColorArray = ['#f83200', '#95DAED'];
|
|
$scope.changePieColorFunction = function () {
|
|
return function (d, i) {
|
|
return pieColorArray[i];
|
|
};
|
|
};
|
|
$scope.clearCache = function () {
|
|
chartModel.clearCache(EntityVO.TYPE_INCIDENT);
|
|
chartModel.clearCache(EntityVO.TYPE_WORKORDER);
|
|
chartModel.clearCache(EntityVO.TYPE_SERVICEREQUEST);
|
|
};
|
|
$scope.fetchDataByCompany = function (company) {
|
|
var userPreference = {}, userPrefId = '', statsUserPrefId = '', updateGroupPref = false;
|
|
populatePerpectiveList(company);
|
|
if (chartModel.statsPerspective !== 'allGroups') {
|
|
$scope.perspectiveDropdown.selectedItem = _.find($scope.perspectiveDropdown.data, { name: 'allGroups' });
|
|
$scope.filterGroups = [];
|
|
chartModel.statsPerspective = 'allGroups';
|
|
userPreference = _.find(userModel.userConfig[userPreferenceGroup], { name: 'statsPerspective' });
|
|
statsUserPrefId = userPreference ? userPreference.id : null;
|
|
updateGroupPref = true;
|
|
}
|
|
userPreference = _.find(userModel.userConfig[userPreferenceGroup], { name: 'statsCompany' });
|
|
userPrefId = userPreference ? userPreference.id : null;
|
|
$scope.selectedCompany = chartModel.statsCompany = angular.copy(company);
|
|
userModel.updateUserPreferences(userPreferenceGroup, {
|
|
id: userPrefId,
|
|
name: 'statsCompany',
|
|
value: chartModel.statsCompany
|
|
}).finally(function () {
|
|
if (updateGroupPref) {
|
|
userModel.updateUserPreferences(userPreferenceGroup, {
|
|
id: statsUserPrefId,
|
|
name: 'statsPerspective',
|
|
value: chartModel.statsPerspective
|
|
});
|
|
}
|
|
});
|
|
$scope.clearCache();
|
|
$scope.fetchData();
|
|
};
|
|
$scope.fetchDataByGroup = function (group) {
|
|
$scope.perspectiveDropdown.selectedItem = group;
|
|
if (group && group.static && (group.name === 'myGroups' || group.name === 'allGroups')) {
|
|
chartModel.statsPerspective = group.name;
|
|
if (group.name === 'myGroups') {
|
|
$scope.filterGroups = $scope.supportGroups.map(function (group) {
|
|
return { name: group.name };
|
|
});
|
|
}
|
|
else {
|
|
$scope.filterGroups = [];
|
|
}
|
|
}
|
|
else {
|
|
chartModel.statsPerspective = group.name;
|
|
$scope.filterGroups = [{ name: group.name }];
|
|
}
|
|
var userPreference = _.find(userModel.userConfig[userPreferenceGroup], { name: 'statsPerspective' }), userPrefId = userPreference ? userPreference.id : null;
|
|
userModel.updateUserPreferences(userPreferenceGroup, {
|
|
id: userPrefId,
|
|
name: 'statsPerspective',
|
|
value: chartModel.statsPerspective
|
|
});
|
|
$scope.clearCache();
|
|
$scope.fetchData();
|
|
};
|
|
$scope.fetchData = function () {
|
|
$scope.state.incidentDataIsLoading = true;
|
|
var incidentChartDataPromise = chartModel.getChartStatisticsData(EntityVO.TYPE_INCIDENT, ['kpi', 'stack', 'area'], [$scope.selectedCompany], $scope.filterGroups, $scope.incidentRadioModel);
|
|
incidentChartDataPromise.then(function () {
|
|
$scope.incidentKeyPerformanceData = chartModel.incidentStatsDataCache.incidentKeyPerformanceData;
|
|
$scope.incidentMultiBarChartData = processMultiBarChartData(EntityVO.TYPE_INCIDENT, chartModel.incidentStatsDataCache.incidentMultiBarChartData, 'urgencies', 'statuses');
|
|
$scope.incidentAreaLineChartData = chartModel.incidentStatsDataCache.incidentAreaLineChartData;
|
|
}).finally(function () {
|
|
$scope.state.incidentDataIsLoading = false;
|
|
});
|
|
if ($scope.isSRMInstalled) {
|
|
$scope.state.requestDataIsLoading = true;
|
|
var requestChartDataPromise = chartModel.getChartStatisticsData(EntityVO.TYPE_SERVICEREQUEST, ['kpi', 'stack', 'area'], [$scope.selectedCompany], $scope.filterGroups, $scope.requestRadioModel);
|
|
requestChartDataPromise.then(function () {
|
|
$scope.requestKeyPerformanceData = chartModel.requestStatsDataCache.requestKeyPerformanceData;
|
|
$scope.requestMultiBarChartData = processMultiBarChartData(EntityVO.TYPE_SERVICEREQUEST, chartModel.requestStatsDataCache.requestMultiBarChartData, 'urgencies', 'statuses');
|
|
$scope.requestAreaLineChartData = chartModel.requestStatsDataCache.requestAreaLineChartData;
|
|
}).finally(function () {
|
|
$scope.state.requestDataIsLoading = false;
|
|
});
|
|
}
|
|
if ($scope.isWOInstalled) {
|
|
$scope.state.workorderDataIsLoading = true;
|
|
var workorderChartDataPromise = chartModel.getChartStatisticsData(EntityVO.TYPE_WORKORDER, ['kpi', 'stack', 'area'], [$scope.selectedCompany], $scope.filterGroups, $scope.workorderRadioModel);
|
|
workorderChartDataPromise.then(function () {
|
|
$scope.workorderKeyPerformanceData = chartModel.workorderStatsDataCache.workorderKeyPerformanceData;
|
|
$scope.workorderMultiBarChartData = processMultiBarChartData(EntityVO.TYPE_WORKORDER, chartModel.workorderStatsDataCache.workorderMultiBarChartData, 'priorities', 'statuses');
|
|
$scope.workorderAreaLineChartData = chartModel.workorderStatsDataCache.workorderAreaLineChartData;
|
|
}).finally(function () {
|
|
$scope.state.workorderDataIsLoading = false;
|
|
});
|
|
}
|
|
if ($scope.isChangeInstalled) {
|
|
$scope.state.changeDataIsLoading = true;
|
|
var changeDataPromise = chartModel.getChangeData([$scope.selectedCompany], $scope.filterGroups);
|
|
changeDataPromise.then(function () {
|
|
var barChartData = [
|
|
{
|
|
key: 'Change Stats',
|
|
values: []
|
|
}
|
|
];
|
|
var pieChartData = [];
|
|
var criticalVal = 0;
|
|
var totalVal = 0;
|
|
_.forEach(chartModel.statsConfig, function (stat) {
|
|
var dataPoint = [], pieDataPoint;
|
|
dataPoint.push($filter('i18n')('chart.change.label.' + stat.label));
|
|
dataPoint.push(stat.value);
|
|
barChartData[0].values.push(dataPoint);
|
|
if (stat.label === 'critical') {
|
|
pieDataPoint = {
|
|
name: stat.label,
|
|
label: $filter('i18n')('chart.changeDonut.label.' + stat.label),
|
|
value: stat.value
|
|
};
|
|
criticalVal = stat.value;
|
|
pieChartData.push(pieDataPoint);
|
|
}
|
|
if (stat.label === 'all') {
|
|
pieDataPoint = {
|
|
name: stat.label,
|
|
label: $filter('i18n')('chart.changeDonut.label.' + stat.label),
|
|
value: stat.value
|
|
};
|
|
totalVal = stat.value;
|
|
pieChartData.push(pieDataPoint);
|
|
}
|
|
});
|
|
$scope.changeBarChartData = barChartData;
|
|
_.forEach(pieChartData, function (stat) {
|
|
if (stat.name === 'all') {
|
|
stat.value = parseInt(stat.value) - criticalVal;
|
|
}
|
|
});
|
|
$scope.changePieChartData = pieChartData;
|
|
//drawing custom pie chart instead of using the directive provided
|
|
//to add custom UI to match the mockup provided (number in the center and legend positioning)
|
|
//and handle legend click behavior to enable redraw of custom UI
|
|
$scope.drawCustomPieChart(totalVal);
|
|
}).finally(function () {
|
|
$scope.state.changeDataIsLoading = false;
|
|
});
|
|
}
|
|
$q.all([incidentChartDataPromise, requestChartDataPromise, workorderChartDataPromise, changeDataPromise]).then(function () {
|
|
setTimeout(function () {
|
|
d3.selectAll("text").attr('tabindex', 0);
|
|
}, 500);
|
|
}, 0);
|
|
$scope.drawCustomPieChart = function (pieAllStatValue) {
|
|
nv.addGraph(function () {
|
|
var totalValue = pieAllStatValue;
|
|
var displayValue = totalValue; //to be displayed in the middle of the donut
|
|
var chart = nv.models.pieChart()
|
|
.x($scope.xchangePieChartFunction())
|
|
.y($scope.ychangePieChartFunction())
|
|
.showLegend(true)
|
|
.donut(true)
|
|
.donutRatio(.42)
|
|
.showLabels(false)
|
|
.tooltips(false)
|
|
.color($scope.changePieColorFunction());
|
|
d3.select('#changeDashboardPieChart svg')
|
|
.datum($scope.changePieChartData)
|
|
.transition().duration(500).call(chart);
|
|
function renderCustomPieChartUI() {
|
|
var chartContainer = d3.select('#changeDashboardPieChart');
|
|
var availableW = chartContainer && chartContainer[0]
|
|
&& chartContainer[0][0] && chartContainer[0][0].clientWidth;
|
|
var minLeftPos = 160;
|
|
if (window.myitsmLocale == 'zh' || window.myitsmLocale == 'ko' || window.myitsmLocale == 'ja') {
|
|
minLeftPos = 60;
|
|
}
|
|
var leftPos = availableW > 440 ? 100 : minLeftPos;
|
|
d3.select('#changeDashboardPieChart .nv-pieChart')
|
|
.attr('transform', 'translate(-30, 30)');
|
|
d3.select('#changeDashboardPieChart .nv-pieChart .nv-legendWrap')
|
|
.attr('transform', window.myitsmLocale == 'de' ? 'translate(20, 80)' : 'translate(0, 80)');
|
|
var series = d3.selectAll('#changeDashboardPieChart .nv-pieChart .nv-legendWrap .nv-series');
|
|
series.each(function (d, i) {
|
|
//if(i === 1)
|
|
//totalValue = d.value;
|
|
var s = d3.select(this);
|
|
//set legend left position based on chart container width for responsiveness
|
|
s.attr('transform', 'translate(' + leftPos + ', ' + i * 30 + ')');
|
|
s.select('text')
|
|
.attr('dx', '12');
|
|
});
|
|
//Append center text showing total items
|
|
var pieOuter = d3.select('#changeDashboardPieChart .nv-pie .nv-pie');
|
|
var textBoxElements = pieOuter.selectAll('.donut-center-box');
|
|
var textBoxExists = textBoxElements && textBoxElements[0]
|
|
&& textBoxElements[0].length;
|
|
if (textBoxExists) {
|
|
textBoxElements.each(function () {
|
|
var tb = d3.select(this);
|
|
tb.select('text')
|
|
.text(displayValue);
|
|
});
|
|
}
|
|
else {
|
|
var textG = pieOuter.append('g')
|
|
.attr('transform', 'translate(0,0)')
|
|
.attr('class', 'donut-center-box');
|
|
textG.append('text')
|
|
.attr('dy', '.35em')
|
|
.attr('text-anchor', 'middle')
|
|
.attr('class', 'donut-center-text')
|
|
.text(displayValue);
|
|
}
|
|
}
|
|
function onLegendClick(pie) {
|
|
if (!pie.hasOwnProperty('disabled')) { //d3 does not set disabled on the pie data by default but only after first chart.update
|
|
pie.disabled = false;
|
|
}
|
|
//this is before chart.update, hence disabled 'true' actually means user clicked on disabled legend
|
|
//thereby activating it
|
|
if (pie.disabled) {
|
|
displayValue = totalValue; //show 'All' value
|
|
}
|
|
else if (!pie.disabled && displayValue === pie.value) {
|
|
//TODO: update logic if dataset more than 2
|
|
displayValue = totalValue;
|
|
}
|
|
else {
|
|
//legend switched off
|
|
displayValue = totalValue - pie.value;
|
|
}
|
|
}
|
|
function handleLegendClick() {
|
|
chart.legend.dispatch.on('legendClick', onLegendClick);
|
|
if (chart.update) {
|
|
var originalUpdate = chart.update;
|
|
chart.update = function () {
|
|
originalUpdate();
|
|
handleLegendClick();
|
|
renderCustomPieChartUI();
|
|
};
|
|
}
|
|
}
|
|
handleLegendClick();
|
|
renderCustomPieChartUI();
|
|
windowResizeHandler = chart.update;
|
|
angular.element($window).on('resize', windowResizeHandler);
|
|
});
|
|
};
|
|
};
|
|
function populatePerpectiveList(company) {
|
|
$scope.supportGroups = [];
|
|
$scope.perspectiveDropdown.data = [];
|
|
$scope.myCompanySelected = !!(company.name === $scope.userData.company.name);
|
|
var myGroups = {
|
|
name: "myGroups",
|
|
static: true,
|
|
id: "mygroups",
|
|
label: $filter("i18n")("chart.perspective.myGroups")
|
|
}, allGroups = {
|
|
name: "allGroups",
|
|
static: true,
|
|
id: "allgroups",
|
|
label: $filter("i18n")("chart.perspective.allGroups")
|
|
};
|
|
if ($scope.myCompanySelected) {
|
|
$scope.perspectiveDropdown.data.push(myGroups, allGroups);
|
|
}
|
|
else {
|
|
$scope.perspectiveDropdown.data.push(allGroups);
|
|
}
|
|
_.forEach($scope.userData.supportGroups, function (group) {
|
|
if (group.company && group.company.name === company.name) {
|
|
$scope.supportGroups.push({ name: group.name });
|
|
$scope.perspectiveDropdown.data.push({ name: group.name, label: group.name });
|
|
}
|
|
});
|
|
}
|
|
function processMultiBarChartData(ticketType, chartData, key_x, key_y) {
|
|
var metadata = metadataModel.cache[ticketType];
|
|
_.each(chartData, function (chartData_x) {
|
|
var metadata_x = _.find(metadata[key_x], { label: chartData_x.key });
|
|
chartData_x.index = metadata_x && metadata_x.index || 0;
|
|
_.each(chartData_x.values, function (chartData_y) {
|
|
var metaData_y = _.find(metadata[key_y], { label: chartData_y[0] });
|
|
chartData_y.index = metaData_y && metaData_y.index || 0;
|
|
});
|
|
chartData_x.values = _.sortBy(chartData_x.values, 'index');
|
|
});
|
|
chartData = _.sortBy(chartData, 'index');
|
|
return chartData;
|
|
}
|
|
init();
|
|
$scope.$on('$destroy', function () {
|
|
if (windowResizeHandler) {
|
|
angular.element($window).off('resize', windowResizeHandler);
|
|
}
|
|
});
|
|
}
|
|
]);
|
|
}());
|