dashboard-utils.service.js

567 lines | 22.216 kB Blame History Raw Download
/*
 * Copyright © 2016-2018 The Thingsboard Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
export default angular.module('thingsboard.dashboardUtils', [])
    .factory('dashboardUtils', DashboardUtils)
    .name;

/*@ngInject*/
function DashboardUtils(types, utils, timeService) {

    var service = {
        validateAndUpdateDashboard: validateAndUpdateDashboard,
        validateAndUpdateWidget: validateAndUpdateWidget,
        getRootStateId: getRootStateId,
        createSingleWidgetDashboard: createSingleWidgetDashboard,
        createSingleEntityFilter: createSingleEntityFilter,
        getStateLayoutsData: getStateLayoutsData,
        createDefaultState: createDefaultState,
        createDefaultLayoutData: createDefaultLayoutData,
        setLayouts: setLayouts,
        updateLayoutSettings: updateLayoutSettings,
        addWidgetToLayout: addWidgetToLayout,
        removeWidgetFromLayout: removeWidgetFromLayout,
        isSingleLayoutDashboard: isSingleLayoutDashboard,
        removeUnusedWidgets: removeUnusedWidgets,
        getWidgetsArray: getWidgetsArray
    };

    return service;

    function validateAndUpdateEntityAliases(configuration, datasourcesByAliasId, targetDevicesByAliasId) {
        var aliasId, entityAlias;
        if (angular.isUndefined(configuration.entityAliases)) {
            configuration.entityAliases = {};
            if (configuration.deviceAliases) {
                var deviceAliases = configuration.deviceAliases;
                for (aliasId in deviceAliases) {
                    var deviceAlias = deviceAliases[aliasId];
                    entityAlias = validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId);
                    configuration.entityAliases[entityAlias.id] = entityAlias;
                }
                delete configuration.deviceAliases;
            }
        } else {
            var entityAliases = configuration.entityAliases;
            for (aliasId in entityAliases) {
                entityAlias = entityAliases[aliasId];
                entityAlias = validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId);
                if (aliasId != entityAlias.id) {
                    delete entityAliases[aliasId];
                }
                entityAliases[entityAlias.id] = entityAlias;
            }
        }
        return configuration;
    }

    function validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId) {
        if (!aliasId || !angular.isString(aliasId) || aliasId.length != 36) {
            var newAliasId = utils.guid();
            var aliasDatasources = datasourcesByAliasId[aliasId];
            if (aliasDatasources) {
                aliasDatasources.forEach(
                      function(datasource) {
                          datasource.entityAliasId = newAliasId;
                      }
                );
            }
            var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
            if (targetDeviceAliasIdsList) {
                targetDeviceAliasIdsList.forEach(
                    function(targetDeviceAliasIds) {
                        targetDeviceAliasIds[0] = newAliasId;
                    }
                );
            }
            return newAliasId;
        } else {
            return aliasId;
        }
    }

    function validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId) {
        aliasId = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
        var alias = deviceAlias.alias;
        var entityAlias = {
            id: aliasId,
            alias: alias,
            filter: {
                type: null,
                entityType: types.entityType.device,
                resolveMultiple: false
            },
        }
        if (deviceAlias.deviceFilter) {
            entityAlias.filter.type =
                deviceAlias.deviceFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value;
            if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
                entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList;
            } else {
                entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter;
            }
        } else {
            entityAlias.filter.type = types.aliasFilterType.entityList.value;
            entityAlias.filter.entityList = [deviceAlias.deviceId];
        }
        return entityAlias;
    }

    function validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId) {
        entityAlias.id = validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId);
        if (!entityAlias.filter) {
            entityAlias.filter = {
                type: entityAlias.entityFilter.useFilter ? types.aliasFilterType.entityName.value : types.aliasFilterType.entityList.value,
                entityType: entityAlias.entityType,
                resolveMultiple: false
            }
            if (entityAlias.filter.type == types.aliasFilterType.entityList.value) {
                entityAlias.filter.entityList = entityAlias.entityFilter.entityList;
            } else {
                entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter;
            }
            delete entityAlias.entityType;
            delete entityAlias.entityFilter;
        }
        return entityAlias;
    }

    function validateAndUpdateWidget(widget) {
        if (!widget.config) {
            widget.config = {};
        }
        if (!widget.config.datasources) {
            widget.config.datasources = [];
        }
        widget.config.datasources.forEach(function(datasource) {
            if (datasource.type === 'device') {
                datasource.type = types.datasourceType.entity;
            }
            if (datasource.deviceAliasId) {
                datasource.entityAliasId = datasource.deviceAliasId;
                delete datasource.deviceAliasId;
            }
        });
        //TODO: Temp workaround
        if (widget.isSystemType  && widget.bundleAlias == 'charts' && widget.typeAlias == 'timeseries') {
            widget.typeAlias = 'basic_timeseries';
        }
        return widget;
    }

    function createDefaultLayoutData() {
        return {
            widgets: {},
            gridSettings: {
                backgroundColor: '#eeeeee',
                color: 'rgba(0,0,0,0.870588)',
                columns: 24,
                margins: [10, 10],
                backgroundSizeMode: '100%'
            }
        };
    }

    function createDefaultLayouts() {
        return {
            'main': createDefaultLayoutData()
        };
    }

    function createDefaultState(name, root) {
        return {
            name: name,
            root: root,
            layouts: createDefaultLayouts()
        }
    }

    function validateAndUpdateDashboard(dashboard) {
        if (!dashboard.configuration) {
            dashboard.configuration = {};
        }
        if (angular.isUndefined(dashboard.configuration.widgets)) {
            dashboard.configuration.widgets = {};
        } else if (angular.isArray(dashboard.configuration.widgets)) {
            var widgetsMap = {};
            dashboard.configuration.widgets.forEach(function (widget) {
                if (!widget.id) {
                    widget.id = utils.guid();
                }
                widgetsMap[widget.id] = widget;
            });
            dashboard.configuration.widgets = widgetsMap;
        }
        for (var id in dashboard.configuration.widgets) {
            var widget = dashboard.configuration.widgets[id];
            dashboard.configuration.widgets[id] = validateAndUpdateWidget(widget);
        }
        if (angular.isUndefined(dashboard.configuration.states)) {
            dashboard.configuration.states = {
                'default': createDefaultState(dashboard.title, true)
            };

            var mainLayout = dashboard.configuration.states['default'].layouts['main'];
            for (id in dashboard.configuration.widgets) {
                widget = dashboard.configuration.widgets[id];
                mainLayout.widgets[id] = {
                    sizeX: widget.sizeX,
                    sizeY: widget.sizeY,
                    row: widget.row,
                    col: widget.col,
                };
                if (angular.isDefined(widget.config.mobileHeight)) {
                    mainLayout.widgets[id].mobileHeight = widget.config.mobileHeight;
                }
                if (angular.isDefined(widget.config.mobileOrder)) {
                    mainLayout.widgets[id].mobileOrder = widget.config.mobileOrder;
                }
            }
        } else {
            var states = dashboard.configuration.states;
            var rootFound = false;
            for (var stateId in states) {
                var state = states[stateId];
                if (angular.isUndefined(state.root)) {
                    state.root = false;
                } else if (state.root) {
                    rootFound = true;
                }
            }
            if (!rootFound) {
                var firstStateId = Object.keys(states)[0];
                states[firstStateId].root = true;
            }
        }

        var datasourcesByAliasId = {};
        var targetDevicesByAliasId = {};
        for (var widgetId in dashboard.configuration.widgets) {
            widget = dashboard.configuration.widgets[widgetId];
            widget.config.datasources.forEach(function (datasource) {
               if (datasource.entityAliasId) {
                   var aliasId = datasource.entityAliasId;
                   var aliasDatasources = datasourcesByAliasId[aliasId];
                   if (!aliasDatasources) {
                       aliasDatasources = [];
                       datasourcesByAliasId[aliasId] = aliasDatasources;
                   }
                   aliasDatasources.push(datasource);
               }
            });
            if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) {
                var aliasId = widget.config.targetDeviceAliasIds[0];
                var targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId];
                if (!targetDeviceAliasIdsList) {
                    targetDeviceAliasIdsList = [];
                    targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList;
                }
                targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds);
            }
        }

        dashboard.configuration = validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId);

        if (angular.isUndefined(dashboard.configuration.timewindow)) {
            dashboard.configuration.timewindow = timeService.defaultTimewindow();
        }
        if (angular.isUndefined(dashboard.configuration.settings)) {
            dashboard.configuration.settings = {};
            dashboard.configuration.settings.stateControllerId = 'entity';
            dashboard.configuration.settings.showTitle = false;
            dashboard.configuration.settings.showDashboardsSelect = true;
            dashboard.configuration.settings.showEntitiesSelect = true;
            dashboard.configuration.settings.showDashboardTimewindow = true;
            dashboard.configuration.settings.showDashboardExport = true;
            dashboard.configuration.settings.toolbarAlwaysOpen = true;
        } else {
            if (angular.isUndefined(dashboard.configuration.settings.stateControllerId)) {
                dashboard.configuration.settings.stateControllerId = 'entity';
            }
        }
        if (angular.isDefined(dashboard.configuration.gridSettings)) {
            var gridSettings = dashboard.configuration.gridSettings;
            if (angular.isDefined(gridSettings.showTitle)) {
                dashboard.configuration.settings.showTitle = gridSettings.showTitle;
                delete gridSettings.showTitle;
            }
            if (angular.isDefined(gridSettings.titleColor)) {
                dashboard.configuration.settings.titleColor = gridSettings.titleColor;
                delete gridSettings.titleColor;
            }
            if (angular.isDefined(gridSettings.showDevicesSelect)) {
                dashboard.configuration.settings.showEntitiesSelect = gridSettings.showDevicesSelect;
                delete gridSettings.showDevicesSelect;
            }
            if (angular.isDefined(gridSettings.showEntitiesSelect)) {
                dashboard.configuration.settings.showEntitiesSelect = gridSettings.showEntitiesSelect;
                delete gridSettings.showEntitiesSelect;
            }
            if (angular.isDefined(gridSettings.showDashboardTimewindow)) {
                dashboard.configuration.settings.showDashboardTimewindow = gridSettings.showDashboardTimewindow;
                delete gridSettings.showDashboardTimewindow;
            }
            if (angular.isDefined(gridSettings.showDashboardExport)) {
                dashboard.configuration.settings.showDashboardExport = gridSettings.showDashboardExport;
                delete gridSettings.showDashboardExport;
            }
            dashboard.configuration.states['default'].layouts['main'].gridSettings = gridSettings;
            delete dashboard.configuration.gridSettings;
        }
        return dashboard;
    }

    function getRootStateId(states) {
        for (var stateId in states) {
            var state = states[stateId];
            if (state.root) {
                return stateId;
            }
        }
        return Object.keys(states)[0];
    }

    function createSingleWidgetDashboard(widget) {
        if (!widget.id) {
            widget.id = utils.guid();
        }
        var dashboard = {};
        dashboard = validateAndUpdateDashboard(dashboard);
        dashboard.configuration.widgets[widget.id] = widget;
        dashboard.configuration.states['default'].layouts['main'].widgets[widget.id] = {
            sizeX: widget.sizeX,
            sizeY: widget.sizeY,
            row: widget.row,
            col: widget.col,
        };
        return dashboard;
    }

    function createSingleEntityFilter(entityType, entityId) {
        return {
            type: types.aliasFilterType.singleEntity.value,
            singleEntity: { entityType: entityType, id: entityId },
            resolveMultiple: false
        };
    }

    function getStateLayoutsData(dashboard, targetState) {
        var dashboardConfiguration = dashboard.configuration;
        var states = dashboardConfiguration.states;
        var state = states[targetState];
        if (state) {
            var allWidgets = dashboardConfiguration.widgets;
            var result = {};
            for (var l in state.layouts) {
                var layout = state.layouts[l];
                if (layout) {
                    result[l] = {
                        widgets: [],
                        widgetLayouts: {},
                        gridSettings: {}
                    }
                    for (var id in layout.widgets) {
                        result[l].widgets.push(allWidgets[id]);
                    }
                    result[l].widgetLayouts = layout.widgets;
                    result[l].gridSettings = layout.gridSettings;
                }
            }
            return result;
        } else {
            return null;
        }
    }

    function setLayouts(dashboard, targetState, newLayouts) {
        var dashboardConfiguration = dashboard.configuration;
        var states = dashboardConfiguration.states;
        var state = states[targetState];
        var addedCount = 0;
        var removedCount = 0;
        for (var l in state.layouts) {
            if (!newLayouts[l]) {
                removedCount++;
            }
        }
        for (l in newLayouts) {
            if (!state.layouts[l]) {
                addedCount++;
            }
        }
        state.layouts = newLayouts;
        var layoutsCount = Object.keys(state.layouts).length;
        var newColumns;
        if (addedCount) {
            for (l in state.layouts) {
                newColumns = state.layouts[l].gridSettings.columns * (layoutsCount - addedCount) / layoutsCount;
                state.layouts[l].gridSettings.columns = newColumns;
            }
        }
        if (removedCount) {
            for (l in state.layouts) {
                newColumns = state.layouts[l].gridSettings.columns * (layoutsCount + removedCount) / layoutsCount;
                state.layouts[l].gridSettings.columns = newColumns;
            }
        }
        removeUnusedWidgets(dashboard);
    }

    function updateLayoutSettings(layout, gridSettings) {
        var prevGridSettings = layout.gridSettings;
        var prevColumns = prevGridSettings ? prevGridSettings.columns : 24;
        var ratio = gridSettings.columns / prevColumns;
        layout.gridSettings = gridSettings;
        var maxRow = 0;
        for (var w in layout.widgets) {
            var widget = layout.widgets[w];
            maxRow = Math.max(maxRow, widget.row + widget.sizeY);
        }
        var newMaxRow = Math.round(maxRow * ratio);
        for (w in layout.widgets) {
            widget = layout.widgets[w];
            if (widget.row + widget.sizeY == maxRow) {
                widget.row = Math.round(widget.row * ratio);
                widget.sizeY = newMaxRow - widget.row;
            } else {
                widget.row = Math.round(widget.row * ratio);
                widget.sizeY = Math.round(widget.sizeY * ratio);
            }
            widget.sizeX = Math.round(widget.sizeX * ratio);
            widget.col = Math.round(widget.col * ratio);
            if (widget.col + widget.sizeX > gridSettings.columns) {
                widget.sizeX = gridSettings.columns - widget.col;
            }
        }
    }

    function addWidgetToLayout(dashboard, targetState, targetLayout, widget, originalColumns, originalSize, row, column) {
        var dashboardConfiguration = dashboard.configuration;
        var states = dashboardConfiguration.states;
        var state = states[targetState];
        var layout = state.layouts[targetLayout];
        var layoutCount = Object.keys(state.layouts).length;
        if (!widget.id) {
            widget.id = utils.guid();
        }
        if (!dashboardConfiguration.widgets[widget.id]) {
            dashboardConfiguration.widgets[widget.id] = widget;
        }
        var widgetLayout = {
            sizeX: originalSize ? originalSize.sizeX : widget.sizeX,
            sizeY: originalSize ? originalSize.sizeY : widget.sizeY,
            mobileOrder: widget.config.mobileOrder,
            mobileHeight: widget.config.mobileHeight
        };

        if (angular.isUndefined(originalColumns)) {
            originalColumns = 24;
        }

        var gridSettings = layout.gridSettings;
        var columns = 24;
        if (gridSettings && gridSettings.columns) {
            columns = gridSettings.columns;
        }

        columns = columns * layoutCount;

        if (columns != originalColumns) {
            var ratio = columns / originalColumns;
            widgetLayout.sizeX *= ratio;
            widgetLayout.sizeY *= ratio;
        }

        if (row > -1 && column > - 1) {
            widgetLayout.row = row;
            widgetLayout.col = column;
        } else {
            row = 0;
            for (var w in layout.widgets) {
                var existingLayout = layout.widgets[w];
                var wRow = existingLayout.row ? existingLayout.row : 0;
                var wSizeY = existingLayout.sizeY ? existingLayout.sizeY : 1;
                var bottom = wRow + wSizeY;
                row = Math.max(row, bottom);
            }
            widgetLayout.row = row;
            widgetLayout.col = 0;
        }

        layout.widgets[widget.id] = widgetLayout;
    }

    function removeWidgetFromLayout(dashboard, targetState, targetLayout, widgetId) {
        var dashboardConfiguration = dashboard.configuration;
        var states = dashboardConfiguration.states;
        var state = states[targetState];
        var layout = state.layouts[targetLayout];
        delete layout.widgets[widgetId];
        removeUnusedWidgets(dashboard);
    }

    function isSingleLayoutDashboard(dashboard) {
        var dashboardConfiguration = dashboard.configuration;
        var states = dashboardConfiguration.states;
        var stateKeys = Object.keys(states);
        if (stateKeys.length === 1) {
            var state = states[stateKeys[0]];
            var layouts = state.layouts;
            var layoutKeys = Object.keys(layouts);
            if (layoutKeys.length === 1) {
                return {
                    state: stateKeys[0],
                    layout: layoutKeys[0]
                }
            }
        }
        return null;
    }

    function removeUnusedWidgets(dashboard) {
        var dashboardConfiguration = dashboard.configuration;
        var states = dashboardConfiguration.states;
        var widgets = dashboardConfiguration.widgets;
        for (var widgetId in widgets) {
            var found = false;
            for (var s in states) {
                var state = states[s];
                for (var l in state.layouts) {
                    var layout = state.layouts[l];
                    if (layout.widgets[widgetId]) {
                        found = true;
                        break;
                    }
                }
            }
            if (!found) {
                delete dashboardConfiguration.widgets[widgetId];
            }

        }
    }

    function getWidgetsArray(dashboard) {
        var widgetsArray = [];
        var dashboardConfiguration = dashboard.configuration;
        var widgets = dashboardConfiguration.widgets;
        for (var widgetId in widgets) {
            var widget = widgets[widgetId];
            widgetsArray.push(widget);
        }
        return widgetsArray;
    }
}